code wiki / (root) / cache_control.nx

cache_control.nx source

↩ module page · 262 lines · 8623 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 26import "syscalls.nx" 27 28struct CacheControl { 29 // Primary mode flags. 30 no_store: i64, 31 no_cache: i64, 32 is_public: i64, 33 is_private: i64, 34 immutable: i64, 35 must_revalidate: i64, 36 proxy_revalidate: i64, 37 no_transform: i64, 38 only_if_cached: i64, 39 40 // Integer values; -1 means \"not specified\". 41 max_age: i64, 42 s_maxage: i64, 43 max_stale: i64, 44 min_fresh: i64, 45 stale_while_revalidate: i64, 46 stale_if_error: i64, 47} 48 49// Init a CacheControl to \"nothing specified\". 50func cache_control_init(cc: *CacheControl) -> i64 { 51 cc.no_store = 0 52 cc.no_cache = 0 53 cc.is_public = 0 54 cc.is_private = 0 55 cc.immutable = 0 56 cc.must_revalidate = 0 57 cc.proxy_revalidate = 0 58 cc.no_transform = 0 59 cc.only_if_cached = 0 60 cc.max_age = -1 61 cc.s_maxage = -1 62 cc.max_stale = -1 63 cc.min_fresh = -1 64 cc.stale_while_revalidate = -1 65 cc.stale_if_error = -1 66 return 0 67} 68 69// ASCII lowercase. 70func cc_lower(b: i64) -> i64 { 71 if b >= 0x41 { 72 if b <= 0x5A { return b + 0x20 } 73 } 74 return b 75} 76 77// Compare two byte ranges case-insensitively. 78func cc_eq_ci(a: *u8, a_off: i64, a_len: i64, 79 b: *u8, b_len: i64) -> i64 { 80 if a_len != b_len { return 0 } 81 var i: i64 = 0 82 while i < a_len { 83 if cc_lower(a[a_off + i]) != cc_lower(b[i]) { return 0 } 84 i = i + 1 85 } 86 return 1 87} 88 89// Parse a non-negative decimal starting at off. 90func cc_parse_int(buf: *u8, off: i64, end: i64) -> i64 { 91 var v: i64 = 0 92 var i: i64 = off 93 var any: i64 = 0 94 while i < end { 95 let b: i64 = buf[i] 96 if b < 0x30 { break } 97 if b > 0x39 { break } 98 v = v * 10 + (b - 0x30) 99 any = 1 100 i = i + 1 101 } 102 if any == 0 { return -1 } 103 return v 104} 105 106// Parse. Returns 0 on success (always -- unknown directives 107// don't error per RFC). 108func cache_control_parse(buf: *u8, n: i64, cc: *CacheControl) -> i64 { 109 cache_control_init(cc) 110 var i: i64 = 0 111 while i < n { 112 // Skip whitespace + commas. 113 while i < n { 114 let b: i64 = buf[i] 115 if b != 0x20 { 116 if b != 0x09 { 117 if b != 0x2C { break } 118 } 119 } 120 i = i + 1 121 } 122 if i >= n { break } 123 124 // Find directive end: '=' or ',' or EOF. 125 let name_start: i64 = i 126 while i < n { 127 if buf[i] == 0x3D { break } 128 if buf[i] == 0x2C { break } 129 if buf[i] == 0x20 { break } 130 i = i + 1 131 } 132 let name_len: i64 = i - name_start 133 134 // Optional argument after '='. 135 var arg_start: i64 = -1 136 var arg_len: i64 = 0 137 if i < n { 138 if buf[i] == 0x3D { 139 i = i + 1 140 // Optional quoted value. 141 if i < n { 142 if buf[i] == 0x22 { 143 i = i + 1 144 arg_start = i 145 while i < n { 146 if buf[i] == 0x22 { break } 147 i = i + 1 148 } 149 arg_len = i - arg_start 150 if i < n { i = i + 1 } 151 } else { 152 arg_start = i 153 while i < n { 154 if buf[i] == 0x2C { break } 155 if buf[i] == 0x20 { break } 156 i = i + 1 157 } 158 arg_len = i - arg_start 159 } 160 } 161 } 162 } 163 164 // Match directive name (case-insensitive). 165 if cc_eq_ci(buf, name_start, name_len, "no-store", 8) == 1 { 166 cc.no_store = 1 167 } 168 if cc_eq_ci(buf, name_start, name_len, "no-cache", 8) == 1 { 169 cc.no_cache = 1 170 } 171 if cc_eq_ci(buf, name_start, name_len, "public", 6) == 1 { 172 cc.is_public = 1 173 } 174 if cc_eq_ci(buf, name_start, name_len, "private", 7) == 1 { 175 cc.is_private = 1 176 } 177 if cc_eq_ci(buf, name_start, name_len, "immutable", 9) == 1 { 178 cc.immutable = 1 179 } 180 if cc_eq_ci(buf, name_start, name_len, "must-revalidate", 15) == 1 { 181 cc.must_revalidate = 1 182 } 183 if cc_eq_ci(buf, name_start, name_len, "proxy-revalidate", 16) == 1 { 184 cc.proxy_revalidate = 1 185 } 186 if cc_eq_ci(buf, name_start, name_len, "no-transform", 12) == 1 { 187 cc.no_transform = 1 188 } 189 if cc_eq_ci(buf, name_start, name_len, "only-if-cached", 14) == 1 { 190 cc.only_if_cached = 1 191 } 192 193 // Integer-valued directives. 194 if cc_eq_ci(buf, name_start, name_len, "max-age", 7) == 1 { 195 if arg_start >= 0 { 196 cc.max_age = cc_parse_int(buf, arg_start, arg_start + arg_len) 197 } 198 } 199 if cc_eq_ci(buf, name_start, name_len, "s-maxage", 8) == 1 { 200 if arg_start >= 0 { 201 cc.s_maxage = cc_parse_int(buf, arg_start, arg_start + arg_len) 202 } 203 } 204 if cc_eq_ci(buf, name_start, name_len, "max-stale", 9) == 1 { 205 if arg_start >= 0 { 206 cc.max_stale = cc_parse_int(buf, arg_start, arg_start + arg_len) 207 } 208 } 209 if cc_eq_ci(buf, name_start, name_len, "min-fresh", 9) == 1 { 210 if arg_start >= 0 { 211 cc.min_fresh = cc_parse_int(buf, arg_start, arg_start + arg_len) 212 } 213 } 214 if cc_eq_ci(buf, name_start, name_len, 215 "stale-while-revalidate", 22) == 1 { 216 if arg_start >= 0 { 217 cc.stale_while_revalidate = cc_parse_int(buf, arg_start, 218 arg_start + arg_len) 219 } 220 } 221 if cc_eq_ci(buf, name_start, name_len, "stale-if-error", 14) == 1 { 222 if arg_start >= 0 { 223 cc.stale_if_error = cc_parse_int(buf, arg_start, 224 arg_start + arg_len) 225 } 226 } 227 } 228 return 0 229} 230 231// Compile-only smoke. 232func main() -> i64 { 233 let cc_raw: *u8 = sys_mmap(256) 234 let cc: *CacheControl = cc_raw as *CacheControl 235 236 cache_control_parse("max-age=3600, public", 20, cc) 237 if cc.max_age != 3600 { return 1 } 238 if cc.is_public != 1 { return 2 } 239 if cc.no_store != 0 { return 3 } 240 241 cache_control_parse("no-store", 8, cc) 242 if cc.no_store != 1 { return 4 } 243 if cc.max_age != -1 { return 5 } 244 245 cache_control_parse("private, max-age=0, must-revalidate", 35, cc) 246 if cc.is_private != 1 { return 6 } 247 if cc.max_age != 0 { return 7 } 248 if cc.must_revalidate != 1 { return 8 } 249 250 cache_control_parse("public, max-age=31536000, immutable", 35, cc) 251 if cc.max_age != 31536000 { return 9 } 252 if cc.immutable != 1 { return 10 } 253 254 cache_control_parse("no-cache, s-maxage=600", 22, cc) 255 if cc.no_cache != 1 { return 11 } 256 if cc.s_maxage != 600 { return 12 } 257 258 // Unknown directives tolerated. 259 cache_control_parse("weird-thing, max-age=1", 22, cc) 260 if cc.max_age != 1 { return 13 } 261 return 0 262}