filetype

tape
import filetype from "filetype";

filetype tells you a file’s media type, the Content-Type to send it with: from its name ("photo.JPG" is "image/jpeg") or from its first bytes, and the other way, the usual extension of a type. The table is built into tape, not read from the operating system, so a program gets the same answer on Windows, Linux and macOS, natively and in tape run. It comes from mime-db, the table Node.js uses, keeping the types registered with IANA or listed by the Apache web server.

filetype allocates nothing and touches nothing outside your program: it works in every profile, t0 included, in pure modules and at compile time. Every answer is a string that lives as long as the program. A program using it carries the table, about 40 KB.

By file name

tape
pub fn media_type(path: []const u8) -> ?[]const u8

The media type for the file name’s extension, upper or lower case. Only the last extension counts, and only the file name (after the last / or \). null when there is no extension, for a dotfile such as .bashrc, and for an extension the table doesn’t know; send those as application/octet-stream.

tape
filetype.media_type("photo.JPG")              // "image/jpeg"
filetype.media_type("C:\\Users\\Ann\\notes.txt")  // "text/plain"
filetype.media_type("backup.tar.gz")          // "application/gzip"
filetype.media_type("report.docx")            // "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
filetype.media_type("Makefile")               // null
filetype.media_type(".bashrc")                // null

fn content_type(name: []const u8) -> []const u8 {
    return filetype.media_type(name) or { "application/octet-stream" };
}

Attaching a file to an email with mime:

tape
d.attach(name, filetype.media_type(name) or { "application/octet-stream" }, data);

The usual extension of a type

tape
pub fn extension(media: []const u8) -> ?[]const u8

The extension files of that type usually have, without the dot. Upper or lower case, and parameters such as ; charset=utf-8 are ignored, so a Content-Type header can be passed as it is. null for a type the table doesn’t know. Use it to name a file you received:

tape
filetype.extension("image/jpeg")                  // "jpg"
filetype.extension("text/html; charset=UTF-8")    // "html"
filetype.extension("application/x-unknown")       // null

let ext: []const u8 = filetype.extension(content_type) or { "bin" };
let name: string = "download.{ext}";

By content

tape
pub fn sniff(data: []const u8) -> ?[]const u8

The media type from the file’s first bytes, the way browsers recognise files (the WHATWG MIME Sniffing Standard’s signatures), when the name is missing or can’t be trusted. It knows images (PNG, JPEG, GIF, WebP, BMP, ICO, TIFF, HEIC, AVIF, JPEG XL), audio and video (MP3, AAC in MP4/M4A, Ogg, Opus, FLAC, WAV, AIFF, MIDI, MP4, QuickTime, 3GP, WebM, Matroska, AVI), fonts (TTF, OTF, WOFF, WOFF2, EOT), archives (zip, gzip, RAR, 7z, bzip2, xz), PDF and PostScript. A few hundred bytes are plenty; a short buffer is fine, it never reads past the end. null when nothing matches, and for text of any kind (HTML, XML, JSON, plain text): text has no signature.

tape
fn type_of(name: []const u8, data: []const u8) -> []const u8 {
    let by_content: ?[]const u8 = filetype.sniff(data);
    if (by_content != null) { return by_content!; }
    return filetype.media_type(name) or { "application/octet-stream" };
}

The answer is the same type media_type gives for that format’s usual extension. Office documents, EPUB and Android packages are zip files inside, and sniff as application/zip: their name is more precise.

Last modified: