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}