code wiki / (root) / nx_archive_server.nx

nx_archive_server.nx source

↩ module page · 311 lines · 22935 B

1// nx_archive_server.nx -- sovereign localhost HTTP server for the time machine (R5 serve side). Binds 0.0.0.0:18801 (ARCSRV_PORT -- the bind DERIVES from that const, see below), 2// serves files under web_assets/ (path-traversal rejected), Content-Type by extension. "/" -> a landing page. Also 3// hosts the SUPERVISED do-not-recover blocklist: GET /blocklist renders it live from seg_store (justification + 4// per-entry action), GET /blocklist/approve?id=N and /blocklist/retract?id=N are the operator ACTIONS (flip status on 5// seg_store, then 303 back to /blocklist). Pending entries never block until approved here. Fetch stack. license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_blocklist_view.nx" 8// *PORT CONFLICT AVERTED 2026-08-06: this bound 0.0.0.0:8080, which nx_hostctl status shows is ALREADY 9// HELD by the live redirect.elf. Deploying on 8080 would have taken down a production service, and the 10// never-brick health probe would have seen a listener on 8080 and called it healthy -- the collision would 11// have looked like success. 18795 is unused across the whole live inventory (checked against nx_hostctl 12// status: 8443 sites, 8080 redirect, 18190/18090 gallery, 8791 media, 8097/18793/6881 torrent, 8446 vroom, 13// 18096 tools, 18097 fin, 7702 mp, 18098 mgmt, 18456/18465/18466 portal, 9091 opaque, 18791 wiki, 18797 dev). 14// *RENAMED from K_MAGIC_8080. The old name encoded a value the const no longer had, and worse, nothing 15// read it at all -- see the bind below. A name that lies about its value on a const that nothing reads is 16// two independent falsehoods stacked on one line. 17const ARCSRV_PORT: i64 = 18801 18const K_MAGIC_8192: i64 = 8192 19const K_MAGIC_2048: i64 = 2048 20const K_MAGIC_2097152: i64 = 2097152 21 22func sw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 23func ap(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { buf[off+i]=s[i]; i=i+1 } return off+i } 24func apn(buf: *u8, off: i64, v: i64) -> i64 { if v==0 { buf[off]=0x30 as u8; return off+1 } var m: i64=v; let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } var o: i64=off; var q: i64=k-1; while q>=0 { buf[o]=t[q]; o=o+1; q=q-1 } return o } 25func endswith(path: *u8, plen: i64, ext: *u8, elen: i64) -> i64 { if plen<elen { return 0 } var i: i64=0; while i<elen { if (path[plen-elen+i] as i64) != (ext[i] as i64) { return 0 } i=i+1 } return 1 } 26// ---- ENHANCED-VIEW TOGGLE -------------------------------------------------------------------- 27// nx_dedither writes a de-dithered render beside each dithered original as <name>.enh.png and NEVER 28// touches the source. That is the right archival choice and it left the enhanced renders invisible: 29// nothing on any page linked to them. *A RESTORATION NOBODY CAN SEE IS INDISTINGUISHABLE FROM ONE THAT 30// WAS NEVER MADE. 31// INJECTED AT SERVE TIME, NOT WRITTEN INTO THE PAGE. The stored 1996 HTML stays byte-exact (it is the 32// record), every archived page gains the toggle at once, and no page needs regenerating. The toggle is 33// OFF by default, so the default view remains what the archive actually captured. 34// FAIL-SOFT BY CONSTRUCTION: only a minority of assets are dithered, so most have no .enh.png at all. 35// The onerror handler restores the original src, meaning a missing enhanced render degrades to the 36// original image rather than a broken one -- the toggle cannot make the page worse than it was. 37const ARC_TOGGLE: *u8 = "<div style='position:fixed;top:0;right:0;z-index:99999;background:#111;color:#eee;font:13px system-ui;padding:6px 11px;opacity:.92;border-radius:0 0 0 8px'><label><input type=checkbox onchange='nxT(this.checked)'> enhanced</label></div><script>function nxT(on){var a=document.images,i;for(i=0;i<a.length;i++){var m=a[i];if(!m.getAttribute('data-nxo'))m.setAttribute('data-nxo',m.getAttribute('src'));var o=m.getAttribute('data-nxo');if(on){m.onerror=function(){this.onerror=null;this.src=this.getAttribute('data-nxo')};m.src=o+'.enh.png'}else{m.onerror=null;m.src=o}}}</script>" as *u8 38 39// ---- DIRECTORY INDEX ------------------------------------------------------------------------ 40// *AN EMPTY 200 IS WORSE THAN A 404. A directory URL used to fall through to the static-file branch, 41// where sys_read_file OPENS a directory successfully and then reads ZERO bytes -- so the server replied 42// `200 OK, Content-Length: 0` and every browser rendered a blank page. The status line claimed the thing 43// was delivered. That is a PARTIAL APPLY WEARING THE SUCCESS WORD, and it cost an operator a bug report 44// that read as "the server is down" when the server was fine and the ANSWER was empty. 45// An archive whose directories cannot be browsed is a filing cabinet welded shut, so the fix is a real 46// listing rather than an honest 404. 47// getdents64 doubles as the type test: it returns -ENOTDIR on a regular file, so one call both DETECTS 48// the directory and READS it -- no stat race between the check and the use. 49const AS_RECLEN_OFF: i64 = 16 50const AS_NAME_OFF: i64 = 19 51const AS_DIRBUF: i64 = 262144 52const AS_DT_DIR: i64 = 4 53 54// Render an HTML index for `path` into out; hrefs are absolute, built from the REQUEST path so the page 55// works whether or not the URL carried a trailing slash. Returns byte length, or -1 if not a directory. 56func as_dirlist(path: *u8, req: *u8, pst: i64, pen: i64, out: *u8) -> i64 { 57 let fd: i64 = sys_openat_rd(path) 58 if fd < 0 { return 0 - 1 } 59 let db: *u8 = sys_mmap(AS_DIRBUF) 60 let n: i64 = sys_getdents64(fd, db, AS_DIRBUF) 61 sys_close(fd) 62 if n <= 0 { return 0 - 1 } 63 var o: i64 = ap(out, 0, "<!DOCTYPE html><meta charset=utf-8><title>Index of " as *u8) 64 var u: i64 = pst 65 while u < pen { out[o] = req[u]; o = o + 1; u = u + 1 } 66 o = ap(out, o, "</title><body style='font-family:system-ui;background:#0e0f13;color:#e7e7ea;max-width:900px;margin:40px auto;padding:0 20px'><h1 style='font-size:1.3rem'>Index of " as *u8) 67 var u2: i64 = pst 68 while u2 < pen { out[o] = req[u2]; o = o + 1; u2 = u2 + 1 } 69 o = ap(out, o, "</h1><ul style='line-height:1.8;list-style:none;padding:0'>" as *u8) 70 var count: i64 = 0 71 var p: i64 = 0 72 while p < n { 73 let reclen: i64 = (db[p+AS_RECLEN_OFF] as i64) | ((db[p+AS_RECLEN_OFF+1] as i64) << 8) 74 if reclen <= 0 { p = n } else { 75 let dtype: i64 = db[p+18] as i64 76 let nm: i64 = p + AS_NAME_OFF 77 // skip "." and ".."; and REFUSE any name carrying HTML metacharacters -- a filename is data 78 // that arrived from the filesystem, which is a boundary (rule 12), not a trusted literal. 79 var skip: i64 = 0 80 if (db[nm] as i64) == 46 { if (db[nm+1] as i64) == 0 { skip = 1 } } 81 if (db[nm] as i64) == 46 { if (db[nm+1] as i64) == 46 { if (db[nm+2] as i64) == 0 { skip = 1 } } } 82 var q: i64 = nm 83 while db[q] != (0 as u8) { 84 let cc: i64 = db[q] as i64 85 if cc == 60 { skip = 1 } 86 if cc == 62 { skip = 1 } 87 if cc == 34 { skip = 1 } 88 q = q + 1 89 } 90 if skip == 0 { 91 o = ap(out, o, "<li><a href='" as *u8) 92 var u3: i64 = pst 93 while u3 < pen { out[o] = req[u3]; o = o + 1; u3 = u3 + 1 } 94 if (req[pen-1] as i64) != 47 { out[o] = 47 as u8; o = o + 1 } 95 var w: i64 = nm 96 while db[w] != (0 as u8) { out[o] = db[w]; o = o + 1; w = w + 1 } 97 o = ap(out, o, "'>" as *u8) 98 var w2: i64 = nm 99 while db[w2] != (0 as u8) { out[o] = db[w2]; o = o + 1; w2 = w2 + 1 } 100 if dtype == AS_DT_DIR { o = ap(out, o, "/" as *u8) } 101 o = ap(out, o, "</a>" as *u8) 102 count = count + 1 103 } 104 p = p + reclen 105 } 106 } 107 o = ap(out, o, "</ul><p style=color:#8a8f9a>" as *u8) 108 o = apn(out, o, count) 109 o = ap(out, o, " entries &mdash; served sovereignly.</p></body>" as *u8) 110 return o 111} 112 113// *A TIME MACHINE FOR THE 90s/2000s WEB MUST SPEAK THE 90s/2000s WEB. This previously knew five types and 114// fell through to text/plain, which does not fail loudly -- the browser simply refuses to render, and an 115// archive that HAS the bytes looks like an archive that lost them. Era formats restored below (Rule 25: 116// build intelligence, never strip features). 117// RENDERABLE by a modern browser: gif (incl. animated), jpeg, png, bmp, ico, svg, webp, avif. 118// ERA-CORRECT but NOT natively renderable: tiff, xbm, pcx (served with true type so they download 119// intact and a converter can reach them, rather than being mangled as text). 120// THE REST OF A PERIOD SITE: midi/wav/au/mp3 background audio, swf, RealMedia .rm/.ram, avi/mov/mpeg. 121// These are a real part of what those sites WERE; serving the honest Content-Type preserves them even 122// where no plugin survives to play them. 123func ctype(path: *u8, plen: i64) -> *u8 { 124 if endswith(path,plen,".html" as *u8,5)==1 { return "text/html" as *u8 } 125 if endswith(path,plen,".htm" as *u8,4)==1 { return "text/html" as *u8 } 126 if endswith(path,plen,".css" as *u8,4)==1 { return "text/css" as *u8 } 127 if endswith(path,plen,".js" as *u8,3)==1 { return "application/javascript" as *u8 } 128 if endswith(path,plen,".json" as *u8,5)==1 { return "application/json" as *u8 } 129 // --- images the browser renders 130 if endswith(path,plen,".gif" as *u8,4)==1 { return "image/gif" as *u8 } 131 if endswith(path,plen,".jpg" as *u8,4)==1 { return "image/jpeg" as *u8 } 132 if endswith(path,plen,".jpeg" as *u8,5)==1 { return "image/jpeg" as *u8 } 133 if endswith(path,plen,".jpe" as *u8,4)==1 { return "image/jpeg" as *u8 } 134 if endswith(path,plen,".png" as *u8,4)==1 { return "image/png" as *u8 } 135 if endswith(path,plen,".bmp" as *u8,4)==1 { return "image/bmp" as *u8 } 136 if endswith(path,plen,".ico" as *u8,4)==1 { return "image/x-icon" as *u8 } 137 if endswith(path,plen,".svg" as *u8,4)==1 { return "image/svg+xml" as *u8 } 138 if endswith(path,plen,".webp" as *u8,5)==1 { return "image/webp" as *u8 } 139 if endswith(path,plen,".avif" as *u8,5)==1 { return "image/avif" as *u8 } 140 // --- era images the browser will not render, but which must survive intact 141 if endswith(path,plen,".tif" as *u8,4)==1 { return "image/tiff" as *u8 } 142 if endswith(path,plen,".tiff" as *u8,5)==1 { return "image/tiff" as *u8 } 143 if endswith(path,plen,".xbm" as *u8,4)==1 { return "image/x-xbitmap" as *u8 } 144 if endswith(path,plen,".pcx" as *u8,4)==1 { return "image/x-pcx" as *u8 } 145 // --- period audio: MIDI background music was ubiquitous on the 90s web 146 if endswith(path,plen,".mid" as *u8,4)==1 { return "audio/midi" as *u8 } 147 if endswith(path,plen,".midi" as *u8,5)==1 { return "audio/midi" as *u8 } 148 if endswith(path,plen,".wav" as *u8,4)==1 { return "audio/wav" as *u8 } 149 if endswith(path,plen,".au" as *u8,3)==1 { return "audio/basic" as *u8 } 150 if endswith(path,plen,".mp3" as *u8,4)==1 { return "audio/mpeg" as *u8 } 151 // --- period video / plugin media 152 if endswith(path,plen,".swf" as *u8,4)==1 { return "application/x-shockwave-flash" as *u8 } 153 if endswith(path,plen,".avi" as *u8,4)==1 { return "video/x-msvideo" as *u8 } 154 if endswith(path,plen,".mov" as *u8,4)==1 { return "video/quicktime" as *u8 } 155 if endswith(path,plen,".mpg" as *u8,4)==1 { return "video/mpeg" as *u8 } 156 if endswith(path,plen,".mpeg" as *u8,5)==1 { return "video/mpeg" as *u8 } 157 if endswith(path,plen,".mp4" as *u8,4)==1 { return "video/mp4" as *u8 } 158 if endswith(path,plen,".rm" as *u8,3)==1 { return "application/vnd.rn-realmedia" as *u8 } 159 if endswith(path,plen,".ram" as *u8,4)==1 { return "audio/x-pn-realaudio" as *u8 } 160 if endswith(path,plen,".pdf" as *u8,4)==1 { return "application/pdf" as *u8 } 161 if endswith(path,plen,".zip" as *u8,4)==1 { return "application/zip" as *u8 } 162 return "text/plain" as *u8 163} 164func has_dotdot(buf: *u8, off: i64, len: i64) -> i64 { var i: i64=off; while i+1<off+len { if (buf[i] as i64)==0x2e { if (buf[i+1] as i64)==0x2e { return 1 } } i=i+1 } return 0 } 165// exact path match: req[pst..pst+plen) == lit (litlen bytes) 166func path_eq(req: *u8, pst: i64, plen: i64, lit: *u8, litlen: i64) -> i64 { if plen!=litlen { return 0 } var i: i64=0; while i<litlen { if (req[pst+i] as i64)!=(lit[i] as i64) { return 0 } i=i+1 } return 1 } 167func path_starts(req: *u8, pst: i64, plen: i64, lit: *u8, litlen: i64) -> i64 { if plen<litlen { return 0 } var i: i64=0; while i<litlen { if (req[pst+i] as i64)!=(lit[i] as i64) { return 0 } i=i+1 } return 1 } 168// extract the value of "id=" from query req[qst..qen) into idbuf (NUL-terminated); returns its length (0 if none) 169func qid(req: *u8, qst: i64, qen: i64, idbuf: *u8) -> i64 { 170 idbuf[0]=0 as u8 171 if qst<0 { return 0 } 172 var i: i64=qst 173 while i+2<qen { if (req[i] as i64)==0x69 { if (req[i+1] as i64)==0x64 { if (req[i+2] as i64)==0x3d { 174 var s: i64=i+3; var o: i64=0 175 while s<qen { let c: i64=req[s] as i64; if c==0x26 { s=qen } else { if o<200 { idbuf[o]=req[s]; o=o+1 } s=s+1 } } 176 idbuf[o]=0 as u8; return o 177 } } } i=i+1 } 178 return 0 179} 180 181func main() -> i64 { 182 let fd: i64=sys_socket(2, 1, 0) 183 if fd<0 { sw("socket fail\n" as *u8); return 1 } 184 let opt: *i64=sys_mmap(8) as *i64; opt[0]=1 185 __syscall(54, fd, 1, 2, opt as i64, 4, 0) 186 let addr: *u8=sys_mmap(16) 187 addr[0]=2 as u8; addr[1]=0 as u8 188 // *DERIVE the port from the const. It used to be hand-written raw bytes -- addr[2]=31, addr[3]=144, 189 // i.e. big-endian 0x1F90 = 8080 -- under a comment claiming "port K_MAGIC_8080". The comment asserted a 190 // relationship the code did not implement: the const was DEAD and the bind was independent of it. 191 // Cost: two "port changes" (8080 -> 18795 -> 18801) that edited only the dead const and moved nothing, 192 // while the daemon kept binding 8080 -- which redirect.elf holds -- and died on every spawn. The log 193 // line `bind fail (port 8080 in use?)` was accurate the whole time and was read as a stale string. 194 // *A CONSTANT THAT NOTHING READS IS NOT CONFIGURATION, IT IS A COMMENT THAT COMPILES. 195 addr[2]=((ARCSRV_PORT >> 8) & 0xff) as u8; addr[3]=(ARCSRV_PORT & 0xff) as u8 196 var z: i64=4; while z<16 { addr[z]=0 as u8; z=z+1 } 197 // *THE PORT IS PRINTED FROM THE CONST, NEVER RETYPED. Both messages below hardcoded "8080" while the 198 // bind used ARCSRV_PORT, so a CORRECT server reported a WRONG port and the failure message named a port 199 // it was not even trying to bind. Same defect class as the bind bug itself -- a literal asserting a value 200 // the code does not use -- and the reason a truthful log line got dismissed as stale for three rounds. 201 let pstr: *u8=sys_mmap(128); var pso: i64=apn(pstr,0,ARCSRV_PORT); pstr[pso]=0 as u8 202 if sys_bind(fd, addr, 16)<0 { sw("bind fail (port " as *u8); sw(pstr); sw(" in use?)\n" as *u8); return 1 } 203 if sys_listen(fd, 16)<0 { sw("listen fail\n" as *u8); return 1 } 204 sw("nx_archive_server: serving web_assets/ + /blocklist on http://localhost:" as *u8); sw(pstr); sw("/\n" as *u8) 205 206 let req: *u8=sys_mmap(K_MAGIC_8192) 207 let hdr: *u8=sys_mmap(K_MAGIC_8192) 208 let fpath: *u8=sys_mmap(K_MAGIC_2048) 209 let lb: *i64=sys_mmap(16) as *i64 210 let htmlbuf: *u8=sys_mmap(K_MAGIC_2097152) 211 212 var go: i64=1 213 while go==1 { 214 let cfd: i64=sys_accept(fd) 215 if cfd<0 { } else { 216 let rn: i64=sys_read(cfd, req, K_MAGIC_8192) 217 if rn<=0 { sys_close(cfd) } else { 218 // path = after first space, up to next space/?/end; query = after '?' up to space 219 var f1: i64=0; var i: i64=0; var g1: i64=1 220 while g1==1 { if i<rn { if (req[i] as i64)==0x20 { f1=i; g1=0 } else { i=i+1 } } else { f1=rn; g1=0 } } 221 var pst: i64=f1+1 222 var pen: i64=pst; var j: i64=pst; var g2: i64=1 223 while g2==1 { if j<rn { let c: i64=req[j] as i64; if c==0x20 { pen=j; g2=0 } else { if c==0x3f { pen=j; g2=0 } else { j=j+1 } } } else { pen=j; g2=0 } } 224 let plen: i64=pen-pst 225 var qst: i64=0-1; var qen: i64=0-1 226 if pen<rn { if (req[pen] as i64)==0x3f { qst=pen+1; var jj: i64=qst; var g3: i64=1; while g3==1 { if jj<rn { if (req[jj] as i64)==0x20 { qen=jj; g3=0 } else { jj=jj+1 } } else { qen=jj; g3=0 } } } } 227 228 var routed: i64=0 229 if has_dotdot(req, pst, plen)==1 { 230 var ho: i64=ap(hdr,0,"HTTP/1.1 403 Forbidden\r\nContent-Length: 9\r\nConnection: close\r\n\r\nForbidden" as *u8) 231 sys_write(cfd, hdr, ho); sys_close(cfd); routed=1 232 } 233 // /blocklist -- live supervised view rendered from seg_store 234 if routed==0 { if path_eq(req,pst,plen,"/blocklist" as *u8,10)==1 { 235 let bo: i64=bl_render_html(BL_PREFIX, htmlbuf) 236 var ho: i64=ap(hdr,0,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " as *u8) 237 ho=apn(hdr,ho,bo); ho=ap(hdr,ho,"\r\nConnection: close\r\n\r\n" as *u8) 238 sys_write(cfd, hdr, ho); sys_write(cfd, htmlbuf, bo); sys_close(cfd); routed=1 239 } } 240 // /blocklist/approve?id=N -- operator ACTION: flip to approved, then back to the list 241 if routed==0 { if path_starts(req,pst,plen,"/blocklist/approve" as *u8,18)==1 { 242 let idb: *u8=sys_mmap(256); qid(req, qst, qen, idb) 243 if (idb[0] as i64)!=0 { bl_approve(idb) } 244 var ho: i64=ap(hdr,0,"HTTP/1.1 303 See Other\r\nLocation: /blocklist\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8) 245 sys_write(cfd, hdr, ho); sys_close(cfd); routed=1 246 } } 247 // /blocklist/retract?id=N -- operator ACTION: retract (stops blocking; history kept) 248 if routed==0 { if path_starts(req,pst,plen,"/blocklist/retract" as *u8,18)==1 { 249 let idb: *u8=sys_mmap(256); qid(req, qst, qen, idb) 250 if (idb[0] as i64)!=0 { bl_retract(idb) } 251 var ho: i64=ap(hdr,0,"HTTP/1.1 303 See Other\r\nLocation: /blocklist\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8) 252 sys_write(cfd, hdr, ho); sys_close(cfd); routed=1 253 } } 254 // "/" -> landing page 255 if routed==0 { if plen<=1 { 256 var bo: i64=0 257 bo=ap(req,bo,"<!DOCTYPE html><meta charset=utf-8><title>Nishi Time Machine</title><body style='font-family:system-ui;background:#0e0f13;color:#e7e7ea;max-width:760px;margin:40px auto;padding:0 20px'><h1>&#128336; Nishi Time Machine</h1><ul style='line-height:2'>" as *u8) 258 bo=ap(req,bo,"<li><a href=/archive/page3.com/index.html><b>page3.com</b> &mdash; scroll the history (134 captures 1996-2003), local media</a>" as *u8) 259 bo=ap(req,bo,"<li><a href=/archive/megastar.co.uk/index.html><b>megastar.co.uk</b> &mdash; scroll the history (61 captures, 1998+)</a>" as *u8) 260 bo=ap(req,bo,"<li><a href=/archive/recovery.html>media recovery &mdash; perceptual fingerprint + CID dedup</a>" as *u8) 261 bo=ap(req,bo,"<li><a href=/blocklist><b>do-not-recover blocklist</b> &mdash; supervised, justified, approve on the page</a>" as *u8) 262 bo=ap(req,bo,"</ul><p style=color:#8a8f9a>Served sovereignly &mdash; our own TLS-1.3 fetch, parsers, image decode, seg_store, and this HTTP server.</p></body>" as *u8) 263 var ho: i64=ap(hdr,0,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " as *u8) 264 ho=apn(hdr,ho,bo); ho=ap(hdr,ho,"\r\nConnection: close\r\n\r\n" as *u8) 265 sys_write(cfd, hdr, ho); sys_write(cfd, req, bo); sys_close(cfd); routed=1 266 } } 267 // static file under web_assets/ 268 if routed==0 { 269 var fp: i64=ap(fpath,0,"web_assets" as *u8) 270 var k: i64=pst 271 while k<pen { fpath[fp]=req[k]; fp=fp+1; k=k+1 } 272 fpath[fp]=0 as u8 273 // DIRECTORY FIRST. getdents64 refuses a regular file (-ENOTDIR), so this is a single 274 // call that both classifies and reads -- and it runs BEFORE sys_read_file, because 275 // sys_read_file SUCCEEDS on a directory with 0 bytes and that is exactly the empty-200. 276 let dl: i64=as_dirlist(fpath, req, pst, pen, htmlbuf) 277 if dl>0 { 278 var dho: i64=ap(hdr,0,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " as *u8) 279 dho=apn(hdr,dho,dl); dho=ap(hdr,dho,"\r\nConnection: close\r\n\r\n" as *u8) 280 sys_write(cfd, hdr, dho); sys_write(cfd, htmlbuf, dl); sys_close(cfd) 281 } else { 282 let body: *u8=sys_read_file(fpath, lb) 283 if body==(0 as *u8) { 284 var ho: i64=ap(hdr,0,"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot found" as *u8) 285 sys_write(cfd, hdr, ho); sys_close(cfd) 286 } else { 287 let fn: i64=lb[0] 288 // bind the const to a LOCAL pointer before indexing it -- a direct CONST[i] read can 289 // compile clean and then fetch garbage (banked nx_cc gotcha), and this one sizes a 290 // Content-Length, so getting it wrong truncates the page rather than failing loudly. 291 let tg: *u8 = ARC_TOGGLE 292 var inj: i64=0 293 if endswith(fpath, fp, ".html" as *u8, 5)==1 { inj=1 } 294 if endswith(fpath, fp, ".htm" as *u8, 4)==1 { inj=1 } 295 var extra: i64=0 296 if inj==1 { var t: i64=0; while tg[t]!=(0 as u8) { t=t+1 } extra=t } 297 var ho: i64=ap(hdr,0,"HTTP/1.1 200 OK\r\nContent-Type: " as *u8) 298 ho=ap(hdr,ho,ctype(fpath,fp)); ho=ap(hdr,ho,"\r\nContent-Length: " as *u8); ho=apn(hdr,ho,fn+extra); ho=ap(hdr,ho,"\r\nConnection: close\r\n\r\n" as *u8) 299 sys_write(cfd, hdr, ho) 300 var sent: i64=0 301 while sent<fn { let wn: i64=sys_write(cfd, ((body as i64)+sent) as *u8, fn-sent); if wn<=0 { sent=fn } else { sent=sent+wn } } 302 if inj==1 { var es: i64=0; while es<extra { let ew: i64=sys_write(cfd, ((tg as i64)+es) as *u8, extra-es); if ew<=0 { es=extra } else { es=es+ew } } } 303 sys_close(cfd) 304 } 305 } 306 } 307 } 308 } 309 } 310 return 0 311}