nx_cache_control.nx source
↩ module page · 270 lines · 8701 B
1// cache_control.nx -- parse HTTP Cache-Control header.
2//
3// RFC 7234 §5.2. Controls caching behaviour at every level
4// (browser, proxy, CDN, reverse-proxy). Getting it right is
5// the difference between a fast site and a stampeded origin.
6//
7// Format: comma-separated directives, some with arguments.
8// Cache-Control: max-age=3600, public
9// Cache-Control: no-store
10// Cache-Control: private, max-age=0, must-revalidate
11// Cache-Control: public, max-age=31536000, immutable
12// Cache-Control: no-cache, s-maxage=600
13//
14// We parse into a typed CacheControl struct with flag bits for
15// the named directives + integer values for max-age / s-maxage /
16// max-stale / min-fresh / stale-while-revalidate.
17//
18// Invariants:
19// CC1 Empty input yields default CacheControl (all flags 0,
20// ages = -1 meaning \"not specified\").
21// CC2 Unknown directives silently ignored per RFC 7234 §5.2
22// tolerance rule.
23// CC3 Directive names are case-insensitive (we lowercase on
24// match). Argument values are case-sensitive integers.
25
26// nx_safety_envelope:
27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
28// sil_target: SIL1
29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
30// verdict: NOT_YET_EVALUATED
31
32import "nx_syscalls.nx"
33const K_MAGIC_3600: i64 = 3600
34const K_MAGIC_31536000: i64 = 31536000
35
36struct CacheControl {
37 // Primary mode flags.
38 no_store: i64,
39 no_cache: i64,
40 is_public: i64,
41 is_private: i64,
42 immutable: i64,
43 must_revalidate: i64,
44 proxy_revalidate: i64,
45 no_transform: i64,
46 only_if_cached: i64,
47
48 // Integer values; -1 means \"not specified\".
49 max_age: i64,
50 s_maxage: i64,
51 max_stale: i64,
52 min_fresh: i64,
53 stale_while_revalidate: i64,
54 stale_if_error: i64,
55}
56
57// Init a CacheControl to \"nothing specified\".
58func cache_control_init(cc: *CacheControl) -> i64 {
59 cc.no_store = 0
60 cc.no_cache = 0
61 cc.is_public = 0
62 cc.is_private = 0
63 cc.immutable = 0
64 cc.must_revalidate = 0
65 cc.proxy_revalidate = 0
66 cc.no_transform = 0
67 cc.only_if_cached = 0
68 cc.max_age = -1
69 cc.s_maxage = -1
70 cc.max_stale = -1
71 cc.min_fresh = -1
72 cc.stale_while_revalidate = -1
73 cc.stale_if_error = -1
74 return 0
75}
76
77// ASCII lowercase.
78func cc_lower(b: i64) -> i64 {
79 if b >= 0x41 {
80 if b <= 0x5A { return b + 0x20 }
81 }
82 return b
83}
84
85// Compare two byte ranges case-insensitively.
86func cc_eq_ci(a: *u8, a_off: i64, a_len: i64,
87 b: *u8, b_len: i64) -> i64 {
88 if a_len != b_len { return 0 }
89 var i: i64 = 0
90 while i < a_len {
91 if cc_lower(a[a_off + i]) != cc_lower(b[i]) { return 0 }
92 i = i + 1
93 }
94 return 1
95}
96
97// Parse a non-negative decimal starting at off.
98func cc_parse_int(buf: *u8, off: i64, end: i64) -> i64 {
99 var v: i64 = 0
100 var i: i64 = off
101 var any: i64 = 0
102 while i < end {
103 let b: i64 = buf[i]
104 if b < 0x30 { break }
105 if b > 0x39 { break }
106 v = v * 10 + (b - 0x30)
107 any = 1
108 i = i + 1
109 }
110 if any == 0 { return -1 }
111 return v
112}
113
114// Parse. Returns 0 on success (always -- unknown directives
115// don't error per RFC).
116func cache_control_parse(buf: *u8, n: i64, cc: *CacheControl) -> i64 {
117 cache_control_init(cc)
118 var i: i64 = 0
119 while i < n {
120 // Skip whitespace + commas.
121 while i < n {
122 let b: i64 = buf[i]
123 if b != 0x20 {
124 if b != 0x09 {
125 if b != 0x2C { break }
126 }
127 }
128 i = i + 1
129 }
130 if i >= n { break }
131
132 // Find directive end: '=' or ',' or EOF.
133 let name_start: i64 = i
134 while i < n {
135 if buf[i] == 0x3D { break }
136 if buf[i] == 0x2C { break }
137 if buf[i] == 0x20 { break }
138 i = i + 1
139 }
140 let name_len: i64 = i - name_start
141
142 // Optional argument after '='.
143 var arg_start: i64 = -1
144 var arg_len: i64 = 0
145 if i < n {
146 if buf[i] == 0x3D {
147 i = i + 1
148 // Optional quoted value.
149 if i < n {
150 if buf[i] == 0x22 {
151 i = i + 1
152 arg_start = i
153 while i < n {
154 if buf[i] == 0x22 { break }
155 i = i + 1
156 }
157 arg_len = i - arg_start
158 if i < n { i = i + 1 }
159 } else {
160 arg_start = i
161 while i < n {
162 if buf[i] == 0x2C { break }
163 if buf[i] == 0x20 { break }
164 i = i + 1
165 }
166 arg_len = i - arg_start
167 }
168 }
169 }
170 }
171
172 // Match directive name (case-insensitive).
173 if cc_eq_ci(buf, name_start, name_len, "no-store", 8) == 1 {
174 cc.no_store = 1
175 }
176 if cc_eq_ci(buf, name_start, name_len, "no-cache", 8) == 1 {
177 cc.no_cache = 1
178 }
179 if cc_eq_ci(buf, name_start, name_len, "public", 6) == 1 {
180 cc.is_public = 1
181 }
182 if cc_eq_ci(buf, name_start, name_len, "private", 7) == 1 {
183 cc.is_private = 1
184 }
185 if cc_eq_ci(buf, name_start, name_len, "immutable", 9) == 1 {
186 cc.immutable = 1
187 }
188 if cc_eq_ci(buf, name_start, name_len, "must-revalidate", 15) == 1 {
189 cc.must_revalidate = 1
190 }
191 if cc_eq_ci(buf, name_start, name_len, "proxy-revalidate", 16) == 1 {
192 cc.proxy_revalidate = 1
193 }
194 if cc_eq_ci(buf, name_start, name_len, "no-transform", 12) == 1 {
195 cc.no_transform = 1
196 }
197 if cc_eq_ci(buf, name_start, name_len, "only-if-cached", 14) == 1 {
198 cc.only_if_cached = 1
199 }
200
201 // Integer-valued directives.
202 if cc_eq_ci(buf, name_start, name_len, "max-age", 7) == 1 {
203 if arg_start >= 0 {
204 cc.max_age = cc_parse_int(buf, arg_start, arg_start + arg_len)
205 }
206 }
207 if cc_eq_ci(buf, name_start, name_len, "s-maxage", 8) == 1 {
208 if arg_start >= 0 {
209 cc.s_maxage = cc_parse_int(buf, arg_start, arg_start + arg_len)
210 }
211 }
212 if cc_eq_ci(buf, name_start, name_len, "max-stale", 9) == 1 {
213 if arg_start >= 0 {
214 cc.max_stale = cc_parse_int(buf, arg_start, arg_start + arg_len)
215 }
216 }
217 if cc_eq_ci(buf, name_start, name_len, "min-fresh", 9) == 1 {
218 if arg_start >= 0 {
219 cc.min_fresh = cc_parse_int(buf, arg_start, arg_start + arg_len)
220 }
221 }
222 if cc_eq_ci(buf, name_start, name_len,
223 "stale-while-revalidate", 22) == 1 {
224 if arg_start >= 0 {
225 cc.stale_while_revalidate = cc_parse_int(buf, arg_start,
226 arg_start + arg_len)
227 }
228 }
229 if cc_eq_ci(buf, name_start, name_len, "stale-if-error", 14) == 1 {
230 if arg_start >= 0 {
231 cc.stale_if_error = cc_parse_int(buf, arg_start,
232 arg_start + arg_len)
233 }
234 }
235 }
236 return 0
237}
238
239// Compile-only smoke.
240func main() -> i64 {
241 let cc_raw: *u8 = sys_mmap(256)
242 let cc: *CacheControl = cc_raw as *CacheControl
243
244 cache_control_parse("max-age=3600, public", 20, cc)
245 if cc.max_age != K_MAGIC_3600 { return 1 }
246 if cc.is_public != 1 { return 2 }
247 if cc.no_store != 0 { return 3 }
248
249 cache_control_parse("no-store", 8, cc)
250 if cc.no_store != 1 { return 4 }
251 if cc.max_age != -1 { return 5 }
252
253 cache_control_parse("private, max-age=0, must-revalidate", 35, cc)
254 if cc.is_private != 1 { return 6 }
255 if cc.max_age != 0 { return 7 }
256 if cc.must_revalidate != 1 { return 8 }
257
258 cache_control_parse("public, max-age=31536000, immutable", 35, cc)
259 if cc.max_age != K_MAGIC_31536000 { return 9 }
260 if cc.immutable != 1 { return 10 }
261
262 cache_control_parse("no-cache, s-maxage=600", 22, cc)
263 if cc.no_cache != 1 { return 11 }
264 if cc.s_maxage != 600 { return 12 }
265
266 // Unknown directives tolerated.
267 cache_control_parse("weird-thing, max-age=1", 22, cc)
268 if cc.max_age != 1 { return 13 }
269 return 0
270}