I am trying to upload a file via Ajax to a server-side script written in classic ASP.
This is the relevant HTML and JavaScript code:
<input type="file" id="fileInput" />
and
function saveToServer(file) {
const fd = new FormData();
fd.append('image', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost/post.asp', true);
xhr.onload = () => {
if (xhr.status === 200) {
// Do stuff with response
}
};
xhr.send(fd);
}
const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", () => {
const file = fileInput.files[0];
if (/^image\//.test(file.type)) {
saveToServer(file);
} else {
console.warn('You can only upload images.');
}
});
My question is: how can I get a reference to the uploaded file in my Classic ASP page (post.asp)?
In PHP there is a global variable $_FILES available, which would contain something like:
Array
(
[image] => Array
(
[name] => cat.png
[type] => image/png
[tmp_name] => /tmp/phpOjXMW3
[error] => 0
[size] => 10603
)
)
Is there something equivalent in Classic ASP?
This is the post page:
Set upl = New FileUploader
upl.Upload()
If upl.Files.Count = 1 Then
For Each File In upl.Files.Items
If File.FileSize < 100000 Then
File.FileName = upl.Form ("id") & ".jpg"
File.SaveToDisk Server.MapPath("/Images")
next
end if
This is an include at the top of the post page:
Class FileUploader
Public Files
Private mcolFormElem
Private Sub Class_Initialize()
Set Files = Server.CreateObject("Scripting.Dictionary")
Set mcolFormElem = Server.CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
If IsObject(Files) Then
Files.RemoveAll()
Set Files = Nothing
End If
If IsObject(mcolFormElem) Then
mcolFormElem.RemoveAll()
Set mcolFormElem = Nothing
End If
End Sub
Public Property Get Form(sIndex)
Form = ""
If mcolFormElem.Exists(LCase(sIndex)) Then Form = mcolFormElem.Item(LCase(sIndex))
End Property
Public Default Sub Upload()
Dim biData, sInputName
Dim nPosBegin, nPosEnd, nPos, vDataBounds, nDataBoundPos
Dim nPosFile, nPosBound
'response.Flush
biData = Request.BinaryRead(Request.TotalBytes)
nPosBegin = 1
nPosEnd = InstrB(nPosBegin, biData, CByteString(Chr(13)))
If (nPosEnd-nPosBegin) <= 0 Then Exit Sub
vDataBounds = MidB(biData, nPosBegin, nPosEnd-nPosBegin)
nDataBoundPos = InstrB(1, biData, vDataBounds)
Do Until nDataBoundPos = InstrB(biData, vDataBounds & CByteString("--"))
nPos = InstrB(nDataBoundPos, biData, CByteString("Content-Disposition"))
nPos = InstrB(nPos, biData, CByteString("name="))
nPosBegin = nPos + 6
nPosEnd = InstrB(nPosBegin, biData, CByteString(Chr(34)))
sInputName = CWideString(MidB(biData, nPosBegin, nPosEnd-nPosBegin))
nPosFile = InstrB(nDataBoundPos, biData, CByteString("filename="))
nPosBound = InstrB(nPosEnd, biData, vDataBounds)
If nPosFile <> 0 And nPosFile < nPosBound Then
Dim oUploadFile, sFileName
Set oUploadFile = New UploadedFile
oUploadFile.FormElement = MidB(biData, nPos, 5)
nPosBegin = nPosFile + 10
nPosEnd = InstrB(nPosBegin, biData, CByteString(Chr(34)))
sFileName = CWideString(MidB(biData, nPosBegin, nPosEnd-nPosBegin))
oUploadFile.FileName = Right(sFileName, Len(sFileName)-InStrRev(sFileName, "\"))
nPos = InstrB(nPosEnd, biData, CByteString("Content-Type:"))
nPosBegin = nPos + 14
nPosEnd = InstrB(nPosBegin, biData, CByteString(Chr(13)))
oUploadFile.ContentType = CWideString(MidB(biData, nPosBegin, nPosEnd-nPosBegin))
nPosBegin = nPosEnd+4
nPosEnd = InstrB(nPosBegin, biData, vDataBounds) - 2
oUploadFile.FileData = MidB(biData, nPosBegin, nPosEnd-nPosBegin)
If oUploadFile.FileSize > 0 Then Files.Add LCase(sInputName), oUploadFile
Else
nPos = InstrB(nPos, biData, CByteString(Chr(13)))
nPosBegin = nPos + 4
nPosEnd = InstrB(nPosBegin, biData, vDataBounds) - 2
If Not mcolFormElem.Exists(LCase(sInputName)) Then mcolFormElem.Add LCase(sInputName), CWideString(MidB(biData, nPosBegin, nPosEnd-nPosBegin))
End If
nDataBoundPos = InstrB(nDataBoundPos + LenB(vDataBounds), biData, vDataBounds)
Loop
End Sub
'String to byte string conversion
Private Function CByteString(sString)
Dim nIndex
For nIndex = 1 to Len(sString)
CByteString = CByteString & ChrB(AscB(Mid(sString,nIndex,1)))
Next
End Function
'Byte string to string conversion
Private Function CWideString(bsString)
Dim nIndex
CWideString =""
For nIndex = 1 to LenB(bsString)
CWideString = CWideString & Chr(AscB(MidB(bsString,nIndex,1)))
Next
End Function
End Class
Class UploadedFile
Public ContentType
Public FileName
Public FileData
Public FormElement
Public Property Get FileSize()
FileSize = LenB(FileData)
End Property
Public Sub SaveToDisk(sPath)
Dim oFS, oFile
Dim nIndex
If sPath = "" Or FileName = "" Then Exit Sub
If Mid(sPath, Len(sPath)) <> "\" Then sPath = sPath & "\"
Set oFS = Server.CreateObject("Scripting.FileSystemObject")
If Not oFS.FolderExists(sPath) Then Exit Sub
Set oFile = oFS.CreateTextFile(sPath & FileName, True)
For nIndex = 1 to LenB(FileData)
oFile.Write Chr(AscB(MidB(FileData,nIndex,1)))
Next
oFile.Close
End Sub
Public Sub SaveToDatabase(ByRef oField)
If LenB(FileData) = 0 Then Exit Sub
If IsObject(oField) Then
oField.AppendChunk FileData
End If
End Sub
End Class
Once a file has been uploaded you could use FileSystemObject
to check the file exists and retrieve its properties via the GetFile
method. Use a function to save space and return a dictionary of properties, making them easier to reference:
function getFileInfo(ByVal fileLocation)
' Use MapPath to convert to an absolute path
fileLocation = Server.MapPath(fileLocation)
' Set the file system and dictionary objects using reserved words
set fileSystem = Server.CreateObject("Scripting.FileSystemObject")
Set dictionary = Server.CreateObject("Scripting.Dictionary")
' Check that the file exists
if fileSystem.FileExists(fileLocation) then
' Use GetFile to retrieve the files properties
set file = fileSystem.GetFile(fileLocation)
' Add each property to the dictionary object
dictionary.add "FileFound",true
dictionary.add "Attributes",file.Attributes
dictionary.add "DateCreated",file.DateCreated
dictionary.add "DateLastAccessed",file.DateLastAccessed
dictionary.add "DateLastModified",file.DateLastModified
dictionary.add "Drive",file.Drive
dictionary.add "Name",file.Name
dictionary.add "ParentFolder",file.ParentFolder
dictionary.add "Path",file.Path
dictionary.add "ShortName",file.ShortName
dictionary.add "ShortPath",file.ShortPath
dictionary.add "Size",file.Size
dictionary.add "Type",file.Type
' Attributes translations:
' 0 = Normal file
' 1 = Read-only file
' 2 = Hidden file
' 4 = System file
' 16 = Folder or directory
' 32 = File has changed since last backup
' 1024 = Link or shortcut
' 2048 = Compressed file
else
' File not found
dictionary.add "FileFound",false
end if
' Return the dictionary object
set getFileInfo = dictionary
' Set all objects to nothing
set fileSystem = nothing
set dictionary = nothing
set file = nothing
end function
To check a file has been uploaded and retrieve its properties:
Dim fileInfo : Set fileInfo = getFileInfo("../../uploads/cat.jpg")
' getFileInfo("/Images/" & upl.Form ("id") & ".jpg")
if fileInfo.item("FileFound") then
' Output all the file properties
for each item in fileInfo
response.write "<b>" & item & "</b>: " & fileInfo.item(item) & "<br>"
next
' Output a specific file property
response.write "<b>The file size is</b>: " & fileInfo.item("Size") & " bytes<br>"
response.write "<b>The file type is</b>: " & fileInfo.item("Type") & "<br>"
else
response.write "File not found"
end if
Set fileInfo = nothing
Example output:
FileFound: True
Attributes: 32
DateCreated: 20/03/2019 12:40:09
DateLastAccessed: 20/03/2019 12:40:09
DateLastModified: 20/03/2019 12:40:09
Drive: C:
Name: cat.jpg
ParentFolder: C:\inetpub\wwwroot\uploads
Path: C:\inetpub\wwwroot\uploads\cat.jpg
ShortName: cat.jpg
ShortPath: C:\inetpub\wwwroot\uploads\cat.jpg
Size: 992514
Type: JPEG image
The file size is: 992514 bytes
The file type is: JPEG image
EDIT: Following on from Lankymart's comment, you can use the upl.Files
object to perform some preliminary checks before saving the file to disk:
function randomFileName()
' Randomize() generates quite a small seed number, so you will generate duplicate
' filenames if you upload enough images. To prevent this, prefix a random number
' with a unix timestamp.
Dim uts : uts = DateDiff("s","1970-01-01 00:00:00",Now())
Randomize()
' filename format: [unix timestamp][random 7 digit number]
randomFileName = cStr(uts & Int((9999999-1000000+1)*Rnd+1000000))
end function
function validExtension(ByVal allowed, ByVal fileName)
Dim extRegexp : Set extRegexp = New RegExp
extRegexp.Pattern = "^.*\.(" & lCase(allowed) & ")$"
validExtension = extRegexp.Test(lCase(fileName))
end function
if Request.TotalBytes > 0 then
Const max_upload_size = 100000 ' bytes
Const allowed_extensions = "jpg|jpeg|png"
Const upload_folder = "/Images/"
Dim upload_successful : upload_successful = false
Dim upload_message : upload_message = ""
Dim rndFileName, fileExt, fileSplit
Dim upl : Set upl = New FileUploader
upl.Upload()
If upl.Files.Count = 1 Then
For Each File In upl.Files.Items
file.ContentType = lCase(file.ContentType)
File.FileName = trim(File.FileName)
if NOT (file.ContentType = "image/jpeg" _
OR file.ContentType = "image/jpg" _
OR file.ContentType = "image/png") then
upload_message = "Invalid file type"
elseif NOT validExtension(allowed_extensions,File.FileName) then
upload_message = "Invalid file type"
elseif File.FileSize > max_upload_size then
upload_message = "File too big"
else
' Extract the file extension
fileSplit = split(File.FileName,".")
fileExt = lCase(fileSplit(uBound(fileSplit)))
' Generate a random file name
rndFileName = randomFileName() & "." & fileExt
' Everything checks out, save to disk
File.FileName = rndFileName
File.SaveToDisk Server.MapPath(upload_folder)
upload_message = "Upload successful"
upload_successful = true
end if
next
else
upload_message = "Maximum upload count exceeded"
end if
Set upl = nothing
' Return a JSON string
Response.ContentType = "application/json"
response.write _
"{""uploaded"":" & lCase(upload_successful) & "," &_
"""message"":""" & upload_message & """," &_
"""file"":""" & upload_folder & rndFileName & """}"
end if