46

#It is possible! Read below.


First of all, let me use this diagram to explain how asynchronous file uploads can be achieved:


Sorry. I've shut down one of my domains, and the image is gone now. It was a really nice image though. This was before I found out that Stack Overflow enables uploading images via Imgur.


As you can see, the trick is to let the HTTP-response load into a hidden IFRAME element instead of the page itself. (This is done by setting the target property of the FORM element when submitting the FORM with JavaScript.)

This works. However, the problem I'm facing is that the server-side script is on a different domain. The FORM-submit is a cross-domain HTTP-request. Now, the server-side script has CORS enabled which gives my web-page the rights to read the response-data of HTTP-requests made from my page to that script - but that works only if I receive the HTTP-response via AJAX, ergo, JavaScript.

However, int this case, the response is directed towards the IFRAME element. And once the XML response lands into the IFRAME, its URL will be the remove script - e.g. http://remote-domain.example/script.pl.

Unfortunately, CORS does not cover this case (at least I think) - I am not able to read the contents of the IFRAME since its URL doesn't match the URL of the page (different domain). I get this error:

Unsafe JavaScript attempt to access frame with URL hxxp://remote-domain.example/script.pl from frame with URL hxxp://example.com/outer.html. Domains, protocols and ports must match.

And since the contents of the IFRAME is an XML document, there is no JavaScript code inside the IFRAME which could make use of postMessage or something.

So my question is: How can I get the XML contents from the IFRAME?

As I said above, I am able to retrieve cross-domain HTTP-responses directly (CORS enabled), but it seems that I am not able to read cross-domain HTTP-responses once they load into an IFRAME.

And as if this question is not unsolvable enough, let me exclude these solutions:

  1. easyXDM and similar techniques which require an end-point on the remote domain,

  2. altering the XML response (to include a SCRIPT element),

  3. server-side proxy - I understand that I could have a server-side script on my domain which could serve as a proxy.

So, apart from those two solutions, can this be done?


#It can be done!!

It turns out that it is possible to forge a XHR-request (Ajax-request) which imitates a multipart/form-data FORM submit (which is used in the image above to upload the file to the server).

The trick is to use FormData constructor - read this Mozilla Hacks article for more information.

This is how you do it:

// STEP 1
// retrieve a reference to the file
// <input type="file"> elements have a "files" property
var file = input.files[0];

// STEP 2
// create a FormData instance, and append the file to it
var fd = new FormData();
fd.append('file', file);

// STEP 3
// send the FormData instance with the XHR object
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://remote-domain.example/script.pl', true);
xhr.onreadystatechange = responseHandler;
xhr.send(fd);

The above method executes an asynchronous file-uplaod, which is equivalent to the regular file-upload described in the image above and achieved by submitting this form:

<form action="http://remote-domain.example/script.pl"
        enctype="multipart/form-data" method="post">
    <input type="file" name="file">
</form>

#Like a Boss :)

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • If you can't edit the remote servers response, then no. You could use a hashchange or postmessage trick if you could edit the upload site's source. – Lime Aug 05 '11 at 06:44
  • If you don't care as much about older browsers you could use a more mordern upload method in which you get the file uploaded by JS and post it via AJAX. If this sounds like a good idea let me know and I'll post it as an answer. – Thomas Shields Aug 05 '11 at 12:40
  • @Thomas I do not care about older browsers - in fact I'm fine even if it works in only one browser `:)`. Could you elaborate a bit more? I'm afraid the server script expects a `
    ` and I'm not sure if I could create such a thing with JavaScript...
    – Šime Vidas Aug 05 '11 at 15:26
  • Is the codebase of the other domain under your control? No? Then it is not possible without proxying or just going synchronous. – BalusC Aug 07 '11 at 00:38
  • @Balus Well, I could contact the admin of the remote domain and ask for some modifications, but my explicit intent for this question is to figure out if it can be done without modifying any files or settings on the remote domain. – Šime Vidas Aug 07 '11 at 01:12
  • 1
    @Šime, Does the edited-in solution work in all major browsers? I could use an AJAX file upload once in awhile. – 700 Software Aug 08 '11 at 14:50
  • 1
    @George No, unfortunately not. The `FormData` constructor is implemented in Firefox, Chrome and Safari, but not Opera and IE. – Šime Vidas Aug 08 '11 at 15:58
  • It isnt a cross-domain solution if it doesn't work in Opera and IE... – Codler Jan 15 '13 at 13:21
  • @Codler I think you're confusing cross-domain with cross-browser. `:-)` – Šime Vidas Jan 15 '13 at 15:14
  • @ŠimeVidas, ah yea, you are right:P i mixed up, sorry. – Codler Jan 15 '13 at 17:46
  • Does this still work? – Anthony Sep 09 '17 at 03:02

4 Answers4

9

Just send a cross-domain XHR request with the data from the form instead of submitting the form. CORS is only for the former.

If you must do it the other way, negotiate with the frame using postMessage.

