Search code examples
javascriptajaxasp-classic

How do I reference an uploaded file in a server-side Classic ASP page?


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

Solution

  • 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