Last active
August 29, 2015 14:07
-
-
Save lsegal/2ba84edf85422887a0c5 to your computer and use it in GitHub Desktop.
iOS 8 Safari If-Modified-Since caching bug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
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