POSTing Multipart/Form-Data from an XMLHttpRequest

I'm working on an add-on where I have a need to POST JSON data to the server. Unfortunately that JSON contains ampersands, so in order to use POST, I would have to encode the data in some way that the server could decode. This seemed like extra unnecessary work to me. I decided to use the "multipart/form-data" header which would allow me to send the data unmodified. Unfortunately, documentation on this was lacking. So this post is just to put up sample code on how to do this in case someone else needs it. In this case, I am simply passing data=foo where foo is the JSON.

var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);

xhr.open("POST", url, true);

var boundary = '---------------------------';
boundary += Math.floor(Math.random()*32768);
boundary += Math.floor(Math.random()*32768);
boundary += Math.floor(Math.random()*32768);
xhr.setRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary);
var body = '';
body += '--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="';
body += "data";
body += '"\r\n\r\n';
body += JSON.stringify(JAVASCRIPT OBJECT);
body += '\r\n'
body += '--' + boundary + '--';
xhr.setRequestHeader('Content-length', body.length);
xhr.onload = function() {
}
xhr.send(body);

There were no server changes required at all. The PHP handled the data the same way it would have if it was an "application/x-www-form-urlencoded" POST.

Belorussian provided by Patricia

11 Responses to “POSTing Multipart/Form-Data from an XMLHttpRequest”

  1. Wladimir Palant May 20, 2010 at 8:19 am #

    Not sure whether you've seen this: http://hacks.mozilla.org/2010/05/formdata-interface-coming-to-firefox/

    • admin May 20, 2010 at 8:34 am #

      Yep, very psyched about that.

  2. Paul Stone May 20, 2010 at 8:22 am #

    What's so difficult about using encodeURIComponent? It will encode your &'s as %26, and the server will automatically decode it.

    • admin May 20, 2010 at 8:34 am #

      It's not done automatically, you still need to decode in PHP. The problem is there is no PHP equivalent to encodeURIComponent so things can end up different. Spaces can be a problem and so could different encodings.

      My goal was to send the JSON data exactly as I had it to the server with no encoding/decoding.

      Reading all the comments here show the various problems:

      http://php.net/manual/en/function.urldecode.php

      Shows the various problems.

  3. Laurens Holst May 20, 2010 at 2:57 pm #

    I don’t get it… why not just set the Content-Type header to application/json and read it with file_get_contents("php://input");?

    • admin May 27, 2010 at 10:54 am #

      This is a cross site XHR I'm doing for an Firefox add-on.

  4. Laurens Holst May 20, 2010 at 3:04 pm #

    I mean, it’s interesting to see how it works, but it would not be my method of choice to transfer a JSON payload :) .

  5. Mook May 21, 2010 at 1:26 am #

    It's probably too late now (since you've got your code written already), but Mossop had a component to construct that:

    http://hg.oxymoronical.com/extensions/NightlyTesterTools/file/tip/src/components/nttIMultipartFormData.idl

    (He did it for things like screenshots, I believe)

  6. Frostyfrog August 9, 2010 at 7:33 am #

    I modified the code a little bit so that php can read file uploads, now my only problem is to get that Content-length in there and correct...
    file is retrieved by the script over here: http://www.thecssninja.com/javascript/drag-and-drop-upload

    xhr.open("POST", "upload.php");
    var boundary = '---------------------------';
    boundary += Math.floor(Math.random()*32768);
    boundary += Math.floor(Math.random()*32768);
    boundary += Math.floor(Math.random()*32768);
    xhr.setRequestHeader("Content-Type", 'multipart/form-data; boundary=' + boundary);
    var body = '';
    body += 'Content-Type: multipart/form-data; boundary=' + boundary;
    //body += '\r\nContent-length: '+body.length;
    body += '\r\n\r\n--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="';
    body += 'myfile"; filename="'+file.fileName+'" \r\n';
    body += "Content-Type: "+file.type;
    body += '\r\n\r\n';
    body += file.getAsBinary();
    body += '\r\n'
    body += '--' + boundary + '\r\n' + 'Content-Disposition: form-data; name="submitBtn"\r\n\r\nUpload\r\n';
    body += '--' + boundary + '--';
    xhr.setRequestHeader('Content-length', body.length);

    xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
    xhr.sendAsBinary(body);

  7. János Harsányi September 11, 2010 at 12:47 pm #

    Why must we use this boundary variable?
    Could anyone tell me why those dashes are needed?
    Thank you in advance.

  8. Tony Mechelynck October 6, 2010 at 1:52 am #

    @János: This is the MIME convention, so that the receiving program will recognise the parts of a multipart sending, see http://en.wikipedia.org/wiki/MIME#Multipart_messages

Leave a Reply:

Gravatar Image