code wiki / (root) / nx_lib_http.nx

nx_lib_http.nx source

↩ module page · 174 lines · 7842 B

1// nx_lib_http.nx -- PURE HTTP routing/response for the sovereign library API. 2// No main, no sockets: request-path -> full HTTP response bytes, straight from 3// nx_lib_store via nx_lib_serve. Gateable in isolation; the daemon nx_lib_httpd 4// wraps it with the socket accept loop. 5// GET /api/works -> JSON array of work_hks 6// GET /api/work/<hk> -> JSON work record (200) or 404 7// license_tier: ORIGINAL 8import "nx_lib_serve.nx" 9import "nx_lib_index.nx" 10import "nx_lib_ingest.nx" 11import "nx_lib_ingest_tsv.nx" // nx_lib_ingest_buf -- the TSV ingest organ deleted by the 07-21 dedupe 12import "nx_lib_page.nx" 13 14func lhd_puts(buf: *u8, off: i64, s: *u8) -> i64 { 15 var o: i64 = off 16 var i: i64 = 0 17 while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 } 18 return o 19} 20 21// append decimal v; nxc2 has no % so mod = x-(x/10)*10. 22func lhd_putdec(buf: *u8, off: i64, v: i64) -> i64 { 23 if v == 0 { buf[off] = 48 as u8; return off + 1 } 24 let tmp: *u8 = sys_mmap(32) 25 var k: i64 = 0 26 var x: i64 = v 27 while x > 0 { tmp[k] = (48 + (x - (x / 10) * 10)) as u8; x = x / 10; k = k + 1 } 28 var o: i64 = off 29 var ri: i64 = k - 1 30 while ri >= 0 { buf[o] = tmp[ri]; o = o + 1; ri = ri - 1 } 31 return o 32} 33 34func lhd_eq(path: *u8, path_n: i64, s: *u8) -> i64 { 35 let sn: i64 = ls_strlen(s) 36 if sn != path_n { return 0 } 37 var i: i64 = 0 38 while i < sn { if path[i] != s[i] { return 0 } i = i + 1 } 39 return 1 40} 41 42func lhd_starts(path: *u8, path_n: i64, pre: *u8) -> i64 { 43 var i: i64 = 0 44 while pre[i] != (0 as u8) { 45 if i >= path_n { return 0 } 46 if path[i] != pre[i] { return 0 } 47 i = i + 1 48 } 49 return 1 50} 51 52// PURE: build the full HTTP response for a request path into out; return length. 53func lhd_response(path: *u8, path_n: i64, out: *u8) -> i64 { 54 // ---- /research edge-prefix alias (ADDITIVE 2026-07-02) ---- 55 // The sites edge routes `nishifamily.com /research 8095 stream` via proxy_routes.conf, 56 // and STREAM mode passes the RAW (unstripped) request through -- so accept the full 57 // /research prefix here by delegating to the identical stripped route. Buffered-mode 58 // clients (nx_http_proxy_relay strips before forwarding) are unaffected. 59 if lhd_starts(path, path_n, "/research" as *u8) == 1 { 60 if path_n == 9 { return lhd_page_home(("" as *u8), 0, out) } 61 let anc: i64 = path[9] as i64 62 if anc == 47 { return lhd_response(((path as i64) + 9) as *u8, path_n - 9, out) } 63 if anc == 63 { 64 // "/research?q=x" -> "/?q=x" (re-root the query form) 65 let ab: *u8 = sys_mmap(path_n + 8) 66 ab[0] = 47 as u8 67 var ai: i64 = 9 68 var ak: i64 = 1 69 while ai < path_n { ab[ak] = path[ai]; ak = ak + 1; ai = ai + 1 } 70 return lhd_response(ab, ak, out) 71 } 72 } 73 // ---- sovereign zero-JS HTML frontend (served before the JSON API) ---- 74 if lhd_eq(path, path_n, "/" as *u8) == 1 { return lhd_page_home(("" as *u8), 0, out) } 75 if lhd_starts(path, path_n, "/?" as *u8) == 1 { 76 // ⛔DERIVED FROM THE INPUT, NOT GUESSED. This was sys_mmap(2048) filled by an UNBOUNDED copy out 77 // of the request path, and path_n is bounded only by the daemon's LHD_REQ_CAP of 131072 -- so a 78 // GET whose query string exceeded 2 KB overflowed this buffer FROM THE WIRE. The query value is a 79 // SUBSTRING of the path, so path_n+1 cannot be exceeded by construction and no bound check is 80 // needed. ★A CEILING THAT HAS TO BE GUESSED IS A DEFECT GENERATOR; THE INPUT ALREADY KNOWS ITS SIZE. 81 let hqbuf: *u8 = sys_mmap(path_n + 1) 82 var hqs: i64 = 0 - 1 83 var hsi: i64 = 0 84 while hsi + 1 < path_n { if hqs < 0 { if path[hsi] == 113 as u8 { if path[hsi + 1] == 61 as u8 { hqs = hsi + 2 } } } hsi = hsi + 1 } 85 var hql: i64 = 0 86 if hqs >= 0 { 87 var hk: i64 = hqs 88 while hk < path_n { 89 let c: i64 = path[hk] as i64 90 if c == 38 { hk = path_n } 91 else { if c == 43 { hqbuf[hql] = 32 as u8; hql = hql + 1; hk = hk + 1 } else { hqbuf[hql] = path[hk]; hql = hql + 1; hk = hk + 1 } } 92 } 93 } 94 return lhd_page_home(hqbuf, hql, out) 95 } 96 if lhd_starts(path, path_n, "/work/" as *u8) == 1 { 97 return lhd_page_work(((path as i64) + 6) as *u8, path_n - 6, out) 98 } 99 100 // ★THE CAPACITY IS NAMED AND PASSED, so the writer enforces it instead of every caller assuming it. 101 let LHD_BODY_CAP: i64 = 1048576 102 let body: *u8 = sys_mmap(LHD_BODY_CAP) 103 var bn: i64 = 0 104 var ok: i64 = 0 105 if lhd_eq(path, path_n, "/api/works" as *u8) == 1 { 106 bn = nx_lib_serve_list(body, LHD_BODY_CAP) 107 // -1 = the catalog no longer fits. REFUSE LOUDLY rather than serve a silently truncated array: 108 // a JSON list cut mid-element is not a smaller answer, it is a WRONG one. 109 if bn >= 0 { ok = 1 } 110 } else { if lhd_starts(path, path_n, "/api/search" as *u8) == 1 { 111 // GET /api/search?q=<terms> ('+' -> space, '&' ends the value) 112 // Same wire-overflow as hqbuf above: was 2048 with an unbounded copy from the path. 113 let qbuf: *u8 = sys_mmap(path_n + 1) 114 var qs: i64 = 0 - 1 115 var si: i64 = 0 116 while si + 1 < path_n { 117 if qs < 0 { if path[si] == 113 as u8 { if path[si + 1] == 61 as u8 { qs = si + 2 } } } 118 si = si + 1 119 } 120 var ql: i64 = 0 121 if qs >= 0 { 122 var kk: i64 = qs 123 while kk < path_n { 124 let c: i64 = path[kk] as i64 125 if c == 38 { kk = path_n } 126 else { 127 if c == 43 { qbuf[ql] = 32 as u8; ql = ql + 1; kk = kk + 1 } 128 else { qbuf[ql] = path[kk]; ql = ql + 1; kk = kk + 1 } 129 } 130 } 131 } 132 bn = nx_lib_index_search(qbuf, ql, body) // O(1) persisted inverted index 133 ok = 1 134 } else { if lhd_starts(path, path_n, "/api/work/" as *u8) == 1 { 135 // Was 1024 with an unbounded copy of path[10..path_n] plus a NUL; path_n+1 bounds both. 136 let hkbuf: *u8 = sys_mmap(path_n + 1) 137 var i: i64 = 10 138 var k: i64 = 0 139 while i < path_n { hkbuf[k] = path[i]; k = k + 1; i = i + 1 } 140 hkbuf[k] = 0 as u8 141 let jl: i64 = nx_lib_serve_work(hkbuf, body) 142 if jl >= 0 { bn = jl; ok = 1 } 143 } } } 144 if ok == 0 { 145 let r404: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: application/json\r\nContent-Length: 14\r\nConnection: close\r\n\r\n{\"error\":\"nf\"}" as *u8 146 return lhd_puts(out, 0, r404) 147 } 148 var o: i64 = 0 149 o = lhd_puts(out, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " as *u8) 150 o = lhd_putdec(out, o, bn) 151 o = lhd_puts(out, o, "\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8) 152 var j: i64 = 0 153 while j < bn { out[o] = body[j]; o = o + 1; j = j + 1 } 154 return o 155} 156 157// PURE: ingest a TSV request body into nx_lib_store, rebuild the search index, 158// return an HTTP 200 {"ingested":N}. Backs POST /api/ingest. Gateable. 159func lhd_ingest_response(body: *u8, body_n: i64, out: *u8) -> i64 { 160 let cnt: i64 = nx_lib_ingest_buf(body, body_n) 161 nx_lib_index_build() // reindex so new works are searchable 162 let jb: *u8 = sys_mmap(256) 163 var jo: i64 = 0 164 jo = lhd_puts(jb, jo, "{\"ingested\":" as *u8) 165 jo = lhd_putdec(jb, jo, cnt) 166 jo = lhd_puts(jb, jo, "}" as *u8) 167 var o: i64 = 0 168 o = lhd_puts(out, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " as *u8) 169 o = lhd_putdec(out, o, jo) 170 o = lhd_puts(out, o, "\r\nConnection: close\r\n\r\n" as *u8) 171 var j2: i64 = 0 172 while j2 < jo { out[o] = jb[j2]; o = o + 1; j2 = j2 + 1 } 173 return o 174}