code wiki / (root) / mime.nx

mime.nx source

↩ module page · 119 lines · 5055 B

1// mime.nx -- MIME type lookup by file extension. 2// 3// Used by HTTP servers (http_resp.nx) to set Content-Type from 4// file extensions when serving static content. Small curated 5// table of ~30 types covering 99% of web workloads. 6// 7// Not exhaustive; callers needing obscure types either extend 8// the table here or pass the Content-Type explicitly. No mime- 9// magic byte sniffing today; that's a future security-concern 10// module (misclassifying an HTML file as text/plain can enable 11// XSS in some contexts). 12// 13// Invariants: 14// M1 Lookup is case-insensitive on the extension. 15// M2 Unknown extensions return "application/octet-stream" 16// (RFC 2046 default for "raw bytes of unknown type"). 17// M3 Returned strings are const cstrings; caller must NOT 18// free them. 19 20import "syscalls.nx" 21 22// Case-insensitive byte compare of a null-terminated cstring 23// against a known-length prefix. 24func mime_ext_eq(ext: *u8, n: i64, lit: *u8) -> i64 { 25 var i: i64 = 0 26 while i < n { 27 let a: i64 = ext[i] 28 let b: i64 = lit[i] 29 var an: i64 = a 30 var bn: i64 = b 31 if an >= 0x41 { if an <= 0x5A { an = an + 32 } } 32 if bn >= 0x41 { if bn <= 0x5A { bn = bn + 32 } } 33 if an != bn { return 0 } 34 i = i + 1 35 } 36 if lit[i] != 0 { return 0 } 37 return 1 38} 39 40// Classify a file extension (without leading dot) into a MIME type. 41// `ext` is the raw bytes of the extension; `n` is its length. 42func mime_from_ext(ext: *u8, n: i64) -> *u8 { 43 // Web document types. 44 if mime_ext_eq(ext, n, "html") == 1 { return "text/html; charset=utf-8" } 45 if mime_ext_eq(ext, n, "htm") == 1 { return "text/html; charset=utf-8" } 46 if mime_ext_eq(ext, n, "css") == 1 { return "text/css; charset=utf-8" } 47 if mime_ext_eq(ext, n, "js") == 1 { return "application/javascript" } 48 if mime_ext_eq(ext, n, "mjs") == 1 { return "application/javascript" } 49 if mime_ext_eq(ext, n, "wasm") == 1 { return "application/wasm" } 50 if mime_ext_eq(ext, n, "json") == 1 { return "application/json" } 51 if mime_ext_eq(ext, n, "xml") == 1 { return "application/xml" } 52 53 // Plain text / source. 54 if mime_ext_eq(ext, n, "txt") == 1 { return "text/plain; charset=utf-8" } 55 if mime_ext_eq(ext, n, "md") == 1 { return "text/markdown; charset=utf-8" } 56 if mime_ext_eq(ext, n, "csv") == 1 { return "text/csv; charset=utf-8" } 57 if mime_ext_eq(ext, n, "tsv") == 1 { return "text/tab-separated-values" } 58 59 // Images. 60 if mime_ext_eq(ext, n, "png") == 1 { return "image/png" } 61 if mime_ext_eq(ext, n, "jpg") == 1 { return "image/jpeg" } 62 if mime_ext_eq(ext, n, "jpeg") == 1 { return "image/jpeg" } 63 if mime_ext_eq(ext, n, "gif") == 1 { return "image/gif" } 64 if mime_ext_eq(ext, n, "svg") == 1 { return "image/svg+xml" } 65 if mime_ext_eq(ext, n, "ico") == 1 { return "image/x-icon" } 66 if mime_ext_eq(ext, n, "webp") == 1 { return "image/webp" } 67 if mime_ext_eq(ext, n, "avif") == 1 { return "image/avif" } 68 69 // Fonts. 70 if mime_ext_eq(ext, n, "woff") == 1 { return "font/woff" } 71 if mime_ext_eq(ext, n, "woff2") == 1 { return "font/woff2" } 72 if mime_ext_eq(ext, n, "ttf") == 1 { return "font/ttf" } 73 if mime_ext_eq(ext, n, "otf") == 1 { return "font/otf" } 74 75 // Audio / video. 76 if mime_ext_eq(ext, n, "mp3") == 1 { return "audio/mpeg" } 77 if mime_ext_eq(ext, n, "mp4") == 1 { return "video/mp4" } 78 if mime_ext_eq(ext, n, "webm") == 1 { return "video/webm" } 79 if mime_ext_eq(ext, n, "ogg") == 1 { return "audio/ogg" } 80 81 // Archives / binary. 82 if mime_ext_eq(ext, n, "pdf") == 1 { return "application/pdf" } 83 if mime_ext_eq(ext, n, "zip") == 1 { return "application/zip" } 84 if mime_ext_eq(ext, n, "gz") == 1 { return "application/gzip" } 85 if mime_ext_eq(ext, n, "tar") == 1 { return "application/x-tar" } 86 87 return "application/octet-stream" 88} 89 90// Convenience: extract the extension from a full path + classify. 91// Path is null-terminated; scans backward for the last '.'. 92// Returns the MIME type string. 93func mime_from_path(path: *u8) -> *u8 { 94 var n: i64 = 0 95 while path[n] != 0 { n = n + 1 } 96 var dot: i64 = -1 97 var i: i64 = n - 1 98 while i >= 0 { 99 if path[i] == 0x2E { dot = i; i = -1 } // '.' 100 else { 101 if path[i] == 0x2F { i = -1 } // '/' stops scan 102 else { i = i - 1 } 103 } 104 } 105 if dot < 0 { return "application/octet-stream" } 106 let ext_ptr: i64 = (path as i64) + dot + 1 107 return mime_from_ext(ext_ptr as *u8, n - dot - 1) 108} 109 110// Compile-only smoke. 111func main() -> i64 { 112 let mime: *u8 = mime_from_path("/var/www/index.html") 113 // Expect "text/html" start. 114 if mime[0] != 0x74 { return 1 } // 't' 115 if mime[5] != 0x68 { return 2 } // 'h' of 'html' 116 let mime2: *u8 = mime_from_path("/tmp/data.bin") 117 if mime2[0] != 0x61 { return 3 } // 'a' of 'application' 118 return 0 119}