code wiki / (root) / nx_cache_control.nx

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}