Skip to content

std/mime ​

Filename extension to media type, for serving static files.

milo
from "std/mime" import { Mime }

A lookup table, nothing more. It reads the extension and answers what that extension conventionally means; it never opens the file and never sniffs the bytes. A .png full of HTML is still image/png here.

milo
print(Mime.fromExtension(".HTML") ?? "?")     // text/html
print(Mime.fromPath("/a/index.html") ?? "?")  // text/html
print(Mime.contentType("notes.txt") ?? "?")   // text/plain; charset=utf-8
print(Mime.isTextual("application/json"))     // true

Serving a file ​

contentType is the one to send, because it appends ; charset=utf-8 to textual types. That is not decoration: a text/html response with no declared charset lets the browser guess, and a guess an attacker can steer (classically UTF-7) turns escaped output back into markup.

milo
let ct = match Mime.contentType(path) {
    Some(t) => t,
    None => "application/octet-stream",
}

Unknown extensions are None rather than a built-in default, because the safe default depends on what you are serving — a static-site server and an upload bucket want different answers, and only the caller knows which it is.

Uploads ​

Do not label an upload with the type its filename claims. That is how a stored .html gets served back as text/html and runs as your origin. Decide the type of untrusted content from your own policy, or serve it from a separate origin with Content-Disposition: attachment and X-Content-Type-Options: nosniff.

API reference ​

Mime ​

milo
pub struct Mime

Mime — media types by filename extension.

Mime.contentType ​

milo
fn Mime.contentType(path: &string): Option<string>

Full Content-Type header value for path: the media type, plus "; charset=utf-8" when the type is textual.

The charset is not decoration. A text/html response with no declared charset lets the browser guess, and a guess an attacker can steer (the classic case is UTF-7) turns escaped output back into markup. Send this, not the bare type, on anything a browser will render.

Mime.fromExtension ​

milo
fn Mime.fromExtension(ext: &string): Option<string>

Media type for ext, or None if the extension is not in the table.

ext may be spelled with or without a leading dot and in any case: "html", ".HTML" and "HtMl" all answer text/html. The answer is the bare type with no parameters — see contentType for a full header value.

".ts" answers video/mp2t, matching the registry and every other server, not TypeScript source.

Mime.fromPath ​

milo
fn Mime.fromPath(path: &string): Option<string>

Media type for the extension of path, or None if it has no extension or the extension is unknown.

The extension is the text after the last '.' in the last path segment, so "/var/www/.htaccess" and "/etc/conf.d/app" have none — a leading dot is a hidden-file marker, not an extension, and a dot in a parent directory belongs to that directory.

Mime.isTextual ​

milo
fn Mime.isTextual(mediaType: &string): bool

Whether mediaType names text: any "text/..." type, or a structured syntax that is text in practice (+json, +xml, and the handful of application/... types that predate the suffix convention).

Accepts a bare type or a full header value — parameters after the first ';' are ignored, so "text/html" and "text/html; charset=utf-8" agree. The match is on the type name only; it is not a claim that the bytes decode.