code wiki / (root) / range_header.nx

range_header.nx source

↩ module page · 191 lines · 6497 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 28import "syscalls.nx" 29 30const RH_ERR_FORMAT: i64 = -1 31const RH_ERR_UNIT: i64 = -2 32const RH_ERR_OVERFLOW: i64 = -3 33 34struct RangeSpec { 35 start: i64, 36 end: i64, // inclusive (RFC 7233 §2.1) 37} 38 39// Is byte a decimal digit? 40func rh_is_digit(b: i64) -> i64 { 41 if b < 0x30 { return 0 } 42 if b > 0x39 { return 0 } 43 return 1 44} 45 46// Parse a decimal u64 from buf[off..end]. Writes new offset into 47// *off. Returns value or -1 if no digits. 48func rh_parse_uint(buf: *u8, off: *i64, end: i64) -> i64 { 49 let start: i64 = *off 50 var v: i64 = 0 51 while *off < end { 52 if rh_is_digit(buf[*off]) == 0 { break } 53 v = v * 10 + (buf[*off] - 0x30) 54 *off = *off + 1 55 } 56 if *off == start { return -1 } 57 return v 58} 59 60// Parse a Range header value. `total_size` is the resource's 61// size in bytes -- used to resolve suffix ranges like \"-500\" and 62// to clamp. Fills ranges[] + returns count parsed, or negative. 63func range_parse(buf: *u8, n: i64, total_size: i64, 64 ranges: *RangeSpec, cap: i64) -> i64 { 65 if n < 6 { return RH_ERR_FORMAT } // \"bytes=\" minimum 66 // Must start with \"bytes=\". 67 if buf[0] != 0x62 { return RH_ERR_UNIT } // 'b' 68 if buf[1] != 0x79 { return RH_ERR_UNIT } // 'y' 69 if buf[2] != 0x74 { return RH_ERR_UNIT } 70 if buf[3] != 0x65 { return RH_ERR_UNIT } 71 if buf[4] != 0x73 { return RH_ERR_UNIT } 72 if buf[5] != 0x3D { return RH_ERR_FORMAT } // '=' 73 74 var count: i64 = 0 75 let pos_raw: *u8 = sys_mmap(16) 76 let pos: *i64 = pos_raw as *i64 77 *pos = 6 78 79 while *pos < n { 80 // Skip whitespace. 81 while *pos < n { 82 if buf[*pos] != 0x20 { break } 83 *pos = *pos + 1 84 } 85 if *pos >= n { break } 86 87 if count >= cap { return RH_ERR_OVERFLOW } 88 let r: *RangeSpec = ranges + count * 16 89 90 var start_val: i64 = -1 91 var end_val: i64 = -1 92 93 // If it starts with '-' it's a suffix range (-500 = last 500). 94 if buf[*pos] == 0x2D { 95 *pos = *pos + 1 96 let suffix: i64 = rh_parse_uint(buf, pos, n) 97 if suffix < 0 { return RH_ERR_FORMAT } 98 if suffix == 0 { 99 // Degenerate. Skip. 100 if *pos < n { 101 if buf[*pos] == 0x2C { *pos = *pos + 1 } 102 } 103 continue 104 } 105 var s: i64 = total_size - suffix 106 if s < 0 { s = 0 } 107 start_val = s 108 end_val = total_size - 1 109 } else { 110 // Explicit start. 111 let s: i64 = rh_parse_uint(buf, pos, n) 112 if s < 0 { return RH_ERR_FORMAT } 113 if *pos >= n { return RH_ERR_FORMAT } 114 if buf[*pos] != 0x2D { return RH_ERR_FORMAT } 115 *pos = *pos + 1 116 // Optional end. 117 if *pos >= n { 118 start_val = s 119 end_val = total_size - 1 120 } else { 121 if buf[*pos] == 0x2C { 122 start_val = s 123 end_val = total_size - 1 124 } else { 125 let e: i64 = rh_parse_uint(buf, pos, n) 126 if e < 0 { return RH_ERR_FORMAT } 127 start_val = s 128 end_val = e 129 } 130 } 131 } 132 133 // Clamp end to size - 1. 134 if end_val > total_size - 1 { end_val = total_size - 1 } 135 // If range is out of bounds entirely, skip. 136 if start_val > end_val { 137 if *pos < n { 138 if buf[*pos] == 0x2C { *pos = *pos + 1 } 139 } 140 continue 141 } 142 143 r.start = start_val 144 r.end = end_val 145 count = count + 1 146 147 // Consume ',' separator. 148 if *pos < n { 149 if buf[*pos] == 0x2C { *pos = *pos + 1 } 150 } 151 } 152 if count == 0 { return RH_ERR_FORMAT } 153 return count 154} 155 156// Compile-only smoke. 157func main() -> i64 { 158 let ranges_raw: *u8 = sys_mmap(256) 159 let ranges: *RangeSpec = ranges_raw as *RangeSpec 160 161 // \"bytes=0-499\" against 1000-byte resource. 162 let n1: i64 = range_parse("bytes=0-499", 11, 1000, ranges, 16) 163 if n1 != 1 { return 1 } 164 if ranges[0].start != 0 { return 2 } 165 if ranges[0].end != 499 { return 3 } 166 167 // \"bytes=500-\" against 1000-byte resource -> (500, 999). 168 let n2: i64 = range_parse("bytes=500-", 10, 1000, ranges, 16) 169 if n2 != 1 { return 4 } 170 if ranges[0].start != 500 { return 5 } 171 if ranges[0].end != 999 { return 6 } 172 173 // \"bytes=-500\" against 1000-byte resource -> (500, 999). 174 let n3: i64 = range_parse("bytes=-500", 10, 1000, ranges, 16) 175 if n3 != 1 { return 7 } 176 if ranges[0].start != 500 { return 8 } 177 if ranges[0].end != 999 { return 9 } 178 179 // Multiple ranges: \"bytes=0-9,20-29\". 180 let n4: i64 = range_parse("bytes=0-9,20-29", 15, 1000, ranges, 16) 181 if n4 != 2 { return 10 } 182 if ranges[0].end != 9 { return 11 } 183 if ranges[1].start != 20 { return 12 } 184 if ranges[1].end != 29 { return 13 } 185 186 // Wrong unit rejected. 187 if range_parse("chars=0-", 8, 1000, ranges, 16) != RH_ERR_UNIT { 188 return 14 189 } 190 return 0 191}