An ActiveX control I have written allows users to submit a file to a Web server. This component uses the WinSocket control to submit files using the file transfer protocol (FTP). It only worked in Internet Explorer 3.02 and higher, and several clients used Netscape, so I had to find another solution.
As I searched the Internet and several newsgroups I found out that I wasn’t the only one. An 11 March 1999, 15Seconds article by Doug Dean, “Down and Dirty Browser Uploading with a VB ASP Component,” gave me a very good hint.
Dean’s component was simple and easy to use. Dean’s article said, however, that he had left us with the problem of “a multiple element header.” Consequently there was some work still to be done.
Before I started to work on my own component, I wanted to know what kind of functionalities other upload controls offered. So I looked at three other well-known components: the upload component of Software Artisans, the upload component of ASPUpload, and the Microsoft Posting Acceptor.
When I compared these components I found out that my component should meet the following requirements:
The HTML form that submits the file should be a black box to the upload component. By this I mean that the component should accept all kinds of form fields and that the form field names and their values should be parsed by the component.
It should be possible to give up a path to upload to.
It should be possible to put a limit on the size of the files that were going to be submitted to the Web server.
The component should be able to handle multiple files.
The component should have a robust error handler.
The component should perform well.
The component should work in Netscape Navigator as well as in Microsoft Internet Explorer.
It should be possible to save files into a database.
Only certain groups of users should be allowed to upload a file to the Web server.
As I looked at my list I saw that I had quite a challenge.Solving the Problem
The first thing I had to do is create an HTML form that contained two form types: a simple text box and a file box. This gives the following code:
Use the ENCTYPE="multipart/form-data" to enable the form to submit a file. We also need a file to receive the file. This is how it looks:
Listing 2: Upload.asp
<%@ Language=VBScript %>
<%
Option explicit
Response.Buffer = True
On Error Resume Next
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim objUpload
Dim lngMaxFileBytes
Dim strUploadPath
Dim varResult
lngMaxFileBytes = 10000
strUploadPath = "c:\inetpub\wwwroot\upload\"
Set objUpload = Server.CreateObject("pjUploadFile.clsUpload")
If Err.Number <> 0 Then
Response.Write "The component wasn’t registered"
Else
varResult = objUpload.DoUpload (lngMaxFileBytes, strUploadPath)
Set objUpload = Nothing
‘Write the result
Dim i
For i = 0 to UBound(varResult,1)
Response.Write varResult(i,0) & " : " & varResult(i,1) & "<br>"
Next
End If
End If
%>
As you can see I wanted to be able to set the following two variables:
lngMaxFileBytes - the maximum amount of bytes a file may contain, and
strUploadPath - the location on the Web server were the file should be written.
I also added a little error handler to check whether the upload component was properly registered on the Web server. This was the only error I wanted to check for in the ASP page. If any other error occurred, I wanted the upload component to handle it.
The last thing I did was to declare a variable varReturn. This variable should receive the return value from the upload component. This return value should contain all the form field names and their values. As you can see by the FOR NEXT loop, this return value must be an array.
This was the easy part. Now we have to create an ActiveX component, which should handle the submitted form. So open up Visual Basic 6 and choose an ActiveX project (see Figure 1).
Figure 1: Creating an ActiveX dll Project
The first thing I did then was to add a reference to the Active Server Pages Object library. For this I clicked on Project on the menu bar and then selected References. In the following drop-down list I selected the Active Server Pages Object library (see Figure 2).
Figure 2 : Project References
With this library we are able to use the Request object of Active Server Pages. Before you can really use it, you must paste in the following code:
Option Explicit
Private MyScriptingContext As ScriptingContext
Private MyRequest As Request
Private MyResponse As Request
Public Sub OnStartPage(PassedScriptingContext As ScriptingContext)
Set MyScriptingContext = PassedScriptingContext
Set MyRequest = MyScriptingContext.Request
Set MyResponse = MySriptingContext.Response
End Sub
Why do we need the Active Server Pages Object library? Well, with the help of the Request object we can read the http stream which was sent to us by the upload.htm. There is however a big "but" … When we try to read the form field names and their according values with, for example, Request.Form("txtTitle"), we aren’t able to read the rest of the raw data that was sent to us.
However, we can use the Request.TotalBytes and the Request.BinaryRead to read the data that was sent to us.
The first time I used the code from Doug Dean resulted in the following:
'~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dim varByteCount
Dim binArray() As Byte
'~~~~~ BYTE COUNT OF RAW FORM DATA ~~~~~~~~~~~~
varByteCount = MyRequest.TotalBytes
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'~~~~~ PLACE RAW DATA INTO BYTE ARRAY ~~~~~~~~~
ReDim binArray(varByteCount)
binArray = MyRequest.BinaryRead(varByteCount)
Doug placed the raw data into a binary array, which of course worked quite fine. But when I tried to parse/filter the form field names and their values, I encountered a problem. Let’s say, for example, that we want to submit an HTML form that contains five text boxes and one file box. The file we want to submit has a size of 100K (=102,400 bytes).
Now our upload control receives over 102,400 bytes. All these bytes will be put into a binary array. When we want to parse the form field names and their values, we have to loop through the entire array. We have to loop through this array six times, because we submitted six form fields. When you try this, it works quite fine, but the performance goes down really hard.
So we need to find another way to can parse the data in the http header. When browsing the MSDN library, I found the solution. We must use Unicode!!! With Unicode we can put the data into a Visual Basic variant and then we can parse the variant, which contains a string.
So now my code looks like this:
Option Explicit
Private MyScriptingContext As ScriptingContext
Private MyRequest As Request
Private MyResponse As Request
Public Sub OnStartPage(PassedScriptingContext As ScriptingContext)
Set MyScriptingContext = PassedScriptingContext
Set MyRequest = MyScriptingContext.Request
Set MyResponse = MySriptingContext.Response
End Sub
Public Function DoUpload (ByVal lngMaxFileBytes As Long, _
ByVal strUploadPath As String) As Variant
‘Variables
Dim varByteCount As Variant
Dim varHTTPHeader As Variant
'Count total bytes in HTTP-header
varByteCount = MyRequest.TotalBytes
‘Conversion of bytes to Unicode
varHTTPHeader = StrConv(MyRequest.BinaryRead(varByteCount), vbUnicode)
‘Write HTTPHeader
MyResponse.Write varHTTPHeader
Function End
So let’s see what happens when we try this code. For this test I used a .txt file named "warp11.txt" that contained the following sentence: "Warp11 builds state-of-the-art applications at the speed of light." The form we submit looks like this (Figure 3).
Figure 3: The HTML Form Sent
This is output the upload control writes into our browser:
-----------------------------7cf28c330254 Content-Disposition: form-data;
name="txtAuthor" Sander Duivestein -----------------------------7cf28c330254 Content-
Disposition: form-data; name="txtFileName"; filename="C:\Download\Warp11.txt"
Content-Type: text/plain Warp11 builds state-of-the-art applications at the speed of light.
-----------------------------7cf28c330254
As you can see this is nothing but a string, and we can parse this string. When an HTML form is submitted by using the multipart/form-data MME type, the form data is divided into seperate parts. Each part of the form data is divided by a boundary: "-----------------------------7cf28c330254". For each form we submit the boundary will have a different string. In the upload application we can use this boundary to parse our formfields and their values, so we put this boundary string in an variable varDelimeter. We can do this by adding the following code:
Dim varDelimeter As Variant
varDelimeter = LeftB(varHTTPHeader, 76)
The first thing we want to know is how many form fields are sent to the upload control. So we have to create a counter:
Dim intFormFieldCounter As Integer
'Count formfields
intFormFieldCounter = Len(varHTTPHeader) - Len(Replace(varHTTPHeader, "; name=", Mid("; name=", 2)))
When we know the number of form fields that are sent to the upload control, we can begin to loop through the varHTTPHeader, and then we can parse the form field names and their form field values.
When we find a form field name and the value, we want store these values. To store these values, we simply create a two-dimensional array:
'Array for containing formfields and their values
ReDim arrFormFields(intFormFieldCounter - 1, 1) As Variant
Later, we want to return these values to the upload.asp file. So that is why we want the array to be of the variant type. Variants are the only array types that ASP pages understand.
Now let’s parse the form fields and their names. First, we create a loop:
'Begin parsing formfield names and values
For i = 0 To intFormFieldCounter - 1
Then we determine the position where the first form field element will start:
If we have the name of the form field, we can look at what type of form field we are dealing with. We can do this by looking at if there is a ";" after the form field name. If this is true, then we are dealing with a file, otherwise we are dealing with a text field. So we have to create an IF THEN condition.
'Check for file
If MidB(varHTTPHeader, lngFormFieldNameEnd, 2) = ";" Then
As we look at our header, we can see that the first time we are going into the loop this condition is False. This means that we are dealing with a normal text field. Now we can parse for the value that belongs to our form field name. First we have to find out where our form field value starts:
Else
‘Determine where formfieldvalue starts
lngFormFieldValueStart = lngFormFieldNameEnd
Second, as before, we have to find out where our form field value ends:
'Determine where formfieldvalue ends
lngFormFieldValueEnd = InStrB(lngFormFieldValueStart, varHTTPHeader, varDelimeter)
At this moment we have parsed the form field name "txtAuthor" and the according form field value "Sander Duivestein." We can put these two values into the array, but before we do that we make the start postion of the next loop the same as the last position we ended parsing:
lngFormFieldNameStart = lngFormFieldValueEnd
End If
'Assign formfieldnames and formfieldvalues to array
arrFormFields(i, 0) = strFormFieldName
arrFormFields(i, 1) = strFormFieldValue
Next
The second time we are going into the loop, we are going for the file. The first thing we find is the form field name "txtFile." The second thing we find is that this time our condition is met:
'Check for file
If MidB(varHTTPHeader, lngFormFieldNameEnd, 2) = ";" Then
We already know the form field name is "txtFile," but we don’t know the name of the file. So we determine the position of where the form field value starts:
At this moment we have a string strFileName that contains the following value: "C:\temp\warp11.txt." We only want to know the name of the file, so we have to remove "C:\temp\." I have created a function called GetFileName to remove the path from a string. This function only returns the file name. The code for the function GetFileName looks like this:
Private Function GetFileName(strFilePath) As String
Dim intPos As Integer
GetFileName = strFilePath
For intPos = Len(strFilePath) To 1 Step -1
If Mid(strFilePath, intPos, 1) = "\" Or Mid(strFilePath, intPos, 1) = ":" Then
GetFileName = Right(strFilePath, Len(strFilePath) - intPos)
Exit Function
End If
Next
End Function
We call this function as follows:
'Remove path from filename
strFileName = GetFileName(strFileName)
Now we know two things: the name of form field "txtfile" and the value of this form field "warp11.txt." At this moment I have built in two checks. The first check is to see if a file name was submitted:
'Check if a file was submitted
If Len(strFileName) = 0 Then
Err.Raise ERR_NO_FILENAME
End If
If the file name is empty, I raise an error and the application quits. I will come back to the error handling later in the article.
The second check I made was to see if the file has an extension:
'Check if file has an extension
If Not CheckFileExtension(strFileName) Then
Err.Raise ERR_NO_EXTENSION
End If
To check if an extension exists, I created a function called CheckFileExtension. This function returns TRUE if the file has an extension, otherwise it returns FALSE and I raise an error. The code for the function CheckFileExtension is as follows:
Private Function CheckFileExtension(strFileName) As Boolean
Dim strFileExtension As String
If InStr(strFileName, ".") Then
strFileExtension = Mid(strFileName, InStrRev(strFileName, ".") + 1)
If Len(strFileExtension) < 3 Then
CheckFileExtension = False
Else
CheckFileExtension = True
End If
Else
CheckFileExtension = False
End If
End Function
If the two checks don’t give any trouble, we can continue our search for the data of the file. We have to find out where the file data begins:
'Determine where filedata ends
lngFileDataEnd = InStr(lngFileDataStart, varHTTPHeader, varDelimeter)
If we have these two values, we can determine what the length of the file is:
Dim lngFileLength As Long
lngFileLength = lngFileDataEnd - lngFileDataStart
At this point I built in two checks again. One check will see if there wasn’t an empty file submitted:
'Check to see if file exists
If lngFileLength <= 2 Then
Err.Raise ERR_EMPTY_FILE
End If
The other check is to verify that the total amount of bytes in the file does not exceed the maximum amount of bytes allowed.
‘Check for maximum bytes allowed
If Not lngMaxFileBytes = 0 Then
If lngMaxFileBytes < lngFileLength Then
Err.Raise ERR_FILESIZE_NOT_ALLOWED
End If
End If
If these two conditions are met, then we don’t have any trouble any more and we can write the file to the Web server. To write the file, we call the subprocedure WriteFile:
WriteFile uses the FileSystem object that comes with Microsoft Internet Information Server 4.0. Before we can use this FileSystem object we have to make a reference to this object. Therefore we add a reference to Microsoft Scripting Runtime. Again, in this function are two checks. One check finds out if the directory to upload to exists:
If Not fs.FolderExists(strUploadPath) Then
Err.Raise ERR_FOLDER_DOES_NOT_EXIST
End If
The other check determines if the file that was submitted to the Web server already exists. If so, then we raise an error.
If fs.FileExists(strUploadPath & strFileName) Then
Err.Raise ERR_FILE_ALREADY_EXISTS
End If
If these checks don’t give any trouble, we can write the file to the Web server:
'Create file
Set sFile = fs.CreateTextFile(strUploadPath & strFileName, True)
'Write file
sFile.Write varContent ', lngFileDataStart, lngFileLength
‘Close File
sFile.Close
Set sFile = Nothing
Set fs = Nothing
We can exit the subprocedure, and we return to the loop we were in. Now we assign our file name to the array, and we leave the FOR NEXT loop. Everything went well, so we can return the array to our upload.asp page. The ASP page then shows our array in the browser.
In case of an error, the following happens. At the top of my function DoUpload I declared an error array:
Dim arrError(0, 1) As Variant
arrError(0, 0) = "Error"
The errors that can occur are defined in the header of my Visual Basic project. The definition of the errors look like this:
'Error Definition
Private Const ERR_NO_FILENAME As Long = vbObjectError + 100
Private Const ERR_NO_EXTENSION As Long = vbObjectError + 101
Private Const ERR_EMPTY_FILE As Long = vbObjectError + 102
Private Const ERR_FILESIZE_NOT_ALLOWED As Long = vbObjectError + 103
Private Const ERR_FOLDER_DOES_NOT_EXIST As Long = vbObjectError + 104
Private Const ERR_FILE_ALREADY_EXISTS As Long = vbObjectError + 105
When an error occurs, I assign an unique message to the arrError(0,1). So the ErrorHandler looks like this:
DoUpload_Err:
Select Case Err.Number
Case ERR_NO_FILENAME
arrError(0, 1) = "There wasn't a file submitted to the server."
Case ERR_NO_EXTENSION
arrError(0, 1) = "The file contains no valid extension."
Case ERR_EMPTY_FILE
arrError(0, 1) = "The file you tried to upload doesn't exist or contains 0 bytes."
Case ERR_FILESIZE_NOT_ALLOWED
arrError(0, 1) = "Total bytes of file sent [" & lngFileLength &_
"] exceeds maximum bytes of file allowed [" &_
lngMaxFileBytes & "]."
Case ERR_FOLDER_DOES_NOT_EXIST
arrError(0, 1) = "The folder to upload to doesn't exist"
Case ERR_FILE_ALREADY_EXISTS
arrError(0, 1) = "The file [" & strFileName & "] already exists."
Case Else
arrError(0, 1) = Err.Description
End Select
We return this error array to the upload.asp. The error is then shown in the browser.
DoUpload = arrError()
Resume DoUpload_Exit
Where are we now? We solved the first seven requirements of my list. So there are still two requirements left: uploading a file into a database and permitting only certain users to use the upload control. These two requirements will be solved in an upcoming article.
The only thing left to do is compile our project and register the pjUpload.dll on the Web server. We also have to create a Web folder. In this Web folder we place our two files - upload.htm and upload.asp. Now open up your browser and surf to http://[servername]/[webfoldername]/upload.htm.
Download the Code
You can download the complete source for the sample contained in this issue:
Sander Duivestein is working for Cap Gemini Netherlands. He is specialized
in building distributed applications for the internet. He can be reached at
Sander.Duivestein@Capgemini.nl.
AspUpload is an Active Server component which enables an ASP application to accept, save and manipulate files uploaded with a browser. The files are uploaded via an HTML POST form using RFC 1867. AspUpload can then manipulate the uploaded files in a number of ways which include ACL manipulation, attribute changes, saving to a database, and ActiveX DLL registration.
Uploading files is as simple as ABC with ABCUpload. Our Pure HTML Progress Bar allows your visitors to see the
progress of their upload in real time with absolutely no client side software. We also offer a number of other advanced technical features including Unicode Compliant, 120% MacBinary Compatible, BLOB Aware, support for foreign language uploads.
ABCUpload also supports COM+ and is also available in a .NET version.
With ActiveFile's advanced features, such as restart of interrupted downloads, download failure detection, and industry standard data compression, it's no wonder that companies like Associated Press and Xerox are Infomentum OEM partners. ActiveFile is the professional’s choice for leading edge capabilities that can’t be found in any other file component.
If you are looking for an intelligent way to exchange files between your ASP or ASP.NET application and web clients, the search is over. Compliant with RFC 1867, ActiveFile provides both file upload and download capabilities that work seamlessly with all of the leading web browsers. Using Active Server Pages or ASP.NET scripting, your application can manipulate files and directories using a robust set of objects and methods provided by the ActiveFile component.
An all-in-one shopping cart system that provides a universal hook to all shopping pages on your Web site, regardless what you sell. Runs for all IIS based Web servers under Windows 95, 98, NT 4.0, NT 2000. Works with all versions of FrontPage.
In this article, Marco Nanni examines an example of multiple binary file uploading for Web applications using XML, without the typical limitations of traditional file upload processing. [Read This Article][Top]
Build multipart MIME upload forms using the InputFile HTML Server Control and learn how to take advantage of the file-upload services built into the HTTP runtime for ASP.NET. Save the uploaded file to disk without granting anonymous users file-write access to folders on your Web server. Then wrap all this in a new ASP.NET user control, which will allow you to add file upload capabilities to almost any Web page quickly and easily. [Read This Article][Top]
To design an industrial-quality solution, one must delve into both how basic uploads work and the more advanced issues of file uploading.
[Read This Article][Top]
Building an upload file mechanism on a Web server can often require using a
costly DLL. Tiago Halm's article shows you how to upload a file using only Active
Server Page (ASP) code and Internet Explorer. Sample code is provided. [Read This Article][Top]
This article was written for VB5/VB6 or ASP programmers want to explore server-side ActiveX ASP components and may be looking for a “how to” code demonstration of uploading files from an Internet browser. [Read This Article][Top]
In this article by Peter Persits, of Persits Software, discusses how the browser uploads files to the server and how to deal with the data streams. He demonstrates Request.BinaryRead and the different data streams returned by browsers that follow RFC 1867. He also shows how to use AspUpload to handle files that are uploaded to the web server. [Read This Article][Top]
In this article David Wihl, of Software Artisans, discusses the options available for uploading files to a web server. He also compares SA-FileUp with the Posting Acceptor and provides example code for uploading files with SA-FileUp. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.