@@ -16,32 +16,57 @@ func TestTTL(t *testing.T) {
1616 wantTTL time.Duration
1717 wantOK bool
1818 }{
19- // public is the opt-in, and on its own it takes the configured default.
20- {name : "public alone" , cacheControl : "public" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
19+ // A positive freshness lifetime is sufficient to store a response. Public
20+ // permits storage in cases that would otherwise forbid it; it is not a
21+ // general prerequisite for shared caching.
22+ {name : "max-age without public" , cacheControl : "max-age=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
23+ {name : "s-maxage without public" , cacheControl : "s-maxage=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
2124 {name : "public with max-age" , cacheControl : "public, max-age=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
2225 {name : "public with s-maxage" , cacheControl : "public, s-maxage=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
26+ {name : "max-age does not need a usable default" , cacheControl : "max-age=60" , wantTTL : time .Minute , wantOK : true },
2327
2428 // s-maxage is the directive addressed to shared caches, so it outranks
2529 // max-age here however the two are ordered in the header.
26- {name : "s-maxage outranks max-age" , cacheControl : "public, s-maxage=60, max-age=3600" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
27- {name : "s-maxage outranks max-age reversed" , cacheControl : "public, max-age=3600, s-maxage=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
30+ {name : "s-maxage outranks max-age" , cacheControl : "s-maxage=60, max-age=3600" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
31+ {name : "s-maxage outranks max-age reversed" , cacheControl : "max-age=3600, s-maxage=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
32+ {name : "zero s-maxage overrides positive max-age" , cacheControl : "max-age=60, s-maxage=0" , defaultTTL : defaultTTL , wantOK : false },
33+ {name : "positive s-maxage overrides zero max-age" , cacheControl : "max-age=0, s-maxage=60" , defaultTTL : defaultTTL , wantTTL : time .Minute , wantOK : true },
34+
35+ // A recognized caching directive without a freshness lifetime opts into
36+ // the configured fallback. Public is one such directive, not a requirement.
37+ {name : "public alone" , cacheControl : "public" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
38+ {name : "must-revalidate without public" , cacheControl : "must-revalidate" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
39+ {name : "proxy-revalidate without public" , cacheControl : "proxy-revalidate" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
40+ {name : "bare stale-if-error without public" , cacheControl : "stale-if-error" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
41+ {name : "stale-if-error without public" , cacheControl : "stale-if-error=60" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
42+ {name : "stale-while-revalidate without public" , cacheControl : "stale-while-revalidate=60" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
43+
44+ // No header or a header without recognized caching intent does not use the
45+ // configured fallback.
46+ {name : "no cache-control header" , defaultTTL : defaultTTL , wantOK : false },
47+ {name : "extension directive without public" , cacheControl : "cdn-cache-control=60" , defaultTTL : defaultTTL , wantOK : false },
48+ {name : "immutable without public" , cacheControl : "immutable" , defaultTTL : defaultTTL , wantOK : false },
49+ {name : "no-transform without public" , cacheControl : "no-transform" , defaultTTL : defaultTTL , wantOK : false },
50+ {name : "must-understand without public" , cacheControl : "must-understand" , defaultTTL : defaultTTL , wantOK : false },
2851
29- // Without public the response is not offered to a shared cache, whatever
30- // lifetime it names.
31- {name : "max-age without public" , cacheControl : "max-age=60" , defaultTTL : defaultTTL , wantOK : false },
32- {name : "s-maxage without public" , cacheControl : "s-maxage=60" , defaultTTL : defaultTTL , wantOK : false },
33- {name : "no cache-control header" , cacheControl : "" , defaultTTL : defaultTTL , wantOK : false },
52+ // Explicit refusals win whether public is present or not.
53+ {name : "no-store" , cacheControl : "max-age=60, no-store" , defaultTTL : defaultTTL , wantOK : false },
54+ {name : "no-cache" , cacheControl : "max-age=60, no-cache" , defaultTTL : defaultTTL , wantOK : false },
55+ {name : "field-specific no-cache" , cacheControl : `max-age=60, no-cache="Set-Cookie"` , defaultTTL : defaultTTL , wantOK : false },
56+ {name : "private" , cacheControl : "max-age=60, private" , defaultTTL : defaultTTL , wantOK : false },
57+ {name : "private wins over public" , cacheControl : "public, max-age=60, private" , defaultTTL : defaultTTL , wantOK : false },
3458
35- // Explicit refusals win over public.
36- {name : "no-store" , cacheControl : "public, no-store" , defaultTTL : defaultTTL , wantOK : false },
37- {name : "no-cache" , cacheControl : "public, no-cache" , defaultTTL : defaultTTL , wantOK : false },
38- {name : "private" , cacheControl : "public, private" , defaultTTL : defaultTTL , wantOK : false },
59+ // A lifetime of zero is a refusal, and malformed lifetimes cannot opt a
60+ // response into caching.
61+ {name : "zero max-age" , cacheControl : "max-age=0" , defaultTTL : defaultTTL , wantOK : false },
62+ {name : "zero s-maxage" , cacheControl : "s-maxage=0" , defaultTTL : defaultTTL , wantOK : false },
63+ {name : "public does not override zero max-age" , cacheControl : "public, max-age=0" , defaultTTL : defaultTTL , wantOK : false },
64+ {name : "invalid max-age" , cacheControl : "max-age=abc" , defaultTTL : defaultTTL , wantOK : false },
65+ {name : "invalid stale-while-revalidate" , cacheControl : "stale-while-revalidate=abc" , defaultTTL : defaultTTL , wantOK : false },
3966
40- // A lifetime of zero is a refusal, and so is a default that was never
41- // usable in the first place.
42- {name : "zero max-age" , cacheControl : "public, max-age=0" , defaultTTL : defaultTTL , wantOK : false },
43- {name : "zero s-maxage" , cacheControl : "public, s-maxage=0" , defaultTTL : defaultTTL , wantOK : false },
44- {name : "public with non-positive default" , cacheControl : "public" , defaultTTL : 0 , wantOK : false },
67+ // The fallback must itself be usable.
68+ {name : "caching intent with zero default" , cacheControl : "must-revalidate" , wantOK : false },
69+ {name : "caching intent with negative default" , cacheControl : "must-revalidate" , defaultTTL : - time .Second , wantOK : false },
4570 } {
4671 t .Run (tc .name , func (t * testing.T ) {
4772 headers := http.Header {}
0 commit comments