MIME type lookup · Guide

MIME Types for File Uploads: Validation Without Trusting the Header

A MIME type is useful evidence about an uploaded file, but it is not proof of what the file contains. Browsers supply a declared type, filenames supply an extension, and server-side detectors infer a type from bytes; all three can disagree. Safe upload handling uses an explicit allow-list, verifies content on the server, applies format-specific limits, and stores the result where it cannot become executable content.

The three file types you may be comparing

An upload normally arrives with three different type signals. The multipart Content-Type is chosen by the client. The extension is merely the last part of a user-controlled filename. A server-side detector examines signatures and byte patterns. Treat the first two as hints and the third as a stronger classification, not as an infallible security verdict.

HTTP
POST /avatar HTTP/1.1
Content-Type: multipart/form-data; boundary=upload

--upload
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

...bytes chosen by the sender...

Nothing prevents a custom client from naming an HTML document photo.jpg and declaring image/jpeg. JavaScript's File.type is also metadata inferred by the browser; an empty string means the platform could not determine it. Client-side checks improve feedback, but the server must repeat every rule that protects data or users.

MIME labels are case-insensitive in principle, yet normalising them to lowercase avoids accidental allow-list misses. Parameters such as text/plain; charset=utf-8 belong to a media type used in a protocol; upload libraries commonly return the bare type. Parse parameters rather than comparing an entire header string blindly.

Use a narrow allow-list, not a block-list

Start from the formats the feature genuinely needs. An avatar endpoint may accept JPEG, PNG and WebP; it has no reason to accept SVG, PDF or ZIP. A block-list inevitably misses aliases, new formats and files with misleading names, while an allow-list fails closed when the detector returns something unexpected.

PHP
$allowed = [
    'image/jpeg' => ['jpg', 'jpeg'],
    'image/png'  => ['png'],
    'image/webp' => ['webp'],
];

$detected = (new finfo(FILEINFO_MIME_TYPE))->file($tmpPath);
if (!array_key_exists($detected, $allowed)) {
    throw new RuntimeException('Unsupported upload type');
}

Use canonical values in the policy but expect real-world aliases at integration boundaries. JPEG is image/jpeg, not image/jpg; JavaScript modules are commonly served as text/javascript; CSV is normally text/csv. Do not silently map a completely unknown value to application/octet-stream and then accept it: that value means arbitrary binary data, not a safe generic document.

Keep policies endpoint-specific. A support-ticket attachment policy should not automatically become the profile-photo policy. Smaller allow-lists make later processing predictable and reduce the number of parsers exposed to hostile input.

Detect content on the server

On Linux, libmagic-backed tools and language bindings inspect known byte signatures and some structural features. In PHP, finfo is the usual interface. In a shell, file --mime-type is useful for debugging the exact temporary file that your application received.

Shell
file --brief --mime-type upload.bin
# image/png

xxd -l 16 upload.bin
# 00000000: 8950 4e47 0d0a 1a0a ...

Magic bytes catch a renamed executable, but they do not prove that the whole file is valid. A file can begin with a legitimate image signature and contain malformed or appended data. Polyglot files are deliberately valid enough for more than one parser. When the risk matters, decode the format with a maintained library, reject decode errors, and re-encode the accepted content into a new file.

Python
from PIL import Image

with Image.open(upload_path) as image:
    image.verify()                 # validate structure without trusting .type

with Image.open(upload_path) as image:
    image.convert('RGB').save(output_path, 'JPEG', quality=88)

Re-encoding images also removes most appended payloads and metadata, although it is not a substitute for keeping image libraries patched. For documents and archives, use a parser appropriate to that format rather than assuming one shared content-sniffer understands every internal constraint.

Require the extension and detected type to agree

The extension still matters because downstream systems often choose behaviour from it. After detecting the MIME type, derive a server-approved extension or verify that the supplied one belongs to the allowed set. Never preserve an arbitrary double extension such as invoice.pdf.php merely because the first component looks familiar.

PHP
$original = $upload->getClientOriginalName();
$extension = strtolower(pathinfo($original, PATHINFO_EXTENSION));
$detected = (new finfo(FILEINFO_MIME_TYPE))->file($upload->getRealPath());

if (!isset($allowed[$detected]) ||
    !in_array($extension, $allowed[$detected], true)) {
    throw new RuntimeException('File extension does not match its content');
}

A mismatch is usually either a user mistake or suspicious input; rejecting it is clearer than guessing. If product requirements say the extension is irrelevant, discard the original name and generate the extension from the detected, successfully decoded format.

Do not use the original filename as a storage path. It can contain separators, control characters, Unicode lookalikes or names that collide on a case-insensitive filesystem. Generate a random identifier and keep the display name as escaped metadata only.

