
Editorial illustration.
Originally published 26 June 2023; rewritten and reviewed on 15 October 2026.
The simplest upload check compares a filename extension with a short byte pattern. The uncomfortable case is a file shorter than the pattern, or a ZIP package wearing the name of a different ZIP-based format. I would design the helper around those cases before using it in a request handler.
Read only the prefix you need
A bounded comparison avoids loading an entire upload into memory merely to inspect its header. This modern .NET example consumes bytes from the stream’s current position:
using System.IO;
using System.Threading;
using System.Threading.Tasks;
static async Task<bool> HasPrefixAsync(
Stream stream, byte[] expected,
CancellationToken cancellationToken = default)
{
if (expected.Length == 0) return false;
byte[] header = new byte[expected.Length];
int read = 0;
while (read < header.Length)
{
int count = await stream.ReadAsync(
header.AsMemory(read), cancellationToken);
if (count == 0) return false;
read += count;
}
return header.AsSpan().SequenceEqual(expected);
}
A caller checking PNG would supply its eight-byte signature. If the caller needs to read the file again, it must manage buffering or reposition a seekable stream deliberately. Not every upload stream supports seeking.
Keep the result narrowly named
HasPrefixAsync describes what was established. Calling the same result IsSafeFile would promise more than the code checks. A crafted file can start with expected bytes and still contain malformed or unwanted content.
Use format-aware parsing after the prefix check, with limits appropriate to the application. Do not infer DOCX versus XLSX solely from the ZIP prefix.
Test the awkward inputs
Include empty files, truncated headers, a correct header, a one-byte mismatch and a stream that returns partial reads. Then test the whole upload path’s size and permission controls. The helper earns its place by answering one small question reliably, while leaving broader validation explicit.