Building an Image Upload and Gallery Page in Classic ASP

From the upload button to a working gallery, including the checks that keep it from becoming a shell.

Step 1: The upload control

Image galleries have a narrower set of requirements than general file upload, and you should encode all of them up front — type, size, and count:

<%@ Language="VBScript" %>
<!-- #include file="aspuploader/include_aspuploader.asp" -->

<form id="form1" method="POST">
    <%
    Dim uploader
    Set uploader = new AspUploader
    uploader.Name = "myuploader"
    uploader.MultipleFilesUpload = true
    uploader.MaxSizeKB = 8192                                   ' 8 MB per photo
    uploader.MaxFilesLimit = 30
    uploader.AllowedFileExtensions = "*.jpg,*.jpeg,*.png,*.gif,*.webp"
    uploader.InsertText = "Add photos"
    uploader.FileTypeNotSupportMsg = "Images only, please - JPG, PNG, GIF or WEBP."
    uploader.FileTooLargeMsg = "That image is over the 8 MB limit."
    %>
    <%= uploader.GetString() %>
    <input type="submit" value="Add to gallery" />
</form>

Because those limits are enforced in the browser, someone picking a 400 MB video finds out immediately rather than after a long upload.

Step 2: Prove the files really are images

An extension is a claim, and Content-Type is a claim the client makes about its own file. Neither is evidence. Read the first bytes:

<%
Function ImageKind(path)
    Dim stm, head, i, hex
    ImageKind = ""

    Set stm = Server.CreateObject("ADODB.Stream")
    stm.Type = 1
    stm.Open
    stm.LoadFromFile path
    head = stm.Read(12)
    stm.Close

    hex = ""
    For i = 1 To LenB(head)
        hex = hex & Right("0" & Hex(AscB(MidB(head, i, 1))), 2)
    Next

    If Left(hex, 6)  = "FFD8FF"           Then ImageKind = "jpg"
    If Left(hex, 16) = "89504E470D0A1A0A" Then ImageKind = "png"
    If Left(hex, 8)  = "47494638"         Then ImageKind = "gif"
    If Mid(hex, 1, 8) = "52494646" And Mid(hex, 17, 8) = "57454250" Then ImageKind = "webp"
End Function
%>

A file can be a perfectly valid JPEG and carry ASP source appended to it. Magic-number checks are a second layer — the first is making sure the folder images land in cannot execute anything. Details in the upload security checklist.

Step 3: Store with a generated name

Never use the visitor's file name as a path. Two people will upload IMG_0042.jpg on the same afternoon, and one of them will be trying something cleverer than that:

<%
Dim fso, destDir, list, i, mvcfile, kind, storedName

Set fso = Server.CreateObject("Scripting.FileSystemObject")
destDir = Server.MapPath("gallery")
If Not fso.FolderExists(destDir) Then fso.CreateFolder destDir

If Request.Form("myuploader") & "" <> "" Then
    list = Split(Request.Form("myuploader"), "/")

    For i = 0 To UBound(list)
        Set mvcfile = uploader.GetUploadedFile(list(i))

        kind = ImageKind(mvcfile.GetTempFilePath())

        If kind = "" Then
            Response.Write "Rejected (not an image): " & Server.HTMLEncode(mvcfile.FileName) & "<br/>"
            mvcfile.Delete
        Else
            ' the GUID is already unique - reuse it, with the extension we verified
            storedName = list(i) & "." & kind
            mvcfile.MoveTo destDir & "\" & storedName

            SavePhotoRow storedName, mvcfile.FileName, mvcfile.FileSize, Session("UserID")
        End If
    Next
End If
%>

The original name is still worth keeping — as a caption, as display text, as something to search on — just never as a path. Store it in the database and HTML-encode it on the way out.

Step 4: Render the gallery

<%
Dim rs
Set rs = cn.Execute("SELECT PhotoId, StoredName, OriginalName " & _
                    "FROM Photos WHERE OwnerId = " & CLng(Session("UserID")) & _
                    " ORDER BY UploadedUtc DESC")
%>
<div class="gallery">
<%
Do While Not rs.EOF
%>
    <figure>
        <img src="gallery/<%= rs("StoredName") %>"
             alt="<%= Server.HTMLEncode(rs("OriginalName")) %>"
             loading="lazy" width="240" height="180" />
        <figcaption><%= Server.HTMLEncode(rs("OriginalName")) %></figcaption>
    </figure>
<%
    rs.MoveNext
Loop
%>
</div>

Three details worth keeping:

  • Server.HTMLEncode on the original name. A file called <img src=x onerror=alert(1)>.jpg is a stored XSS payload otherwise.
  • loading="lazy" plus explicit width/height — the browser skips off-screen images and the page stops jumping as they load.
  • Full-size images in an <img> tag are the classic gallery performance mistake. Thumbnails matter, which brings us to the next step.

Step 5: Thumbnails

Classic ASP has no image processing of its own, so resizing needs help from outside the script. The realistic options:

ApproachNeedsNotes
CSS/HTML sizing onlyNothingSimplest, but the browser still downloads full-size images. Fine for a handful, not for a hundred
Resize in the browser before uploadCanvas API in JavaScriptSaves bandwidth too; the original never leaves the machine unless you also send it
An image COM componentServer admin rightsReintroduces the registration problem a pure ASP stack avoids
A scheduled task or side serviceA process outside IISKeeps the web tier simple; thumbnails appear a moment later

For most galleries, resizing on the client before upload is the best trade — smaller uploads, faster pages, and no server dependency:

function shrink(file, maxEdge, done) {
    var img = new Image();
    img.onload = function () {
        var scale = Math.min(1, maxEdge / Math.max(img.width, img.height));
        var c = document.createElement("canvas");
        c.width  = Math.round(img.width  * scale);
        c.height = Math.round(img.height * scale);
        c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
        c.toBlob(done, "image/jpeg", 0.85);
    };
    img.src = URL.createObjectURL(file);
}

Gallery checklist

  1. Extension allow-list and magic-number verification.
  2. Execute permission removed from the gallery folder.
  3. Generated storage names; original names kept only as data.
  4. Server.HTMLEncode on every name you render.
  5. Per-file size cap and a per-batch file count cap.
  6. Thumbnails, or at least lazy loading with explicit dimensions.
  7. Ownership recorded, so deletion can be authorised.
  8. Total-storage monitoring — galleries grow without anyone deciding they should.

Frequently asked questions

How do I allow only images to be uploaded in Classic ASP?

Set AllowedFileExtensions to the image types you accept, then verify the magic-number bytes on the server. The extension alone is not evidence of anything.

Can Classic ASP resize an uploaded image?

Not on its own — it has no image processing. Resize in the browser with the canvas API before upload, or hand the work to a process outside IIS.

Where should uploaded images be stored?

On disk under generated names, with metadata in a database. Keep the folder non-executable, or store outside the webroot and serve through a script.

Skip the plumbing

ASP Uploader does everything on this page out of the box: multi-file selection, a real progress bar, client and server validation, incremental large-file handling, and no COM component to register. Drop the folder on your server and add one include line.

Download the free trial Try the live demo Pricing