code wiki / _hdl_build / nx_http_chunked.nx
nx_http_chunked.nx source
↩ module page · 73 lines · 3281 B
1// nx_http_chunked.nx -- LIB: HTTP/1.1 chunked transfer-encoding DECODER (RFC 7230 ยง4.1). THE concrete M2 unblock:
2// HuggingFace's dynamic API responses (the repo /tree enumerate) are chunked with NO Content-Length, so our
3// Content-Length reader blocks on them (this is exactly why nx_mirror_live_probe's tree fetch hung). This decodes a
4// chunked body buffer -> the raw bytes, so enumerate can proceed. Pure parsing (no crypto, no TLS) -> imports
5// nx_syscalls only (composes cleanly beside the TLS fetch stack, unlike nx_sha256). No 'break' (nx) -> flag-driven
6// loops. Bounded by the input length (never reads past src[n]). license_tier: ORIGINAL
7import "nx_syscalls.nx"
8
9// hex digit -> value, or -1 if not a hex char.
10func hc_hexval(c: i64) -> i64 {
11 if c >= 48 { if c <= 57 { return c - 48 } } // 0-9
12 if c >= 97 { if c <= 102 { return c - 87 } } // a-f
13 if c >= 65 { if c <= 70 { return c - 55 } } // A-F
14 return 0 - 1
15}
16
17// substring search: 1 if needle (NUL-terminated) occurs in hay[0..n), else 0.
18func hc_has(hay: *u8, n: i64, needle: *u8) -> i64 {
19 var m: i64 = 0; while needle[m] != (0 as u8) { m = m + 1 }
20 if m == 0 { return 0 }
21 var i: i64 = 0
22 while i + m <= n {
23 var j: i64 = 0; var ok: i64 = 1
24 while j < m { if hay[i+j] != needle[j] { ok = 0; j = m } else { j = j + 1 } }
25 if ok == 1 { return 1 }
26 i = i + 1
27 }
28 return 0
29}
30
31// True if a response header block advertises Transfer-Encoding: chunked (case tolerant on the value token).
32func hc_is_chunked(hdr: *u8, n: i64) -> i64 {
33 if hc_has(hdr, n, "chunked" as *u8) == 1 { return 1 }
34 if hc_has(hdr, n, "Chunked" as *u8) == 1 { return 1 }
35 return 0
36}
37
38// Decode chunked body src[0..n) -> out (raw bytes). Returns the decoded byte count (>=0), or -1 if the first
39// chunk-size line is malformed (no hex). Tolerates chunk extensions (";ext" after the size) and stops at the
40// terminating 0-size chunk (trailers, if any, are ignored).
41func chunked_decode(src: *u8, n: i64, out: *u8) -> i64 {
42 var i: i64 = 0
43 var o: i64 = 0
44 var done: i64 = 0
45 while done == 0 {
46 // 1) parse hex chunk-size
47 var sz: i64 = 0
48 var got: i64 = 0
49 var st: i64 = 0
50 while st == 0 {
51 if i >= n { st = 1 } else {
52 let hv: i64 = hc_hexval(src[i] as i64)
53 if hv >= 0 { sz = (sz * 16) + hv; got = got + 1; i = i + 1 } else { st = 1 }
54 }
55 }
56 if got == 0 { return 0 - 1 }
57 // 2) skip chunk extensions (up to CR), then the CRLF
58 var st2: i64 = 0
59 while st2 == 0 { if i >= n { st2 = 1 } else { if (src[i] as i64) == 13 { st2 = 1 } else { i = i + 1 } } }
60 if i + 1 < n { if (src[i] as i64) == 13 { if (src[i+1] as i64) == 10 { i = i + 2 } } }
61 // 3) terminating chunk?
62 if sz == 0 { done = 1 } else {
63 // 4) copy sz data bytes (bounded)
64 var j: i64 = 0
65 while j < sz {
66 if i < n { out[o] = src[i]; o = o + 1; i = i + 1; j = j + 1 } else { j = sz; done = 1 }
67 }
68 // 5) skip the CRLF after the data
69 if i + 1 < n { if (src[i] as i64) == 13 { if (src[i+1] as i64) == 10 { i = i + 2 } } }
70 }
71 }
72 return o
73}