@@ -16,32 +16,47 @@ 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+ // Public without a freshness lifetime opts into the configured fallback.
36+ // No header or a header without storage intent does not.
37+ {name : "public alone" , cacheControl : "public" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
38+ {name : "public with another directive" , cacheControl : "public, must-revalidate" , defaultTTL : defaultTTL , wantTTL : defaultTTL , wantOK : true },
39+ {name : "no cache-control header" , defaultTTL : defaultTTL , wantOK : false },
40+ {name : "unrelated directive without public" , cacheControl : "must-revalidate" , defaultTTL : defaultTTL , wantOK : false },
41+ {name : "extension directive without public" , cacheControl : "cdn-cache-control=60" , defaultTTL : defaultTTL , wantOK : false },
2842
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 },
43+ // Explicit refusals win whether public is present or not.
44+ {name : "no-store" , cacheControl : "max-age=60, no-store" , defaultTTL : defaultTTL , wantOK : false },
45+ {name : "no-cache" , cacheControl : "max-age=60, no-cache" , defaultTTL : defaultTTL , wantOK : false },
46+ {name : "field-specific no-cache" , cacheControl : `max-age=60, no-cache="Set-Cookie"` , defaultTTL : defaultTTL , wantOK : false },
47+ {name : "private" , cacheControl : "max-age=60, private" , defaultTTL : defaultTTL , wantOK : false },
48+ {name : "private wins over public" , cacheControl : "public, max-age=60, private" , defaultTTL : defaultTTL , wantOK : false },
3449
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 },
50+ // A lifetime of zero is a refusal, and malformed lifetimes cannot opt a
51+ // response into caching.
52+ {name : "zero max-age" , cacheControl : "max-age=0" , defaultTTL : defaultTTL , wantOK : false },
53+ {name : "zero s-maxage" , cacheControl : "s-maxage=0" , defaultTTL : defaultTTL , wantOK : false },
54+ {name : "public does not override zero max-age" , cacheControl : "public, max-age=0" , defaultTTL : defaultTTL , wantOK : false },
55+ {name : "invalid max-age" , cacheControl : "max-age=abc" , defaultTTL : defaultTTL , wantOK : false },
3956
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 },
57+ // The fallback must itself be usable.
58+ {name : "public with zero default" , cacheControl : "public" , wantOK : false },
59+ {name : "public with negative default" , cacheControl : "public" , defaultTTL : - time .Second , wantOK : false },
4560 } {
4661 t .Run (tc .name , func (t * testing.T ) {
4762 headers := http.Header {}
0 commit comments