11#if PC_ENABLE_HTTP_CACHE
15static const size_t CC_SENT = (size_t)-1;
17void cache_control_init(pc_cache_control *cc)
19 cc->cc_public =
false;
20 cc->cc_private =
false;
23 cc->no_transform =
false;
24 cc->must_revalidate =
false;
25 cc->proxy_revalidate =
false;
26 cc->must_understand =
false;
27 cc->cc_immutable =
false;
28 cc->only_if_cached =
false;
31 cc->stale_while_revalidate = -1;
32 cc->stale_if_error = -1;
39static size_t cc_emit_uint(
char *buf,
size_t cap,
size_t n,
unsigned v)
51 rev[ri++] = (char)(
'0' + (v % 10));
55 if (n + (
size_t)ri > cap)
59 for (
int k = 0; k < ri; k++)
61 buf[n + k] = rev[ri - 1 - k];
63 return n + (size_t)ri;
67static size_t cc_tok(
char *buf,
size_t cap,
size_t n,
bool *first,
const char *tok)
73 size_t tlen = strnlen(tok, cap);
74 size_t need = (*first ? 0 : 2) + tlen;
84 memcpy(buf + n, tok, tlen);
90static size_t cc_kv(
char *buf,
size_t cap,
size_t n,
bool *first,
const char *key,
long v)
92 n = cc_tok(buf, cap, n, first, key);
93 if (n == CC_SENT || n >= cap)
98 return cc_emit_uint(buf, cap, n, (
unsigned)v);
101size_t cache_control_build(
char *buf,
size_t cap,
const pc_cache_control *cc)
103 if (!buf || !cc || cap == 0)
112 n = cc_tok(buf, cap, n, &first,
"public");
116 n = cc_tok(buf, cap, n, &first,
"private");
120 n = cc_tok(buf, cap, n, &first,
"no-store");
124 n = cc_tok(buf, cap, n, &first,
"no-cache");
126 if (cc->max_age >= 0)
128 n = cc_kv(buf, cap, n, &first,
"max-age", cc->max_age);
130 if (cc->s_maxage >= 0)
132 n = cc_kv(buf, cap, n, &first,
"s-maxage", cc->s_maxage);
134 if (cc->must_revalidate)
136 n = cc_tok(buf, cap, n, &first,
"must-revalidate");
138 if (cc->proxy_revalidate)
140 n = cc_tok(buf, cap, n, &first,
"proxy-revalidate");
142 if (cc->no_transform)
144 n = cc_tok(buf, cap, n, &first,
"no-transform");
146 if (cc->must_understand)
148 n = cc_tok(buf, cap, n, &first,
"must-understand");
150 if (cc->cc_immutable)
152 n = cc_tok(buf, cap, n, &first,
"immutable");
154 if (cc->stale_while_revalidate >= 0)
156 n = cc_kv(buf, cap, n, &first,
"stale-while-revalidate", cc->stale_while_revalidate);
158 if (cc->stale_if_error >= 0)
160 n = cc_kv(buf, cap, n, &first,
"stale-if-error", cc->stale_if_error);
162 if (cc->only_if_cached)
164 n = cc_tok(buf, cap, n, &first,
"only-if-cached");
166 if (cc->max_stale == -2)
168 n = cc_tok(buf, cap, n, &first,
"max-stale");
170 else if (cc->max_stale >= 0)
172 n = cc_kv(buf, cap, n, &first,
"max-stale", cc->max_stale);
174 if (cc->min_fresh >= 0)
176 n = cc_kv(buf, cap, n, &first,
"min-fresh", cc->min_fresh);
179 if (n == CC_SENT || first || n + 1 > cap)
190static bool cc_ci_eq(
const char *s,
size_t len,
const char *target)
193 for (; i < len && target[i]; i++)
196 if (c >=
'A' && c <=
'Z')
205 return i == len && target[i] == 0;
210static int32_t cc_parse_delta(
const char *v,
size_t vlen)
217 while (i < vlen && (v[i] ==
' ' || v[i] ==
'\t' || v[i] ==
'"'))
226 while (i < vlen && v[i] >=
'0' && v[i] <=
'9')
233 val = val * 10 + (v[i] -
'0');
234 if (val > 2147483647)
240 return any ? (int32_t)val : -1;
243static bool cc_match(pc_cache_control *cc,
const char *name,
size_t nlen,
const char *val,
size_t vlen)
245 if (cc_ci_eq(name, nlen,
"public"))
247 cc->cc_public =
true;
249 else if (cc_ci_eq(name, nlen,
"private"))
251 cc->cc_private =
true;
253 else if (cc_ci_eq(name, nlen,
"no-store"))
257 else if (cc_ci_eq(name, nlen,
"no-cache"))
261 else if (cc_ci_eq(name, nlen,
"no-transform"))
263 cc->no_transform =
true;
265 else if (cc_ci_eq(name, nlen,
"must-revalidate"))
267 cc->must_revalidate =
true;
269 else if (cc_ci_eq(name, nlen,
"proxy-revalidate"))
271 cc->proxy_revalidate =
true;
273 else if (cc_ci_eq(name, nlen,
"must-understand"))
275 cc->must_understand =
true;
277 else if (cc_ci_eq(name, nlen,
"immutable"))
279 cc->cc_immutable =
true;
281 else if (cc_ci_eq(name, nlen,
"only-if-cached"))
283 cc->only_if_cached =
true;
285 else if (cc_ci_eq(name, nlen,
"max-age"))
287 cc->max_age = cc_parse_delta(val, vlen);
289 else if (cc_ci_eq(name, nlen,
"s-maxage"))
291 cc->s_maxage = cc_parse_delta(val, vlen);
293 else if (cc_ci_eq(name, nlen,
"stale-while-revalidate"))
295 cc->stale_while_revalidate = cc_parse_delta(val, vlen);
297 else if (cc_ci_eq(name, nlen,
"stale-if-error"))
299 cc->stale_if_error = cc_parse_delta(val, vlen);
301 else if (cc_ci_eq(name, nlen,
"max-stale"))
303 cc->max_stale = val ? cc_parse_delta(val, vlen) : -2;
305 else if (cc_ci_eq(name, nlen,
"min-fresh"))
307 cc->min_fresh = cc_parse_delta(val, vlen);
318static bool cache_parse_one_directive(
const char *s,
size_t len,
size_t *i, pc_cache_control *cc)
320 while (*i < len && (s[*i] ==
',' || s[*i] ==
' ' || s[*i] ==
'\t'))
329 while (*i < len && s[*i] !=
',')
338 while (end > start &&
339 (s[end - 1] ==
' ' || s[end - 1] ==
'\t'))
344 while (eq < end && s[eq] !=
'=')
348 size_t nlen = (eq < end ? eq : end) - start;
349 while (nlen > 0 && (s[start + nlen - 1] ==
' ' || s[start + nlen - 1] ==
'\t'))
353 const char *val = (eq < end) ? s + eq + 1 : nullptr;
354 size_t vlen = (eq < end) ? end - (eq + 1) : 0;
355 return nlen && cc_match(cc, s + start, nlen, val, vlen);
358bool cache_control_parse(
const char *s,
size_t len, pc_cache_control *cc)
360 cache_control_init(cc);
369 if (cache_parse_one_directive(s, len, &i, cc))
379void cache_immutable_asset(pc_cache_control *cc, uint32_t max_age)
381 cache_control_init(cc);
382 cc->cc_public =
true;
383 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
384 cc->cc_immutable =
true;
387void cache_revalidatable(pc_cache_control *cc, uint32_t max_age, int32_t stale_while_revalidate)
389 cache_control_init(cc);
390 cc->cc_public =
true;
391 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
392 if (stale_while_revalidate >= 0)
394 cc->stale_while_revalidate = stale_while_revalidate;
398void cache_no_store(pc_cache_control *cc)
400 cache_control_init(cc);
404void cache_shared(pc_cache_control *cc, uint32_t max_age, uint32_t s_maxage)
406 cache_control_init(cc);
407 cc->cc_public =
true;
408 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
409 cc->s_maxage = (int32_t)(s_maxage > 2147483647u ? 2147483647u : s_maxage);
412long cache_freshness_lifetime(
const pc_cache_control *cc,
bool shared,
long expires_minus_date)
414 if (shared && cc->s_maxage >= 0)
418 if (cc->max_age >= 0)
422 if (expires_minus_date >= 0)
424 return expires_minus_date;
HTTP Cache-Control directive builder + parser + freshness helper (RFC 9111), PC_ENABLE_HTTP_CACHE.