code wiki / (root) / nx_http_response_complete_candidate_t314.nx

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}