Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ stages:
py3.13_wag7:
PYTHON_VERSION: "3.13"
WAGTAIL: '7.*'
py3.13_wag8:
PYTHON_VERSION: "3.13"
WAGTAIL: '8.*'

steps:
- task: UsePythonVersion@0
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ classifiers = [
"Framework :: Wagtail :: 5",
"Framework :: Wagtail :: 6",
"Framework :: Wagtail :: 7",
"Framework :: Wagtail :: 8",
]
dependencies = [
"wagtail>=3.0,<8.0",
"wagtail>=3.0,<9.0",
]
description = "A simple page cache for Wagtail based on the Django cache middleware."
dynamic = ["version"]
Expand Down
8 changes: 8 additions & 0 deletions testproject/home/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,14 @@ def test_vary_header_parse(self):
# case and order of the other items.
self.assertEqual(r["Vary"], "A, B, C")

@override_settings(WAGTAIL_CACHE_IGNORE_COOKIES=True)
def test_vary_header_parse_whitespace(self):
self.get_miss(reverse("vary_whitespace_view"))
r = self.get_hit(reverse("vary_whitespace_view"))
# Whitespace around every token is stripped, including the first and
# last, so a padded ``Cookie`` is still recognized and removed.
self.assertEqual(r["Vary"], "A, B, C")

def test_page_restricted(self):
auth_url = "/_util/authenticate_with_password/%d/%d/" % (
self.view_restriction.id,
Expand Down
6 changes: 6 additions & 0 deletions testproject/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def vary_view(request):
return r


def vary_whitespace_view(request):
r = HttpResponse("Variety is the spice of life.")
r.headers["Vary"] = " Cookie ,A, B, C "
return r


@cache_page
def template_response_view(request):
response = TemplateResponse(request, "home/page.html", {})
Expand Down
5 changes: 5 additions & 0 deletions testproject/testproject/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
path("views/cached/", views.cached_view, name="cached_view"),
path("views/nocache/", views.nocached_view, name="nocached_view"),
path("views/vary/", views.vary_view, name="vary_view"),
path(
"views/vary-whitespace/",
views.vary_whitespace_view,
name="vary_whitespace_view",
),
path(
"views/template-response-view/",
views.template_response_view,
Expand Down
15 changes: 13 additions & 2 deletions wagtailcache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from enum import Enum
from functools import wraps
from typing import Callable
from typing import Iterator
from typing import List
from typing import Optional
from urllib.parse import unquote
Expand All @@ -17,7 +18,6 @@
from django.core.handlers.wsgi import WSGIRequest
from django.http.response import HttpResponse
from django.template.response import SimpleTemplateResponse
from django.utils.cache import cc_delim_re
from django.utils.cache import get_cache_key
from django.utils.cache import get_max_age
from django.utils.cache import has_vary_header
Expand All @@ -29,6 +29,17 @@
from wagtailcache.settings import wagtailcache_settings


try:
# Django 6.1 replaced ``cc_delim_re`` with this helper (CVE-2026-48587).
from django.utils.http import split_header_value
except ImportError: # Django < 6.1

def split_header_value(value: str, sep: str = ",") -> Iterator[str]:
for part in value.split(sep):
if stripped := part.strip():
yield stripped


logger = logging.getLogger("wagtail-cache")


Expand Down Expand Up @@ -72,7 +83,7 @@ def _delete_vary_cookie(response: HttpResponse) -> None:
if not response.has_header("Vary"):
return
# Parse the value of Vary header.
vary_headers = cc_delim_re.split(response["Vary"])
vary_headers = split_header_value(response["Vary"])
# Build a lowercase-keyed dict to preserve the original case.
vhdict = {}
for item in vary_headers:
Expand Down
Loading