Javascript: Uploading a file... without a file
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 tech开发者_如何学JAVAniques 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).
If you don't need support for older browsers, you can use the FormData
Object, which is part of the File API:
const formData = new FormData();
const blob = new Blob(['Lorem ipsum'], { type: 'plain/text' });
formData.append('file', blob, 'readme.txt');
const request = new XMLHttpRequest();
request.open('POST', 'http://example.org/upload');
request.send(formData);
File API is supported by all current browsers (IE10+)
- https://developer.mozilla.org/en-US/docs/Web/API/FormData
- http://caniuse.com/#feat=fileapi
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.
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+
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
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
});
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.
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 + '--';
精彩评论