code wiki / _hdl_build / nx_host_router_test.nx

nx_host_router_test.nx source

↩ module page · 86 lines · 5549 B

1// nx_host_router_test.nx -- ENGINEER gate for the config-driven host router. Real files on disk, 2// real request bytes; proves: (1) Host-> doc-root routing, (2) per-request file serve = HOT content, 3// (3) path-traversal rejected, (4) unknown host + missing file = 404, (5) MIME by extension, 4// (6) LIVE hot-swap -- rewrite the file, re-serve, see new bytes with NO recompile. license_tier: ORIGINAL 5import "nx_host_router.nx" 6import "nx_syscalls.nx" 7 8func t_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 9func t_pn(v: i64) -> i64 { let b: *u8=sys_mmap(28); let nn: i64=hr_putdec(b,v); sys_write(1,b,nn); return 0 } 10func t_wr(path: *u8, data: *u8, n: i64) -> i64 { let fd: i64=sys_openat_wr(path,0x1a4); if fd<0{return 0-1} sys_write(fd,data,n); sys_close(fd); return 0 } 11// response contains needle? 12func t_has(hay: *u8, hn: i64, needle: *u8) -> i64 { 13 var nn: i64=0; while needle[nn]!=(0 as u8){nn=nn+1} 14 if nn==0 { return 1 } 15 var i: i64=0 16 while i+nn<=hn { var k: i64=0; var ok: i64=1; while k<nn { if hay[i+k]!=needle[k]{ok=0; k=nn} k=k+1 } if ok==1 { return 1 } i=i+1 } 17 return 0 18} 19// print PASS/FAIL; return cond so main can accumulate (no module-level mutable state). 20func t_check(name: *u8, cond: i64) -> i64 { 21 if cond==1 { t_puts(" PASS " as *u8) } else { t_puts(" FAIL " as *u8) } 22 t_puts(name); t_puts("\n" as *u8); return cond 23} 24func t_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 25 26func main() -> i64 { 27 t_wr("/tmp/hrt/aw/index.html" as *u8, "<h1>AW-HOME</h1>" as *u8, 16) 28 t_wr("/tmp/hrt/aw/style.css" as *u8, "body{color:#000}" as *u8, 16) 29 t_wr("/tmp/hrt/fam/index.html" as *u8, "<h1>FAM-HOME</h1>" as *u8, 17) 30 31 let cfg: *u8 = "# sovereign hosting sites table\nandelinwest.com /tmp/hrt/aw\nnishifamily.com /tmp/hrt/fam\n" as *u8 32 let cfgn: i64 = t_slen(cfg) 33 let out: *u8 = sys_mmap(65536) 34 var pass: i64 = 0 35 var total: i64 = 0 36 37 let r1: *u8 = "GET / HTTP/1.1\r\nHost: andelinwest.com\r\n\r\n" as *u8 38 let r1n: i64 = t_slen(r1) 39 var n: i64 = hr_serve(cfg, cfgn, r1, r1n, out, 65536) 40 pass = pass + t_check("andelinwest / -> 200" as *u8, t_has(out,n,"200 OK" as *u8)); total = total + 1 41 pass = pass + t_check("andelinwest / -> AW-HOME body (hot file read)" as *u8, t_has(out,n,"AW-HOME" as *u8)); total = total + 1 42 pass = pass + t_check("andelinwest / -> text/html ctype" as *u8, t_has(out,n,"text/html" as *u8)); total = total + 1 43 44 let r2: *u8 = "GET / HTTP/1.1\r\nHost: nishifamily.com\r\n\r\n" as *u8 45 n = hr_serve(cfg, cfgn, r2, t_slen(r2), out, 65536) 46 pass = pass + t_check("nishifamily / -> FAM-HOME (multi-site routing)" as *u8, t_has(out,n,"FAM-HOME" as *u8)); total = total + 1 47 48 let r3: *u8 = "GET /style.css HTTP/1.1\r\nHost: andelinwest.com\r\n\r\n" as *u8 49 n = hr_serve(cfg, cfgn, r3, t_slen(r3), out, 65536) 50 pass = pass + t_check("/style.css -> text/css MIME" as *u8, t_has(out,n,"text/css" as *u8)); total = total + 1 51 52 let r4: *u8 = "GET /../fam/index.html HTTP/1.1\r\nHost: andelinwest.com\r\n\r\n" as *u8 53 n = hr_serve(cfg, cfgn, r4, t_slen(r4), out, 65536) 54 pass = pass + t_check("path traversal -> 400 blocked" as *u8, t_has(out,n,"400 Bad Request" as *u8)); total = total + 1 55 56 let r5: *u8 = "GET / HTTP/1.1\r\nHost: evil.example\r\n\r\n" as *u8 57 n = hr_serve(cfg, cfgn, r5, t_slen(r5), out, 65536) 58 pass = pass + t_check("unknown host -> 404" as *u8, t_has(out,n,"404 Not Found" as *u8)); total = total + 1 59 60 let r6: *u8 = "GET /nope.html HTTP/1.1\r\nHost: andelinwest.com\r\n\r\n" as *u8 61 n = hr_serve(cfg, cfgn, r6, t_slen(r6), out, 65536) 62 pass = pass + t_check("missing file -> 404" as *u8, t_has(out,n,"404 Not Found" as *u8)); total = total + 1 63 64 // HOT-SWAP: rewrite the file, re-serve same request, see new bytes -- NO recompile 65 t_wr("/tmp/hrt/aw/index.html" as *u8, "<h1>AW-V2-HOTSWAPPED</h1>" as *u8, 25) 66 n = hr_serve(cfg, cfgn, r1, r1n, out, 65536) 67 pass = pass + t_check("HOT-SWAP: same request now serves AW-V2 (no recompile)" as *u8, t_has(out,n,"AW-V2-HOTSWAPPED" as *u8)); total = total + 1 68 69 // CLEAN-URL fix: a bare path that is a directory (no '.' in last segment) 70 // must resolve to <dir>/index.html, so /econsim renders instead of being 71 // served as a 0-byte octet-stream the browser downloads. 72 let rt: *u8 = "/tmp/hrt/fam" as *u8 73 let rtn: i64 = t_slen(rt) 74 let fp: *u8 = sys_mmap(5120) 75 var fl: i64 = hr_resolve(rt, rtn, "/econsim" as *u8, t_slen("/econsim" as *u8), fp, 5120) 76 pass = pass + t_check("clean-url: bare /econsim -> /econsim/index.html" as *u8, t_has(fp, fl, "/econsim/index.html" as *u8)); total = total + 1 77 fl = hr_resolve(rt, rtn, "/wiki/charter.html" as *u8, t_slen("/wiki/charter.html" as *u8), fp, 5120) 78 pass = pass + t_check("clean-url: real .html file left unchanged" as *u8, t_has(fp, fl, "/wiki/charter.html" as *u8)); total = total + 1 79 pass = pass + t_check("clean-url: .html NOT mangled with index.html" as *u8, 1 - t_has(fp, fl, "charter.html/index.html" as *u8)); total = total + 1 80 fl = hr_resolve(rt, rtn, "/econsim/" as *u8, t_slen("/econsim/" as *u8), fp, 5120) 81 pass = pass + t_check("clean-url: trailing slash still -> index.html" as *u8, t_has(fp, fl, "/econsim/index.html" as *u8)); total = total + 1 82 83 t_puts("---- router gate: passed " as *u8); t_pn(pass); t_puts(" / " as *u8); t_pn(total); t_puts(" ----\n" as *u8) 84 if pass == total { sys_exit(0); return 0 } 85 sys_exit(1); return 1 86}