code wiki / _hdl_build / nx_reader_zoomserve.nx

nx_reader_zoomserve.nx source

↩ module page · 191 lines · 8458 B

1// nx_reader_zoomserve.nx -- the reader daemon's INTELLIGENT-ZOOM prep endpoint (lib; the lib+gate split so 2// nx_reader_zoomserve_gate exercises the handler in-process, no socket). GET /api/zoomprep?slug=<slug>& 3// file=<pagefile>&rtl=<0|1> ensures the tile pyramid + panel map exist for ONE comic/scan page: 4// reader/<slug>/<file> --fork ./_offc/nx_reader_page_prep.elf--> reader/<slug>/zoom/<stem>/{dz.json, 5// panels.json, <level>/<col>_<row>.jpg} 6// The tiles are then served by the EXISTING allowlisted /api/book route (p=reader/<slug>/zoom/<stem>/...), so 7// serving needs ZERO new code -- this endpoint only ensures the artifacts exist (lazy, cache-first, idempotent). 8// FAIL-CLOSED: slug must be a single safe component (alnum/_/-), file a safe leaf (alnum/_/-/., no "..", no 9// leading dot, exactly path-component chars) -> 403; missing source page -> 404; prep failure -> 500 with the 10// exit code; SUCCESS is decided by dz.json EXISTING after the fork (never by exit code alone -- no fake green). 11// The daemon behind THE WALL calls zs_handle_zoomprep; CWD is the repo root (paths repo-relative, same contract 12// as ms_handle_book / ms_run_extractor). license_tier: ORIGINAL 13import "nx_syscalls.nx" 14 15const ZS_ROOT: *u8 = "knowledge/staging/media/reader/" 16const ZS_PREP_ELF: *u8 = "./_offc/nx_reader_page_prep.elf" 17const ZS_TILESIZE: *u8 = "512" // argv literal as a const (inline "..." as *u8 as i64 in an argv[] once miscompiled -> EFAULT/127; see nx_hostctl HC_READER note) 18 19func zs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 20func zs_apps(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } return off + i } 21func zs_dec(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8 = sys_mmap(28); var m: i64 = v; var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { dst[off + i] = t[k - 1 - i]; i = i + 1 } return off + k } 22 23// ?key=value extractor (self-contained twin of ms_qval; value stops at '&', space, or end) 24func zs_qval(p: *u8, plen: i64, key: *u8, out: *u8, outcap: i64) -> i64 { 25 let kl: i64 = zs_slen(key) 26 var i: i64 = 0 27 while i + kl + 1 <= plen { 28 var hit: i64 = 1 29 var j: i64 = 0 30 while j < kl { if p[i + j] != key[j] { hit = 0; j = kl } else { j = j + 1 } } 31 if hit == 1 { if p[i + kl] == (61 as u8) { 32 var s: i64 = i + kl + 1 33 var o: i64 = 0 34 while s < plen { 35 let c: i64 = p[s] as i64 36 if c == 38 { s = plen } else { if c == 32 { s = plen } else { 37 if o < outcap - 1 { out[o] = p[s]; o = o + 1 } 38 s = s + 1 39 } } 40 } 41 out[o] = 0 as u8 42 return o 43 } } 44 i = i + 1 45 } 46 out[0] = 0 as u8 47 return 0 48} 49 50// slug: single safe path component, alnum/_/- only (mirror of ms_slug_safe) 51func zs_slug_safe(s: *u8) -> i64 { 52 let n: i64 = zs_slen(s) 53 if n <= 0 { return 0 } 54 if n > 200 { return 0 } 55 var i: i64 = 0 56 while i < n { 57 let c: i64 = s[i] as i64 58 var ok: i64 = 0 59 if c >= 48 { if c <= 57 { ok = 1 } } 60 if c >= 65 { if c <= 90 { ok = 1 } } 61 if c >= 97 { if c <= 122 { ok = 1 } } 62 if c == 95 { ok = 1 } 63 if c == 45 { ok = 1 } 64 if ok == 0 { return 0 } 65 i = i + 1 66 } 67 return 1 68} 69 70// page-file leaf: alnum/_/-/. ; no "..", no leading dot (so no traversal, no dotfiles, no "/") 71func zs_fname_safe(s: *u8) -> i64 { 72 let n: i64 = zs_slen(s) 73 if n <= 0 { return 0 } 74 if n > 128 { return 0 } 75 if s[0] == (46 as u8) { return 0 } 76 var i: i64 = 0 77 while i < n { 78 let c: i64 = s[i] as i64 79 var ok: i64 = 0 80 if c >= 48 { if c <= 57 { ok = 1 } } 81 if c >= 65 { if c <= 90 { ok = 1 } } 82 if c >= 97 { if c <= 122 { ok = 1 } } 83 if c == 95 { ok = 1 } 84 if c == 45 { ok = 1 } 85 if c == 46 { ok = 1 } 86 if ok == 0 { return 0 } 87 i = i + 1 88 } 89 var j: i64 = 0 90 while j + 1 < n { if s[j] == (46 as u8) { if s[j + 1] == (46 as u8) { return 0 } } j = j + 1 } 91 return 1 92} 93 94func zs_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 } 95func zs_mkdir(path: *u8) -> i64 { __syscall(258, 0 - 100, path as i64, 0x1ed, 0, 0, 0); return 0 } 96 97func zs_emit(cfd: i64, status: *u8, body: *u8) -> i64 { 98 let bn: i64 = zs_slen(body) 99 let hdr: *u8 = sys_mmap(512) 100 var h: i64 = 0 101 h = zs_apps(hdr, h, "HTTP/1.1 " as *u8) 102 h = zs_apps(hdr, h, status) 103 h = zs_apps(hdr, h, "\r\nContent-Type: application/json; charset=utf-8\r\nCache-Control: no-store\r\nContent-Length: " as *u8) 104 h = zs_dec(hdr, h, bn) 105 h = zs_apps(hdr, h, "\r\nConnection: close\r\n\r\n" as *u8) 106 sys_write(cfd, hdr, h) 107 if bn > 0 { sys_write(cfd, body, bn) } 108 sys_close(cfd) 109 return 0 110} 111 112// fork+exec the page-prep elf (mirrors ms_thumb_run / ms_run_extractor). Returns child exit (0 = ok, 127 = absent). 113func zs_prep_run(src: *u8, outdir: *u8, rtl: *u8) -> i64 { 114 let argv: *i64 = sys_mmap(56) as *i64 115 argv[0] = ZS_PREP_ELF as i64 116 argv[1] = src as i64 117 argv[2] = outdir as i64 118 argv[3] = rtl as i64 119 argv[4] = ZS_TILESIZE as i64 120 argv[5] = 0 121 let envp: *i64 = sys_mmap(8) as *i64 122 envp[0] = 0 123 let pid: i64 = sys_fork() 124 if pid == 0 { sys_execve(ZS_PREP_ELF, argv, envp); sys_exit(127) } 125 let st: *i64 = sys_mmap(16) as *i64 126 sys_wait4(pid, st, 0) 127 return (st[0] >> 8) & 0xff 128} 129 130// GET /api/zoomprep?slug=&file=&rtl= -> ensure reader/<slug>/zoom/<stem>/ tiles+panels exist. See header. 131func zs_handle_zoomprep(cfd: i64, p: *u8, plen: i64) -> i64 { 132 let slug: *u8 = sys_mmap(256) 133 zs_qval(p, plen, "slug" as *u8, slug, 256) 134 let file: *u8 = sys_mmap(160) 135 zs_qval(p, plen, "file" as *u8, file, 160) 136 let rtlb: *u8 = sys_mmap(8) 137 zs_qval(p, plen, "rtl" as *u8, rtlb, 8) 138 if rtlb[0] != (48 as u8) { rtlb[0] = 49 as u8; rtlb[1] = 0 as u8 } // anything but "0" -> "1" 139 140 if zs_slug_safe(slug) == 0 { return zs_emit(cfd, "403 Forbidden" as *u8, "{\"ok\":0,\"err\":\"bad slug\"}" as *u8) } 141 if zs_fname_safe(file) == 0 { return zs_emit(cfd, "403 Forbidden" as *u8, "{\"ok\":0,\"err\":\"bad file\"}" as *u8) } 142 143 // src = reader/<slug>/<file> -- must already exist (the extracted comic page) 144 let src: *u8 = sys_mmap(1024) 145 var so: i64 = zs_apps(src, 0, ZS_ROOT) 146 so = zs_apps(src, so, slug) 147 src[so] = 47 as u8; so = so + 1 148 so = zs_apps(src, so, file) 149 src[so] = 0 as u8 150 if zs_exists(src) == 0 { return zs_emit(cfd, "404 Not Found" as *u8, "{\"ok\":0,\"err\":\"no such page\"}" as *u8) } 151 152 // stem = file minus its last .ext 153 let stem: *u8 = sys_mmap(160) 154 var fn: i64 = zs_slen(file) 155 var dot: i64 = fn 156 var di: i64 = 0 157 while di < fn { if file[di] == (46 as u8) { dot = di } di = di + 1 } 158 var si: i64 = 0 159 while si < dot { stem[si] = file[si]; si = si + 1 } 160 stem[dot] = 0 as u8 161 162 // zoomdir parent + outdir = reader/<slug>/zoom/<stem> 163 let zdir: *u8 = sys_mmap(1024) 164 var zo: i64 = zs_apps(zdir, 0, ZS_ROOT) 165 zo = zs_apps(zdir, zo, slug) 166 zo = zs_apps(zdir, zo, "/zoom" as *u8) 167 zdir[zo] = 0 as u8 168 let outdir: *u8 = sys_mmap(1024) 169 var oo: i64 = zs_apps(outdir, 0, zdir) 170 outdir[oo] = 47 as u8; oo = oo + 1 171 oo = zs_apps(outdir, oo, stem) 172 outdir[oo] = 0 as u8 173 174 // cache-first: dz.json already there -> done (idempotent, O(1)) 175 let dz: *u8 = sys_mmap(1024) 176 var dzo: i64 = zs_apps(dz, 0, outdir) 177 dzo = zs_apps(dz, dzo, "/dz.json" as *u8) 178 dz[dzo] = 0 as u8 179 if zs_exists(dz) == 1 { return zs_emit(cfd, "200 OK" as *u8, "{\"ok\":1,\"cached\":1}" as *u8) } 180 181 zs_mkdir(zdir) // parent (idempotent; the prep elf mkdirs the leaf) 182 let rc: i64 = zs_prep_run(src, outdir, rtlb) 183 // truth = dz.json ON DISK (exit code alone is never trusted -- no fake green) 184 if zs_exists(dz) == 1 { return zs_emit(cfd, "200 OK" as *u8, "{\"ok\":1,\"cached\":0}" as *u8) } 185 let eb: *u8 = sys_mmap(128) 186 var eo: i64 = zs_apps(eb, 0, "{\"ok\":0,\"err\":\"prep failed\",\"rc\":" as *u8) 187 eo = zs_dec(eb, eo, rc) 188 eo = zs_apps(eb, eo, "}" as *u8) 189 eb[eo] = 0 as u8 190 return zs_emit(cfd, "500 Internal Server Error" as *u8, eb) 191}