Programmer to ProgrammerTM | |||||
|
|
|
|
|
|
|
|
|
|
|
| |||||||||||||||||||
The
ASPToday Article March 16, 2000 |
Previous article - March 15, 2000 |
Next article - March 17, 2000 | |||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
ABSTRACT |
| ||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
Article Discussion | Rate this article | Related Links | Index Entries | ||||||||
ARTICLE |
File uploading from a browser is an easy way to transfer files from client to server. Most browsers, from the third generation browsers from Netscape and Microsoft (with an add-on) and up, can upload files to server. No special access or software has to be provided to the user.
Many ASP components have been developed for file upload, including:
The beginning of this article should give you enough information to build such components, which are usually written in VB, C++ or Java.
The problem with those components is they are third party products and they are not part of standard ASP. As third party components, they have to be installed on the server. This means you have to copy the DLL on the server and register it. Most hosting organizations do not allow such setups on their servers since it produces configuration issues (especially for virtual hosting). A second disadvantage is that most of them are not free, source code is not provided and so, you cannot customize them as you want.
These are all the reasons I needed to write a VBScript code that gives a complete upload solution. It was not an obvious choice because VBScript is a scripting language designed to work only with datatype variants and it does not provide many built-in functions to manage binary data and byte arrays.
To understand the uploading process, you first need to know the way data is sent from browser to server with the HTTP protocol. That means understanding the " multipart/form-data " submit.
Usually, you use an HTML FORM to submit data from the browser to the web server. That form can contain text fields, checkbox, button and also a file type control to upload files. The user fills in the form with his data and submits the form to the server.
The enctype attribute of the <FORM> element specifies the content type used to encode the form data set for submission to the server. The enctype attribute used by default is " application/x-www-form-urlencoded ". However, that enctype is inefficient for sending large quantities of text, text containing non-ASCII characters or binary data to the server. The content type " multipart/form-data " should be used to submit forms in case of file uploading. Actually, it can be used to submit files and binary data.
A " multipart/form-data " message contains a series of parts, where each part is expected to contain:
For a file type control, the part contains some more information:
Those headers are followed by the binary or text content of the control.
The following example illustrates " multipart/form-data " encoding. The client would have this form in the browser:
<FORM METHOD="POST" ENCTYPE="multipart/form-data" ACTION="upload.asp"> <INPUT TYPE="Text" NAME="email" VALUE="[email protected]"><BR> <INPUT TYPE="file" NAME="blob"><BR> <INPUT TYPE="submit" NAME="Enter"> </FORM>
If this form is submitted, the following request can be read on the server:
-----------------------------7cf87224d2020a Content-Disposition: form-data; name="email" [email protected] -----------------------------7cf87224d2020a Content-Disposition: form-data; name="blob"; filename="c:\image.gif" Content-Type: image/pjpeg ÿØÿàJFIFHHÿÛC… …ÿÙ -----------------------------7cf87224d2020a Content-Disposition: form-data; name="Enter" Submit Query -----------------------------7cf87224d2020a--
That content can be displayed if it is sent back as a response to the client. The binary data should be read and written with Request.binaryRead and Response.binaryWrite methods.
<% Response.BinaryWrite(Request.BinaryRead(Request.TotalBytes)) %>
You can see that parts of the response are delimited by boundaries:
-----------------------------7cf87224d2020a
and the last boundary is followed by: ' -- '
There is one Content-Disposition for each control. The name attribute identifies the control sent by the HTML form ( email , blob and Enter ). For a file type control (blob), the file name is also part of Content-Disposition header and, a Content-Type header gives the content type of the binary data.
All the content presented above has to be parsed. In VB or C++, this is quite obvious because many objects and methods are provided for that. To do this in VBScript, we have to use some functions provided by the language and get round the problem of unicode variant string used in VBScript.
The raw data is in a binary format, so we have to use VBScript function designed to manage binary data. As we can consider the raw data as a string of bytes, the MidB , InstrB and LenB functions become very useful. We have to avoid classic VBScript strings, which are unicode strings not suitable for parsing single bytes.
These are the only VBScript functions available to parse bytes. We still need a method to get a unicode string from the parsed data in order to use that string in the VBScript code. It should also be useful to have a function that converts unicode string to a bytes string in order to use that string as an argument in InstrB .
I wrote two functions for that purpose, they are called getString() and getByteString() . They will be explained later in this article.
The parsed data are stored in VBScript Dictionary objects. Dictionary objects are hash table objects that store (key, item) pairs. This object is part of VBScript and ASP2.0.
A first Dictionary object " UploadRequest " is defined. That dictionary object contains all the controls submitted by the upload form. Keys are the names of the controls and Items are themselves dictionary objects that contains information about the control:
"ControlName1", Dictionary control1 "ControlName2", Dictionary control2
A Dictionary object that represents a control contains the following (key, item) pairs.
"Value", String or binary content "FileName", Name of uploaded file "ContentType", ContentType of uploaded file
Combining all, we have for example:
UploadRequest :
"email", UploadControl 1 :
"Value", [email protected]
"blob" , UploadControl 2 :
"filename", C:/image/file.gif
"ContentType" : image/gif
"Value" : GIF89ai…
This is an "object" approach quite useful to access and use data afterwards.
Here is the code to parse, read and record upload controls. The process is done by a routine "BuildUploadRequest" ; this routine takes only one argument that is the raw binary data RequestBin .
Sub BuildUploadRequest(RequestBin)
First we try to find the boundary. This boundary is useful to know when the loop on controls ends.
'Get the boundary PosBeg = 1 PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(13))) boundary = MidB(RequestBin,PosBeg,PosEnd-PosBeg) boundaryPos = InstrB(1,RequestBin,boundary)
A problem is that InstrB needs byte strings as arguments. A function was written for that purpose: getByteString(String) method can convert the unicode VBScript string to a byte string. This function is described at the end of the code explanations.
Let’s do a loop until we find the closing boundary.
'Get all data inside the boundaries Do until (boundaryPos=InstrB(RequestBin,boundary & getByteString("--")))
For each step in the loop, we process one control. All data about that control are saved in a dictionary object. A new dictionary object UploadControl is created at each loop.
'Members variable of objects are put in a dictionary object Dim UploadControl Set UploadControl = CreateObject("Scripting.Dictionary")
We first get the name of the control, which can be found in the " Content-Disposition " header. The end of the name is delimited by the " character or chr(34).
'Get an object name Pos = InstrB(BoundaryPos,RequestBin,_ getByteString("Content-Disposition")) Pos = InstrB(Pos,RequestBin,getByteString("name=")) PosBeg = Pos+6 PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(34))) Name = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg))
Here we have to test if the control is a file type control or a text type control. If the control is a text type control, no other data than its name are provided. If the control is a file type control, we can get more information such as file name and Content-Type.
PosFile=InstrB(BoundaryPos,RequestBin,getByteString("filename=")) PosBound = InstrB(PosEnd,RequestBin,boundary) 'Test if object is of file type If PosFile<>0 AND (PosFile<PosBound) Then
If control is a file type control, we parse the path and file name and add them to the dictionary object of the control. The parsed file name is a string of bytes, it is necessary to convert it to unicode so that it can be used as a variant string variable. This is done by the getString() method defined at the end.
'Get Filename, content-type and content of file PosBeg = PosFile + 10 PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(34))) FileName = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg)) 'Add filename to dictionary object UploadControl.Add "FileName", FileName Pos = InstrB(PosEnd,RequestBin,getByteString("Content-Type:")) PosBeg = Pos+14 PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(13))) 'Add content-type to dictionary object ContentType = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg)) UploadControl.Add "ContentType",ContentType
Now we can get the core content of the file. The content does not have to be converted because it is a binary content. It should be saved to a file system or put as binary long object (blob) in a database.
'Get content of object PosBeg = PosEnd+4 PosEnd = InstrB(PosBeg,RequestBin,boundary)-2 Value = MidB(RequestBin,PosBeg,PosEnd-PosBeg) Else
In the case of a text type control, there is no other data to parse than the content. The content is converted to a unicode string so that it can be used later in the VBScript code.
'Get content of object Pos = InstrB(Pos,RequestBin,getByteString(chr(13))) PosBeg = Pos+4 PosEnd = InstrB(PosBeg,RequestBin,boundary)-2 Value = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg)) End If
The content is added to the dictionary object. The key is set to " Value ", and the item is the content. That content can be either a string or a binary data depending on the type of the control.
'Add content to dictionary object UploadControl.Add "Value" , Value
Finally, the dictionary object of the control is added to the global dictionary object. The key used is the name of the control. The item is the dictionary objet UploadControl we have just built.
'Add dictionary object to main dictionary UploadRequest.Add name, UploadControl 'Loop to next object BoundaryPos=InstrB(BoundaryPos+LenB(boundary),RequestBin,boundary) Loop End Sub
Here is the function used to convert a byte string to a unicode string.
'Byte string to string conversion Function getString(StringBin) getString ="" For intCount = 1 to LenB(StringBin) getString = getString & chr(AscB(MidB(StringBin,intCount,1))) Next End Function
The other function I wrote was GetByteString, to convert a string to a byte string. Here it is:
'String to byte string conversion Function getByteString(StringStr) For i = 1 to Len(StringStr) char = Mid(StringStr,i,1) getByteString = getByteString & chrB(AscB(char)) Next End Function
Here are sample uses of the upload script developed here. Samples files and code are provided with the download file of this article. Extract the zip file to a directory and configure a virtual directory for your web server. You can test and launch uploadForm.html from your browser.
Here is the way to call the upload BuildUploadRequest method. You have first to define the global dictionary UploadRequest . Then you have to call the BuilUploadRequest method and pass to the request raw binary data in the argument.
byteCount = Request.TotalBytes RequestBin = Request.BinaryRead(byteCount) Dim UploadRequest Set UploadRequest = CreateObject("Scripting.Dictionary") BuildUploadRequest RequestBin
The data is parsed and saved in the dictionary object that you can retrieve with Item() method. These item data can be stored in VBScript variables and used everywhere in the code. Data can simply be sent back as response to the client, used in ASP code, written to a file or put in a database field.
The data of the UploadRequest object can be accessed by the Item("key") function. Let’s consider a scenario where you would like to access the value of the email control; this can be done by:
email = UploadRequest.Item("email").Item("Value")
As this is a text type control, content is a string and can be used as any other VBScript string. For binary data, content can be retrieved in the same way.
picture = UploadRequest.Item("blob").Item("Value")
It is also possible to access other information like file name or content-type. They are text type controls.
contentType = UploadRequest.Item("blob").Item("ContentType") filepathname = UploadRequest.Item("blob").Item("FileName")
The uploaded data can be used in VBScript code as any other variable. They can, for example, be sent back as response to the client.
Your email is : <%=email%> File name of you picture is <%=filepathname%> File type of your picture is <%=contentType%>
Binary data can also be sent back to the client. A content-type has to be set and the binary data should be written with the BinaryWrite method.
Response.ContentType = contentType Response.BinaryWrite picture
In the case of a file type control, the goal is usually to store the binary data in a file or in a database field rather than sending them back to the client. This "persistent" feature is one of the main goals of file uploading. The FileSystem object can be used to store the uploaded file to the file system of the server.
First let us create a FileSystem object:
'Create FileSytemObject Component Set ScriptObject = Server.CreateObject("Scripting.FileSystemObject")
Create a file in a directory with the fileSystem object. That directory can be absolute and refer directly to the file system (e.g. c:\temp ). The directory can also be relative to a virtual directory defined on the web server. This virtual directory is mapped to the absolute directory with the mappath method and the PATH_INFO server variable.
The Write method needs a unicode string as argument so we convert the byte array to a string. The Write method is responsible for converting that unicode string and writes it using ASCII format. This builds a file that contains the binary content of our original byte string. I have called that file ' uploaded+filename ', just for distinguishing the file, but you can choose whatever filename you like:
'Create and Write to a File Set MyFile = ScriptObject.CreateTextFile(Server.mappath(Request.ServerVariables_ ("PATH_INFO")) & "uploaded" & filename) MyFile.Write getString(value) MyFile.Close
The data could also be stored in a database. The content-type should also be stored in the database to be able to display the data afterwards. You first have to establish a connection to your database, assuming you already have set up the appropriate DSN:
Set conn = Server.CreateObject("ADODB.Connection") conn.open "DSN=wroxdns","user","pass"
Then create a Recordset from that connection:
sql = "SELECT PHOTO, CONTENTTYPE FROM MYTABLE" Set rs = Server.CreateObject("ADODB.Recordset") rs.Open sql, conn, 3, 3
When the Recordset is created, you have to put the binary data in the blob field of the database:
picturechunk = picture & chrB(0) rs.Fields("PICTURE").appendChunk picturechunk rs.Fields("CONTENTTYPE") = contentType rs.Update conn.close
I had to get round a small bug in appendChunk method. In fact, I noticed that when my binary data had an odd number of bytes, the appendChunk method did not transfer the last byte! A solution was to add a chr(0) to be sure to transfer all the bytes. Maybe there is another solution for that, if so, let me know.
To get the image from the database, use the same Recordset and send it back as response to the client with the right content type.
Response.contentType = rs.Fields("CONTENTTYPE") size = rs.Fields("PICTURE").ActualSize blob = rs.Fields("PICTURE").GetChunk(size) Response.binarywrite blob
This article presents a complete VBScript solution to uploading files. This code is pure VBScript and is independent of third party products.
It focuses first on the upload process (HTML form submission with the content type " multipart/form-data "). Then the uploading VBScript code is explained in detail. This starts with a brief recall of VBScript functions needed to manipulate strings and byte arrays. I then presented the code of the upload script and the structure of the uploaded data.
Finally we showed several uses for this script, from simply using an uploaded variable in ASP code to the storage of an uploaded file in a database or in the file system.
|
| |||||||
| |||||||||||||||
|
ASPToday is brought to you by
Wrox Press (www.wrox.com). Please see our terms and conditions and privacy policy. ASPToday is optimised for Microsoft Internet Explorer 5 browsers. Please report any website problems to [email protected]. Copyright © 2001 Wrox Press. All Rights Reserved. |