3

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
Dale K
  • 25,246
  • 15
  • 42
  • 71

2 Answers2

1

This question doesn't make much sense...

My question is:

How can I get a reference to the uploaded file in my Classic ASP page (post.asp)?

From the code you have posted it's clear you are using a custom class to support the upload FileUploader which parses the binary for you and builds a collection of objects that represent the uploaded files (the UploadedFile class).

In the code you posted you are accessing the UploadedFile object in a For Each loop;

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")
    End If
  Next
End If

Corrected typo where End If was missing before the For Each ends.

In that example, your File object is the UploadedFile class object containing the file properties populated during the parse of the binary by the FileUploader.

Community
  • 1
  • 1
user692942
  • 16,398
  • 7
  • 76
  • 175
  • I have a feeling they're wanting to check the file before saving to disk. In the same way that PHP's `$_FILES` allows you to view the properties of a file in the temporary upload folder before renaming and moving. – Adam Mar 20 '19 at 16:58
  • @Adam I haven't used PHP much but still don't understand what you mean, as far as I'm aware [`$_FILES`](http://php.net/manual/en/features.file-upload.post-method.php) is just a collection of the parsed files at that point they don't exist anywhere but in memory *(unless PHP has some weird config I'm not aware of)*. – user692942 Mar 20 '19 at 18:08
  • Yes, I want to be able to see the information in the console. – AspClassic-Guy Mar 20 '19 at 18:11
  • @AspClassic-Guy what console? The client browser dev tools do you mean? – user692942 Mar 20 '19 at 18:12
  • @Lankymart I'm pretty sure PHP handles uploads by storing files in a temporary directory rather than memory, you would then use `$_FILES` to check the file size/MIME type etc before renaming the temporary file and moving to your target directory. It may have changed in later versions of PHP, but from what I remember that's how it used to work. It still doesn't make the OP's question make much sense though, as they're already checking the filesize before saving to disk. Perhaps they wanted to check the MIME type and file extension too? I'm not sure, but I updated my answer if that's the case. – Adam Mar 20 '19 at 18:23
  • 1
    What I meant to say is, I want the classic ASP script to return the uploaded file’s properties, so that in the Ajax success callback (on the client), I can output these properties to the console. – AspClassic-Guy Mar 20 '19 at 18:58
0

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
Adam
  • 836
  • 2
  • 8
  • 13
  • 1
    They already have the `UploadedFile` object which gives them all the values parsed from the binary during the upload. The parse in `FileUploader` creates a `Scripting.Dictionary` to contain each `UploadedFile` as most custom uploader classes for Classic ASP do. – user692942 Mar 20 '19 at 15:08
  • If you use that in post.asp and upload a jpg or a png file which is < 100kb, can you save the file to disk with the correct extension? – AspClassic-Guy Mar 20 '19 at 18:54
  • @AspClassic-Guy yes, the filesize check is accepting 100000 bytes or less. The file extension is extracted from the original filename and the mime type is extracted and analysed from the image headers. – Adam Mar 20 '19 at 18:58
  • @Lankymart The file that posts to the post.asp page is a regular form post. Does this: upl.Form ("id") have to be on the post.asp page for the uploader to work? – AspClassic-Guy Mar 20 '19 at 22:37
  • 2
    @AspClassic-Guy that's just how you had it set in your original code. You could generate a random file name within your post.asp page. I've updated by code with an example. – Adam Mar 21 '19 at 00:04
  • @Adam Want to do a simple JS with this and not sure where to add in edited code for JS alert over 100k in size – AspClassic-Guy Mar 22 '19 at 21:24
  • @AspClassic-Guy This post outlines several methods for file upload size validation in JS: https://stackoverflow.com/questions/3717793/javascript-file-upload-size-validation – Adam Mar 22 '19 at 21:31
  • @Adam I would think that after upload_message = "File too big" -- the response.write would work? Doesn't though. – AspClassic-Guy Mar 22 '19 at 22:03
  • @AspClassic-Guy The post.asp page is returning a JSON string which you need to process in your aJax callback. This is a good aJax upload example using jQuery: https://stackoverflow.com/a/10811427/4901783 in the `// your callback here` section you could do `alert(data.message);` to display the message generated by post.asp, or check `if(data.uploaded)` then redirect to `data.file` etc... – Adam Mar 22 '19 at 22:29