And since the contents of the IFRAME is an XML document, there is no JavaScript code inside the IFRAME which could make use of postMessage or something.

How does that stop you? Include a script element under the HTML or SVG namespace (<script xmlns="http://www.w3.org/1999/xhtml" type="application/ecmascript" src="..."/>) anywhere in the XML.

Eli Grey
  • 35,104
  • 14
  • 75
  • 93
  • 1
    [Read here](http://jquery.malsup.com/form/#file-upload). It is not possible to upload files using the `XMLHttpRequest` object. That's why this hidden-IFRAME-hack was introduced in the first place. As for including a SCRIPT element inside the XML response, that's a great idea. Unfortunately, my requirement is that the remote-domain is not modified in any way (I failed to mention this). So, easyXDM is out of the question, and modifying the XML response, too. The intent of my question is to figure out if this can be done without modifying the remote-domain or using a proxy. – Šime Vidas Aug 06 '11 at 13:29
  • 2
    No, it is possible to upload `Files` (which are just `Blobs`) via XHR. All current browsers & IE10 support uploading files using the W3C File API http://www.w3.org/TR/FileAPI/ - Just do `xhr.send(file_input.files[0])`. – Eli Grey Aug 07 '11 at 00:44
  • I think we have to wait 5 years before we can drop the iframe solution as fallback. – BalusC Aug 07 '11 at 00:57
  • @Eli Your comment led my to the `FormData` solution described in my answer. Here, catch some rep `:)` – Šime Vidas Aug 07 '11 at 14:21
0

I think it can't be done with the way you're describing. Normally if you have cross domain issues you can solve it by a JSONp approach but that only works for GET requests. With HTML5 you could potentially send binary with the GET request but that's just iffy.

  • A solution would be to make the remote webservice available locally by proxying the request on the local webserver. This will cause additional load for your local webserver so I can imagine that it is infeasible. If the files are small and infrequent though, this will do nicely.

  • Another solution would be to start polling the server after you've sent the file. You could send along a token and poll the status of the server using regular JSONp. This way you don't need to read from the iframe.

  • Put the whole page in an iframe that runs on the remote server. This might just move the problem, but if the XML output is the final step in some process it's quite feasible.

I'm sure you have a good reasons for the processing server to be on a different domain, but if it weren't you wouldn't have all these problems. Perhaps it's worthwhile to reconsider?

Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • #1 is explicitly excluded by question. #2 and #3 are only possible if the codebase of the other side is under full control. This is apparently not the case as per OP's comment on Mic's answer. – BalusC Aug 07 '11 at 00:40
-1

If you can, return an HTML page instead of the XML.
In that page you can use in a SCRIPT tag the command:parent.postMessage

If you have to support older browsers(< IE8 mainly), you can write and read window.name for messages below 2Mb.

Both techniques allows you to pass string data between frames of different domains.

Another technique is to use a setInterval that will call repeatedly the remote domain from the parent page using JSONP to know the status.

In any case, you will need a cooperation from the remote domain to get the data.

Mic
  • 24,812
  • 9
  • 57
  • 70
-1

The following approach is working in my setup (Firefox 3.6):

<!-- hidden target frame -->
<iframe name="load_target" id="load_target" onload="process(this);" src="#" ...>

<!-- get data from iframe after load and process them --> 
<script type="text/javascript">
    function process(iframe) {
       var data = iframe.contentWindow.document.body.innerHTML; 
       // got test data="<xml><a>b</a></xml>"
    }
</script>

It is working in Chrome as well, but it is needed to exclude a first onload call after the loading of the parent page. This is easily accomplished by setting a "global" variable which is tested in process().

ADDITION

The method works together with a form

<form action="URL" method="post" enctype="multipart/form-data" target="load_target">

which is submitted to URL. This URL needs to reside on the same domain as the parent page page.html. If data from a REMOTE_URL are to be downloaded, then URL would be a PHP proxy.php on the own domain with the content

<?php echo file_get_contents("REMOTE_URL"); ?>

This is a simple approach - however, it is probably excluded by the condition (2) of the question. I have added it here to make my answer complete.

Other approaches, considering iframes only, are discussed by Mahemoff and Georges Auberger.

Jiri Kriz
  • 9,192
  • 3
  • 29
  • 36
  • You missed the point. Cross-domain IFRAME contents cannot be read with JavaScript. Your code works only if the page in the IFRAME and the page containing that IFRAME have the same domain. Load `http://google.com` into the IFRAME and try your code - it won't work. – Šime Vidas Aug 05 '11 at 18:13
  • @Šime: thank you for your comment. I have extended my answer just to make it complete and working for "remote" pages. I am aware that this approach is probably excluded by your question. – Jiri Kriz Aug 06 '11 at 12:45
  • Proxying is explicitly disallowed by the question. – BalusC Aug 07 '11 at 00:43
  • @BalusC: I mentioned that in my answer and in my comment. – Jiri Kriz Aug 07 '11 at 07:04