diff options
| author | Natalia <124304+nessita@users.noreply.github.com> | 2026-06-02 21:38:35 -0300 |
|---|---|---|
| committer | nessita <124304+nessita@users.noreply.github.com> | 2026-06-08 20:06:46 -0300 |
| commit | 526b1b414d8e215bf627b5722df12a09346dbf6b (patch) | |
| tree | bd1bbda69168233244ec57d7a49fa049f5e566e1 /django/utils/cache.py | |
| parent | 7f2afdd0f6a872ccb48d7fbdaacce9b11216af52 (diff) | |
Refs CVE-2026-48587 -- Added helper to properly split header values.
Extracted the repeated `split(",")` + per-token `.strip()` pattern into
a `split_header_value()` generator in django/utils/http.py. The previous
`cc_delim_re` regex only stripped whitespace adjacent to the comma
delimiter, leaving leading or trailing whitespace on the first and last
tokens. Now, `split_header_value()` strips every token fully, matching
RFC 9110's optional-whitespace rules.
Thanks to Shai Berger, Jacob Walls, and Sarah Boyce for reviews.
Diffstat (limited to 'django/utils/cache.py')
| -rw-r--r-- | django/utils/cache.py | 23 |
1 files changed, 13 insertions, 10 deletions
diff --git a/django/utils/cache.py b/django/utils/cache.py index 8fe856ed7e..4bdae65b7b 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -22,14 +22,17 @@ from hashlib import md5 from django.conf import settings from django.core.cache import caches from django.http import HttpResponse, HttpResponseNotModified -from django.utils.http import http_date, parse_etags, parse_http_date_safe, quote_etag +from django.utils.http import ( + http_date, + parse_etags, + parse_http_date_safe, + quote_etag, + split_header_value, +) from django.utils.log import log_response -from django.utils.regex_helper import _lazy_re_compile from django.utils.timezone import get_current_timezone_name from django.utils.translation import get_language -cc_delim_re = _lazy_re_compile(r"\s*,\s*") - def patch_cache_control(response, **kwargs): """ @@ -59,7 +62,7 @@ def patch_cache_control(response, **kwargs): cc = defaultdict(set) if response.get("Cache-Control"): - for field in cc_delim_re.split(response.headers["Cache-Control"]): + for field in split_header_value(response.headers["Cache-Control"]): directive, value = dictitem(field) if directive == "no-cache": # no-cache supports multiple field names. @@ -108,7 +111,7 @@ def get_max_age(response): if not response.has_header("Cache-Control"): return cc = dict( - _to_tuple(el) for el in cc_delim_re.split(response.headers["Cache-Control"]) + _to_tuple(el) for el in split_header_value(response.headers["Cache-Control"]) ) try: return int(cc["max-age"]) @@ -308,11 +311,12 @@ def patch_vary_headers(response, newheaders): # implementations may rely on the order of the Vary contents in, say, # computing an MD5 hash. if response.has_header("Vary"): - vary_headers = cc_delim_re.split(response.headers["Vary"]) + vary_headers = list(split_header_value(response.headers["Vary"])) else: vary_headers = [] # Use .lower() here so we treat headers as case-insensitive. existing_headers = {header.lower() for header in vary_headers} + newheaders = [newheader.strip() for newheader in newheaders] additional_headers = [ newheader for newheader in newheaders @@ -331,8 +335,7 @@ def has_vary_header(response, header_query): """ if not response.has_header("Vary"): return False - vary_headers = cc_delim_re.split(response.headers["Vary"]) - existing_headers = {header.lower().strip() for header in vary_headers} + existing_headers = {h.lower() for h in split_header_value(response.headers["Vary"])} return header_query.lower().strip() in existing_headers @@ -424,7 +427,7 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cach # in that case and would result in storing the same content under # multiple keys in the cache. See #18191 for details. headerlist = [] - for header in cc_delim_re.split(response.headers["Vary"]): + for header in split_header_value(response.headers["Vary"]): header = header.upper().replace("-", "_") if header != "ACCEPT_LANGUAGE" or not is_accept_language_redundant: headerlist.append("HTTP_" + header) |
