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

View File

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