code wiki / (root) / nx_range_header.nx

nx_range_header.nx source

↩ module page · 152 lines · 6243 B

1// Native byte-range parsing, RFC 9110 sections 14.1.1 and 14.1.2. 2// Negative outcomes retain caller output. Serving policy owns HTTP status selection. 3import "nx_syscalls.nx" 4const RH_ERR_FORMAT: i64 = -1 5const RH_ERR_UNIT: i64 = -2 6const RH_ERR_OVERFLOW: i64 = -3 // caller's range-array capacity 7const RH_ERR_UNSATISFIABLE: i64 = -4 8const RH_EMPTY_REPRESENTATION: i64 = -5 9const RH_ERR_NUMBER: i64 = -6 // legacy integer helper cannot represent the numeral 10struct RangeSpec { start: i64, end: i64, } 11func rh_is_digit(b: i64) -> i64 { return ((b>=48)&&(b<=57)) as i64 } 12func rh_ows(b: i64) -> i64 { return ((b==32)||(b==9)) as i64 } 13// Legacy helper retains its ABI. This bound is the signed file-offset representation, 14// not a configured resource budget. The range parser below does not need this bound. 15func rh_parse_uint(buf: *u8, off: *i64, end: i64) -> i64 { 16 if off[0]<0 || end<off[0] { return RH_ERR_FORMAT } 17 let first: i64=off[0];var v: i64=0 18 let top: i64=9223372036854775807 19 while off[0]<end { 20 let d: i64=(buf[off[0]] as i64)-48 21 if d<0 || d>9 { break } 22 if v>top/10 || (v==top/10 && d>top%10) { return RH_ERR_NUMBER } 23 v=v*10+d;off[0]=off[0]+1 24 } 25 if off[0]==first { return RH_ERR_FORMAT } 26 return v 27} 28// Consumes all digits but saturates at the resource size. Huge valid suffix/end 29// values resolve correctly without bigint allocation or integer conversion overflow. 30func rh_bounded(buf: *u8,off: *i64,n: i64,limit: i64) -> i64 { 31 let first: i64=off[0];var v: i64=0 32 while off[0]<n { 33 let d: i64=(buf[off[0]] as i64)-48 34 if d<0 || d>9 { break } 35 if v>limit/10 || (v==limit/10 && d>limit%10) { v=limit } 36 else { v=v*10+d } 37 off[0]=off[0]+1 38 } 39 if off[0]==first { return RH_ERR_FORMAT } 40 return v 41} 42// Decimal comparison uses source spans, preserving ordering even above file-offset range. 43func rh_decimal_cmp(buf: *u8,a: i64,ae: i64,b: i64,be: i64) -> i64 { 44 var x: i64=a;var y: i64=b 45 while x<ae { if buf[x]!=(48 as u8) { break };x=x+1 } 46 while y<be { if buf[y]!=(48 as u8) { break };y=y+1 } 47 if ae-x<be-y { return -1 };if ae-x>be-y { return 1 } 48 while x<ae { if buf[x]<buf[y] { return -1 };if buf[x]>buf[y] { return 1 };x=x+1;y=y+1 } 49 return 0 50} 51func rh_nonzero(buf: *u8,a: i64,b: i64) -> i64 { 52 var i: i64=a;while i<b { if buf[i]!=(48 as u8) { return 1 };i=i+1 };return 0 53} 54// The first pass supplies a null destination; the second sees the same immutable input. 55func rh_parse_pass(buf: *u8,n: i64,total: i64,ranges: *RangeSpec,cap: i64,pos: *i64) -> i64 { 56 let unit: *u8="bytes=" 57 var i: i64=0;while unit[i]!=(0 as u8) { 58 if i>=n { return RH_ERR_FORMAT } 59 var c: i64=buf[i] as i64;if c>=65 && c<=90 { c=c+32 } 60 if c!=(unit[i] as i64) { return RH_ERR_UNIT };i=i+1 61 } 62 pos[0]=i;var count: i64=0;var seen: i64=0;var empty_suffix: i64=0 63 while pos[0]<n { 64 // RFC list syntax tolerates empty list members. Work remains bounded by input bytes. 65 while pos[0]<n { 66 let c: i64=buf[pos[0]] as i64 67 if rh_ows(c)==0 && c!=44 { break };pos[0]=pos[0]+1 68 } 69 if pos[0]>=n { break } 70 var start: i64=0;var finish: i64=total-1;var usable: i64=0 71 if buf[pos[0]]==(45 as u8) { 72 pos[0]=pos[0]+1;let a: i64=pos[0] 73 let suffix: i64=rh_bounded(buf,pos,n,total) 74 if suffix<0 { return RH_ERR_FORMAT } 75 if rh_nonzero(buf,a,pos[0])==1 { 76 if total==0 { empty_suffix=1 } else { start=total-suffix;usable=1 } 77 } 78 } else { 79 let a: i64=pos[0];start=rh_bounded(buf,pos,n,total);let ae: i64=pos[0] 80 if start<0 || pos[0]>=n { return RH_ERR_FORMAT } 81 if buf[pos[0]]!=(45 as u8) { return RH_ERR_FORMAT };pos[0]=pos[0]+1 82 if pos[0]<n { 83 if rh_is_digit(buf[pos[0]] as i64)==1 { 84 let b: i64=pos[0];let last: i64=rh_bounded(buf,pos,n,total) 85 if rh_decimal_cmp(buf,a,ae,b,pos[0])>0 { return RH_ERR_FORMAT } 86 finish=last;if finish>=total { finish=total-1 } 87 } 88 } 89 if start<total { usable=1 } 90 } 91 seen=seen+1 92 while pos[0]<n { if rh_ows(buf[pos[0]] as i64)==0 { break };pos[0]=pos[0]+1 } 93 if pos[0]<n { if buf[pos[0]]!=(44 as u8) { return RH_ERR_FORMAT };pos[0]=pos[0]+1 } 94 if usable==1 { 95 if count>=cap { return RH_ERR_OVERFLOW } 96 if (ranges as i64)!=0 { 97 let r: *RangeSpec=(ranges as i64+count*__size_of(RangeSpec)) as *RangeSpec 98 r.start=start;r.end=finish 99 } 100 count=count+1 101 } 102 } 103 if seen==0 { return RH_ERR_FORMAT } 104 if count==0 { if empty_suffix==1 { return RH_EMPTY_REPRESENTATION };return RH_ERR_UNSATISFIABLE } 105 return count 106} 107func range_parse(buf: *u8,n: i64,total_size: i64,ranges: *RangeSpec,cap: i64) -> i64 { 108 if n<=0 || total_size<0 || cap<0 || (buf as i64)==0 { return RH_ERR_FORMAT } 109 if cap>0 && (ranges as i64)==0 { return RH_ERR_FORMAT } 110 let pos: *i64=sys_mmap(__size_of(i64)) as *i64 111 let checked: i64=rh_parse_pass(buf,n,total_size,0 as *RangeSpec,cap,pos) 112 var result: i64=checked 113 if checked>0 { result=rh_parse_pass(buf,n,total_size,ranges,cap,pos) } 114 sys_munmap(pos as *u8,__size_of(i64)) 115 return result 116} 117// Retained standalone smoke entry point; full boundary coverage is in nx_range_header_gate. 118func main() -> i64 { 119 let ranges_raw: *u8 = sys_mmap(2*__size_of(RangeSpec)) 120 let ranges: *RangeSpec = ranges_raw as *RangeSpec 121 122 // \"bytes=0-499\" against 1000-byte resource. 123 let n1: i64 = range_parse("bytes=0-499", 11, 1000, ranges, 2) 124 if n1 != 1 { return 1 } 125 if ranges[0].start != 0 { return 2 } 126 if ranges[0].end != 499 { return 3 } 127 128 // \"bytes=500-\" against 1000-byte resource -> (500, 999). 129 let n2: i64 = range_parse("bytes=500-", 10, 1000, ranges, 2) 130 if n2 != 1 { return 4 } 131 if ranges[0].start != 500 { return 5 } 132 if ranges[0].end != 999 { return 6 } 133 134 // \"bytes=-500\" against 1000-byte resource -> (500, 999). 135 let n3: i64 = range_parse("bytes=-500", 10, 1000, ranges, 2) 136 if n3 != 1 { return 7 } 137 if ranges[0].start != 500 { return 8 } 138 if ranges[0].end != 999 { return 9 } 139 140 // Multiple ranges: \"bytes=0-9,20-29\". 141 let n4: i64 = range_parse("bytes=0-9,20-29", 15, 1000, ranges, 2) 142 if n4 != 2 { return 10 } 143 if ranges[0].end != 9 { return 11 } 144 if ranges[1].start != 20 { return 12 } 145 if ranges[1].end != 29 { return 13 } 146 147 // Wrong unit rejected. 148 if range_parse("chars=0-", 8, 1000, ranges, 2) != RH_ERR_UNIT { 149 return 14 150 } 151 return 0 152}