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}