code wiki / (root) / nx_range_header.nx

nx_range_header.nx source

↩ module page · 198 lines · 6601 B

1// range_header.nx -- parse HTTP Range request header. 2// 3// RFC 7233 §3.1. Range requests let a client ask for a byte 4// range of a resource instead of the whole thing. Essential for: 5// - Video / audio seeking (HTML5 <video> sends Range requests 6// when you click the scrubber) 7// - Resumable downloads (browsers retry interrupted downloads 8// with \"Range: bytes=<last-byte-received>-\") 9// - Large-file diff tools 10// 11// Format (most useful forms): 12// Range: bytes=0-499 <- first 500 bytes 13// Range: bytes=500- <- byte 500 to end 14// Range: bytes=-500 <- last 500 bytes 15// Range: bytes=0-499,1000-1499 <- multiple ranges (multipart) 16// 17// We only support bytes= unit; other units (chars=, seconds= 18// in some video specs) are non-standard and rare. 19// 20// Invariants: 21// RH1 Returns RangeSpec array with (start, end) resolved vs 22// the file size. "-500" -> (size-500, size-1). 23// "500-" -> (500, size-1). 24// RH2 Ranges that extend past the file are clamped. 25// RH3 Empty / unparseable input returns RH_ERR_FORMAT so 26// caller falls back to serving 200 with full body. 27 28// nx_safety_envelope: 29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 30// sil_target: SIL1 31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 32// verdict: NOT_YET_EVALUATED 33 34import "nx_syscalls.nx" 35const RH_MAGIC_7233: i64 = 7233 36 37const RH_ERR_FORMAT: i64 = -1 38const RH_ERR_UNIT: i64 = -2 39const RH_ERR_OVERFLOW: i64 = -3 40 41struct RangeSpec { 42 start: i64, 43 end: i64, // inclusive (RFC RH_MAGIC_7233 §2.1) 44} 45 46// Is byte a decimal digit? 47func rh_is_digit(b: i64) -> i64 { 48 if b < 0x30 { return 0 } 49 if b > 0x39 { return 0 } 50 return 1 51} 52 53// Parse a decimal u64 from buf[off..end]. Writes new offset into 54// *off. Returns value or -1 if no digits. 55func rh_parse_uint(buf: *u8, off: *i64, end: i64) -> i64 { 56 let start: i64 = *off 57 var v: i64 = 0 58 while *off < end { 59 if rh_is_digit(buf[*off]) == 0 { break } 60 v = v * 10 + (buf[*off] - 0x30) 61 *off = *off + 1 62 } 63 if *off == start { return -1 } 64 return v 65} 66 67// Parse a Range header value. `total_size` is the resource's 68// size in bytes -- used to resolve suffix ranges like \"-500\" and 69// to clamp. Fills ranges[] + returns count parsed, or negative. 70func range_parse(buf: *u8, n: i64, total_size: i64, 71 ranges: *RangeSpec, cap: i64) -> i64 { 72 if n < 6 { return RH_ERR_FORMAT } // \"bytes=\" minimum 73 // Must start with \"bytes=\". 74 if buf[0] != 0x62 { return RH_ERR_UNIT } // 'b' 75 if buf[1] != 0x79 { return RH_ERR_UNIT } // 'y' 76 if buf[2] != 0x74 { return RH_ERR_UNIT } 77 if buf[3] != 0x65 { return RH_ERR_UNIT } 78 if buf[4] != 0x73 { return RH_ERR_UNIT } 79 if buf[5] != 0x3D { return RH_ERR_FORMAT } // '=' 80 81 var count: i64 = 0 82 let pos_raw: *u8 = sys_mmap(16) 83 let pos: *i64 = pos_raw as *i64 84 *pos = 6 85 86 while *pos < n { 87 // Skip whitespace. 88 while *pos < n { 89 if buf[*pos] != 0x20 { break } 90 *pos = *pos + 1 91 } 92 if *pos >= n { break } 93 94 if count >= cap { return RH_ERR_OVERFLOW } 95 let r: *RangeSpec = ranges + count * 16 96 97 var start_val: i64 = -1 98 var end_val: i64 = -1 99 100 // If it starts with '-' it's a suffix range (-500 = last 500). 101 if buf[*pos] == 0x2D { 102 *pos = *pos + 1 103 let suffix: i64 = rh_parse_uint(buf, pos, n) 104 if suffix < 0 { return RH_ERR_FORMAT } 105 if suffix == 0 { 106 // Degenerate. Skip. 107 if *pos < n { 108 if buf[*pos] == 0x2C { *pos = *pos + 1 } 109 } 110 continue 111 } 112 var s: i64 = total_size - suffix 113 if s < 0 { s = 0 } 114 start_val = s 115 end_val = total_size - 1 116 } else { 117 // Explicit start. 118 let s: i64 = rh_parse_uint(buf, pos, n) 119 if s < 0 { return RH_ERR_FORMAT } 120 if *pos >= n { return RH_ERR_FORMAT } 121 if buf[*pos] != 0x2D { return RH_ERR_FORMAT } 122 *pos = *pos + 1 123 // Optional end. 124 if *pos >= n { 125 start_val = s 126 end_val = total_size - 1 127 } else { 128 if buf[*pos] == 0x2C { 129 start_val = s 130 end_val = total_size - 1 131 } else { 132 let e: i64 = rh_parse_uint(buf, pos, n) 133 if e < 0 { return RH_ERR_FORMAT } 134 start_val = s 135 end_val = e 136 } 137 } 138 } 139 140 // Clamp end to size - 1. 141 if end_val > total_size - 1 { end_val = total_size - 1 } 142 // If range is out of bounds entirely, skip. 143 if start_val > end_val { 144 if *pos < n { 145 if buf[*pos] == 0x2C { *pos = *pos + 1 } 146 } 147 continue 148 } 149 150 r.start = start_val 151 r.end = end_val 152 count = count + 1 153 154 // Consume ',' separator. 155 if *pos < n { 156 if buf[*pos] == 0x2C { *pos = *pos + 1 } 157 } 158 } 159 if count == 0 { return RH_ERR_FORMAT } 160 return count 161} 162 163// Compile-only smoke. 164func main() -> i64 { 165 let ranges_raw: *u8 = sys_mmap(256) 166 let ranges: *RangeSpec = ranges_raw as *RangeSpec 167 168 // \"bytes=0-499\" against 1000-byte resource. 169 let n1: i64 = range_parse("bytes=0-499", 11, 1000, ranges, 16) 170 if n1 != 1 { return 1 } 171 if ranges[0].start != 0 { return 2 } 172 if ranges[0].end != 499 { return 3 } 173 174 // \"bytes=500-\" against 1000-byte resource -> (500, 999). 175 let n2: i64 = range_parse("bytes=500-", 10, 1000, ranges, 16) 176 if n2 != 1 { return 4 } 177 if ranges[0].start != 500 { return 5 } 178 if ranges[0].end != 999 { return 6 } 179 180 // \"bytes=-500\" against 1000-byte resource -> (500, 999). 181 let n3: i64 = range_parse("bytes=-500", 10, 1000, ranges, 16) 182 if n3 != 1 { return 7 } 183 if ranges[0].start != 500 { return 8 } 184 if ranges[0].end != 999 { return 9 } 185 186 // Multiple ranges: \"bytes=0-9,20-29\". 187 let n4: i64 = range_parse("bytes=0-9,20-29", 15, 1000, ranges, 16) 188 if n4 != 2 { return 10 } 189 if ranges[0].end != 9 { return 11 } 190 if ranges[1].start != 20 { return 12 } 191 if ranges[1].end != 29 { return 13 } 192 193 // Wrong unit rejected. 194 if range_parse("chars=0-", 8, 1000, ranges, 16) != RH_ERR_UNIT { 195 return 14 196 } 197 return 0 198}