code wiki / (root) / nx_http_form.nx

nx_http_form.nx source

↩ module page · 261 lines · 9478 B

1// nx_http_form.nx -- application/x-www-form-urlencoded body parser. 2// 3// HTTP form submission per WHATWG URL §5 + RFC 1866 §8.2.1: 4// 5// key1=value1&key2=value2&key3=value3 6// 7// Where: 8// - `+` decodes to space 9// - `%HH` decodes to a single byte (HH = uppercase or lowercase hex) 10// - Other bytes pass through 11// - Keys + values may be empty 12// - Final `&` is tolerated 13// 14// Per cardinal feedback-no-third-party-trust-native-or-nothing: 15// substrate's own parser; no querystring lib, no urlencoded-body 16// middleware. ~250 LOC; auditable. 17// 18// Per cardinal feedback-privacy-by-default-no-tracking: 19// substrate doesn't store the parsed form data anywhere; caller 20// supplies output buffers + decides retention. 21// 22// nx_capability_claims: 23// needs: [sealed_enum, byte_ops] 24// provides: [form_urlencoded_decode, named_field_extract, 25// pct_hex_decode, plus_to_space] 26// safety: [no_unchecked_deref, no_floating_point, no_syscall, 27// bounded_iteration, bit_equal_reproducible, 28// malformed_input_rejected] 29// verdict: [sealed_enum_5_state] 30// license: ORIGINAL 31// kind: racing_crew_specialist 32// layer: L2 (transform: bytes -> decoded key=value pairs) 33 34// ---- Sealed enum: parse verdict ---------------------------------- 35 36const NXF_OK: i64 = 0 37const NXF_OOM_BUFFER: i64 = 1 38const NXF_BAD_PCT: i64 = 2 39const NXF_NOT_FOUND: i64 = 3 40const NXF_BAD_ARG: i64 = 4 41const NXF_VERDICT_N: i64 = 5 42 43func nxf_verdict_is_valid(v: i64) -> i64 { 44 if v < 0 { return 0 } 45 if v >= NXF_VERDICT_N { return 0 } 46 return 1 47} 48 49func nxf_verdict_name(v: i64) -> *u8 { 50 if v == NXF_OK { return "OK" as *u8 } 51 if v == NXF_OOM_BUFFER { return "OOM_BUFFER" as *u8 } 52 if v == NXF_BAD_PCT { return "BAD_PCT" as *u8 } 53 if v == NXF_NOT_FOUND { return "NOT_FOUND" as *u8 } 54 if v == NXF_BAD_ARG { return "BAD_ARG" as *u8 } 55 return "INVALID" as *u8 56} 57 58// ---- Hex nibble decode ------------------------------------------ 59// 60// Returns 0..15 for '0'-'9'/'a'-'f'/'A'-'F'; -1 otherwise. 61 62func nxf_hex_nibble(c: i64) -> i64 { 63 if c >= 0x30 && c <= 0x39 { return c - 0x30 } 64 if c >= 0x61 && c <= 0x66 { return 10 + (c - 0x61) } 65 if c >= 0x41 && c <= 0x46 { return 10 + (c - 0x41) } 66 return -1 67} 68 69// ---- Decode a single URL-encoded chunk --------------------------- 70// 71// Reads from in[in_off..in_end] until either `&` or `=` or end. 72// Writes decoded bytes to out[*out_off..]. Advances in_off via 73// out_in_after. 74// 75// Returns NXF_OK on success or NXF_BAD_PCT / NXF_OOM_BUFFER. 76 77func nxf_decode_chunk( 78 in_bytes: *u8, in_end: i64, in_start: i64, 79 stop_a: i64, stop_b: i64, 80 out: *u8, out_off: *i64, out_cap: i64, 81 out_in_after: *i64) -> i64 { 82 var p: i64 = in_start 83 while p < in_end { 84 let b: i64 = in_bytes[p] as i64 85 if b == stop_a { 86 *out_in_after = p 87 return NXF_OK 88 } 89 if b == stop_b { 90 *out_in_after = p 91 return NXF_OK 92 } 93 if *out_off >= out_cap { return NXF_OOM_BUFFER } 94 if b == 0x2b { 95 // `+` -> space 96 out[*out_off] = 0x20 as u8 97 *out_off = *out_off + 1 98 p = p + 1 99 } else { 100 if b == 0x25 { 101 // `%HH` -- two hex digits required. 102 if p + 2 >= in_end { return NXF_BAD_PCT } 103 let hi: i64 = nxf_hex_nibble(in_bytes[p + 1] as i64) 104 if hi < 0 { return NXF_BAD_PCT } 105 let lo: i64 = nxf_hex_nibble(in_bytes[p + 2] as i64) 106 if lo < 0 { return NXF_BAD_PCT } 107 out[*out_off] = ((hi << 4) | lo) as u8 108 *out_off = *out_off + 1 109 p = p + 3 110 } else { 111 // Passthrough byte. 112 out[*out_off] = b as u8 113 *out_off = *out_off + 1 114 p = p + 1 115 } 116 } 117 } 118 *out_in_after = in_end 119 return NXF_OK 120} 121 122// ---- Find a named field's decoded value ------------------------- 123// 124// Walks body as key=value&key=value pairs; for each pair, decodes 125// the key and compares to `field_name`; if match, decodes the value 126// into out_val and returns NXF_OK. Returns NXF_NOT_FOUND if absent. 127// 128// Caller supplies: 129// body, body_n urlencoded request body bytes 130// field_name, name_n plain (non-encoded) field name to match 131// (typically literal "username", "password", 132// "csrf_token", etc.) 133// out_val, out_cap decoded value destination 134// out_val_len bytes written to out_val 135// 136// The field name in the body is decoded before comparison so that 137// e.g., "user%20name" in the body matches field_name "user name". 138 139func nx_http_form_get_field( 140 body: *u8, body_n: i64, 141 field_name: *u8, field_name_n: i64, 142 out_val: *u8, out_cap: i64, 143 out_val_len: *i64) -> i64 { 144 if body == (0 as *u8) { return NXF_BAD_ARG } 145 if field_name == (0 as *u8) { return NXF_BAD_ARG } 146 if out_val == (0 as *u8) { return NXF_BAD_ARG } 147 if out_val_len == (0 as *i64) { return NXF_BAD_ARG } 148 if body_n < 0 { return NXF_BAD_ARG } 149 if field_name_n <= 0 { return NXF_BAD_ARG } 150 if out_cap <= 0 { return NXF_BAD_ARG } 151 152 var p: i64 = 0 153 while p < body_n { 154 // Decode key into a scratch buffer. 155 let key_buf: *u8 = sys_mmap(256) 156 let key_off: *i64 = sys_mmap(8) as *i64 157 key_off[0] = 0 158 let after_key: *i64 = sys_mmap(8) as *i64 159 after_key[0] = 0 160 let rc1: i64 = nxf_decode_chunk(body, body_n, p, 161 0x3d, 0x26, 162 key_buf, key_off, 256, 163 after_key) 164 if rc1 != NXF_OK { return rc1 } 165 // After key: either `=`, `&`, or end-of-body. 166 if after_key[0] >= body_n { 167 // Last pair with no `=` -> key with empty value. 168 if key_off[0] == field_name_n { 169 var eq: i64 = 1 170 var i: i64 = 0 171 while i < key_off[0] { 172 if key_buf[i] != field_name[i] { eq = 0; i = key_off[0] } 173 i = i + 1 174 } 175 if eq == 1 { 176 *out_val_len = 0 177 return NXF_OK 178 } 179 } 180 return NXF_NOT_FOUND 181 } 182 let sep: i64 = body[after_key[0]] as i64 183 if sep == 0x26 { 184 // No value; advance past `&`. 185 // Optionally match key here. 186 if key_off[0] == field_name_n { 187 var eq2: i64 = 1 188 var j: i64 = 0 189 while j < key_off[0] { 190 if key_buf[j] != field_name[j] { eq2 = 0; j = key_off[0] } 191 j = j + 1 192 } 193 if eq2 == 1 { 194 *out_val_len = 0 195 return NXF_OK 196 } 197 } 198 p = after_key[0] + 1 199 } 200 if sep == 0x3d { 201 // Match key first; if no match, decode value to advance. 202 var key_match: i64 = 0 203 if key_off[0] == field_name_n { 204 var eq3: i64 = 1 205 var k: i64 = 0 206 while k < key_off[0] { 207 if key_buf[k] != field_name[k] { eq3 = 0; k = key_off[0] } 208 k = k + 1 209 } 210 if eq3 == 1 { key_match = 1 } 211 } 212 // Decode value (key_match=1 -> into out_val; else scratch). 213 if key_match == 1 { 214 let v_off: *i64 = sys_mmap(8) as *i64 215 v_off[0] = 0 216 let after_val: *i64 = sys_mmap(8) as *i64 217 after_val[0] = 0 218 let rc2: i64 = nxf_decode_chunk(body, body_n, after_key[0] + 1, 219 0x26, 0x26, 220 out_val, v_off, out_cap, 221 after_val) 222 if rc2 != NXF_OK { return rc2 } 223 *out_val_len = v_off[0] 224 return NXF_OK 225 } 226 let scratch_val: *u8 = sys_mmap(256) 227 let sv_off: *i64 = sys_mmap(8) as *i64 228 sv_off[0] = 0 229 let after_val2: *i64 = sys_mmap(8) as *i64 230 after_val2[0] = 0 231 let rc3: i64 = nxf_decode_chunk(body, body_n, after_key[0] + 1, 232 0x26, 0x26, 233 scratch_val, sv_off, 256, 234 after_val2) 235 if rc3 != NXF_OK { return rc3 } 236 if after_val2[0] >= body_n { return NXF_NOT_FOUND } 237 p = after_val2[0] + 1 238 } 239 } 240 return NXF_NOT_FOUND 241} 242 243// ---- Count pairs in body ----------------------------------------- 244// 245// Useful for caller pre-allocation. Counts the number of `key=val` 246// pairs (or just `key` pairs without `=`) in the body. 247 248func nx_http_form_count_pairs(body: *u8, body_n: i64) -> i64 { 249 if body == (0 as *u8) { return 0 } 250 if body_n <= 0 { return 0 } 251 var n_pairs: i64 = 1 252 var i: i64 = 0 253 while i < body_n { 254 if body[i] == 0x26 as u8 { 255 // Skip trailing `&` -- doesn't add a pair. 256 if i + 1 < body_n { n_pairs = n_pairs + 1 } 257 } 258 i = i + 1 259 } 260 return n_pairs 261}