By LiraNuna


2010-02-04 09:18:41 8 Comments

I am trying to fake a file upload without actually using a file input from the user. The file's content will be dynamically generated from a string.

Is this possible? Have anyone ever done this before? Are there examples/theory available?

To clarify, I know how to upload a file using AJAX techniques using a hidden iframe and friends - the problem is uploading a file that is not in the form.

I am using ExtJS, but jQuery is feasible as well since ExtJS can plug into it (ext-jquery-base).

7 comments

@Josa 2014-04-04 09:32:00

If you don't need support for older browsers you can use the FormData Object, which is part of the File API:

var formData = new FormData();
var blob = new Blob(['Lorem ipsum'], { type: 'plain/text' });
formData.append('file', blob,'readme.txt');

var request = new XMLHttpRequest();
request.open('POST', 'http://example.org/upload');
request.send(formData);

File Api is supported by all current browsers (IE10+)

@Chris 2016-06-02 19:00:30

I avoid writing my own XMLHttpRequests. This is definitely my preferred answer!

@domoarigato 2016-10-05 09:14:18

This should be the accepted answer - I spent 8 hours combing through various posts, and this is what worked, and in very few lines of code.

@ixpl0 2016-11-15 07:50:38

Easy way to imitate "fake" file upload with jQuery:

var fd = new FormData();
var file = new Blob(['file contents'], {type: 'plain/text'});

fd.append('formFieldName', file, 'fileName.txt');

$.ajax({
  url: 'http://example.com/yourAddress',
  method: 'post',
  data: fd,
  processData: false,        //this...
  contentType: false         //and this is for formData type
});

@supercalifragilistichespirali 2014-01-25 10:44:52

https://stackoverflow.com/a/2198524/2914587 worked for me, after I added an extra '--' before the final boundary in the payload:

var body = '--' + boundary + '\r\n'
         // Parameter name is "file" and local filename is "temp.txt"
         + 'Content-Disposition: form-data; name="file";'
         + 'filename="temp.txt"\r\n'
         // Add the file's mime-type
         + 'Content-type: plain/text\r\n\r\n'
         + data + '\r\n'
         + '--' + boundary + '--';

@LiraNuna 2010-02-06 21:29:36

Just sharing the final result, which works - and has clean way of adding/removing parameters without hardcoding anything.

var boundary = '-----------------------------' +
            Math.floor(Math.random() * Math.pow(10, 8));

    /* Parameters go here */
var params = {
    file: {
        type: 'text/plain',
        filename: Path.utils.basename(currentTab.id),
        content: GET_CONTENT() /* File content goes here */
    },
    action: 'upload',
    overwrite: 'true',
    destination: '/'
};

var content = [];
for(var i in params) {
    content.push('--' + boundary);

    var mimeHeader = 'Content-Disposition: form-data; name="'+i+'"; ';
    if(params[i].filename)
        mimeHeader += 'filename="'+ params[i].filename +'";';
    content.push(mimeHeader);

    if(params[i].type)
        content.push('Content-Type: ' + params[i].type);

    content.push('');
    content.push(params[i].content || params[i]);
};

    /* Use your favorite toolkit here */
    /* it should still work if you can control headers and POST raw data */
Ext.Ajax.request({
    method: 'POST',
    url: 'www.example.com/upload.php',
    jsonData: content.join('\r\n'),
    headers: {
        'Content-Type': 'multipart/form-data; boundary=' + boundary,
        'Content-Length': content.length
    }
});

This was tested to work on all modern browsers, including but not limited to:

  • IE6+
  • FF 1.5+
  • Opera 9+
  • Chrome 1.0+
  • Safari 3.0+

@Mariano Desanze 2010-09-03 19:21:04

+1 Nice solution. But I think is something wrong with your algorithm. Why you use a for in for the params object? It seams like it's prepared for more than one file but the second file how will be named in the object? Where are action, overwrite, and destination used? and how they not break the code inside the for in?

@LiraNuna 2010-09-04 03:38:12

@Protron: The reason I use for( in ) is to get the keys from the description object. The code will detect if filename is set on a nested object (that describes a file to upload). The other parameters (overwrite, action, destination) are just extra parameters passed as if you used a form.

@nemesisfixx 2012-06-07 13:37:47

@LiraNuna, I see all you guys getting all magical about the -----------------------------, the only requirement by the MIME spec (see RFC 1341, sec 7.2.1) is that the the boundary commence with --followed by a valid token (see RFC 1341 sec.4). Hope this helps others know their freedom too :-)

@Swannie 2012-10-17 16:14:29

Hi, this code isn't quite correct. Content-Length is incorrectly calculated - it doesn't include the '\r\n' in the array join. Also technically this doesn't do the boundary correctly. It should be '--boundary' initially, then 'boundary' between parts and 'boundary--' afterwards. With these fixes it appears to work OK for me against Tomcat/JBoss. Great work :-)

@Andy E 2010-02-04 09:29:23

Why not just use XMLHttpRequest() with POST?

function beginQuoteFileUnquoteUpload(data)
{
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true);
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function ()
    {
        if (xhr.readyState == 4 && xhr.status == 200)
            alert("File uploaded!");
    }
    xhr.send("filedata="+encodeURIComponent(data));
}

The handler script at the server just writes the file data to a file.

EDIT
File upload is still a http post with a different content type. You can use this content type and separate your content with boundaries:

