Validate the file extension when uploading files in ASP.NET
Restricting uploads to, say, PDF and images starts in the browser, for the user's convenience, and ends on the server, for security. Client checks catch honest mistakes; only the server check can be trusted.
1. Narrow the file picker
<asp:FileUpload ID="fuDoc" runat="server" accept=".pdf,.png,.jpg,.jpeg" />
accept filters what the file dialog shows by default. Users can still switch to "All files".
2. Validator in the page
<asp:RegularExpressionValidator runat="server" ControlToValidate="fuDoc" Display="Dynamic"
ValidationExpression="^.+\.([pP][dD][fF]|[pP][nN][gG]|[jJ][pP][eE]?[gG])$"
ErrorMessage="Only PDF, PNG or JPEG files are allowed." />
The validator checks the file name the browser reports. The expression spells out upper and lower case because client-side regular expression validation does not offer a case-insensitive option. On the server, the same validator runs against the posted file name.
3. JavaScript check with a clear message
document.getElementById("<%= fuDoc.ClientID %>").addEventListener("change", function () {
var allowed = ["pdf", "png", "jpg", "jpeg"];
var bad = Array.from(this.files).filter(function (f) {
var ext = f.name.split(".").pop().toLowerCase();
return allowed.indexOf(ext) < 0 || f.size > 10 * 1024 * 1024;
});
if (bad.length) {
alert("Not accepted: " + bad.map(function (f) { return f.name; }).join(", "));
this.value = ""; // clear the selection
}
});
Reading files also gives you the size, which a validator cannot check. The image preview article extends this with a thumbnail: Preview an image before uploading.
4. The server check (required)
private static readonly HashSet<string> Allowed =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".pdf", ".png", ".jpg", ".jpeg" };
protected void btnUpload_Click(object sender, EventArgs e)
{
if (!IsValid || !fuDoc.HasFile) return;
string ext = Path.GetExtension(fuDoc.FileName);
if (!Allowed.Contains(ext) || !SignatureMatches(fuDoc.PostedFile.InputStream, ext))
{
lblError.Text = "This file type is not accepted.";
return;
}
// save under a generated name, outside the web root (see the upload article)
}
private static bool SignatureMatches(Stream s, string ext)
{
var head = new byte[8];
int n = s.Read(head, 0, head.Length);
s.Position = 0; // rewind for saving
switch (ext.ToLowerInvariant())
{
case ".pdf": return n >= 4 && head[0] == 0x25 && head[1] == 0x50 && head[2] == 0x44 && head[3] == 0x46; // %PDF
case ".png": return n >= 8 && head[0] == 0x89 && head[1] == 0x50 && head[2] == 0x4E && head[3] == 0x47; // .PNG
case ".jpg":
case ".jpeg": return n >= 3 && head[0] == 0xFF && head[1] == 0xD8 && head[2] == 0xFF;
default: return false;
}
}
The extension check is an allow-list: anything not listed is refused, including double extensions such as report.pdf.aspx, because Path.GetExtension returns the last one. Checking the first bytes (the file signature) catches files simply renamed to an allowed extension. It does not prove a file is harmless; files served to other users should also be stored safely and, ideally, scanned. The full upload pattern is in Upload and download files.
Official documentation
- Microsoft Learn: FileUpload, RegularExpressionValidator, Path.GetExtension
- MDN: accept attribute
- OWASP: File Upload Cheat Sheet