Table of contents
- 1. HTTP File Upload
- 2. HTTP PUT
- 3. Downloading Files
File and Stream Guide: [ nsIScriptableIO | Accessing Files | Getting File Information | Reading from Files | Writing to Files | Moving, Copying and Deleting Files | Uploading and Downloading Files | Working With Directories ]
IO
object (
nsIScriptableIO
), which was not available in any released version of the platform (pending some fixes). There are alternative XPCOM APIs you can use, your help in updating this pages to use the supported API is very much welcome!
Other documentation on files and I/O not using the unavailable nsIScriptableIO APIs: Code snippets: File I/O, Open and Save Dialogs, Reading textual data, Writing textual data, List of file-related error codes.
The IO object and stream objects can be used to upload files to servers in various ways. The exact method depends on the type of upload that you wish to perform.
HTTP File Upload
Some web sites allow one to upload a file. This is done by using an HTML <input> element using the file
type from within a form. When the user selects a file, it can be uploaded to a server. This operation can also be performed via a script using the XMLHttpRequest object. This is a common technique for uploading files as it is compatible with existing servers. However, this is a fairly complicated method.
File upload via a form involves the use of data in a multipart stream. One part will contain the file data and the other part will contain any other form parameters. However, each part is optional. Each part is separated with a boundary string which can be any string, but should not occur within the file or elsewhere in the data being sent. This allows the host to distinguish where each part ends and the next one begins.
For example, the raw data for a typical upload might look like the following:
Content-type: multipart/form-data; boundary=abcd Content-length: 20 --abcd Content-Disposition: form-data; name="fieldname"; filename="sample.txt" Content-type: text/plain This is sample text --abcd
As can be seen, the boundary is specified in the first line as 'abcd'. When used on subsequent lines, it is always preceded by two hyphens. The first part after the initial header indicates a file that is being uploaded. The desired filename is specified as 'sample.txt'. The name specified as 'fieldname' corresponds to the name specified on an HTML input form control if this had been uploaded from an HTML form.
The content of the file to upload is specified as the body of the part, in this case with the type text/plain
. Other content types could be used to upload content of other types.
You could perform the upload by just writing content like that above. However, it may be convenient in some cases to use the stream interfaces instead. The following is a sample function which uploads a file given by the first argument, to a url given by the second argument. The name argument is the field name.
function upload(file, posturl, name) { var boundary = "--------XX" + Math.random(); var req = new XMLHttpRequest(); req.open("POST", posturl); req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary); req.setRequestHeader("Content-length", file.fileSize); req.onload = function(event) { alert(event.target.responseText); } var prefix = "--" + boundary + "\n" + "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + file.leafName + "\"\n" + "Content-type: text/plain\n\n"; var stream = IO.newInputStream(prefix, "multi"); stream.appendStream(IO.newInputStream(file, "")); stream.appendStream(IO.newInputStream("\n--" + boundary + "\n", "")); req.send(stream); }
When the content is uploaded, the 'onload' handler will display the response. You would want to alter this portion to perform whatever operations are necessary with the response data.
The boundary string is generated from a random number. You can see here that this string is used twice to mark the beginning and end of the file data. Note that in this example, no check is performed to ensure that the boundary string doesn't exist within the file. Although unlikely, an error could occur if the boundary happened to appear within the file data, so you may wish to read the file first.
The interesting aspect of this example is that it uses a multi stream, to indicate multiple parts of a stream. Here is this portion of the function above.
var stream = IO.newInputStream(prefix, "multi"); stream.appendStream(IO.newInputStream(file, "")); stream.appendStream(IO.newInputStream("\n--" + boundary + "\n", "")); req.send(stream);
Three streams are created, one for the data that comes before the file data, one for the file itself, and the third for the final boundary string. A multi stream is created using the multi
flag. A multi stream indicates a stream where additional pieces may be appended to it. Each piece will be merged together in sequence, as if all of the pieces were one larger stream. The first stream is read from the 'prefix' string. Each additional stream is added to the multi stream using nsIMultiplexInputStream.appendStream()
. The second stream is created from a file, which was supplied as an argument to the upload
function. Finally, the third stream is created from a string. The result is sent to the remote host and the streams will be read from in sequence as needed.
A multi stream works even though two of the parts are read from a string and one part is read from a file. To the XMLHttpRequest object, it is as if all of the data was in one stream all along.
Example HTTP POST for multipart/form-data (Using XPCOM syntax)
//Buffer the upload file var inStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); inStream.init(nsIFile, 1, 1, inStream.CLOSE_ON_EOF); var bufInStream = Cc["@mozilla.org/network/buffered-input-stream;1"].createInstance(Ci.nsIBufferedInputStream); bufInStream.init(inStream, 4096); //Setup the start Boundery stream var boundary = "X-------------X" + Math.random(); var startBoundaryStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); startBoundaryStream.setData("\r\n--"+boundary+"\r\n", -1); //Setup the middle Boundary stream var boundaryStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); boundaryStream.setData("\r\n--"+boundary+"\r\n", -1); //Setup the end Boundery stream var endBoundaryStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); endBoundaryStream.setData("\r\n--"+boundary+"--", -1); //Setup the binary data (file upload) mime-stream var file_mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].createInstance(Ci.nsIMIMEInputStream); file_mimeStream.addContentLength = false; file_mimeStream.addHeader("Content-disposition","form-data; name=\"aValue\"; filename=\""+nsIFile.leafName+"\""); file_mimeStream.addHeader("Content-type","application/octet-stream;"); file_mimeStream.setData(bufInStream); //Setup the "user" mime-stream var user_mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].createInstance(Ci.nsIMIMEInputStream); user_mimeStream.addContentLength = false; user_mimeStream.addHeader("Content-disposition", "form-data; name=\"user\""); var strStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); strStream.setData("SteveJobs", -1); user_mimeStream.setData(strStream); //Setup the multiplex stream to combine the necessary streams var multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"].createInstance(Ci.nsIMultiplexInputStream); multiStream.appendStream(startBoundaryStream); multiStream.appendStream(file_mimeStream); multiStream.appendStream(boundaryStream); multiStream.appendStream(user_mimeStream); multiStream.appendStream(endBoundaryStream); //Setup the POST request var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); req.open("POST", URL_VALUE, false); //false makes this an asyncronous request req.setRequestHeader("Content-type", "multipart/form-data; boundary="+boundary); req.setRequestHeader("Content-length", multiStream.available()); //Send the request (finally!) req.send(multiStream);
HTTP PUT
You can also upload a file using an HTTP PUT operation. This doesn't require the multipart content as above and is much simpler, however web servers usually need special configuration to support PUT operations.
function uploadPut(file, posturl) { var req = new XMLHttpRequest(); req.open("PUT", posturl); req.setRequestHeader("Content-type", "text/plain"); req.setRequestHeader("Content-length", file.fileSize); req.onload = function(event) { alert(event.target.responseText); } var stream = IO.newInputStream(file, ""); req.send(stream); }
In this example, a new input stream is created for a file, and is passed to the XMLHttpRequest's send
method.
Downloading Files
To come...