Validate dimensions, size and decompression cost

A correct MIME type can still hide an operational attack. Enforce the HTTP body limit before buffering the upload, then enforce a per-file size after multipart parsing. For images, limit decoded width, height and total pixels; a tiny compressed image can expand to enormous memory. For archives, cap entry count, total expanded bytes and nesting depth.

JavaScript
const MAX_BYTES = 5 * 1024 * 1024;
const allowed = new Set(['image/jpeg', 'image/png', 'image/webp']);

if (file.size > MAX_BYTES) throw new Error('Image exceeds 5 MiB');
if (!allowed.has(file.type)) throw new Error('Choose JPEG, PNG or WebP');
// User feedback only: repeat and strengthen these checks on the server.

Read dimensions from a decoder rather than trusting EXIF fields or request parameters. Apply timeouts and memory limits to conversion jobs. Antivirus scanning can be useful for general attachments, but a clean scan does not make an active format safe to render inline and cannot replace format validation.

Reject password-protected archives if the service cannot inspect them. Avoid recursively unpacking user-controlled archives in a web request; isolate that work and prevent entries such as ../../app.php from escaping the extraction directory.

Store uploads so they cannot execute

Validation reduces risk; storage design contains mistakes. Put untrusted files outside the web root or in object storage. Give them random keys, deny script execution, and serve them through a handler or a separate static-files origin. A missed PHP payload is far less damaging when no PHP runtime will execute anything in the upload directory.

HTTP
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
X-Content-Type-Options: nosniff
Content-Security-Policy: sandbox

...validated bytes...

Set the response Content-Type from stored server metadata, never from a query parameter. X-Content-Type-Options: nosniff tells browsers not to reinterpret a response as HTML or script. Use Content-Disposition: attachment for formats that users need to download rather than display, particularly content with active features.

SVG deserves a deliberate decision. It is an image MIME type, but SVG is XML and may contain scripts, links and external references. If you only need raster avatars, exclude it. If you need SVG, sanitise it with an SVG-specific library and serve it under a restrictive policy from an isolated origin.

A practical upload validation sequence

Apply cheap checks before expensive ones: authenticate and authorise the request, enforce transport and file-size limits, detect the type, compare it with a narrow allow-list, decode or parse the complete format, scan where appropriate, generate a safe name, and store outside executable paths. Only publish the file after every step succeeds.

Text
1. Limit request and file size
2. Ignore the supplied path; retain display name as metadata
3. Detect MIME type from temporary-file bytes
4. Require an endpoint-specific allow-list
5. Decode/parse and enforce format limits
6. Re-encode or scan when the use case warrants it
7. Store under a random key outside the web root
8. Serve with fixed headers and nosniff

Log the declared type, detected type, extension, size and rejection reason, but do not log file contents or sensitive filenames unnecessarily. These fields reveal detector changes and client-specific problems without retaining hostile payloads in application logs.

Test disagreement cases explicitly: valid content with the wrong extension, a valid extension with random bytes, oversized dimensions, double extensions, empty files and a truncated otherwise-valid format. The successful path alone says almost nothing about whether an upload boundary is safe.

Frequently asked questions

Can I trust the MIME type sent with a file upload?

No. The multipart Content-Type is supplied by the client and can be changed freely. Use it for early feedback, then detect the type from bytes on the server and validate the complete format where the risk warrants it.

Should I validate a file by MIME type or extension?

Use both, and require them to agree with an endpoint-specific allow-list. The extension is user-controlled and byte detection is imperfect, so sensitive formats should also be opened by a real parser or decoder.

What MIME type should I allow for any file?

There is no safe MIME type meaning any acceptable file. application/octet-stream means arbitrary binary data; accepting it bypasses type restrictions. Define the exact formats the feature needs instead.

Are magic bytes enough to validate an upload?

No. They identify many renamed files, but a valid signature can precede malformed, appended or polyglot content. Parse the complete file and, for images, consider decoding and re-encoding it.

Is SVG safe to accept as an image upload?

Not by default. SVG can contain scripts, links and external references because it is an XML document, not a passive bitmap. Exclude it unless required; otherwise use a dedicated sanitiser and serve it from an isolated origin with restrictive headers.

Where should uploaded files be stored?

Outside the executable web root or in object storage, under server-generated random names. Serve them with a server-selected Content-Type, X-Content-Type-Options: nosniff, and attachment disposition where inline rendering is unnecessary.

Ready to try it?

Open the free browser-based MIME type lookup and apply what you just read — no sign-up, runs locally.

Open the MIME type lookup tool