code wiki / (root) / nx_static_io.nx

nx_static_io.nx source

↩ module page · 253 lines · 9342 B

1// nx_static_io.nx -- sovereign static-file serving over HTTP. 2// 3// Companion to nx_http_io that knows how to: 4// - Map a URL path to a filesystem path (with safety: no .. escape) 5// - Detect content-type from filename suffix 6// - stream the file body to a TCP socket 7// 8// Built for nishi-web: a NishiLang HTTP server that hosts a static 9// site (e.g. andelinwest.com) without nginx / apache / wordpress. 10// 11// genealogy_id: rfc_7230_http_1_1 + nginx_static_serve + caddy_file_server 12// lineage_id: nx_static_serve_sovereign_q10 13 14// nx_safety_envelope: 15// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 16// sil_target: SIL1 17// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 18// verdict: NOT_YET_EVALUATED 19 20// Unified syscalls (per-ISA via @ifdef TARGET_X86_64 macro, commit 21// 164209c9) -- avoids symbol collision with nx_http_io which also 22// imports nx_syscalls.nx. 23import "nx_syscalls.nx" 24 25// Content-type table. Caller writes the type bytes into `out` and 26// returns the byte length. We accept these suffixes: 27// .html / .htm -> text/html; charset=utf-8 (24 bytes) 28// .css -> text/css; charset=utf-8 (23 bytes) 29// .js -> application/javascript (22 bytes) 30// .json -> application/json (16 bytes) 31// .md -> text/markdown (13 bytes) 32// .png -> image/png (9 bytes) 33// .jpg / .jpeg -> image/jpeg (10 bytes) 34// .svg -> image/svg+xml (13 bytes) 35// .ico -> image/x-icon (12 bytes) 36// default -> application/octet-stream (24 bytes) 37 38func _ct_html(out: *u8) -> i64 { 39 out[0]=116;out[1]=101;out[2]=120;out[3]=116;out[4]=47 // text/ 40 out[5]=104;out[6]=116;out[7]=109;out[8]=108 // html 41 out[9]=59;out[10]=32 // ; (space) 42 out[11]=99;out[12]=104;out[13]=97;out[14]=114;out[15]=115;out[16]=101;out[17]=116 43 out[18]=61 // = 44 out[19]=117;out[20]=116;out[21]=102;out[22]=45;out[23]=56 // utf-8 45 return 24 46} 47func _ct_css(out: *u8) -> i64 { 48 out[0]=116;out[1]=101;out[2]=120;out[3]=116;out[4]=47 // text/ 49 out[5]=99;out[6]=115;out[7]=115 // css 50 out[8]=59;out[9]=32 51 out[10]=99;out[11]=104;out[12]=97;out[13]=114;out[14]=115;out[15]=101;out[16]=116 52 out[17]=61 53 out[18]=117;out[19]=116;out[20]=102;out[21]=45;out[22]=56 54 return 23 55} 56func _ct_js(out: *u8) -> i64 { 57 out[0]=97;out[1]=112;out[2]=112;out[3]=108;out[4]=105;out[5]=99;out[6]=97;out[7]=116;out[8]=105;out[9]=111;out[10]=110 58 out[11]=47 // / 59 out[12]=106;out[13]=97;out[14]=118;out[15]=97;out[16]=115;out[17]=99;out[18]=114;out[19]=105;out[20]=112;out[21]=116 60 return 22 61} 62func _ct_json(out: *u8) -> i64 { 63 out[0]=97;out[1]=112;out[2]=112;out[3]=108;out[4]=105;out[5]=99;out[6]=97;out[7]=116;out[8]=105;out[9]=111;out[10]=110 64 out[11]=47 65 out[12]=106;out[13]=115;out[14]=111;out[15]=110 66 return 16 67} 68func _ct_md(out: *u8) -> i64 { 69 out[0]=116;out[1]=101;out[2]=120;out[3]=116;out[4]=47 // text/ 70 out[5]=109;out[6]=97;out[7]=114;out[8]=107;out[9]=100;out[10]=111;out[11]=119;out[12]=110 71 return 13 72} 73func _ct_png(out: *u8) -> i64 { 74 out[0]=105;out[1]=109;out[2]=97;out[3]=103;out[4]=101;out[5]=47;out[6]=112;out[7]=110;out[8]=103 75 return 9 76} 77func _ct_jpeg(out: *u8) -> i64 { 78 out[0]=105;out[1]=109;out[2]=97;out[3]=103;out[4]=101;out[5]=47;out[6]=106;out[7]=112;out[8]=101;out[9]=103 79 return 10 80} 81func _ct_svg(out: *u8) -> i64 { 82 out[0]=105;out[1]=109;out[2]=97;out[3]=103;out[4]=101;out[5]=47;out[6]=115;out[7]=118;out[8]=103;out[9]=43;out[10]=120;out[11]=109;out[12]=108 83 return 13 84} 85func _ct_octet(out: *u8) -> i64 { 86 out[0]=97;out[1]=112;out[2]=112;out[3]=108;out[4]=105;out[5]=99;out[6]=97;out[7]=116;out[8]=105;out[9]=111;out[10]=110 87 out[11]=47 88 out[12]=111;out[13]=99;out[14]=116;out[15]=101;out[16]=116;out[17]=45;out[18]=115;out[19]=116;out[20]=114;out[21]=101;out[22]=97;out[23]=109 89 return 24 90} 91 92// Pick a content-type for a path by walking the filename suffix. 93// Writes the type bytes into `out`, returns byte length. 94func nx_static_content_type(path: *u8, path_len: i64, out: *u8) -> i64 { 95 // Find the last '.' in the path (within the last component). 96 var dot: i64 = -1 97 var i: i64 = 0 98 while i < path_len { 99 if path[i] == 46 { dot = i } // '.' 100 i = i + 1 101 } 102 if dot < 0 { return _ct_octet(out) } 103 104 let suf_len: i64 = path_len - dot - 1 105 let s0: i64 = path[dot + 1] 106 // .html / .htm 107 if s0 == 104 { 108 if suf_len >= 3 { 109 if path[dot+2] == 116 { 110 if path[dot+3] == 109 { 111 return _ct_html(out) 112 } 113 } 114 } 115 } 116 // .css 117 if s0 == 99 { 118 if suf_len == 3 { 119 if path[dot+2] == 115 { 120 if path[dot+3] == 115 { return _ct_css(out) } 121 } 122 } 123 } 124 // .js / .json 125 if s0 == 106 { 126 if suf_len == 2 { 127 if path[dot+2] == 115 { return _ct_js(out) } 128 } 129 if suf_len == 4 { 130 if path[dot+2] == 115 { 131 if path[dot+3] == 111 { 132 if path[dot+4] == 110 { return _ct_json(out) } 133 } 134 } 135 } 136 } 137 // .md 138 if s0 == 109 { 139 if suf_len == 2 { 140 if path[dot+2] == 100 { return _ct_md(out) } 141 } 142 } 143 // .png 144 if s0 == 112 { 145 if suf_len == 3 { 146 if path[dot+2] == 110 { 147 if path[dot+3] == 103 { return _ct_png(out) } 148 } 149 } 150 } 151 // .jpg / .jpeg 152 if s0 == 106 { 153 if suf_len == 3 { 154 if path[dot+2] == 112 { 155 if path[dot+3] == 103 { return _ct_jpeg(out) } 156 } 157 } 158 } 159 // .svg 160 if s0 == 115 { 161 if suf_len == 3 { 162 if path[dot+2] == 118 { 163 if path[dot+3] == 103 { return _ct_svg(out) } 164 } 165 } 166 } 167 return _ct_octet(out) 168} 169 170// Read a file at `path` into a fresh buffer. Returns the buffer 171// pointer + writes byte count into *out_len. Cap-bounded; longer 172// files truncate. Returns NULL ptr if open failed. 173func nx_static_load_file(path: *u8, cap: i64, out_len: *i64) -> *u8 { 174 *out_len = 0 175 let fd: i64 = sys_openat_rd(path) 176 if fd < 0 { return 0 as *u8 } 177 let buf: *u8 = sys_mmap(cap + 16) 178 var off: i64 = 0 179 var keep_reading: i64 = 1 180 while keep_reading == 1 { 181 if off >= cap { keep_reading = 0 } 182 else { 183 let r: i64 = sys_read(fd, (buf as i64 + off) as *u8, cap - off) 184 if r <= 0 { keep_reading = 0 } 185 else { off = off + r } 186 } 187 } 188 sys_close(fd) 189 *out_len = off 190 return buf 191} 192 193// Detect dangerous path traversal. Returns 1 if path is safe (no .. 194// sequence, no double slashes, etc), 0 otherwise. Belt-and-braces; 195// callers should also constrain to a known DOC_ROOT. 196func nx_static_path_is_safe(path: *u8, path_len: i64) -> i64 { 197 if path_len <= 0 { return 0 } 198 if path[0] != 47 { return 0 } // must start with / 199 var i: i64 = 0 200 while i + 2 < path_len { 201 // Reject "/.." prefix 202 if path[i] == 47 { 203 if path[i+1] == 46 { 204 if path[i+2] == 46 { return 0 } 205 } 206 } 207 i = i + 1 208 } 209 return 1 210} 211 212// Build the filesystem path: DOC_ROOT + url_path. url_path is the 213// thing after the host, starts with '/'. If url_path ENDS in '/' -- 214// the directory-index case -- we append "index.html" so /foo/ serves 215// /foo/index.html and / serves /index.html. 216// 217// out must point at >= DOC_ROOT_len + url_path_len + 16 bytes. 218// Returns the byte length written (NOT including a trailing NUL, 219// which IS written one past the returned length so the buffer is 220// usable as a POSIX C-string). 221func nx_static_join_path( 222 doc_root: *u8, doc_root_len: i64, 223 url_path: *u8, url_path_len: i64, 224 out: *u8 225) -> i64 { 226 var o: i64 = 0 227 var k: i64 = 0 228 while k < doc_root_len { out[o] = doc_root[k]; o = o + 1; k = k + 1 } 229 // url_path always starts with '/'. Append it. 230 var u: i64 = 0 231 while u < url_path_len { out[o] = url_path[u]; o = o + 1; u = u + 1 } 232 // Directory-index: if the URL ended in '/' (this includes the 233 // single-byte path "/", which is path_len==1). 234 var append_index: i64 = 0 235 if url_path_len == 1 { append_index = 1 } 236 if url_path_len > 1 { 237 if url_path[url_path_len - 1] == 47 { append_index = 1 } 238 } 239 if append_index == 1 { 240 out[o] = 105; o = o + 1 // i 241 out[o] = 110; o = o + 1 // n 242 out[o] = 100; o = o + 1 // d 243 out[o] = 101; o = o + 1 // e 244 out[o] = 120; o = o + 1 // x 245 out[o] = 46; o = o + 1 // . 246 out[o] = 104; o = o + 1 // h 247 out[o] = 116; o = o + 1 // t 248 out[o] = 109; o = o + 1 // m 249 out[o] = 108; o = o + 1 // l 250 } 251 out[o] = 0 // NUL terminator (caller-friendly) 252 return o 253}