I'm successfully sending a file from localhost:8888
to localhost:8080
(different domain in production), but I can't read the HTTP response after the transfer finishes.
Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://localhost:8888" from accessing a frame with origin "http://localhost:8080". The frame requesting access set "document.domain" to "localhost", but the frame being accessed did not. Both must set "document.domain" to the same value to allow access.
To send the file, for compatibility support, I'm trying to get this to work for <form>
based file uploads; not XHR
based. This is the basic HTML structure:
<form target="file-iframe" enctype="multipart/form-data" method="POST" action="invalid">
<input type="file" id="file-input" class="file-input" title="select files">
</form>
<iframe src="javascript:false;" id="file-iframe" name="file-iframe"></iframe>
To insert the <iframe>
element into the DOM, I do the following:
document.domain = document.domain;
var domainHack = 'javascript:document.write("<script type=text/javascript>document.domain=document.domain;</script>")';
var html = '<iframe id="file-iframe" name="file-iframe"></iframe>';
var parent = document.getElementById('wrapper');
var iframe = UTILS.createDomElement(html, parent);
iframe.src = domainHack;
UTILS.attachEvent(iframe, 'load', function(e) {
// this throws the above SecurityError
var doc = iframe.contentDocument || iframe.contentWindow.document;
// do other stuff...
});
Before I submit the form, I set the action
attribute on the <form>
to be the target cross-domain URL:
action="http://localhost:8080/"
After submitting the <form>
, the <iframe>
's load
event is fired, and I try to access the <iframe>
's content to read the HTTP response. However, doing so throws the above error since this is is a cross-origin request, and I don't have access to the <iframe>
's content.
I thought the document.domain
hack would work, but the error message is telling me that the iframe
did not set the domain to localhost
, even though I set the iframe
's src
attribute to the domainHack
variable, which seems to execute.
Any ideas as to what I might be doing wrong? How can I set document.domain
to be localhost
for both the <iframe>
and its parent (which is the current page).
I've read through several StackOverflow questions, some MDN articles, and other random results on Google, but I haven't been able to get this to work. Some stuff I've looked through already:
After digging around to try and figure this out, I finally came across a solution that seems to work for me. However, it isn't an exact answer to my question.
In summary, I'm working on supporting <form>
based file uploads. For browsers that do not support file upload via XHR
, we have to resort to the traditional <form>
submission, using a hidden <iframe>
to avoid page refresh. The form redirects the reload action into the hidden <iframe>
, and then the HTTP response is written to the body of the <iframe>
after the file has been transferred.
Due to the same-origin policy, and hence the reason I asked this question, I don't have access to the <iframe>
's content. The same-origin policy for <iframe>
s restricts how a document or script loaded from one origin can interact with a resource from another origin. Since we can't get access to the <iframe>
's document, which is where the file upload HTTP response gets written to, the server will return a redirect response to which the server will append the upload response JSON. When the <iframe>
loads, the JS will parse out the response JSON, and write it to the <iframe>
's body. Finally, since the redirect was to the same origin, we can access the <iframe>
's contents :)
A huge thanks to jQuery File Uploader; they did all the hard work ;)
https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads
JS
function setupForm() {
// form is declared outside of this scope
form = document.createElement('form');
form.setAttribute('id', 'upload-form');
form.setAttribute('target', 'target-iframe');
form.setAttribute('enctype', 'multipart/form-data');
form.setAttribute('method', 'POST');
// set the 'action' attribute before submitting the form
form.setAttribute('action', 'invalid');
};
function setupIframe() {
// iframe is declared outside of this scope
iframe = document.createElement('iframe');
/*
* iframe needs to have the 'name' attribute set so that some versions of
* IE and Firefox 3.6 don't open a new window/tab
*/
iframe.id = 'target-iframe';
iframe.name = 'target-iframe';
/*
* "javascript:false" as initial iframe src to prevent warning popups on
* HTTPS in IE6
*/
iframe.src = 'javascript:false;';
iframe.style.display = 'none';
$(iframe).bind('load', function() {
$(iframe)
.unbind('load')
.bind('load', function() {
try {
/*
* the HTTP response will have been written to the body of the iframe.
* we're assuming the server appended the response JSON to the URL,
* and did the redirect correctly
*/
var content = $(iframe).contents().find("body").html();
response = $.parseJSON(content);
if (!response) {
// handle error
return;
}
uploadFile(...); // upload the next file
}
catch (e) {
// handle error
}
});
});
/*
* insert the iframe as a sibling to the form. I don't think it really
* matters where the iframe is on the page
*/
$(form).after(iframe);
};
HTML - redirect page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
// grabs the JSON from the end of the URL
document.body.innerHTML = decodeURIComponent(window.location.search.slice(1));
</script>
</body>
</html>
The only thing left to do is set the action
attribute on the <form>
to the cross-domain URL that we're sending the upload to:
form.setAttribute('action', 'http://sub.example.com:8080/upload?id=ab123');
form.submit();
// send redirect to the iframe redirect page, where the above HTML lives
// generates URL: http://example.com/hack?%7B%22error%22%3Afalse%2C%22status%22%3A%22success%22%7D
response.sendRedirect("http://example.com/hack?{\"error\":false,\"status\":\"success\"}");
I know this is a massive hack to get around the same-origin policy with <iframe>
s, but it seems to work, and I think it's pretty good with cross-browser compatibility. I haven't tested it in all browsers, but I'll get around to doing that, and post an update.