diff options
| author | Kevin Christopher Henry <k@severian.com> | 2016-09-12 23:26:24 -0400 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2016-09-16 15:45:53 -0400 |
| commit | 22e303887b7f807b39239880e33b9018566e0137 (patch) | |
| tree | cd23bbabeb1af217f9dc9e7d63a940b0de25919c /django/utils/cache.py | |
| parent | 5a51b449360ed2b84c2a54b90127a5faafa6f8f7 (diff) | |
Refs #27083 -- Updated conditional header comparison to match RFC 7232.
Diffstat (limited to 'django/utils/cache.py')
| -rw-r--r-- | django/utils/cache.py | 138 |
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: |
