summaryrefslogtreecommitdiff
path: root/django/utils/cache.py
diff options
context:
space:
mode:
authorKevin Christopher Henry <k@severian.com>2016-09-12 23:26:24 -0400
committerTim Graham <timograham@gmail.com>2016-09-16 15:45:53 -0400
commit22e303887b7f807b39239880e33b9018566e0137 (patch)
treecd23bbabeb1af217f9dc9e7d63a940b0de25919c /django/utils/cache.py
parent5a51b449360ed2b84c2a54b90127a5faafa6f8f7 (diff)
Refs #27083 -- Updated conditional header comparison to match RFC 7232.
Diffstat (limited to 'django/utils/cache.py')
-rw-r--r--django/utils/cache.py138
1 files changed, 84 insertions, 54 deletions
diff --git a/django/utils/cache.py b/django/utils/cache.py
index ad732327e8..ac469195da 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -131,72 +131,102 @@ def _not_modified(request, response=None):
def get_conditional_response(request, etag=None, last_modified=None, response=None):
- # Get HTTP request headers
- if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
- if if_modified_since:
- if_modified_since = parse_http_date_safe(if_modified_since)
+ # Only return conditional responses on successful requests.
+ if response and not (200 <= response.status_code < 300):
+ return response
+
+ # Get HTTP request headers.
+ if_match_etags = parse_etags(request.META.get('HTTP_IF_MATCH', ''))
if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
if if_unmodified_since:
if_unmodified_since = parse_http_date_safe(if_unmodified_since)
- if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
- if_match = request.META.get('HTTP_IF_MATCH')
- etags = []
- if if_none_match or if_match:
- # There can be more than one ETag in the request, so we
- # consider the list of values.
- try:
- etags = parse_etags(if_none_match or if_match)
- except ValueError:
- # In case of an invalid ETag, ignore all ETag headers.
- # Apparently Opera sends invalidly quoted headers at times
- # (we should be returning a 400 response, but that's a
- # little extreme) -- this is bug #10681.
- if_none_match = None
- if_match = None
+ if_none_match_etags = parse_etags(request.META.get('HTTP_IF_NONE_MATCH', ''))
+ if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
+ if if_modified_since:
+ if_modified_since = parse_http_date_safe(if_modified_since)
- # If-None-Match must be ignored if original result would be anything
- # other than a 2XX or 304 status. 304 status would result in no change.
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
- if response and not (200 <= response.status_code < 300):
- if_none_match = None
- if_match = None
+ # Step 1 of section 6 of RFC 7232: Test the If-Match precondition.
+ if (if_match_etags and not _if_match_passes(etag, if_match_etags)):
+ return _precondition_failed(request)
- # If-Modified-Since must be ignored if the original result was not a 200.
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
- if response and response.status_code != 200:
- if_modified_since = None
- if_unmodified_since = None
+ # Step 2: Test the If-Unmodified-Since precondition.
+ if (not if_match_etags and if_unmodified_since and
+ not _if_unmodified_since_passes(last_modified, if_unmodified_since)):
+ return _precondition_failed(request)
- if not ((if_match and if_modified_since) or
- (if_none_match and if_unmodified_since) or
- (if_modified_since and if_unmodified_since) or
- (if_match and if_none_match)):
- # We only get here if no undefined combinations of headers are
- # specified.
- if ((if_none_match and (etag in etags or '*' in etags and etag)) and
- (not if_modified_since or
- (last_modified and if_modified_since and last_modified <= if_modified_since))):
- if request.method in ('GET', 'HEAD'):
- return _not_modified(request, response)
- else:
- return _precondition_failed(request)
- elif (if_match and (
- (not etag and '*' in etags) or (etag and etag not in etags) or
- (last_modified and if_unmodified_since and last_modified > if_unmodified_since)
- )):
- return _precondition_failed(request)
- elif (not if_none_match and request.method in ('GET', 'HEAD') and
- last_modified and if_modified_since and
- last_modified <= if_modified_since):
+ # Step 3: Test the If-None-Match precondition.
+ if (if_none_match_etags and not _if_none_match_passes(etag, if_none_match_etags)):
+ if request.method in ('GET', 'HEAD'):
return _not_modified(request, response)
- elif (not if_match and
- last_modified and if_unmodified_since and
- last_modified > if_unmodified_since):
+ else:
return _precondition_failed(request)
+ # Step 4: Test the If-Modified-Since precondition.
+ if (not if_none_match_etags and if_modified_since and
+ not _if_modified_since_passes(last_modified, if_modified_since)):
+ if request.method in ('GET', 'HEAD'):
+ return _not_modified(request, response)
+
+ # Step 5: Test the If-Range precondition (not supported).
+ # Step 6: Return original response since there isn't a conditional response.
return response
+def _if_match_passes(target_etag, etags):
+ """
+ Test the If-Match comparison as defined in section 3.1 of RFC 7232.
+ """
+ if not target_etag:
+ # If there isn't an ETag, then there can't be a match.
+ return False
+ elif etags == ['*']:
+ # The existence of an ETag means that there is "a current
+ # representation for the target resource", even if the ETag is weak,
+ # so there is a match to '*'.
+ return True
+ elif target_etag.startswith('W/'):
+ # A weak ETag can never strongly match another ETag.
+ return False
+ else:
+ # Since the ETag is strong, this will only return True if there's a
+ # strong match.
+ return target_etag in etags
+
+
+def _if_unmodified_since_passes(last_modified, if_unmodified_since):
+ """
+ Test the If-Unmodified-Since comparison as defined in section 3.4 of
+ RFC 7232.
+ """
+ return last_modified and last_modified <= if_unmodified_since
+
+
+def _if_none_match_passes(target_etag, etags):
+ """
+ Test the If-None-Match comparison as defined in section 3.2 of RFC 7232.
+ """
+ if not target_etag:
+ # If there isn't an ETag, then there isn't a match.
+ return True
+ elif etags == ['*']:
+ # The existence of an ETag means that there is "a current
+ # representation for the target resource", so there is a match to '*'.
+ return False
+ else:
+ # The comparison should be weak, so look for a match after stripping
+ # off any weak indicators.
+ target_etag = target_etag.strip('W/')
+ etags = (etag.strip('W/') for etag in etags)
+ return target_etag not in etags
+
+
+def _if_modified_since_passes(last_modified, if_modified_since):
+ """
+ Test the If-Modified-Since comparison as defined in section 3.3 of RFC 7232.
+ """
+ return not last_modified or last_modified > if_modified_since
+
+
def patch_response_headers(response, cache_timeout=None):
"""
Adds some useful headers to the given HttpResponse object: