Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.mime

import std.mime parses, builds, formats and compares media types such as text/html; charset=utf-8, and maps file extensions to a Content-Type. The module is checked Plenty in stdlib/std/mime.plenty and links no native code. std.http.files uses its extension table. The signature index lists the complete API.

import std.mime
from std.mime import MimeError

def main() -> Result[(), AllocError | IoError | MimeError]:
    value = std.mime.parse("Text/HTML; Charset=\"UTF-8\"; title=\"a \\\"b\\\"\"")?
    print(value.to_string()?)?
    print(value.type_name(), value.subtype(), value.param("charset"), value.param("TITLE"))?
    print(value.matches(&std.mime.parse("text/*")?), value.matches(&std.mime.parse("image/*")?))?
    mut upload = std.mime.new("multipart", "form-data")?
    upload.set_param("boundary", "x7 y")?
    print(upload.to_string()?)?
    print(std.mime.from_file_name("logo.SVG"), std.mime.from_extension("woff2"))?
    Ok(())
text/html; charset=utf-8; title="a \"b\""
text html Option[str].Some("utf-8") Option[str].Some("a \"b\"")
True False
multipart/form-data; boundary="x7 y"
image/svg+xml Option[str].Some("font/woff2")

Media types

ItemMeaning
parse(text)Result[MediaType, MimeError]: the media type in text
new(type_name, subtype)Result[MediaType, MimeError]: a media type without parameters; both names must be tokens
value.type_name(), value.subtype()&str: the lowercase names
value.params()&list[Param]: parameters in order; each Param has name and value
value.param(name)Option[str]: the value of the parameter name, compared without case
value.essence()Result[str, AllocError]: type/subtype without parameters
value.suffix()Result[Option[str], AllocError]: the structured syntax suffix after the last +, such as json in application/ld+json
value.is_wildcard()bool: the type or subtype is *
value.matches(&pattern)bool: value falls within the media range pattern
value.set_param(name, value)Result[(), MimeError]: add a parameter, or replace the value of one with the same name
value.remove_param(name)bool: remove the parameter name; False when absent
value.to_string()Result[str, AllocError]: canonical text

MimeError has two variants: Invalid, for text that is not a media type or a name or value that is not allowed, and Allocation(AllocError).

parse follows the media-type rule of RFC 9110 section 8.3.1:

media-type = type "/" subtype *( OWS ";" OWS [ parameter ] )
parameter  = token "=" ( token / quoted-string )

Spaces and tabs are allowed around the whole text and around each ;, and empty parameters such as a/b;;x=1 are skipped. A quoted-string may hold tab, space, visible ASCII and non-ASCII text, and \ escapes the next character; the stored value is unquoted and unescaped. Everything else returns MimeError.Invalid: a missing or empty type or subtype, a character outside a token, a parameter without = or value, an unclosed quote, text after a closing quote, and a parameter name given twice (compared without case, as RFC 6838 section 4.3 requires).

Type, subtype and parameter names are case-insensitive, so they are stored in lowercase. Parameter values keep their case, except charset, whose values are case-insensitive and are stored in lowercase. == therefore treats Text/HTML;Charset=UTF-8 and text/html; charset=utf-8 as equal. It still compares parameters in order, so a/b; x=1; y=2 and a/b; y=2; x=1 differ.

to_string writes type/subtype, then ; name=value for each parameter in order. A value that is empty or not a token is written as a quoted-string, with " and \ escaped. Parsing formatted text gives an equal value.

set_param requires a token name and a value without control characters other than tab, or returns MimeError.Invalid and leaves the value unchanged. It lowercases the name, and the value of charset.

Media ranges

value.matches(&pattern) reads pattern as a media range, as in an Accept header: */* matches every type, text/* every text subtype, and any other type and subtype must be equal. Each parameter of pattern must appear in value with the same value; extra parameters of value are ignored. So text/html; charset=utf-8 matches text/* and text/html; charset=utf-8, but not text/html; level=1.

std.mime does not parse whole Accept headers: splitting the list and reading q weights belongs to the HTTP layer.

File extensions

ItemMeaning
from_extension(extension)Option[str]: the Content-Type for an extension given without its dot, or Nothing
from_file_name(name)str: the Content-Type for the extension after the last . of name, or application/octet-stream

Both compare without ASCII case and allocate nothing. A name with no ., such as html, has no extension; .html has the extension html. The table is fixed:

ExtensionType
html, htmtext/html; charset=utf-8
csstext/css; charset=utf-8
js, mjstext/javascript; charset=utf-8
jsonapplication/json
txttext/plain; charset=utf-8
csvtext/csv; charset=utf-8
xmlapplication/xml
svgimage/svg+xml
png, jpg, jpeg, gif, webp, icoimage/png, image/jpeg, image/gif, image/webp, image/vnd.microsoft.icon
wasmapplication/wasm
pdfapplication/pdf
woff2, wofffont/woff2, font/woff

The result is Content-Type text, ready for a response header; pass it to parse for a MediaType.

Allocation

parse copies the text once into a byte buffer and allocates the names, values and parameter list of the result. new, set_param, essence, suffix and to_string allocate their results. Every allocation failure returns AllocError and leaves existing values unchanged. to_string checks its fragment-list capacity before allocating; an unrepresentable capacity returns AllocError.CapacityOverflow. Accessors, param, matches, is_wildcard, remove_param and the extension lookups allocate nothing.

Limits

  • No length limit: the caller bounds input, as HTTP header limits do.
  • No content sniffing, and no lookup from a media type back to extensions.
  • The extension table is the fixed list above, not the full IANA registry.

Acknowledgements