code wiki / (root) / nx_http_header_find.nx

nx_http_header_find.nx source

↩ module page · 180 lines · 6510 B

1// nx_http_header_find.nx -- case-insensitive HTTP header search. 2// 3// Per RFC 7230 §3.2: header field names are case-insensitive. This 4// primitive scans a raw response header block + finds a named header 5// + returns its value start offset + length via out params. 6// 7// Used by: 8// - ACME (RFC 8555) Replay-Nonce extraction 9// - HTTP/HTTPS clients reading Location / Content-Type / etc. 10// - Audit-log header parsing 11// - Substrate browser cookie + content-disposition extraction 12// 13// Header block format (RFC 7230 §3): 14// <status-line> CRLF 15// <name>: <value> CRLF 16// <name>: <value> CRLF 17// ... 18// CRLF 19// 20// (We accept LF-only too for tolerance per RFC 7230 §3.5.) 21// 22// Per cardinal feedback-defensive-at-boundaries-trusting-internally: 23// search is bounded by the caller-supplied header_block_len; never 24// walks past. 25// 26// nx_capability_claims: 27// needs: [sealed_enum, byte_ops] 28// provides: [http_header_case_insensitive_search] 29// safety: [no_unchecked_deref, no_floating_point, no_syscall, 30// bounded_iteration, bit_equal_reproducible] 31// verdict: [sealed_enum_4_state] 32// license: ORIGINAL 33// kind: racing_crew_specialist 34// layer: L2 (transform: bytes -> offset+length) 35 36// ---- Sealed enum: header-find verdict ---------------------------- 37 38const NXHF_FOUND: i64 = 0 39const NXHF_NOT_FOUND: i64 = 1 40const NXHF_MALFORMED: i64 = 2 41const NXHF_BAD_ARG: i64 = 3 42const NXHF_VERDICT_N: i64 = 4 43 44func nxhf_verdict_is_valid(v: i64) -> i64 { 45 if v < 0 { return 0 } 46 if v >= NXHF_VERDICT_N { return 0 } 47 return 1 48} 49 50func nxhf_verdict_name(v: i64) -> *u8 { 51 if v == NXHF_FOUND { return "FOUND" as *u8 } 52 if v == NXHF_NOT_FOUND { return "NOT_FOUND" as *u8 } 53 if v == NXHF_MALFORMED { return "MALFORMED" as *u8 } 54 if v == NXHF_BAD_ARG { return "BAD_ARG" as *u8 } 55 return "INVALID" as *u8 56} 57 58// ---- ASCII case-folding ------------------------------------------ 59 60// Lowercase an ASCII byte (A-Z -> a-z). Non-letter bytes pass through. 61func nxhf_tolower(b: i64) -> i64 { 62 if b >= 0x41 && b <= 0x5a { return b + 0x20 } 63 return b 64} 65 66// Compare two byte sequences case-insensitively (ASCII). Returns 1 67// on match, 0 on mismatch. Both must be exactly n bytes. 68func nxhf_eq_ci(a: *u8, b: *u8, n: i64) -> i64 { 69 var i: i64 = 0 70 while i < n { 71 let ca: i64 = nxhf_tolower(a[i] as i64) 72 let cb: i64 = nxhf_tolower(b[i] as i64) 73 if ca != cb { return 0 } 74 i = i + 1 75 } 76 return 1 77} 78 79// ---- Line iteration ---------------------------------------------- 80 81// Return offset of the first byte after the next CRLF (or LF) at or 82// after `off`, or `len` if no line-ending found. 83func nxhf_next_line(buf: *u8, len: i64, off: i64) -> i64 { 84 var p: i64 = off 85 while p < len { 86 if buf[p] == 0x0a { return p + 1 } // LF 87 p = p + 1 88 } 89 return len 90} 91 92// ---- Entry point ------------------------------------------------- 93// 94// Search the header block for `name` (case-insensitive). Returns 95// NXHF_FOUND with out_val_off + out_val_len set to the value 96// substring (without leading SP/HTAB), or NXHF_NOT_FOUND. 97// 98// Skips the first line (the status line "HTTP/1.1 200 OK"). 99// 100// Header value: 101// - leading whitespace (SP / HTAB) is trimmed 102// - trailing whitespace before CRLF is trimmed 103// - inner whitespace preserved 104// - CRLF or LF line ending is NOT included in the value length 105 106func nx_http_header_find( 107 headers: *u8, headers_n: i64, 108 name: *u8, name_n: i64, 109 out_val_off: *i64, out_val_len: *i64) -> i64 { 110 if headers == (0 as *u8) { return NXHF_BAD_ARG } 111 if name == (0 as *u8) { return NXHF_BAD_ARG } 112 if out_val_off == (0 as *i64) { return NXHF_BAD_ARG } 113 if out_val_len == (0 as *i64) { return NXHF_BAD_ARG } 114 if headers_n <= 0 { return NXHF_BAD_ARG } 115 if name_n <= 0 { return NXHF_BAD_ARG } 116 117 // Skip status line (first line). 118 var line_start: i64 = nxhf_next_line(headers, headers_n, 0) 119 120 while line_start < headers_n { 121 let line_end: i64 = nxhf_next_line(headers, headers_n, line_start) 122 // The line bytes are [line_start, line_end-1) -- line_end points 123 // PAST the LF. Compute the content-only end (excluding CRLF/LF). 124 var content_end: i64 = line_end - 1 // past the LF 125 if content_end > line_start && headers[content_end - 1] == 0x0d as u8 { 126 content_end = content_end - 1 // also strip CR 127 } 128 129 // Empty line = end of headers. 130 if content_end == line_start { return NXHF_NOT_FOUND } 131 132 // Find the colon. 133 var colon: i64 = -1 134 var p: i64 = line_start 135 while p < content_end { 136 if headers[p] == 0x3a as u8 { colon = p; p = content_end } else { 137 p = p + 1 138 } 139 } 140 if colon < 0 { 141 // Malformed header line (no colon). Per RFC 7230 §3.2, 142 // we could be strict; substrate is lenient + skips. 143 line_start = line_end 144 } else { 145 let nm_len: i64 = colon - line_start 146 if nm_len == name_n { 147 let nm_ptr: *u8 = ((headers as i64) + line_start) as *u8 148 if nxhf_eq_ci(nm_ptr, name, nm_len) == 1 { 149 // Found. Trim leading SP/HTAB after colon. 150 var v_start: i64 = colon + 1 151 var done_l: i64 = 0 152 while done_l == 0 && v_start < content_end { 153 let bL: i64 = headers[v_start] as i64 154 if bL == 0x20 || bL == 0x09 { 155 v_start = v_start + 1 156 } else { 157 done_l = 1 158 } 159 } 160 // Trim trailing SP/HTAB before content_end. 161 var v_end: i64 = content_end 162 var done_t: i64 = 0 163 while done_t == 0 && v_end > v_start { 164 let bT: i64 = headers[v_end - 1] as i64 165 if bT == 0x20 || bT == 0x09 { 166 v_end = v_end - 1 167 } else { 168 done_t = 1 169 } 170 } 171 *out_val_off = v_start 172 *out_val_len = v_end - v_start 173 return NXHF_FOUND 174 } 175 } 176 line_start = line_end 177 } 178 } 179 return NXHF_NOT_FOUND 180}