Fixed #28104 -- Prevented condition decorator from setting ETag/Last-Modified headers for non-safe requests.

This commit is contained in:
Josh Schneier 2017-06-06 15:37:14 -04:00 committed by Tim Graham
parent 268a646353
commit 37c9b81ebc
3 changed files with 30 additions and 12 deletions

View File

@ -70,9 +70,9 @@ def condition(etag_func=None, last_modified_func=None):
This decorator will either pass control to the wrapped view function or This decorator will either pass control to the wrapped view function or
return an HTTP 304 response (unmodified) or 412 response (precondition return an HTTP 304 response (unmodified) or 412 response (precondition
failed), depending upon the request method. In either case, it will add the failed), depending upon the request method. In either case, the decorator
generated ETag and Last-Modified headers to the response if it doesn't will add the generated ETag and Last-Modified headers to the response if
already have them. the headers aren't already set and if the request's method is safe.
""" """
def decorator(func): def decorator(func):
@wraps(func) @wraps(func)
@ -98,11 +98,13 @@ def condition(etag_func=None, last_modified_func=None):
if response is None: if response is None:
response = func(request, *args, **kwargs) response = func(request, *args, **kwargs)
# Set relevant headers on the response if they don't already exist. # Set relevant headers on the response if they don't already exist
if res_last_modified and not response.has_header('Last-Modified'): # and if the request method is safe.
response['Last-Modified'] = http_date(res_last_modified) if request.method in ('GET', 'HEAD'):
if res_etag and not response.has_header('ETag'): if res_last_modified and not response.has_header('Last-Modified'):
response['ETag'] = res_etag response['Last-Modified'] = http_date(res_last_modified)
if res_etag and not response.has_header('ETag'):
response['ETag'] = res_etag
return response return response

View File

@ -66,6 +66,10 @@ last time the resource was modified, or ``None`` if the resource doesn't
exist. The function passed to the ``etag`` decorator should return a string exist. The function passed to the ``etag`` decorator should return a string
representing the `ETag`_ for the resource, or ``None`` if it doesn't exist. representing the `ETag`_ for the resource, or ``None`` if it doesn't exist.
The decorator sets the ``ETag`` and ``Last-Modified`` headers on the response
if they are not already set by the view and if the request's method is safe
(``GET`` or ``HEAD``).
.. versionchanged:: 1.11 .. versionchanged:: 1.11
In older versions, the return value from ``etag_func()`` was interpreted as In older versions, the return value from ``etag_func()`` was interpreted as
@ -198,6 +202,14 @@ to compute the ETag and last modification values in all situations. In fact,
you **should** use the same functions, so that the same values are returned you **should** use the same functions, so that the same values are returned
every time. every time.
.. admonition:: Validator headers with non-safe request methods
The ``condition`` decorator only sets validator headers (``ETag`` and
``Last-Modified``) for safe HTTP methods, i.e. ``GET`` and ``HEAD``. If you
wish to return them in other cases, set them in your view. See
:rfc:`7231#section-4.3.4` to learn about the distinction between setting a
validator header in response to requests made with ``PUT`` versus ``POST``.
Comparison with middleware conditional processing Comparison with middleware conditional processing
================================================= =================================================

View File

@ -19,10 +19,14 @@ class ConditionalGet(SimpleTestCase):
def assertFullResponse(self, response, check_last_modified=True, check_etag=True): def assertFullResponse(self, response, check_last_modified=True, check_etag=True):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, FULL_RESPONSE.encode()) self.assertEqual(response.content, FULL_RESPONSE.encode())
if check_last_modified: if response.request['REQUEST_METHOD'] in ('GET', 'HEAD'):
self.assertEqual(response['Last-Modified'], LAST_MODIFIED_STR) if check_last_modified:
if check_etag: self.assertEqual(response['Last-Modified'], LAST_MODIFIED_STR)
self.assertEqual(response['ETag'], ETAG) if check_etag:
self.assertEqual(response['ETag'], ETAG)
else:
self.assertNotIn('Last-Modified', response)
self.assertNotIn('ETag', response)
def assertNotModified(self, response): def assertNotModified(self, response):
self.assertEqual(response.status_code, 304) self.assertEqual(response.status_code, 304)