code wiki / (root) / nx_https_get_cli2.nx

nx_https_get_cli2.nx source

↩ module page · 400 lines · 18612 B

1// nx_https_get_cli2.nx -- CANARY of the sovereign HTTPS fetch with HELLO AUTO-FALLBACK (seq759 design, finally real). 2// nx_https_get <url> [connect-host:port] 3// WHY THIS EXISTS (measured 2026-07-25): the live chrome-ONLY build regressed every host that rejects the 4// Chrome-mimic hello but accepts our plain minimal hello (api.nhtsa.gov proven 200 on 07-23, verdict=5 now; 5// same class: www.sec.gov, efts.sec.gov, www.justice.gov). Cloudflare-walled hosts (api.worldbank.org) still 6// NEED the chrome hello. No single hello serves both sets, so: attempt 1 = chrome-JA3 (identical to live 7// behavior for every currently-working feed), on handshake failure reconnect and attempt 2 = plain hello. 8// Fallback is LEGIBLE (stderr nishi-hello line, never a silently-different code path). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 11import "nx_csprng.nx" 12import "nx_x509_trust_store.nx" 13import "nx_trust_store_load_from_certdata.nx" 14import "nx_tls13_client_validate_certificate.nx" 15import "nx_tls13_client_session_run.nx" 16import "nx_tls13_chrome_session.nx" // Chrome-JA3 ClientHello runner (beats anti-bot CDN TLS walls; inherits the recv_hs reassembly fix) 17import "nx_https_url_for_fetch.nx" 18import "nx_https_url_connect.nx" 19import "nx_https_get_complete.nx" 20import "nx_tls_cert_cache.nx" 21import "nx_gzip_wrap.nx" // Content-Encoding: gzip -- the canonical in-tree inflate (KAT: nx_gzip_inflate_kat_test) 22import "nx_zlib_wrap.nx" // Content-Encoding: deflate -- HTTP `deflate` means RFC 1950 zlib 23const HGC_MAGIC_1950: i64 = 1950 24const HGC_MAGIC_4194304: i64 = 4194304 25const HGC_MAGIC_65535: i64 = 65535 26 27const HGC_CERTDATA: *u8 = "data/mozilla_certdata.txt" as *u8 28const HGC_OUTCAP: i64 = 33554432 // 32 MiB response cap (4 MiB refused a 5MB Koikatsu card 2026-08-04 -- code=8 overflow; real assets are routinely >4MiB) 29 30func hgc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 31func hgc_put(s: *u8) -> i64 { sys_write(1, s, hgc_slen(s)); return 0 } 32func hgc_putn(v: i64) -> i64 { 33 let b: *u8 = sys_mmap(24) 34 var m: i64 = v 35 if m == 0 { b[0] = 48 as u8; sys_write(1, b, 1); return 0 } 36 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 37 var nd: i64 = 0 38 var t: i64 = m 39 while t > 0 { nd = nd + 1; t = t / 10 } 40 var i: i64 = nd - 1 41 while i >= 0 { b[i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 42 sys_write(1, b, nd) 43 return 0 44} 45// stderr twins (the fallback diagnostic must not pollute the stdout HTTP payload) 46func hgc_put2(s: *u8) -> i64 { sys_write(2, s, hgc_slen(s)); return 0 } 47func hgc_putn2(v: i64) -> i64 { 48 let b: *u8 = sys_mmap(24) 49 var m: i64 = v 50 if m == 0 { b[0] = 48 as u8; sys_write(2, b, 1); return 0 } 51 if m < 0 { sys_write(2, "-" as *u8, 1); m = 0 - m } 52 var nd: i64 = 0 53 var t: i64 = m 54 while t > 0 { nd = nd + 1; t = t / 10 } 55 var i: i64 = nd - 1 56 while i >= 0 { b[i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 57 sys_write(2, b, nd) 58 return 0 59} 60 61// ---- BODY MODE (additive; the DEFAULT output is byte-for-byte unchanged, rule 19) ------------- 62// This client has always emitted the RAW response: status line, headers, and -- when the origin 63// uses Transfer-Encoding: chunked -- the hex chunk-size markers interleaved with the payload. 64// That is right for a debugging client and CORRUPTING for any caller that content-addresses what 65// it fetched: a sha256 over that stream hashes a framing artifact, not the document. 66// ★★★★★IT DOES NOT FAIL -- IT SUCCEEDS WITH THE WRONG BYTES, which is why it went unnoticed. A 67// fetch that errors gets fixed; a fetch that returns plausible-but-reframed bytes gets TRUSTED. 68// Measured 2026-07-31 against https://www.rfc-editor.org/rfc/rfc7748.txt: the first chunk header 69// read `9982` = 39298 decimal, exactly the true body length, sitting inline in the output. 70// `--body` emits the de-chunked entity body only. Callers of the default path are untouched. 71// 2026-08-04: --body also DECODES the transport coding (Content-Encoding gzip/deflate via 72// nx_gzip_wrap / nx_zlib_wrap, the canonical in-tree inflates). Live-hit that forced this: 73// db.bepis.moe answered gzip and the emitted "body" was compressed bytes -- success with the 74// wrong bytes again. Decode failure or a coding we cannot decode (br, zstd, stacked) is a LOUD 75// nonzero exit (8/9), never wrong bytes; the raw default path still serves the actual wire bytes. 76// NOTE ON DRY (rule 15): _hdl_build/nx_http_chunked.nx already implements this exact decode, but 77// NO file in this corpus imports across directories, so it is unreachable from runtime/. Fixing 78// the import model is the right long-term move; blocking a pinned-evidence chain on it is not. 79func hgc_streq(a: *u8, b: *u8) -> i64 { 80 var i: i64 = 0 81 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 82 if b[i] != (0 as u8) { return 0 } 83 return 1 84} 85 86func hgc_hexval(c: i64) -> i64 { 87 if c >= 48 { if c <= 57 { return c - 48 } } 88 if c >= 97 { if c <= 102 { return c - 87 } } 89 if c >= 65 { if c <= 70 { return c - 55 } } 90 return 0 - 1 91} 92 93func hgc_hdr_end(b: *u8, n: i64) -> i64 { 94 var i: i64 = 0 95 while i + 3 < n { 96 if (b[i] as i64) == 13 { if (b[i+1] as i64) == 10 { if (b[i+2] as i64) == 13 { if (b[i+3] as i64) == 10 { return i + 4 } } } } 97 i = i + 1 98 } 99 return 0 - 1 100} 101 102// Case-insensitive scan of the HEADER REGION ONLY. Scanning the whole response would false-positive 103// on any document that merely contains the word, and RFC 7748 is not it -- but the next fetched doc 104// might be, and a framing decision must never depend on payload text. 105func hgc_hdr_chunked(b: *u8, n: i64, he: i64) -> i64 { 106 let pat: *u8 = "chunked" as *u8 107 var i: i64 = 0 108 while i + 7 <= he { 109 var m: i64 = 1 110 var j: i64 = 0 111 while j < 7 { 112 var c: i64 = b[i+j] as i64 113 if c >= 65 { if c <= 90 { c = c + 32 } } 114 if c != (pat[j] as i64) { m = 0 } 115 j = j + 1 116 } 117 if m == 1 { return 1 } 118 i = i + 1 119 } 120 return 0 121} 122 123// Decode a chunked body. Tolerates chunk extensions after the size, stops at the 0-size chunk. 124// Returns the decoded length, or -1 when the first size line has no hex digits at all. 125func hgc_dechunk(src: *u8, n: i64, out: *u8) -> i64 { 126 var i: i64 = 0 127 var o: i64 = 0 128 var done: i64 = 0 129 while done == 0 { 130 var sz: i64 = 0 131 var got: i64 = 0 132 var st: i64 = 0 133 while st == 0 { 134 if i >= n { st = 1 } else { 135 let hv: i64 = hgc_hexval(src[i] as i64) 136 if hv >= 0 { sz = (sz * 16) + hv; got = got + 1; i = i + 1 } else { st = 1 } 137 } 138 } 139 if got == 0 { return 0 - 1 } 140 var st2: i64 = 0 141 while st2 == 0 { if i >= n { st2 = 1 } else { if (src[i] as i64) == 13 { st2 = 1 } else { i = i + 1 } } } 142 if i + 1 < n { if (src[i] as i64) == 13 { if (src[i+1] as i64) == 10 { i = i + 2 } } } 143 if sz == 0 { done = 1 } else { 144 var k: i64 = 0 145 while k < sz { if i < n { out[o] = src[i]; o = o + 1; i = i + 1 } k = k + 1 } 146 if i + 1 < n { if (src[i] as i64) == 13 { if (src[i+1] as i64) == 10 { i = i + 2 } } } 147 } 148 } 149 return o 150} 151 152// Which Content-Encoding did the server apply? HEADER REGION ONLY -- a body that merely 153// contains the words must never drive a framing decision (same discipline as hgc_hdr_chunked). 154// Header-driven ON PURPOSE, not magic-sniff: a .gz FILE fetched over identity encoding IS the 155// document, and sniff-inflating it would corrupt the download. Transport coding is the only 156// thing --body may strip. Returns 0 = identity/none, 1 = gzip, 2 = deflate, 3 = undecodable 157// here (br, zstd, stacked codings) -- the caller must REFUSE, because emitting still-compressed 158// bytes from a mode whose contract is "the decoded document" is success-with-wrong-bytes, the 159// exact shape that poisoned the search index (debt 1785794199). 160func hgc_hdr_enc(b: *u8, n: i64, he: i64) -> i64 { 161 let pat: *u8 = "content-encoding:" as *u8 162 var at: i64 = 0 - 1 163 var i: i64 = 0 164 while i + 17 <= he { 165 var m: i64 = 1 166 var j: i64 = 0 167 while j < 17 { 168 var c: i64 = b[i+j] as i64 169 if c >= 65 { if c <= 90 { c = c + 32 } } 170 if c != (pat[j] as i64) { m = 0 } 171 j = j + 1 172 } 173 if m == 1 { at = i + 17; i = he + 1 } else { i = i + 1 } 174 } 175 if at < 0 { return 0 } 176 var sk: i64 = 0 177 while sk == 0 { 178 if at >= he { sk = 1 } else { 179 let cs: i64 = b[at] as i64 180 if cs == 32 { at = at + 1 } else { if cs == 9 { at = at + 1 } else { sk = 1 } } 181 } 182 } 183 let tok: *u8 = sys_mmap(16) 184 var tl: i64 = 0 185 var sc: i64 = 0 186 while sc == 0 { 187 if at >= he { sc = 1 } else { 188 var cv: i64 = b[at] as i64 189 if cv == 13 { sc = 1 } else { 190 if cv == 10 { sc = 1 } else { 191 if cv == 44 { return 3 } else { 192 if cv == 32 { sc = 1 } else { 193 if cv == 59 { sc = 1 } else { 194 if tl >= 15 { return 3 } 195 if cv >= 65 { if cv <= 90 { cv = cv + 32 } } 196 tok[tl] = cv as u8 197 tl = tl + 1 198 at = at + 1 199 } } } } } 200 } 201 } 202 tok[tl] = 0 as u8 203 if tl == 0 { return 0 } 204 if hgc_streq(tok, "gzip" as *u8) == 1 { return 1 } 205 if hgc_streq(tok, "x-gzip" as *u8) == 1 { return 1 } 206 if hgc_streq(tok, "deflate" as *u8) == 1 { return 2 } 207 if hgc_streq(tok, "identity" as *u8) == 1 { return 0 } 208 return 3 209} 210 211func main(argc: i64, argv: *i64) -> i64 { 212 if argc < 2 { hgc_put("ERROR: usage: nx_https_get <url> [connect-host:port] [--body]\n" as *u8); return 2 } 213 let url: *u8 = argv[1] as *u8 214 let now: i64 = sys_now_realtime_sec() 215 216 // ---- trust store + real TLS entropy buffers (refilled per attempt) ---- 217 let r: i64 = nx_trust_store_load_from_certdata(HGC_CERTDATA, 512, HGC_MAGIC_4194304) 218 if r <= 0 { hgc_put("ERROR: trust-store load failed (data/mozilla_certdata.txt on daemon CWD?)\n" as *u8); return 3 } 219 let store: *TrustStore = r as *TrustStore 220 let cr: *u8 = sys_mmap(32) 221 let priv: *u8 = sys_mmap(32) 222 223 // ---- parse the REAL url (drives SNI / Host / path / cert-name) ---- 224 let url_p: *NxUrl = nx_url_new() 225 let target_raw: *u8 = sys_mmap(32) 226 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget 227 target.url = url_p 228 target.port = 0 229 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { hgc_put("ERROR: bad url\n" as *u8); return 2 } 230 231 // ---- connect override parsed ONCE (an attempt reconnects with the same endpoint) ---- 232 var have_ov: i64 = 0 233 let ipbox: *i64 = sys_mmap(8) as *i64 234 let portbox: *i64 = sys_mmap(8) as *i64 235 // Flags and the connect-override are position-independent so `--body` may appear before or 236 // after the override. Parsing argv[2] positionally would have made `nx_https_get <url> --body` 237 // fail as a malformed ip:port -- an error message pointing at the wrong argument. 238 var body_only: i64 = 0 239 var ai: i64 = 2 240 while ai < argc { 241 let a: *u8 = argv[ai] as *u8 242 if hgc_streq(a, "--body" as *u8) == 1 { body_only = 1 } else { 243 if hgc_parse_ipport(a, ipbox, portbox) != 1 { hgc_put("ERROR: bad connect-override (want a.b.c.d:port)\n" as *u8); return 2 } 244 have_ov = 1 245 } 246 ai = ai + 1 247 } 248 249 let fd_p: *i64 = sys_mmap(16) as *i64 250 let val_raw: *u8 = sys_mmap(128) 251 let val_ctx: *TlsValidationContext = val_raw as *TlsValidationContext 252 253 // ---- connect + handshake, TWO attempts: 0 = chrome-JA3 hello (live-identical), 1 = plain hello fallback ---- 254 var attempt: i64 = 0 255 var session_i: i64 = 0 256 var fd: i64 = 0 - 1 257 while attempt < 2 { 258 // fresh connect per attempt (a rejected handshake kills the socket) 259 if have_ov == 1 { 260 let sa: *u8 = sys_mmap(16) 261 nx_https_build_sockaddr(sa, ipbox[0], portbox[0]) 262 let cfd: i64 = sys_socket(NX_HTTPS_AF_INET, NX_HTTPS_SOCK_STREAM, 0) 263 if cfd < 0 { hgc_put("ERROR: socket failed\n" as *u8); return 3 } 264 sys_set_socket_timeout(cfd, 15) 265 if nx_connect_bounded(cfd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(cfd); hgc_put("ERROR: connect override failed (edge unreachable at that ip:port)\n" as *u8); return 3 } 266 fd_p[0] = cfd 267 } else { 268 if nx_https_url_connect(target, url, now, fd_p) != NX_HTTPS_CONNECT_OK { hgc_put("ERROR: connect failed\n" as *u8); return 3 } 269 } 270 fd = fd_p[0] 271 272 // fresh entropy per attempt (never reuse a client-random across handshakes) 273 nx_csprng_fill(cr, 32) 274 nx_csprng_fill(priv, 32) 275 276 // validation ctx + F799 certloop cache, re-armed per attempt 277 val_ctx.store = store 278 val_ctx.sni_host = url + target.url.host_off 279 val_ctx.sni_host_len = target.url.host_len 280 val_ctx.now_epoch = now 281 let tcc_hit: i64 = tcc_load(url + target.url.host_off, target.url.host_len, now, val_ctx) 282 tcc_arm(val_ctx) 283 if tcc_hit == 1 { sys_write(2, "nishi-tcc candidate loaded\n" as *u8, 27) } 284 285 var sr: i64 = 0 286 if attempt == 0 { sr = nx_tls13_client_session_run_chrome(fd, url + target.url.host_off, target.url.host_len, cr, priv, val_ctx) } 287 else { sr = nx_tls13_client_session_run(fd, url + target.url.host_off, target.url.host_len, cr, priv, val_ctx) } 288 289 if sr >= 0 { session_i = sr; attempt = 99 } 290 else { 291 sys_close(fd) 292 if attempt == 0 { 293 hgc_put2("nishi-hello chrome-hello REJECTED verdict=" as *u8) 294 hgc_putn2(0 - sr) 295 hgc_put2(" -- retrying with plain hello\n" as *u8) 296 attempt = 1 297 } else { 298 hgc_put("ERROR: TLS handshake failed verdict=" as *u8) 299 hgc_putn(0 - sr) 300 hgc_put(" (2=emitCH 3=wrCH 4=readSH 5=recvSH 6=readHS 7=recvHS/CERT 8=emitCF 10=deriveApp 11=budget)\n" as *u8) 301 return 4 302 } 303 } 304 } 305 if session_i == 0 { hgc_put("ERROR: TLS handshake failed (no session)\n" as *u8); return 4 } 306 let session: *Tls13ClientSession = session_i as *Tls13ClientSession 307 tcc_save(url + target.url.host_off, target.url.host_len, now, val_ctx) 308 309 // ---- path (default "/", append ?query verbatim -- else query-string APIs silently drop) ---- 310 var path_ptr: *u8 = url + target.url.path_off 311 var path_len: i64 = target.url.path_len 312 if path_len == 0 { 313 let dp: *u8 = sys_mmap(2) 314 dp[0] = 47 as u8 315 path_ptr = dp 316 path_len = 1 317 } 318 if target.url.query_len > 0 { 319 let full: *u8 = sys_mmap(path_len + target.url.query_len + 4) 320 var fo: i64 = 0 321 var pci: i64 = 0 322 while pci < path_len { full[fo] = path_ptr[pci]; fo = fo + 1; pci = pci + 1 } 323 full[fo] = 63 as u8; fo = fo + 1 // '?' 324 let qp: *u8 = url + target.url.query_off 325 var qci: i64 = 0 326 while qci < target.url.query_len { full[fo] = qp[qci]; fo = fo + 1; qci = qci + 1 } 327 path_ptr = full 328 path_len = fo 329 } 330 331 // ---- HTTP GET; Host header = the REAL url host ---- 332 let out: *u8 = sys_mmap(HGC_OUTCAP) 333 let n: i64 = nx_https_get_complete(session, fd, path_ptr, path_len, url + target.url.host_off, target.url.host_len, out, HGC_OUTCAP) 334 sys_close(fd) 335 if n < 0 { hgc_put("ERROR: HTTP fetch failed code=" as *u8); hgc_putn(0 - n); hgc_put("\n" as *u8); return 5 } 336 if body_only == 0 { sys_write(1, out, n); return 0 } 337 let he: i64 = hgc_hdr_end(out, n) 338 if he < 0 { hgc_put("ERROR: --body: no CRLFCRLF header terminator; refusing to guess where the body starts\n" as *u8); return 6 } 339 var ent: *u8 = out + he 340 var en: i64 = n - he 341 if hgc_hdr_chunked(out, n, he) == 1 { 342 let db: *u8 = sys_mmap(HGC_OUTCAP) 343 let dn: i64 = hgc_dechunk(ent, en, db) 344 if dn < 0 { hgc_put("ERROR: --body: malformed chunked framing; refusing to emit a partial document\n" as *u8); return 7 } 345 ent = db 346 en = dn 347 } 348 let enc: i64 = hgc_hdr_enc(out, n, he) 349 if enc == 3 { hgc_put("ERROR: --body: unsupported Content-Encoding (not identity/gzip/deflate); rerun without --body for the raw response\n" as *u8); return 9 } 350 if enc == 1 { 351 let gr: *NxGzipResult = nx_gzip_inflate(ent, en, HGC_OUTCAP) 352 let gerr: i64 = gr.error_code 353 if gerr != 0 { hgc_put("ERROR: --body: Content-Encoding gzip but inflate failed code=" as *u8); hgc_putn(gerr); hgc_put(" -- refusing to emit compressed bytes as the document\n" as *u8); return 8 } 354 let gout: *u8 = gr.output_data 355 let gsz: i64 = gr.output_size 356 ent = gout 357 en = gsz 358 } 359 if enc == 2 { 360 let zr: *NxZlibResult = nx_zlib_inflate(ent, en, HGC_OUTCAP) 361 let zerr: i64 = zr.error_code 362 if zerr != 0 { hgc_put("ERROR: --body: Content-Encoding deflate but zlib inflate failed code=" as *u8); hgc_putn(zerr); hgc_put(" -- refusing to emit compressed bytes as the document\n" as *u8); return 8 } 363 let zout: *u8 = zr.output_data 364 let zsz: i64 = zr.output_size 365 ent = zout 366 en = zsz 367 } 368 sys_write(1, ent, en) 369 return 0 370} 371 372// parse "a.b.c.d:port" -> ip_out (big-endian packed u32, e.g. 127.0.0.1 -> 0x7F000001) + port_out. 1 ok / 0 bad. 373func hgc_parse_ipport(s: *u8, ip_out: *i64, port_out: *i64) -> i64 { 374 var packed: i64 = 0 375 var val: i64 = 0 376 var nocts: i64 = 0 377 var port: i64 = 0 378 var indots: i64 = 1 379 var i: i64 = 0 380 while s[i] != (0 as u8) { 381 let c: i64 = s[i] as i64 382 if indots == 1 { 383 if c == 46 { packed = (packed << 8) | (val & 0xff); nocts = nocts + 1; val = 0 } 384 else { if c == 58 { packed = (packed << 8) | (val & 0xff); nocts = nocts + 1; val = 0; indots = 0 } 385 else { if c < 48 { return 0 } if c > 57 { return 0 } val = val * 10 + (c - 48) } } 386 } else { 387 if c < 48 { return 0 } 388 if c > 57 { return 0 } 389 port = port * 10 + (c - 48) 390 } 391 i = i + 1 392 } 393 if indots == 1 { return 0 } // no ':' -> no port given 394 if nocts != 4 { return 0 } // need exactly 4 octets 395 if port <= 0 { return 0 } 396 if port > HGC_MAGIC_65535 { return 0 } 397 ip_out[0] = packed 398 port_out[0] = port 399 return 1 400}