std.mime
Media types (MIME types, RFC 2045 and RFC 6838): the type of a file
by its extension (typeByExtension), the extensions of a type
(extensionsByType), and a type with its parameters read from its
text (parseMediaType, RFC 2045 §5.1 and RFC 2231’s continuations
and encodings) and written back (renderMediaType). Go’s mime is
the model. A Content-Disposition (RFC 2183) has the same shape, a
disposition and its parameters, and is read the same way. Import it
by a name, import std.mime as MIME.
import std.mime as MIME
-- | >>> charset "text/html; charset=\"UTF-8\""-- | Just "UTF-8"charset : String -> Maybe Stringcharset text = case MIME.parseMediaType text of Nothing -> Nothing Just m -> List.lookup "charset" m.parametersMediaType
type MediaType = { mediaType : String, parameters : List (String, String) }A media type as parseMediaType reads it: the type and its subtype,
text/html, or a disposition, form-data — in lower case — and
its parameters in their order, each a name, in lower case, and its
value, unquoted and unescaped.
typeByExtension
typeByExtension : String -> Maybe StringThe type of a file by its extension, its dot before it, its case not
minded, as Go’s TypeByExtension has it from its own table: .avif
.css .gif .htm .html .jpeg .jpg .js .json .mjs
.pdf .png .svg .wasm .webp .xml; and .csv .gz .ico
.md .mp3 .mp4 .ogg .otf .tar .toml .ttf .txt .wav
.webm .woff .woff2 .yaml .yml .zip, as IANA registers
them (.ico image/vnd.microsoft.icon, .tar application/x-tar,
.wav audio/wav, as browsers name it). A text type has
charset=utf-8; no file is read, as Go reads the system’s.
>>> List.map typeByExtension [".html", ".PNG", ".Woff2", ".yml"][Just "text/html; charset=utf-8", Just "image/png", Just "font/woff2", Just "application/yaml"]>>> List.map typeByExtension ["html", ".exe", ""][Nothing, Nothing, Nothing]parseMediaType
parseMediaType : String -> Maybe MediaTypeA media type read from its text, as Go’s ParseMediaType reads it:
a token, / and a token — or a token alone, a disposition — in
either case, then ; and a parameter each, a token, = and a value,
a token or a quoted string, spaces around them all. In a quoted
string a \ escapes the tspecial after it, any other it is itself
(as Internet Explorer writes a path, "C:\dir\a.txt"); a line break
in one is none. A ; at the end is no parameter. A name may be
there twice with the same value, kept once; with another, nothing.
RFC 2231’s are read: name*=utf-8''%E2%82%AC (its charset utf-8 or
us-ascii, the language not kept), and name*0, name*1… in
pieces, any of them *-encoded, each one parameter name; one that
does not read is left out.
>>> parseMediaType "Text/HTML; Charset=\"utf-8\"; format=fixed"Just { mediaType = "text/html", parameters = [MkPair "charset" "utf-8", MkPair "format" "fixed"] }>>> parseMediaType " form-data ; name=foo ;"Just { mediaType = "form-data", parameters = [MkPair "name" "foo"] }>>> parseMediaType "text/plain"Just { mediaType = "text/plain", parameters = [] }(quoted strings: escapes, a \ before what is no tspecial, empty)
>>> parseMediaType "attachment; filename=\"C:\\dev\\go\\robots.txt\""Just { mediaType = "attachment", parameters = [MkPair "filename" "C:\\dev\\go\\robots.txt"] }>>> parseMediaType "attachment; filename=\"C:\\\\dev\\\\go\\\\robots.txt\""Just { mediaType = "attachment", parameters = [MkPair "filename" "C:\\dev\\go\\robots.txt"] }>>> parseMediaType "foo; a=\"say \\\"hi\\\"; ok\"; b=\"\""Just { mediaType = "foo", parameters = [MkPair "a" "say \"hi\"; ok", MkPair "b" ""] }(a name twice: once with the same value, nothing with another)
>>> parseMediaType "foo; key=val; KEY=\"val\""Just { mediaType = "foo", parameters = [MkPair "key" "val"] }>>> parseMediaType "foo; key=val1; key=the-key-appears-again-which-is-bogus"Nothing(RFC 2231’s examples)
>>> parseMediaType "message/external-body; access-type=URL; URL*0=\"ftp://\"; URL*1=\"cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar\""Just { mediaType = "message/external-body", parameters = [MkPair "access-type" "URL", MkPair "url" "ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"] }>>> parseMediaType "application/x-stuff; title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A"Just { mediaType = "application/x-stuff", parameters = [MkPair "title" "This is ***fun***"] }>>> parseMediaType "application/x-stuff; title*0*=us-ascii'en'This%20is%20even%20more%20; title*1*=%2A%2A%2Afun%2A%2A%2A%20; title*2=\"isn't it!\""Just { mediaType = "application/x-stuff", parameters = [MkPair "title" "This is even more ***fun*** isn't it!"] }>>> parseMediaType "attachment; filename=\"EURO rates\"; filename*=utf-8''%e2%82%ac%20rates"Just { mediaType = "attachment", parameters = [MkPair "filename" "\u{20ac} rates"] }>>> parseMediaType "attachment; filename=plain; filename*=iso-8859-1''%A3"Just { mediaType = "attachment", parameters = [MkPair "filename" "plain"] }(what is not one: no type, no subtype, two slashes, a space in it, a
parameter without = or a value, a quote not closed, a line break in
one, text after one, a ; alone)
>>> (List.map (\s -> maybe True (\_ -> False) (parseMediaType s)) ["", "/html", "text/", "text/html/x", "text html", "foo; bar", "foo; bar=", "foo; =x", "foo; a=\"b", "foo; a=\"b\nc\"", "foo; a=\"b\"c", "foo; a=b c", "foo; ; a=b", "bogus ;========="] : List Bool)[True, True, True, True, True, True, True, True, True, True, True, True, True, True]extensionsByType
extensionsByType : String -> List StringThe extensions of a media type, its parameters not minded, in their
order: those typeByExtension has of it.
>>> extensionsByType "text/html; charset=utf-8"[".htm", ".html"]>>> List.map extensionsByType ["IMAGE/JPEG", "application/yaml", "text/x-nothing", "not a type"][[".jpeg", ".jpg"], [".yaml", ".yml"], [], []]renderMediaType
renderMediaType : MediaType -> StringThe text of a media type, as Go’s FormatMediaType writes it, but in
the parameters’ order: the type, then ; and a parameter each, the
type and the names in lower case, a value a token as it is, any
other quoted, a " and a \ in it escaped — one with a control
byte or one not ASCII (a tab is neither) written name*=utf-8''
and its bytes, those not of a token or *'% escaped %XX, as RFC
2231 writes it. Of a type or a name that is not one, "".
>>> renderMediaType { mediaType = "Text/HTML", parameters = [("Charset", "utf-8"), ("title", "say \"hi\"\t\\o/")] }"text/html; charset=utf-8; title=\"say \\\"hi\\\"\t\\\\o/\"">>> renderMediaType { mediaType = "attachment", parameters = [("filename", "\u{20ac} 100%.txt"), ("empty", "")] }"attachment; filename*=utf-8''%E2%82%AC%20100%25.txt; empty=\"\"">>> List.map renderMediaType [{ mediaType = "text/", parameters = [] }, { mediaType = "text/plain", parameters = [("a b", "c")] }]["", ""](what it writes, parseMediaType reads back)
>>> parseMediaType (renderMediaType { mediaType = "form-data", parameters = [("name", "a;b"), ("filename", "\u{e9}t\u{e9} \"x\".txt"), ("x", "\\")] })Just { mediaType = "form-data", parameters = [MkPair "name" "a;b", MkPair "filename" "\u{e9}t\u{e9} \"x\".txt", MkPair "x" "\\"] }