summaryrefslogtreecommitdiff
path: root/django/utils
diff options
context:
space:
mode:
authorDenis Cornehl <syphar@fastmail.fm>2015-06-05 14:26:48 +0100
committerTim Graham <timograham@gmail.com>2015-08-15 09:08:45 -0400
commit7a40fef17ab7918cbb1ddc3ba080f42b420f7a48 (patch)
treed5514a1ea0e059da9a9338beb0c1236a05a6b5bd /django/utils
parent1f7b25c1a7a85426675a04380f37b180edc08bbc (diff)
Fixed #24935 -- Refactored common conditional GET handling.
Diffstat (limited to 'django/utils')
-rw-r--r--django/utils/cache.py106
1 files changed, 101 insertions, 5 deletions
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 73d9c3dca5..c8ab96fe3d 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -19,18 +19,24 @@ An example: i18n middleware would need to distinguish caches by the
from __future__ import unicode_literals
import hashlib
+import logging
import re
import time
from django.conf import settings
from django.core.cache import caches
+from django.http import HttpResponse, HttpResponseNotModified
from django.utils.encoding import force_bytes, force_text, iri_to_uri
-from django.utils.http import http_date
+from django.utils.http import (
+ http_date, parse_etags, parse_http_date_safe, quote_etag,
+)
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language
cc_delim_re = re.compile(r'\s*,\s*')
+logger = logging.getLogger('django.request')
+
def patch_cache_control(response, **kwargs):
"""
@@ -97,9 +103,99 @@ def get_max_age(response):
pass
-def _set_response_etag(response):
+def set_response_etag(response):
if not response.streaming:
- response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
+ response['ETag'] = quote_etag(hashlib.md5(response.content).hexdigest())
+ return response
+
+
+def _precondition_failed(request):
+ logger.warning('Precondition Failed: %s', request.path,
+ extra={
+ 'status_code': 412,
+ 'request': request,
+ },
+ )
+ return HttpResponse(status=412)
+
+
+def _not_modified(request, response=None):
+ if response:
+ # We need to keep the cookies, see ticket #4994.
+ cookies = response.cookies
+ response = HttpResponseNotModified()
+ response.cookies = cookies
+ return response
+ else:
+ return HttpResponseNotModified()
+
+
+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)
+ 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 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
+
+ # 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
+
+ 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):
+ return _not_modified(request, response)
+ elif (not if_match and
+ last_modified and if_unmodified_since and
+ last_modified > if_unmodified_since):
+ return _precondition_failed(request)
+
return response
@@ -119,9 +215,9 @@ def patch_response_headers(response, cache_timeout=None):
cache_timeout = 0 # Can't have max-age negative
if settings.USE_ETAGS and not response.has_header('ETag'):
if hasattr(response, 'render') and callable(response.render):
- response.add_post_render_callback(_set_response_etag)
+ response.add_post_render_callback(set_response_etag)
else:
- response = _set_response_etag(response)
+ response = set_response_etag(response)
if not response.has_header('Last-Modified'):
response['Last-Modified'] = http_date()
if not response.has_header('Expires'):