The following cross-origin POST request, with a content-type of multipart/form-data and only simple headers is preflighted. According to the W3C spec, unless I am reading it wrong, it should not be preflighted. I've confirmed this happens with Chrome. I have not tested Safari, but the code I will point out at the end of this report shows that this is a Webkit issue. Here are the request headers, etc: Request URL:http://192.168.130.135:8081/upload/receiver Request Method:POST Status Code:200 OK Request Headersview source Accept:*/* Accept-Encoding:gzip,deflate,sdch Accept-Language:en-US,en;q=0.8 Connection:keep-alive Content-Length:27129 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryix5VzTyVtCMwcNv6 Host:192.168.130.135:8081 Origin:http://192.168.130.135:8080 Referer:http://192.168.130.135:8080/test/raytest-jquery.html User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.37 Safari/537.36 And here is the OPTIONS (preflight) request: Request URL:http://192.168.130.135:8081/upload/receiver Request Method:OPTIONS Status Code:200 OK Request Headersview source Accept:*/* Accept-Encoding:gzip,deflate,sdch Accept-Language:en-US,en;q=0.8 Access-Control-Request-Headers:origin, content-type Access-Control-Request-Method:POST Connection:keep-alive Host:192.168.130.135:8081 Origin:http://192.168.130.135:8080 Referer:http://192.168.130.135:8080/test/raytest-jquery.html User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.37 Safari/537.36 The spec seems pretty clear: Only simple headers: CHECK Only simple methods: CHECK Here's some simple client-side code that will reproduce this: var xhr = new XMLHttpRequest(), formData = new FormData(); formData.append('myfile', someFileObj); xhr.upload.progress = function(e) { //insert upload progress logic here }; xhr.open('POST', 'http://192.168.130.135:8080/upload/receiver', true); xhr.send(formData); According to the W3C spec, this request should not be preflighted, as far as I can tell. So, I dove into the Webkit source code, and found this in XMLHttpRequest.cpp: void XMLHttpRequest::createRequest(ExceptionCode& ec) { ... options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight; ... // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all. // Also, only async requests support upload progress events. bool uploadEvents = false; if (m_async) { m_progressEventThrottle.dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent)); if (m_requestEntityBody && m_upload) { uploadEvents = m_upload->hasEventListeners(); m_upload->dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadstartEvent)); } } ... } The same behavior is exhibited in Firefox, and I confirmed that the Firefox source uses the same logic. IE10 also does this, but I can't verify since Microsoft doesn't make the source available. Seems like Webkit, Firefox, and IE10 have inserted some logic into their preflight-determination code that is not sanctioned by the W3C spec. If I'm missing something in the spec, please let me know.
Turns out this is not a bug. The spec for XMLHttpRequest does mention that upload progress event handlers should cause the "force preflight" flag to be set. I was a bit confused when this was not specifically mentioned in the CORS spec, even though that spec does reference the existence of a "force preflight" flag.