Skip to content

Commit b3deb77

Browse files
authored
Strip 'Cookie' from Vary header if the Vary header contains other entries (#48)
The feature to strip cookies will not be triggered if the Vary header contains something other than "Cookie", e.g. Vary: Language, Cookie. This change properly inspects and rebuilds the Vary header in such cases.
1 parent 20e9ad3 commit b3deb77

4 files changed

Lines changed: 48 additions & 4 deletions

File tree

testproject/home/tests.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,14 @@ def test_client_tracking_cookies_ignore(self):
342342
self.client.cookies["_dataminer"] = "precious data"
343343
self.get_hit(self.page_cachedpage.get_url())
344344

345+
@override_settings(WAGTAIL_CACHE_IGNORE_COOKIES=True)
346+
def test_vary_header_parse(self):
347+
self.get_miss(reverse("vary_view"))
348+
r = self.get_hit(reverse("vary_view"))
349+
# Cookie should have been stripped from the Vary header while preserving
350+
# case and order of the other items.
351+
self.assertEqual(r["Vary"], "A, B, C")
352+
345353
def test_page_restricted(self):
346354
auth_url = "/_util/authenticate_with_password/%d/%d/" % (
347355
self.view_restriction.id,

testproject/home/views.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ def cached_view(request):
1010
@nocache_page
1111
def nocached_view(request):
1212
return HttpResponse("Hello, World!")
13+
14+
15+
def vary_view(request):
16+
r = HttpResponse("Variety is the spice of life.")
17+
r.headers["Vary"] = "A, B, Cookie, C"
18+
return r

testproject/testproject/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
path("django-admin/", admin.site.urls),
1313
path("admin/", include(wagtailadmin_urls)),
1414
path("documents/", include(wagtaildocs_urls)),
15-
path("views/cached-view/", views.cached_view, name="cached_view"),
16-
path("views/nocache-view/", views.nocached_view, name="nocached_view"),
15+
path("views/cached/", views.cached_view, name="cached_view"),
16+
path("views/nocache/", views.nocached_view, name="nocached_view"),
17+
path("views/vary/", views.vary_view, name="vary_view"),
1718
# For anything not caught by a more specific rule above, hand over to
1819
# Wagtail's page serving mechanism. This should be the last pattern in
1920
# the list:

wagtailcache/cache.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.http.response import HttpResponse
1414
from django.template.response import SimpleTemplateResponse
1515
from django.utils.cache import (
16+
cc_delim_re,
1617
get_cache_key,
1718
get_max_age,
1819
has_vary_header,
@@ -45,6 +46,9 @@ class Status(Enum):
4546

4647

4748
def _patch_header(response: HttpResponse, status: Status) -> None:
49+
"""
50+
Adds our Cache Control status to the response headers.
51+
"""
4852
# Patch cache-control with no-cache if it is not already set.
4953
if status == Status.SKIP and not response.get("Cache-Control", None):
5054
response["Cache-Control"] = CacheControl.NOCACHE.value
@@ -53,6 +57,31 @@ def _patch_header(response: HttpResponse, status: Status) -> None:
5357
response[wagtailcache_settings.WAGTAIL_CACHE_HEADER] = status.value
5458

5559

60+
def _delete_vary_cookie(response: HttpResponse) -> None:
61+
"""
62+
Deletes the ``Vary: Cookie`` header while keeping other items of the
63+
Vary header in tact. Inspired by ``django.utils.cache.patch_vary_headers``.
64+
"""
65+
if not response.has_header("Vary"):
66+
return
67+
# Parse the value of Vary header.
68+
vary_headers = cc_delim_re.split(response["Vary"])
69+
# Build a lowercase-keyed dict to preserve the original case.
70+
vhdict = {}
71+
for item in vary_headers:
72+
vhdict.update({item.lower(): item})
73+
# Delete "Cookie".
74+
if "cookie" in vhdict:
75+
del vhdict["cookie"]
76+
# Delete the header if it's now empty.
77+
if not vhdict:
78+
del response["Vary"]
79+
return
80+
# Else patch the header.
81+
vary_headers = [vhdict[k] for k in vhdict]
82+
response["Vary"] = ", ".join(vary_headers)
83+
84+
5685
def _chop_querystring(r: WSGIRequest) -> WSGIRequest:
5786
"""
5887
Given a request object, remove any of our ignored querystrings from it.
@@ -103,15 +132,15 @@ def _chop_response_vary(r: WSGIRequest, s: HttpResponse) -> HttpResponse:
103132
if (
104133
not s.has_header("Set-Cookie")
105134
and s.has_header("Vary")
106-
and s["Vary"].lower() == "cookie"
135+
and has_vary_header(s, "Cookie")
107136
and not (
108137
settings.CSRF_COOKIE_NAME in s.cookies
109138
or settings.CSRF_COOKIE_NAME in r.COOKIES
110139
or settings.SESSION_COOKIE_NAME in s.cookies
111140
or settings.SESSION_COOKIE_NAME in r.COOKIES
112141
)
113142
):
114-
del s["Vary"]
143+
_delete_vary_cookie(s)
115144
return s
116145

117146

0 commit comments

Comments
 (0)