diff --git a/django/middleware/common.py b/django/middleware/common.py index c7b1c1c772..c2eb88631c 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -8,6 +8,7 @@ from django.core.mail import mail_managers from django.urls import is_valid_path from django.utils.cache import get_conditional_response, set_response_etag from django.utils.encoding import force_text +from django.utils.http import unquote_etag from django.utils.six.moves.urllib.parse import urlparse logger = logging.getLogger('django.request') @@ -120,9 +121,7 @@ class CommonMiddleware(object): if response.has_header('ETag'): return get_conditional_response( request, - # get_conditional_response() requires an unquoted version - # of the response's ETag. - etag=response['ETag'].strip('"'), + etag=unquote_etag(response['ETag']), response=response, ) diff --git a/django/middleware/http.py b/django/middleware/http.py index 7a6f237c96..7912200634 100644 --- a/django/middleware/http.py +++ b/django/middleware/http.py @@ -1,5 +1,5 @@ from django.utils.cache import get_conditional_response -from django.utils.http import http_date, parse_http_date_safe +from django.utils.http import http_date, parse_http_date_safe, unquote_etag class ConditionalGetMiddleware(object): @@ -23,7 +23,7 @@ class ConditionalGetMiddleware(object): if etag or last_modified: return get_conditional_response( request, - etag=etag, + etag=unquote_etag(etag), last_modified=last_modified, response=response, ) diff --git a/django/utils/http.py b/django/utils/http.py index 3a3f665443..2dce7d3add 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -253,6 +253,13 @@ def quote_etag(etag): return '"%s"' % etag.replace('\\', '\\\\').replace('"', '\\"') +def unquote_etag(etag): + """ + Unquote an ETag string; i.e. revert quote_etag(). + """ + return etag.strip('"').replace('\\"', '"').replace('\\\\', '\\') if etag else etag + + def is_same_domain(host, pattern): """ Return ``True`` if the host is either an exact match or a match diff --git a/docs/releases/1.9.2.txt b/docs/releases/1.9.2.txt index 484fb6e4c2..8e04de6e29 100644 --- a/docs/releases/1.9.2.txt +++ b/docs/releases/1.9.2.txt @@ -9,4 +9,5 @@ Django 1.9.2 fixes several bugs in 1.9.1. Bugfixes ======== -* ... +* Fixed a regression in ``ConditionalGetMiddleware`` causing ``If-None-Match`` checks + to always return HTTP 200 (:ticket:`26024`). diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 2804e743f7..bf96f05048 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -470,6 +470,11 @@ class ConditionalGetMiddlewareTest(SimpleTestCase): self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) self.assertEqual(self.resp.status_code, 304) + def test_if_none_match_and_same_etag_with_quotes(self): + self.req.META['HTTP_IF_NONE_MATCH'] = self.resp['ETag'] = '"spam"' + self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) + self.assertEqual(self.resp.status_code, 304) + def test_if_none_match_and_different_etag(self): self.req.META['HTTP_IF_NONE_MATCH'] = 'spam' self.resp['ETag'] = 'eggs' diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index f90486003a..6051818958 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -158,8 +158,10 @@ class ETagProcessingTests(unittest.TestCase): self.assertEqual(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak']) def test_quoting(self): - quoted_etag = http.quote_etag(r'e\t"ag') + original_etag = r'e\t"ag' + quoted_etag = http.quote_etag(original_etag) self.assertEqual(quoted_etag, r'"e\\t\"ag"') + self.assertEqual(http.unquote_etag(quoted_etag), original_etag) class HttpDateProcessingTests(unittest.TestCase):