11#if DETWS_ENABLE_HTTP_CACHE
15static const size_t CC_SENT = (size_t)-1;
17void cache_control_init(DetwsCacheControl *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)
48 rev[ri++] = (char)(
'0' + (v % 10));
51 if (n + (
size_t)ri > cap)
53 for (
int k = 0; k < ri; k++)
54 buf[n + k] = rev[ri - 1 - k];
55 return n + (size_t)ri;
59static size_t cc_tok(
char *buf,
size_t cap,
size_t n,
bool *first,
const char *tok)
63 size_t tlen = strnlen(tok, cap);
64 size_t need = (*first ? 0 : 2) + tlen;
72 memcpy(buf + n, tok, tlen);
78static size_t cc_kv(
char *buf,
size_t cap,
size_t n,
bool *first,
const char *key,
long v)
80 n = cc_tok(buf, cap, n, first, key);
81 if (n == CC_SENT || n >= cap)
84 return cc_emit_uint(buf, cap, n, (
unsigned)v);
87size_t cache_control_build(
char *buf,
size_t cap,
const DetwsCacheControl *cc)
89 if (!buf || !cc || cap == 0)
95 n = cc_tok(buf, cap, n, &first,
"public");
97 n = cc_tok(buf, cap, n, &first,
"private");
99 n = cc_tok(buf, cap, n, &first,
"no-store");
101 n = cc_tok(buf, cap, n, &first,
"no-cache");
102 if (cc->max_age >= 0)
103 n = cc_kv(buf, cap, n, &first,
"max-age", cc->max_age);
104 if (cc->s_maxage >= 0)
105 n = cc_kv(buf, cap, n, &first,
"s-maxage", cc->s_maxage);
106 if (cc->must_revalidate)
107 n = cc_tok(buf, cap, n, &first,
"must-revalidate");
108 if (cc->proxy_revalidate)
109 n = cc_tok(buf, cap, n, &first,
"proxy-revalidate");
110 if (cc->no_transform)
111 n = cc_tok(buf, cap, n, &first,
"no-transform");
112 if (cc->must_understand)
113 n = cc_tok(buf, cap, n, &first,
"must-understand");
114 if (cc->cc_immutable)
115 n = cc_tok(buf, cap, n, &first,
"immutable");
116 if (cc->stale_while_revalidate >= 0)
117 n = cc_kv(buf, cap, n, &first,
"stale-while-revalidate", cc->stale_while_revalidate);
118 if (cc->stale_if_error >= 0)
119 n = cc_kv(buf, cap, n, &first,
"stale-if-error", cc->stale_if_error);
120 if (cc->only_if_cached)
121 n = cc_tok(buf, cap, n, &first,
"only-if-cached");
122 if (cc->max_stale == -2)
123 n = cc_tok(buf, cap, n, &first,
"max-stale");
124 else if (cc->max_stale >= 0)
125 n = cc_kv(buf, cap, n, &first,
"max-stale", cc->max_stale);
126 if (cc->min_fresh >= 0)
127 n = cc_kv(buf, cap, n, &first,
"min-fresh", cc->min_fresh);
129 if (n == CC_SENT || first || n + 1 > cap)
138static bool cc_ci_eq(
const char *s,
size_t len,
const char *target)
141 for (; i < len && target[i]; i++)
144 if (c >=
'A' && c <=
'Z')
149 return i == len && target[i] == 0;
154static int32_t cc_parse_delta(
const char *v,
size_t vlen)
159 while (i < vlen && (v[i] ==
' ' || v[i] ==
'\t' || v[i] ==
'"'))
163 while (i < vlen && v[i] >=
'0' && v[i] <=
'9')
170 val = val * 10 + (v[i] -
'0');
171 if (val > 2147483647L)
175 return any ? (int32_t)val : -1;
178static bool cc_match(DetwsCacheControl *cc,
const char *name,
size_t nlen,
const char *val,
size_t vlen)
180 if (cc_ci_eq(name, nlen,
"public"))
181 cc->cc_public =
true;
182 else if (cc_ci_eq(name, nlen,
"private"))
183 cc->cc_private =
true;
184 else if (cc_ci_eq(name, nlen,
"no-store"))
186 else if (cc_ci_eq(name, nlen,
"no-cache"))
188 else if (cc_ci_eq(name, nlen,
"no-transform"))
189 cc->no_transform =
true;
190 else if (cc_ci_eq(name, nlen,
"must-revalidate"))
191 cc->must_revalidate =
true;
192 else if (cc_ci_eq(name, nlen,
"proxy-revalidate"))
193 cc->proxy_revalidate =
true;
194 else if (cc_ci_eq(name, nlen,
"must-understand"))
195 cc->must_understand =
true;
196 else if (cc_ci_eq(name, nlen,
"immutable"))
197 cc->cc_immutable =
true;
198 else if (cc_ci_eq(name, nlen,
"only-if-cached"))
199 cc->only_if_cached =
true;
200 else if (cc_ci_eq(name, nlen,
"max-age"))
201 cc->max_age = cc_parse_delta(val, vlen);
202 else if (cc_ci_eq(name, nlen,
"s-maxage"))
203 cc->s_maxage = cc_parse_delta(val, vlen);
204 else if (cc_ci_eq(name, nlen,
"stale-while-revalidate"))
205 cc->stale_while_revalidate = cc_parse_delta(val, vlen);
206 else if (cc_ci_eq(name, nlen,
"stale-if-error"))
207 cc->stale_if_error = cc_parse_delta(val, vlen);
208 else if (cc_ci_eq(name, nlen,
"max-stale"))
209 cc->max_stale = val ? cc_parse_delta(val, vlen) : -2;
210 else if (cc_ci_eq(name, nlen,
"min-fresh"))
211 cc->min_fresh = cc_parse_delta(val, vlen);
219static bool cache_parse_one_directive(
const char *s,
size_t len,
size_t *i, DetwsCacheControl *cc)
221 while (*i < len && (s[*i] ==
',' || s[*i] ==
' ' || s[*i] ==
'\t'))
226 while (*i < len && s[*i] !=
',')
229 while (end > start && (s[end - 1] ==
' ' || s[end - 1] ==
'\t'))
232 while (eq < end && s[eq] !=
'=')
234 size_t nlen = (eq < end ? eq : end) - start;
235 while (nlen > 0 && (s[start + nlen - 1] ==
' ' || s[start + nlen - 1] ==
'\t'))
237 const char *val = (eq < end) ? s + eq + 1 : nullptr;
238 size_t vlen = (eq < end) ? end - (eq + 1) : 0;
239 return nlen && cc_match(cc, s + start, nlen, val, vlen);
242bool cache_control_parse(
const char *s,
size_t len, DetwsCacheControl *cc)
244 cache_control_init(cc);
251 if (cache_parse_one_directive(s, len, &i, cc))
259void cache_immutable_asset(DetwsCacheControl *cc, uint32_t max_age)
261 cache_control_init(cc);
262 cc->cc_public =
true;
263 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
264 cc->cc_immutable =
true;
267void cache_revalidatable(DetwsCacheControl *cc, uint32_t max_age, int32_t stale_while_revalidate)
269 cache_control_init(cc);
270 cc->cc_public =
true;
271 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
272 if (stale_while_revalidate >= 0)
273 cc->stale_while_revalidate = stale_while_revalidate;
276void cache_no_store(DetwsCacheControl *cc)
278 cache_control_init(cc);
282void cache_shared(DetwsCacheControl *cc, uint32_t max_age, uint32_t s_maxage)
284 cache_control_init(cc);
285 cc->cc_public =
true;
286 cc->max_age = (int32_t)(max_age > 2147483647u ? 2147483647u : max_age);
287 cc->s_maxage = (int32_t)(s_maxage > 2147483647u ? 2147483647u : s_maxage);
290long cache_freshness_lifetime(
const DetwsCacheControl *cc,
bool shared,
long expires_minus_date)
292 if (shared && cc->s_maxage >= 0)
294 if (cc->max_age >= 0)
296 if (expires_minus_date >= 0)
297 return expires_minus_date;
HTTP Cache-Control directive builder + parser + freshness helper (RFC 9111), DETWS_ENABLE_HTTP_CACHE.