CORS request is preflighted, but it seems like it should not be
Asked Answered
T

2

14

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 in Chrome 27 and Firefox 10.8.3. I haven't tested any other browsers.

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:

UPDATE: 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);

Does anyone know why this is being preflighted?

Ten answered 12/6, 2013 at 3:37 Comment(0)
T
21

I ended up checking out the Webkit source code in an attempt to figure this out (after Google did not yield any helpful hits). It turns out that Webkit will force any cross-origin request to be preflighted simply if you register an onprogress event handler. I'm not entirely sure, even after reading the code comments, why this logic was applied.

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));
        }
    }

    ...
}


UPDATE: Firefox applies the same logic as Webkit, it appears. Here is the relevant code from nsXMLHttpRequest.cpp:

nsresult
nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
{
    ...

    // Check if we need to do a preflight request.
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
    NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);

    nsAutoCString method;
    httpChannel->GetRequestMethod(method);
    if (!mCORSUnsafeHeaders.IsEmpty() ||
        (mUpload && mUpload->HasListeners()) ||
        (!method.LowerCaseEqualsLiteral("get") &&
         !method.LowerCaseEqualsLiteral("post") &&
         !method.LowerCaseEqualsLiteral("head"))) {
      mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
    }

    ...
}

Notice the mUpload && mUpload->HasListeners() portion of the conditional.

Seems like Webkit and Firefox (and possibly others) 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 comment.

Ten answered 12/6, 2013 at 3:37 Comment(12)
That is an amazing find! Its weird for client-side code to trigger a preflight; there is nothing in the spec about that. Also the comment doesn't make things clearer. I would recommend bringing this up on the WebKit boards for clarification.Geminius
@Geminius I suspect this logic isn't limited to Webkit. I also ran into the same issue when using Firefox. I haven't tested IE10 yet. I intend to take a peek at The Firefox source as well and see if I can confirm my suspicion.Ten
Can you update the original question to include the client-side JavaScript code you are using to make this request? That will help put things in context.Geminius
@Geminius I found what appears to be the same logic in Firefox source code and have updated my answer. I also confirmed that IE10 behaves this way as well, but I can't look at the source for obvious reasons. I'm not sure why Webkit, FF, IE all apparently decided to violate the spec here.Ten
@Geminius I've updated my question w/ the client-side code. Note that if I comment out the code that sets the upload onprogress handler, the CORS request is not preflighted.Ten
I've filed a Webkit bug, though this logic appears in Firefox and IE10 as well. bugs.webkit.org/show_bug.cgi?id=117577Ten
So it sounds like this is not a bug. Search for the 2nd instance of the word "preflight" in the XHR spec: w3.org/TR/XMLHttpRequest This indicates that upload events force a preflight, since upload events introduce new information that wasn't available before CORS. It is however confusing that this isn't captured in the CORS spec.Geminius
Yep, you are right. The XHR spec does seem to suggest that upload events force a preflight. When looking at the CORS spec (and the Webkit/FF code) mentions of a "force preflight" flag make a bit more sense in this context. I suppose I was confused that this was not mentioned specifically in the CORS spec, though the spec does mention that the "force preflight flag" must be unset. Thanks for your input on this.Ten
Glad to find this. Felt like a quantum bug: the CORS error only happened when I had an upload event listener (preflight CORS errors if the response is a redirect)Pasteurism
So is there a way to register to on progress events for a CORS file upload without the browser performing a pre-flight check? I have control over the server I am posting the file to and the client side code but adding the on progress is causing the browser to strip the headers for the file upload (I'm assuming because it is failing the pre flight check?). I have "Allow-Origin: *" set on the headers along with allow-methods: POST, GET but still not workingLilylilyan
@Lilylilyan no, there is notTen
If you use angular and angular http client, to disable progress tracking do: const optons = {headers, reportProgress: false}; this.http.post(uploadUrl, formData, optons)...Lidstone
G
1

My guess is that the "boundary" on the Content-Type header is causing issues. If you are able to reproduce this, it should be filed as a browser bug, since the spec states that the Content-Type header check should exclude parameters.

Geminius answered 12/6, 2013 at 4:5 Comment(1)
That was my initial suspicion, but it's more sinister then that. I just checked out the Webkit source, and figured out why this is happening. I'll post my answer as a community wiki.Ten

© 2022 - 2024 — McMap. All rights reserved.