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}