function beginQuoteFileUnquoteUpload(data)
{
    // Define a boundary, I stole this from IE but you can use any string AFAIK
    var boundary = "---------------------------7da24f2e50046";
    var xhr = new XMLHttpRequest();
    var body = '--' + boundary + '\r\n'
             // Parameter name is "file" and local filename is "temp.txt"
             + 'Content-Disposition: form-data; name="file";'
             + 'filename="temp.txt"\r\n'
             // Add the file's mime-type
             + 'Content-type: plain/text\r\n\r\n'
             + data + '\r\n'
             + boundary + '--';

    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true);
    xhr.setRequestHeader(
        "Content-type", "multipart/form-data; boundary="+boundary

    );
    xhr.onreadystatechange = function ()
    {
        if (xhr.readyState == 4 && xhr.status == 200)
            alert("File uploaded!");
    }
    xhr.send(body);
}

If you want to send additional data, you just separate each section with a boundary and describe the content-disposition and content-type headers for each section. Each header is separated by a newline and the body is separated from the headers by an additional newline. Naturally, uploading binary data in this fashion would be slightly more difficult :-)

Further edit: forgot to mention, make sure whatever boundary string isn't in the text "file" that you're sending, otherwise it will be treated as a boundary.

@LiraNuna 2010-02-04 09:30:43

Because the server will not recognize it as an uploaded 'file'.

@Luca Matteis 2010-02-04 09:31:00

I think he wants to know how to generate data.

@Andy E 2010-02-04 09:32:12

@LiraNuna: Why does that matter if you're generating the content from a string? Can't it just recognize it as a string and write it?

@LiraNuna 2010-02-04 09:33:17

That would require me to change server-side code, which is in my case, impossible (remote service).

@Andy E 2010-02-04 12:11:43

@LiraNuna: maybe my edit will help you?

@LiraNuna 2010-02-04 21:47:24

Maybe I'm doing something bad, but I just can't get the server to recognize the request as valid POST.

@LiraNuna 2010-02-05 00:49:21

I edited the answer to be more correct.

@Andy E 2010-02-05 09:39:59

@LiraNuna: My bad, thanks for the fix.

@LiraNuna 2010-02-05 19:42:02

@Andy: It's okay, I had to read the RFC several times to get it working!

@Rubens Farias 2010-02-04 09:35:03

A file upload it's just a POST request with that file content properly encoded and with an special multipart/formdata header. You need to use that <input type=file /> because your browser security forbid you to access user disk directly.

As you don't need to read user disk, YES, you can fake it using Javascript. It will be just a XMLHttpRequest. To forge an "authentic" upload request, you can install Fiddler and inspect your outgoing request.

You'll need to encode that file correctly, so this link can be very useful: RFC 2388: Returning Values from Forms: multipart/form-data

@LiraNuna 2010-02-04 09:37:07

What should go in that request then? how is that protocol defined? how to fake it?

@Rubens Farias 2010-02-04 09:40:08

that isn't a protocol, it's just a regular HTTP request; I updated my answer

@LiraNuna 2010-02-04 09:49:41

I didn't use Fiddler (Linux user here), but Firebug does show how it should look. This brings me one step closer. I am upvoting as it is helpful, but not yet selecting the answer.

@Tom Bartel 2010-02-04 09:53:41

I just caught this POST_DATA string with the Firefox TamperData addon. I submitted a form with one type="file" field named "myfile" and a submit button named "btn-submit" with value "Upload". The contents of the uploaded file are

Line One
Line Two
Line Three

So here is the POST_DATA string:

-----------------------------192642264827446\r\n
Content-Disposition: form-data;    \n
name="myfile"; filename="local-file-name.txt"\r\n
Content-Type: text/plain\r\n
\r\n
Line \n
One\r\n
Line Two\r\n
Line Three\r\n
\r\n
-----------------------------192642264827446\n
\r\n
Content-Disposition: form-data; name="btn-submit"\r\n
\r\n
Upload\n
\r\n
-----------------------------192642264827446--\r\n

I'm not sure what the number means (192642264827446), but that should not be too hard to find out.

@John La Rooy 2010-02-04 10:16:21

I reformatted the POST_DATA to make it easier to read, the 192642264827446 looks like a boundary marker

@Tom Bartel 2010-02-04 11:13:14

Thanks, gnibbler. Yeah, I thought it might be something like a boundary marker, probably just some random number.

@Andy E 2010-02-04 12:14:29

Yeah, it's a boundary marker. If you check the multipart/form-data header, the boundary will follow it. The random number at the end is to avoid any conflictions with the data being sent.

Related Questions

Sponsored Content

33 Answered Questions

[SOLVED] How can I upload files asynchronously?

80 Answered Questions

[SOLVED] How do I remove a particular element from an array in JavaScript?

  • 2011-04-23 22:17:18
  • Walker
  • 5904945 View
  • 7364 Score
  • 80 Answer
  • Tags:   javascript arrays

3 Answered Questions

69 Answered Questions

[SOLVED] What is the most efficient way to deep clone an object in JavaScript?

27 Answered Questions

[SOLVED] What does "use strict" do in JavaScript, and what is the reasoning behind it?

57 Answered Questions

[SOLVED] How do I include a JavaScript file in another JavaScript file?

38 Answered Questions

[SOLVED] How do I remove a property from a JavaScript object?

86 Answered Questions

[SOLVED] How do JavaScript closures work?

33 Answered Questions

[SOLVED] For-each over an array in JavaScript?

Sponsored Content