cybersecurity · beginner · ~15 min

Validate a filename against an extension allowlist

Suffix matching that respects extension boundaries and case.

Challenge

Validate a filename against an extension allowlist — the classic upload-handler check, done with end-anchored suffix matching so the evil.png.exe trick can't sneak through.

Task

Implement int has_allowed_extension(const char *name, const char *const *exts, size_t n_exts) that returns 1 if name ends with one of the allowed extensions (case-insensitive ASCII), else 0.

Input

  • name: the filename to check, or NULL.
  • exts: an array of n_exts allowed extensions; each starts with . and contains no path separators (e.g. {".png", ".jpg", ".jpeg"}). The grader passes a fixed list.

Output

Returns int: 1 if name ends with any allowed extension (compared case-insensitively), else 0.

Example

exts = {".png", ".jpg", ".jpeg"}
has_allowed_extension("avatar.png", exts, 3)   ->   1
has_allowed_extension("AVATAR.PNG", exts, 3)   ->   1   (case-insensitive)
has_allowed_extension("doc.PDF", exts, 3)      ->   0
has_allowed_extension("noext", exts, 3)        ->   0
has_allowed_extension(".png", exts, 3)         ->   1   (whole name is the extension)
has_allowed_extension("file.png.exe", exts, 3) ->   0   (double-extension trick)

Edge cases

  • NULL or empty name returns 0.
  • A name shorter than an extension cannot match it.
  • The match is anchored at the END of the name (suffix), so .png inside evil.png.exe does not count.

Rules

  • ASCII, case-insensitive compare. No path-traversal handling here — that's a separate concern.

Why this matters

Upload handlers, attachment scanners, and static-file servers all need to reject filenames they shouldn't serve. The classic check is an extension allowlist — short, defensive, and catches a lot of mischief.

Input format

A filename name (or NULL), an array exts of allowed extensions, and its count n_exts.

Output format

An int: 1 if name ends with an allowed extension (case-insensitive), else 0.

Constraints

ASCII case-insensitive suffix match anchored at the end of the name.

Starter code

#include <stddef.h>
int has_allowed_extension(const char *name, const char *const *exts, size_t n_exts) { /* TODO */ return 0; }

Common mistakes

Using strstr for the extension — matches .png inside evil.png.exe. Forgetting case-insensitive compare. Trusting the extension as the only check (defence in depth: also check magic bytes).

Edge cases to handle

No dot in name; extension is the entire filename; double extension; uppercase extension.

Complexity

O(name_len * n_exts).

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.