nx_http_response_complete_candidate_t314.nx source
↩ module page · 570 lines · 22950 B
1// nx_http_response_parse.nx -- client-side HTTP/1.1 response parser.
2//
3// Parses the bytes that come back over a TLS record stream into:
4// - status_code (integer, e.g. 200)
5// - reason_offset (byte offset of reason phrase in input)
6// - reason_len
7// - headers_offset (byte offset of first header line)
8// - headers_len (bytes covering all headers, EXCLUDING the
9// blank line terminator)
10// - body_offset (byte offset of body start in input, after
11// the CRLF CRLF boundary)
12// - body_len (Content-Length value if present and decoded,
13// else 0 -- callers can dechunk explicitly via
14// nx_http_dechunk)
15// - transfer_kind (NX_HTTP_BODY_CONTENT_LENGTH | NX_HTTP_BODY_
16// CHUNKED | NX_HTTP_BODY_UNTIL_CLOSE | NX_HTTP
17// _BODY_EMPTY)
18//
19// Field accessors (case-insensitive header name match):
20// nx_http_response_header(parsed, name, name_len, val_off, val_len)
21//
22// Chunked decoder (RFC 7230 §4.1):
23// nx_http_dechunk(src, src_len, out, out_cap) -> decoded_len
24//
25// Built per F7 post-order DFS: callees defined before callers.
26// Built per F6 size discipline: <12 lets per function.
27//
28// expect_exit: 0
29// license_tier: ORIGINAL
30
31import "nx_syscalls.nx"
32
33// ===== Verdicts ===================================================
34
35const NX_HTTP_RESP_OK: i64 = 0
36const NX_HTTP_RESP_ERR_TOO_SHORT: i64 = 1
37const NX_HTTP_RESP_ERR_BAD_STATUS_LINE: i64 = 2
38const NX_HTTP_RESP_ERR_BAD_VERSION: i64 = 3
39const NX_HTTP_RESP_ERR_BAD_CODE: i64 = 4
40const NX_HTTP_RESP_ERR_NO_BODY_SEP: i64 = 5
41const NX_HTTP_RESP_ERR_HEADER_OVERFLOW: i64 = 6
42const NX_HTTP_RESP_ERR_BAD_CHUNK_SIZE: i64 = 7
43const NX_HTTP_RESP_ERR_TRUNCATED: i64 = 8
44
45const NX_HTTP_BODY_EMPTY: i64 = 0
46const NX_HTTP_BODY_CONTENT_LENGTH: i64 = 1
47const NX_HTTP_BODY_CHUNKED: i64 = 2
48const NX_HTTP_BODY_UNTIL_CLOSE: i64 = 3
49
50// Coverage: strict HTTP/1.0 and HTTP/1.1 status-line grammar has a dedicated boundary gate.
51// Existing full-parser tests cover selected headers, Content-Length and chunk decoding.
52// This is not full HTTP framing conformance: header numeric overflow, duplicate framing,
53// trailer exposure and allocation cleanup need independent qualification. Compression decode
54// belongs to its existing separate primitive; no whole-response validity follows from status alone.
55
56// ===== Helpers (LEAF) =============================================
57
58func nx_http_resp_is_digit(c: i64) -> i64 {
59 if c >= 0x30 { if c <= 0x39 { return 1 } }
60 return 0
61}
62
63func nx_http_resp_is_hex(c: i64) -> i64 {
64 if nx_http_resp_is_digit(c) == 1 { return 1 }
65 if c >= 0x41 { if c <= 0x46 { return 1 } } // A-F
66 if c >= 0x61 { if c <= 0x66 { return 1 } } // a-f
67 return 0
68}
69
70// ASCII case-fold to lowercase
71func nx_http_resp_lower(c: i64) -> i64 {
72 if c >= 0x41 { if c <= 0x5a { return c + 0x20 } }
73 return c
74}
75
76// hex digit (0-9, A-F, a-f) → integer value 0..15; returns -1 if not hex
77func nx_http_resp_hex_val(c: i64) -> i64 {
78 if c >= 0x30 { if c <= 0x39 { return c - 0x30 } }
79 if c >= 0x41 { if c <= 0x46 { return c - 0x41 + 10 } }
80 if c >= 0x61 { if c <= 0x66 { return c - 0x61 + 10 } }
81 return 0 - 1
82}
83
84// ASCII bytes-equal case-insensitive
85func nx_http_resp_cieq(a: *u8, a_len: i64, b: *u8, b_len: i64) -> i64 {
86 if a_len != b_len { return 0 }
87 var i: i64 = 0
88 while i < a_len {
89 let ca: i64 = nx_http_resp_lower(a[i] & 0xff)
90 let cb: i64 = nx_http_resp_lower(b[i] & 0xff)
91 if ca != cb { return 0 }
92 i = i + 1
93 }
94 return 1
95}
96
97// Find next byte == c starting at off, up to src_len. Returns offset
98// or -1 if not found.
99func nx_http_resp_find_byte(src: *u8, src_len: i64, off: i64, c: i64) -> i64 {
100 var i: i64 = off
101 while i < src_len {
102 if (src[i] & 0xff) == c { return i }
103 i = i + 1
104 }
105 return 0 - 1
106}
107
108// Find CRLF starting at off. Returns offset of CR or -1.
109func nx_http_resp_find_crlf(src: *u8, src_len: i64, off: i64) -> i64 {
110 var i: i64 = off
111 while i + 1 < src_len {
112 if (src[i] & 0xff) == 0x0d {
113 if (src[i + 1] & 0xff) == 0x0a { return i }
114 }
115 i = i + 1
116 }
117 return 0 - 1
118}
119
120// Find double-CRLF (end of headers). Returns offset of first CR.
121func nx_http_resp_find_crlfcrlf(src: *u8, src_len: i64, off: i64) -> i64 {
122 var i: i64 = off
123 while i + 3 < src_len {
124 if (src[i] & 0xff) == 0x0d {
125 if (src[i + 1] & 0xff) == 0x0a {
126 if (src[i + 2] & 0xff) == 0x0d {
127 if (src[i + 3] & 0xff) == 0x0a { return i }
128 }
129 }
130 }
131 i = i + 1
132 }
133 return 0 - 1
134}
135
136// Parse decimal integer at off until non-digit. Returns value, sets
137// *out_end to offset of first non-digit byte. Returns -1 if no
138// digits seen.
139func nx_http_resp_parse_dec(src: *u8, src_len: i64, off: i64, out_end: *i64) -> i64 {
140 var i: i64 = off
141 var val: i64 = 0
142 var n: i64 = 0
143 while i < src_len {
144 let c: i64 = src[i] & 0xff
145 if nx_http_resp_is_digit(c) != 1 {
146 out_end[0] = i
147 if n == 0 { return 0 - 1 }
148 return val
149 }
150 val = val * 10 + (c - 0x30)
151 n = n + 1
152 i = i + 1
153 }
154 out_end[0] = i
155 if n == 0 { return 0 - 1 }
156 return val
157}
158
159// Parse hex integer at off until non-hex. Returns value, sets
160// *out_end. Returns -1 if no hex digits.
161func nx_http_resp_parse_hex(src: *u8, src_len: i64, off: i64, out_end: *i64) -> i64 {
162 var i: i64 = off
163 var val: i64 = 0
164 var n: i64 = 0
165 while i < src_len {
166 let c: i64 = src[i] & 0xff
167 let hv: i64 = nx_http_resp_hex_val(c)
168 if hv < 0 {
169 out_end[0] = i
170 if n == 0 { return 0 - 1 }
171 return val
172 }
173 val = (val * 16) + hv
174 n = n + 1
175 i = i + 1
176 }
177 out_end[0] = i
178 if n == 0 { return 0 - 1 }
179 return val
180}
181
182// Skip horizontal whitespace (space + htab) starting at off.
183func nx_http_resp_skip_ows(src: *u8, src_len: i64, off: i64) -> i64 {
184 var i: i64 = off
185 while i < src_len {
186 let c: i64 = src[i] & 0xff
187 if c != 0x20 { if c != 0x09 { return i } }
188 i = i + 1
189 }
190 return i
191}
192
193// ===== Mid-level: status line + one header ========================
194
195// Parse "HTTP/1.1 <code> <reason>\r\n" starting at offset 0.
196// Returns verdict (NX_HTTP_RESP_OK or error). On OK, populates:
197// *out_code = status code
198// *out_reason_off = byte offset of reason phrase
199// *out_reason_len = length of reason phrase (excluding CRLF)
200// *out_next = offset of first header byte (after CRLF)
201// Strict RFC 9112 section 4 grammar, within this owner's HTTP/1.0 and 1.1 support.
202// Returns status or a negative existing verdict. No allocation and no scanning for a later start line.
203func nx_http_resp_status_code(src:*u8,src_len:i64)->i64{
204 if (src as i64)==0||src_len<15{return 0-NX_HTTP_RESP_ERR_TOO_SHORT}
205 let prefix:*u8="HTTP/1.";var i:i64=0
206 while i<7{if src[i]!=prefix[i]{return 0-NX_HTTP_RESP_ERR_BAD_VERSION};i=i+1}
207 if src[7]!=48 as u8&&src[7]!=49 as u8{return 0-NX_HTTP_RESP_ERR_BAD_VERSION}
208 if src[8]!=32 as u8{return 0-NX_HTTP_RESP_ERR_BAD_STATUS_LINE}
209 i=9;var code:i64=0
210 while i<12{let c:i64=src[i] as i64;if nx_http_resp_is_digit(c)!=1{return 0-NX_HTTP_RESP_ERR_BAD_CODE};code=code*10+c-48;i=i+1}
211 if code<100||code>599{return 0-NX_HTTP_RESP_ERR_BAD_CODE}
212 if src[12]!=32 as u8{return 0-NX_HTTP_RESP_ERR_BAD_STATUS_LINE}
213 i=13
214 while i<src_len{
215 let c:i64=src[i] as i64
216 if c==13{if i+1<src_len{if src[i+1]==10 as u8{return code}};return 0-NX_HTTP_RESP_ERR_BAD_STATUS_LINE}
217 // reason-phrase admits HTAB, SP, VCHAR and obs-text, excluding DEL and other controls.
218 if c!=9{if c<32||c==127{return 0-NX_HTTP_RESP_ERR_BAD_STATUS_LINE}}
219 i=i+1
220 }
221 return 0-NX_HTTP_RESP_ERR_BAD_STATUS_LINE
222}
223func nx_http_resp_parse_status_line(src:*u8,src_len:i64,out_code:*i64,out_reason_off:*i64,out_reason_len:*i64,out_next:*i64)->i64{
224 out_code[0]=0;out_reason_off[0]=0;out_reason_len[0]=0;out_next[0]=0
225 let code:i64=nx_http_resp_status_code(src,src_len);if code<0{return 0-code}
226 // Preserve exact reason bytes, including legal whitespace after the required separator.
227 let end:i64=nx_http_resp_find_crlf(src,src_len,13)
228 out_code[0]=code;out_reason_off[0]=13;out_reason_len[0]=end-13;out_next[0]=end+2
229 return NX_HTTP_RESP_OK
230}
231
232// Parse one "Name: Value\r\n" header line starting at off.
233// Returns NX_HTTP_RESP_OK on success, populating slots and *out_next
234// = offset after the trailing CRLF. Returns NX_HTTP_RESP_OK with
235// *out_name_len == 0 to signal end-of-headers (the blank line at
236// CRLF CRLF).
237func nx_http_resp_parse_header_line(
238 src: *u8,
239 src_len: i64,
240 off: i64,
241 out_name_off: *i64,
242 out_name_len: *i64,
243 out_val_off: *i64,
244 out_val_len: *i64,
245 out_next: *i64
246) -> i64 {
247 if off + 1 >= src_len { return NX_HTTP_RESP_ERR_TRUNCATED }
248 if (src[off] & 0xff) == 0x0d {
249 if (src[off + 1] & 0xff) == 0x0a {
250 out_name_len[0] = 0
251 out_next[0] = off + 2
252 return NX_HTTP_RESP_OK
253 }
254 }
255 let colon: i64 = nx_http_resp_find_byte(src, src_len, off, 0x3a)
256 if colon < 0 { return NX_HTTP_RESP_ERR_HEADER_OVERFLOW }
257 out_name_off[0] = off
258 out_name_len[0] = colon - off
259
260 let val_start: i64 = nx_http_resp_skip_ows(src, src_len, colon + 1)
261 let line_end: i64 = nx_http_resp_find_crlf(src, src_len, val_start)
262 if line_end < 0 { return NX_HTTP_RESP_ERR_HEADER_OVERFLOW }
263 out_val_off[0] = val_start
264 // Trim trailing OWS (RFC 7230 §3.2.4) -- bounded-loop pattern.
265 var v_end: i64 = line_end
266 var trimming: i64 = 1
267 while trimming == 1 {
268 if v_end <= val_start { trimming = 0 }
269 if trimming == 1 {
270 let c: i64 = src[v_end - 1] & 0xff
271 if c == 0x20 { v_end = v_end - 1 }
272 if c == 0x09 { v_end = v_end - 1 }
273 if c != 0x20 { if c != 0x09 { trimming = 0 } }
274 }
275 }
276 out_val_len[0] = v_end - val_start
277 out_next[0] = line_end + 2
278 return NX_HTTP_RESP_OK
279}
280
281// ===== High-level: full message parse =============================
282
283// NxHttpResp packed layout (we use a flat i64 array to dodge struct
284// types for now):
285// slot 0: verdict
286// slot 1: status_code
287// slot 2: reason_off
288// slot 3: reason_len
289// slot 4: headers_off
290// slot 5: headers_len
291// slot 6: body_off
292// slot 7: body_len (declared if Content-Length seen)
293// slot 8: transfer_kind
294// slot 9: content_length (parsed value, or -1 if absent)
295// slot 10: te_chunked_seen (1 if Transfer-Encoding: chunked)
296
297func nx_http_resp_alloc() -> *i64 { return sys_mmap(128) as *i64 }
298
299// Walk all headers from headers_off, classify transfer model.
300// Caller passes a pre-allocated NxHttpResp slot ptr.
301func nx_http_resp_classify_body(src: *u8, src_len: i64, r: *i64) -> i64 {
302 let no: *i64 = sys_mmap(8) as *i64
303 let nl: *i64 = sys_mmap(8) as *i64
304 let vo: *i64 = sys_mmap(8) as *i64
305 let vl: *i64 = sys_mmap(8) as *i64
306 let nx: *i64 = sys_mmap(8) as *i64
307 var pos: i64 = r[4]
308 var done: i64 = 0
309 var verdict: i64 = NX_HTTP_RESP_OK
310 while done == 0 {
311 let v: i64 = nx_http_resp_parse_header_line(src, src_len, pos, no, nl, vo, vl, nx)
312 if v != NX_HTTP_RESP_OK { verdict = v; done = 1 }
313 if done == 0 {
314 if nl[0] == 0 {
315 r[5] = pos - r[4]
316 r[6] = nx[0]
317 done = 1
318 }
319 if done == 0 {
320 // Compare name CI against "Content-Length" and "Transfer-Encoding"
321 let cl_name: *u8 = sys_mmap(16)
322 cl_name[0]=0x43; cl_name[1]=0x6f; cl_name[2]=0x6e; cl_name[3]=0x74
323 cl_name[4]=0x65; cl_name[5]=0x6e; cl_name[6]=0x74; cl_name[7]=0x2d
324 cl_name[8]=0x4c; cl_name[9]=0x65; cl_name[10]=0x6e; cl_name[11]=0x67
325 cl_name[12]=0x74; cl_name[13]=0x68
326 if nx_http_resp_cieq(src + no[0], nl[0], cl_name, 14) == 1 {
327 let end_slot: *i64 = sys_mmap(8) as *i64
328 let n: i64 = nx_http_resp_parse_dec(src, src_len, vo[0], end_slot)
329 if n >= 0 { r[9] = n }
330 }
331 let te_name: *u8 = sys_mmap(32)
332 te_name[0]=0x54; te_name[1]=0x72; te_name[2]=0x61; te_name[3]=0x6e
333 te_name[4]=0x73; te_name[5]=0x66; te_name[6]=0x65; te_name[7]=0x72
334 te_name[8]=0x2d; te_name[9]=0x45; te_name[10]=0x6e; te_name[11]=0x63
335 te_name[12]=0x6f; te_name[13]=0x64; te_name[14]=0x69; te_name[15]=0x6e
336 te_name[16]=0x67
337 if nx_http_resp_cieq(src + no[0], nl[0], te_name, 17) == 1 {
338 let chunked: *u8 = sys_mmap(16)
339 chunked[0]=0x63; chunked[1]=0x68; chunked[2]=0x75; chunked[3]=0x6e
340 chunked[4]=0x6b; chunked[5]=0x65; chunked[6]=0x64
341 if nx_http_resp_cieq(src + vo[0], vl[0], chunked, 7) == 1 { r[10] = 1 }
342 }
343 pos = nx[0]
344 }
345 }
346 }
347 return verdict
348}
349
350// Parse full HTTP response. Returns verdict; populates *r.
351func nx_http_response_parse(src: *u8, src_len: i64, r: *i64) -> i64 {
352 var i: i64 = 0
353 while i < 16 { r[i] = 0; i = i + 1 }
354 r[9] = 0 - 1 // content_length absent sentinel
355
356 let code: *i64 = sys_mmap(8) as *i64
357 let ro: *i64 = sys_mmap(8) as *i64
358 let rl: *i64 = sys_mmap(8) as *i64
359 let nx: *i64 = sys_mmap(8) as *i64
360 let v: i64 = nx_http_resp_parse_status_line(src, src_len, code, ro, rl, nx)
361 if v != NX_HTTP_RESP_OK { r[0] = v; return v }
362 r[1] = code[0]
363 r[2] = ro[0]
364 r[3] = rl[0]
365 r[4] = nx[0]
366
367 let cb: i64 = nx_http_resp_classify_body(src, src_len, r)
368 if cb != NX_HTTP_RESP_OK { r[0] = cb; return cb }
369
370 // Decide transfer kind
371 if r[10] == 1 {
372 r[8] = NX_HTTP_BODY_CHUNKED
373 } else {
374 if r[9] >= 0 {
375 r[8] = NX_HTTP_BODY_CONTENT_LENGTH
376 r[7] = r[9]
377 } else {
378 // No Content-Length, no chunked: HTTP/1.0-style read-until-close
379 r[8] = NX_HTTP_BODY_UNTIL_CLOSE
380 r[7] = src_len - r[6]
381 }
382 }
383 r[0] = NX_HTTP_RESP_OK
384 return NX_HTTP_RESP_OK
385}
386
387// Case-insensitive header lookup. Returns 1 if found, sets *val_off,
388// *val_len; 0 if not found.
389func nx_http_response_header(
390 src: *u8,
391 src_len: i64,
392 r: *i64,
393 name: *u8,
394 name_len: i64,
395 val_off: *i64,
396 val_len: *i64
397) -> i64 {
398 let no: *i64 = sys_mmap(8) as *i64
399 let nl: *i64 = sys_mmap(8) as *i64
400 let vo: *i64 = sys_mmap(8) as *i64
401 let vl: *i64 = sys_mmap(8) as *i64
402 let nx: *i64 = sys_mmap(8) as *i64
403 var pos: i64 = r[4]
404 var found: i64 = 0
405 var done: i64 = 0
406 while done == 0 {
407 let v: i64 = nx_http_resp_parse_header_line(src, src_len, pos, no, nl, vo, vl, nx)
408 if v != NX_HTTP_RESP_OK { done = 1 }
409 if done == 0 {
410 if nl[0] == 0 { done = 1 }
411 if done == 0 {
412 if nx_http_resp_cieq(src + no[0], nl[0], name, name_len) == 1 {
413 val_off[0] = vo[0]
414 val_len[0] = vl[0]
415 found = 1
416 done = 1
417 }
418 pos = nx[0]
419 }
420 }
421 }
422 return found
423}
424
425// Decode RFC 7230 §4.1 chunked body. Returns decoded length, or
426// negative verdict on malformed input.
427func nx_http_dechunk_consumed(src: *u8, src_len: i64, out: *u8, out_cap: i64, consumed: *i64) -> i64 {
428 consumed[0]=0
429 if src_len < 0 || out_cap < 0 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
430 var pos: i64 = 0
431 var written: i64 = 0
432 while pos < src_len {
433 var size: i64 = 0
434 var digits: i64 = 0
435 while pos < src_len {
436 let digit: i64 = nx_http_resp_hex_val(src[pos] as i64)
437 if digit < 0 { break }
438 // A chunk cannot exceed the entire supplied representation. Bound BEFORE multiplication.
439 if digit > src_len { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
440 if size > (src_len-digit)/16 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
441 size=size*16+digit; digits=digits+1; pos=pos+1
442 }
443 if digits == 0 { return 0 - NX_HTTP_RESP_ERR_BAD_CHUNK_SIZE }
444 if pos >= src_len { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
445 // Extensions remain opaque; require their separator and reject control bytes in the size line.
446 if src[pos] == 59 as u8 {
447 pos=pos+1
448 while pos < src_len {
449 let c: i64=src[pos] as i64
450 if c == 13 { break }
451 if c < 32 && c != 9 { return 0 - NX_HTTP_RESP_ERR_BAD_CHUNK_SIZE }
452 if c == 127 { return 0 - NX_HTTP_RESP_ERR_BAD_CHUNK_SIZE }
453 pos=pos+1
454 }
455 }
456 if src_len-pos < 2 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
457 if src[pos] != 13 as u8 || src[pos+1] != 10 as u8 { return 0 - NX_HTTP_RESP_ERR_BAD_CHUNK_SIZE }
458 pos=pos+2
459 if size == 0 {
460 // Last-chunk alone is incomplete; consume the trailer section through its final empty line.
461 while pos < src_len {
462 let end: i64=nx_http_resp_find_crlf(src,src_len,pos)
463 if end < 0 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
464 if end == pos { consumed[0]=end+2; return written }
465 var colon: i64=0
466 var ti: i64=pos
467 while ti < end {
468 let c: i64=src[ti] as i64
469 if c == 58 && ti > pos { colon=1 }
470 if c < 32 && c != 9 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
471 if c == 127 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
472 ti=ti+1
473 }
474 if colon == 0 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
475 pos=end+2
476 }
477 return 0 - NX_HTTP_RESP_ERR_TRUNCATED
478 }
479 if src_len-pos < 2 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
480 if size > src_len-pos-2 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
481 if size > out_cap-written { return 0 - NX_HTTP_RESP_ERR_HEADER_OVERFLOW }
482 if src[pos+size] != 13 as u8 || src[pos+size+1] != 10 as u8 { return 0 - NX_HTTP_RESP_ERR_TRUNCATED }
483 var i: i64=0
484 while i < size { out[written+i]=src[pos+i]; i=i+1 }
485 written=written+size; pos=pos+size+2
486 }
487 return 0 - NX_HTTP_RESP_ERR_TRUNCATED
488}
489
490// Additive strict admission for an entire EOF-delimited HTTP GET response.
491// Existing permissive parser ABI remains unchanged. Unsupported encodings are explicit.
492const NX_HTTP_RESP_ERR_FRAMING: i64 = 9
493const NX_HTTP_RESP_ERR_ENCODING: i64 = 10
494const NX_HTTP_RESP_ERR_RESOURCE: i64 = 11
495func nx_http_dechunk(src:*u8,n:i64,out:*u8,cap:i64)->i64 {
496 var used:i64=0
497 return nx_http_dechunk_consumed(src,n,out,cap,&used)
498}
499func nx_http_response_complete(src:*u8,n:i64,out:*u8,cap:i64,code:*i64,bodylen:*i64)->i64 {
500 code[0]=0; bodylen[0]=0
501 if n<0 { return NX_HTTP_RESP_ERR_TRUNCATED }
502 if cap<0 { return NX_HTTP_RESP_ERR_HEADER_OVERFLOW }
503 if (src as i64)<=0 { return NX_HTTP_RESP_ERR_TRUNCATED }
504 if cap>0 { if (out as i64)<=0 { return NX_HTTP_RESP_ERR_HEADER_OVERFLOW } }
505 var reason:i64=0; var reasonn:i64=0; var pos:i64=0
506 let st:i64=nx_http_resp_parse_status_line(src,n,code,&reason,&reasonn,&pos)
507 if st!=NX_HTTP_RESP_OK { return st }
508 // Interim and protocol-switch responses require a separate streaming exchange.
509 if code[0]<200 { return NX_HTTP_RESP_ERR_FRAMING }
510 var cl:i64=0-1; var te:i64=0; var end:i64=0
511 while pos<n {
512 let le:i64=nx_http_resp_find_crlf(src,n,pos)
513 if le<0 { return NX_HTTP_RESP_ERR_TRUNCATED }
514 if le==pos { end=le+2; break }
515 var no:i64=0;var nn:i64=0;var vo:i64=0;var vn:i64=0;var next:i64=0
516 // Bound the existing header reader to this line so it cannot find a colon in a later line.
517 let hv:i64=nx_http_resp_parse_header_line(src,le+2,pos,&no,&nn,&vo,&vn,&next)
518 if hv!=NX_HTTP_RESP_OK { return hv }
519 if nn<=0 { return NX_HTTP_RESP_ERR_FRAMING }
520 var i:i64=0
521 while i<nn {
522 let c:i64=src[no+i] as i64
523 var valid:i64=0
524 if c>=48 { if c<=57 { valid=1 } }
525 if c>=65 { if c<=90 { valid=1 } }
526 if c>=97 { if c<=122 { valid=1 } }
527 if c==33 || c==35 || c==36 || c==37 || c==38 || c==39 || c==42 || c==43 || c==45 || c==46 || c==94 || c==95 || c==96 || c==124 || c==126 { valid=1 }
528 if valid==0 { return NX_HTTP_RESP_ERR_FRAMING }
529 i=i+1
530 }
531 i=vo
532 while i<le { let c:i64=src[i] as i64; if c<32 { if c!=9 { return NX_HTTP_RESP_ERR_FRAMING } } if c==127 { return NX_HTTP_RESP_ERR_FRAMING } i=i+1 }
533 if nx_http_resp_cieq(src+no,nn,"Content-Length" as *u8,14)==1 {
534 if vn<=0 { return NX_HTTP_RESP_ERR_FRAMING }
535 var v:i64=0;i=0
536 while i<vn { let c:i64=src[vo+i] as i64; if c<48 || c>57 { return NX_HTTP_RESP_ERR_FRAMING } let d:i64=c-48; if v>(9223372036854775807-d)/10 { return NX_HTTP_RESP_ERR_FRAMING } v=v*10+d;i=i+1 }
537 if cl>=0 { if cl!=v { return NX_HTTP_RESP_ERR_FRAMING } }
538 cl=v
539 }
540 if nx_http_resp_cieq(src+no,nn,"Transfer-Encoding" as *u8,17)==1 {
541 if te!=0 { return NX_HTTP_RESP_ERR_FRAMING }
542 if nx_http_resp_cieq(src+vo,vn,"chunked" as *u8,7)!=1 { return NX_HTTP_RESP_ERR_ENCODING }
543 te=1
544 }
545 if nx_http_resp_cieq(src+no,nn,"Content-Encoding" as *u8,16)==1 {
546 if nx_http_resp_cieq(src+vo,vn,"identity" as *u8,8)!=1 { return NX_HTTP_RESP_ERR_ENCODING }
547 }
548 pos=next
549 }
550 if end==0 { return NX_HTTP_RESP_ERR_TRUNCATED }
551 if te==1 { if cl>=0 { return NX_HTTP_RESP_ERR_FRAMING } }
552 if code[0]==204 || code[0]==304 {
553 if te!=0 { return NX_HTTP_RESP_ERR_FRAMING }
554 if code[0]==204 { if cl>=0 { return NX_HTTP_RESP_ERR_FRAMING } }
555 if end!=n { return NX_HTTP_RESP_ERR_FRAMING }
556 return NX_HTTP_RESP_OK
557 }
558 if te==1 {
559 var used:i64=0
560 let result:i64=nx_http_dechunk_consumed(src+end,n-end,out,cap,&used)
561 if result<0 { return 0-result }
562 if used!=n-end { return NX_HTTP_RESP_ERR_FRAMING }
563 bodylen[0]=result;return NX_HTTP_RESP_OK
564 }
565 let available:i64=n-end
566 if cl>=0 { if cl!=available { return NX_HTTP_RESP_ERR_TRUNCATED } }
567 if available>cap { return NX_HTTP_RESP_ERR_HEADER_OVERFLOW }
568 var i:i64=0;while i<available { out[i]=src[end+i];i=i+1 }
569 bodylen[0]=available;return NX_HTTP_RESP_OK
570}