Skip to content

Instantly share code, notes, and snippets.

@lsegal
Last active August 29, 2015 14:07
Show Gist options
  • Save lsegal/2ba84edf85422887a0c5 to your computer and use it in GitHub Desktop.
Save lsegal/2ba84edf85422887a0c5 to your computer and use it in GitHub Desktop.
iOS 8 Safari If-Modified-Since caching bug
<!--
iOS 8 Safari If-Modified-Since caching bug:
Safari sends an invalid If-Modified-Since header on PUT requests if a prior
GET request on the same URI returns a Last-Modified header and the "upload"
property is accessed on the XHR object prior to calling .send():
PUT /path
If-Modified-Since: Wed, 08 Oct 2014 22:20:57 GMT
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_9_5 like Mac OS X) \
AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 \
Mobile/12A365 Safari/600.1.4
...
The semantics of the above request are invalid because it translates to:
"Only update /path IFF the object HAS been changed since the last GET request."
That is likely the opposite of what the user wants to do.
Evidence that this is a bug are hinted at by the fact that this is
only reproducible if you use XHR2 "upload" property support (xhr.upload). More
importantly, this header is toggled on by simply *ACCESSING* the .upload
property. You do not have to attach listeners, print it, or anything. The
following line of code, by itself, will turn on the If-Modified-Since header:
xhr.upload; // If-Modified-Since is now enabled
Further evidence that this is a bug: Safari doesn't show the If-Modified-Since
header in the WebKit inspector.
Summary of why this is a bug:
1) The If-Modified-Since header, if honored, would not do what the user expects.
2) Header is only sent if "xhr.upload" property is *accessed*, not used.
3) The WebKit inspector does NOT show the header being sent. It is only seen
in wire logs.
Full reproduction steps outlined here with a proof of concept below:
1) You must GET a URI with a Last-Modified response header
2) A subsequent XHR request on the same URI must access its ".upload" property
prior to sending the PUT request.
This was reproduced on an iPhone 6 device running iOS 8 as well as in the
iPhone Simulator in XCode on iOS 8.
-->
<!DOCTYPE html>
<html>
<body style="-webkit-transform: scale(2);-webkit-transform-origin: top left;">
<input id="xhr1" type="checkbox" checked /> GET
<input id="xhr2" type="checkbox" checked /> PUT
<input id="progress" type="checkbox" checked /> Progress
<button id="submit">Submit</button>
<div id="result"></div>
<script type="text/javascript">
var el = document.getElementById.bind(document);
var url = "http://example.org/path"; // this URL should support GET and PUT operations
el('submit').onclick = function() {
if (el('xhr1').checked) {
xhr1 = new XMLHttpRequest;
xhr1.open("GET", url, false); // sync for simplicity
xhr1.send();
console.log(xhr1);
}
if (el('xhr2').checked) {
xhr2 = new XMLHttpRequest;
xhr2.open("PUT", url, false); // sync for simplicity
if (el('progress').checked) {
// simply accessing this property causes Safari to add If-Modified-Since
// we don't even need to attach listeners
xhr2.upload;
}
xhr2.send("BODY");
el('result').innerHTML = xhr2.status + " " + xhr2.responseText;
console.log(xhr2);
}
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment