设置setRequestHeader Content-Type会导致POST请求变成OPTIONS请求

3

如果你不知道答案,点个赞吧。

function local_upload_photo(form_data)
{    
    var boundary    = "-----------------------------" + (new Date).getTime();
    var CRLF        = "\r\n";
    var parts       = [];

    // form text fields
    for(var i in form_data)
    {
        if(form_data.hasOwnProperty(i))
        {
            var part = 'Content-Disposition: form-data; name="' + i + '"' + CRLF + CRLF + form_data[i] + CRLF;

            parts.push(part);
        }
    }

    var data    = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQImWNgYGAAAAAEAAGjChXjAAAAAElFTkSuQmCC');

    // photo file
    var part = 'Content-Disposition: form-data; name="file1"; filename="me.gif"' + CRLF + "Content-Type: image/gif" + CRLF + CRLF + data + CRLF;

    //console.log( base64_encode(element.files[0].getAsBinary()) );

    parts.push(part);

    // prepare the query
    var request = 'Content-Type: multipart/form-data; boundary=' + boundary + CRLF + CRLF; 
        // content-length is missing    
        request += "--" + boundary + CRLF;
        request += parts.join("--" + boundary + CRLF);
        request += "--" + boundary + "--" + CRLF;

    // send the data
    var xhr      = new XMLHttpRequest();

    xhr.open('post', 'http://upload.guy.com/storage.php');

    xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
    xhr.setRequestHeader('Content-Length', String(request.length));


    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
           console.log(xhr.responseText);
        }

    };

    // finally send the request as binary data
    xhr.sendAsBinary(request);
}

故事:用户来到 guy.lt 并在 URL 栏中使用 javascript: 运行给出的 JS 代码。这应该会将一个文件上传到 storage.guy.lt,你可以在其中看到 base64。然而,同源策略在此处生效并不允许它。一种解决方案是简单地要求人们在 storage.guy.lt 上执行相同的操作或者只是将上传操作移动到 guy.lt,但是客户不同意。
因此,在搜索了一段时间后,我发现了 Facebook 网站。现在,如果你监视 FB 上传照片的过程,你会注意到用户是从 facebook.com 进行此操作的,但 POST 请求(也可能使用 XMLHttpRequest)被发送到 uploads.facebook.com。他们是如何做到的?
在一个地方,他们加载带有内容的 iframe http://static.ak.facebook.com/common/redirectiframe.html
if (navigator && navigator.userAgent && !(parseInt((/Gecko\/([0-9]+)/.exec(navigator.userAgent) || []).pop()) <= 20060508)) {
        //document.domain='facebook.com';
    }

我尝试在我的情况下做类似的事情,但这似乎没有任何共同之处。
1个回答

2

不完全清楚问题是什么,但我来试着回答一下:

  1. What you're seeing with the POST 'becoming' an OPTIONS request is preflighting - when making a cross-domain XHR request, the browser decides under certain circumstances (for example, when making a POST with the content-type set to something other than application/x-www-form-urlencoded, multipart/form-data, or text/plain) to first check if the request will be allowed by the server before actually making it.

  2. You haven't mentioned if you have control over the server-side of things but if you do, you have the option of responding to the OPTIONS request with

    Access-Control-Allow-Origin: http://guy.lt
    Access-Control-Allow-Methods: POST, OPTIONS
    

    to allow your JavaScript upload to go through.

  3. The approach Facebook seems to be taking is that of setting the document.domain attribute which, if the parent window (www.facebook.com) and an iframe from another server on the same domain (uploads.facebook.com) set to the same value (facebook.com), scripts from each one can talk to the other1. This can be used to do cross-[sub]domain requests to the original domain of the window or the iframe. So the parent window from www.facebook.com can call JavaScript loaded from uploads.facebook.com in the iframe which will then allow requests back to uploads.facebook.com. This blog post describes this technique in more detail.


好的。我不太理解JS的“安全”策略。我的域名是foo.com。我无法将多部分数据POST到bar.foo.com,但是我可以在foo.com上动态创建一个iframe来加载bar.foo.com,并附加JS以允许发布多部分表单数据。那么为什么要限制从foo.com直接向bar.foo.com POST多部分表单数据呢? : / - Gajus
想象一下 - 如果我在sites.google.com上托管一个站点,我的JavaScript是否应该被允许直接与google.com通信?在您的情况下,您认为这是一个问题,因为根域和子域都在您的控制之下,但在sites.google.com的情况下,从安全角度来看,这是一件好事。但另一方面,允许这种情况(您的情况)有一个有效的用例,这就是浏览器制造商使用document.domain包含此解决方法的原因。 - no.good.at.coding
好的,那么为什么在 foo.com 上设置 document.domainbar.foo.com 是受限制的呢? - Gajus
如果我被允许更改baz.foo.comdocument.domain,那么我可以通过iframe在您的网站上调用任何JavaScript或读取任何字段(例如用户名/密码或信用卡字段)!这不是您希望默认允许的事情!但是,如果我们双方决定将我们的document.domain值设置为foo.com,我们可以允许跨脚本,这可能很有用。关键是要让双方都同意一个共同的值,如果子域名被允许,这可能并不总是达成共识。设置较小的后缀是可以的,因为子域名已经属于那里了。 - no.good.at.coding
嗯,你的一些论点并不是很有道理。例如,如果您是 foo.bar.com,而 bar.com 是您的托管公司/子域提供商,他们已经完全访问了您的隐私。 - Gajus
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接