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}