Ticket #717: 717.2.diff
File 717.2.diff, 17.8 KB (added by , 14 years ago) |
---|
-
django/views/decorators/http.py
9 9 10 10 from calendar import timegm 11 11 from datetime import timedelta 12 from email.Utils import formatdate13 12 14 13 from django.utils.decorators import decorator_from_middleware, available_attrs 15 from django.utils.http import parse_etags, quote_etag14 from django.utils.http import http_date, parse_http_date_safe, parse_etags, quote_etag 16 15 from django.utils.log import getLogger 17 16 from django.middleware.http import ConditionalGetMiddleware 18 17 from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse … … 79 78 def inner(request, *args, **kwargs): 80 79 # Get HTTP request headers 81 80 if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE") 81 if if_modified_since: 82 if_modified_since = parse_http_date_safe(if_modified_since) 82 83 if_none_match = request.META.get("HTTP_IF_NONE_MATCH") 83 84 if_match = request.META.get("HTTP_IF_MATCH") 84 85 if if_none_match or if_match: … … 102 103 if last_modified_func: 103 104 dt = last_modified_func(request, *args, **kwargs) 104 105 if dt: 105 res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT'106 res_last_modified = timegm(dt.utctimetuple()) 106 107 else: 107 108 res_last_modified = None 108 109 else: … … 116 117 if ((if_none_match and (res_etag in etags or 117 118 "*" in etags and res_etag)) and 118 119 (not if_modified_since or 119 res_last_modified == if_modified_since)): 120 (res_last_modified and if_modified_since and 121 res_last_modified <= if_modified_since))): 120 122 if request.method in ("GET", "HEAD"): 121 123 response = HttpResponseNotModified() 122 124 else: … … 136 138 } 137 139 ) 138 140 response = HttpResponse(status=412) 139 elif (not if_none_match and if_modified_sinceand140 re quest.method == "GET"and141 res_last_modified == if_modified_since):141 elif (not if_none_match and request.method == "GET" and 142 res_last_modified and if_modified_since and 143 res_last_modified <= if_modified_since): 142 144 response = HttpResponseNotModified() 143 145 144 146 if response is None: … … 146 148 147 149 # Set relevant headers on the response if they don't already exist. 148 150 if res_last_modified and not response.has_header('Last-Modified'): 149 response['Last-Modified'] = res_last_modified151 response['Last-Modified'] = http_date(res_last_modified) 150 152 if res_etag and not response.has_header('ETag'): 151 153 response['ETag'] = quote_etag(res_etag) 152 154 -
django/views/static.py
10 10 import stat 11 11 import urllib 12 12 import warnings 13 from email.Utils import parsedate_tz, mktime_tz14 13 15 14 from django.template import loader 16 15 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified -
django/utils/http.py
1 import calendar 2 import datetime 1 3 import re 2 4 import sys 3 5 import urllib … … 8 10 9 11 ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"') 10 12 13 MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split() 14 __D = r'(?P<day>\d{2})' 15 __D2 = r'(?P<day>[ \d]\d)' 16 __M = r'(?P<mon>\w{3})' 17 __Y = r'(?P<year>\d{4})' 18 __Y2 = r'(?P<year>\d{2})' 19 __T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' 20 RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T)) 21 RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T)) 22 ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y)) 23 11 24 def urlquote(url, safe='/'): 12 25 """ 13 26 A version of Python's urllib.quote() function that can operate on unicode … … 70 83 rfcdate = formatdate(epoch_seconds) 71 84 return '%s GMT' % rfcdate[:25] 72 85 86 def parse_http_date(date): 87 """ 88 Parses a date format as specified by HTTP RFC2616 section 3.3.1. 89 90 The three formats allowed by the RFC are accepted, even if only the first 91 one is still in widespread use. 92 93 Returns an floating point number expressed in seconds since the epoch, in 94 UTC. 95 """ 96 # emails.Util.parsedate does the job for RFC1123 dates; unfortunately 97 # RFC2616 makes it mandatory to support RFC850 dates too. So we roll 98 # our own RFC-compliant parsing. 99 for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE: 100 m = regex.match(date) 101 if m is not None: 102 break 103 else: 104 raise ValueError("%r is not in a valid HTTP date format" % date) 105 try: 106 year = int(m.group('year')) 107 if year < 100: 108 year += 2000 if year < 70 else 1900 109 month = MONTHS.index(m.group('mon').lower()) + 1 110 day = int(m.group('day')) 111 hour = int(m.group('hour')) 112 min = int(m.group('min')) 113 sec = int(m.group('sec')) 114 result = datetime.datetime(year, month, day, hour, min, sec) 115 return calendar.timegm(result.utctimetuple()) 116 except Exception: 117 raise ValueError("%r is not a valid date" % date) 118 119 def parse_http_date_safe(date): 120 """ 121 Same as parse_http_date, but returns None if the input is invalid. 122 """ 123 try: 124 return parse_http_date(date) 125 except Exception: 126 pass 127 73 128 # Base 36 functions: useful for generating compact URLs 74 129 75 130 def base36_to_int(s): -
django/contrib/staticfiles/views.py
9 9 import re 10 10 import stat 11 11 import urllib 12 from email.Utils import parsedate_tz, mktime_tz13 12 14 13 from django.conf import settings 15 14 from django.core.exceptions import ImproperlyConfigured 16 15 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified 17 16 from django.template import loader, Template, Context, TemplateDoesNotExist 18 from django.utils.http import http_date 17 from django.utils.http import http_date, parse_http_date 19 18 20 19 from django.contrib.staticfiles import finders, utils 21 20 … … 151 150 raise ValueError 152 151 matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, 153 152 re.IGNORECASE) 154 header_date = parsedate_tz(matches.group(1)) 155 if header_date is None: 156 raise ValueError 157 header_mtime = mktime_tz(header_date) 153 header_mtime = parse_http_date(matches.group(1)) 158 154 header_len = matches.group(3) 159 155 if header_len and int(header_len) != size: 160 156 raise ValueError -
django/middleware/http.py
1 1 from django.core.exceptions import MiddlewareNotUsed 2 from django.utils.http import http_date 2 from django.utils.http import http_date, parse_http_date_safe 3 3 4 4 class ConditionalGetMiddleware(object): 5 5 """ … … 15 15 response['Content-Length'] = str(len(response.content)) 16 16 17 17 if response.has_header('ETag'): 18 if_none_match = request.META.get('HTTP_IF_NONE_MATCH' , None)18 if_none_match = request.META.get('HTTP_IF_NONE_MATCH') 19 19 if if_none_match == response['ETag']: 20 20 # Setting the status is enough here. The response handling path 21 21 # automatically removes content for this status code (in … … 23 23 response.status_code = 304 24 24 25 25 if response.has_header('Last-Modified'): 26 if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) 27 if if_modified_since == response['Last-Modified']: 28 # Setting the status code is enough here (same reasons as 29 # above). 30 response.status_code = 304 26 if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE') 27 if if_modified_since is not None: 28 if_modified_since = parse_http_date_safe(if_modified_since) 29 if if_modified_since is not None: 30 last_modified = parse_http_date_safe(response['Last-Modified']) 31 if last_modified is not None and last_modified <= if_modified_since: 32 # Setting the status code is enough here (same reasons as 33 # above). 34 response.status_code = 304 31 35 32 36 return response -
tests/regressiontests/views/tests/static.py
48 48 file_name = 'file.txt' 49 49 response = self.client.get( 50 50 '/views/site_media/%s' % file_name, 51 HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 UTC'51 HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 GMT' 52 52 # This is 24h before max Unix time. Remember to fix Django and 53 53 # update this test well before 2038 :) 54 54 ) -
tests/regressiontests/conditional_processing/models.py
1 1 # -*- coding:utf-8 -*- 2 from datetime import datetime, timedelta 3 from calendar import timegm 2 from datetime import datetime 4 3 5 4 from django.test import TestCase 6 from django.utils.http import parse_etags, quote_etag 5 from django.utils.http import parse_etags, quote_etag, parse_http_date 7 6 8 7 FULL_RESPONSE = 'Test conditional get response' 9 8 LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47) 10 9 LAST_MODIFIED_STR = 'Sun, 21 Oct 2007 23:21:47 GMT' 10 LAST_MODIFIED_NEWER_STR = 'Mon, 18 Oct 2010 16:56:23 GMT' 11 LAST_MODIFIED_INVALID_STR = 'Mon, 32 Oct 2010 16:56:23 GMT' 11 12 EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT' 12 13 ETAG = 'b4246ffc4f62314ca13147c9d4f76974' 13 14 EXPIRED_ETAG = '7fae4cd4b0f81e7d2914700043aa8ed6' … … 33 34 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR 34 35 response = self.client.get('/condition/') 35 36 self.assertNotModified(response) 37 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_NEWER_STR 38 response = self.client.get('/condition/') 39 self.assertNotModified(response) 40 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_INVALID_STR 41 response = self.client.get('/condition/') 42 self.assertFullResponse(response) 36 43 self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR 37 44 response = self.client.get('/condition/') 38 45 self.assertFullResponse(response) … … 118 125 self.assertFullResponse(response, check_last_modified=False) 119 126 120 127 121 class ETagProces ing(TestCase):128 class ETagProcessing(TestCase): 122 129 def testParsing(self): 123 130 etags = parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') 124 131 self.assertEquals(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak']) … … 126 133 def testQuoting(self): 127 134 quoted_etag = quote_etag(r'e\t"ag') 128 135 self.assertEquals(quoted_etag, r'"e\\t\"ag"') 136 137 138 class HttpDateProcessing(TestCase): 139 def testParsingRfc1123(self): 140 parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') 141 self.assertEqual(datetime.utcfromtimestamp(parsed), 142 datetime(1994, 11, 06, 8, 49, 37)) 143 144 def testParsingRfc850(self): 145 parsed = parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') 146 self.assertEqual(datetime.utcfromtimestamp(parsed), 147 datetime(1994, 11, 06, 8, 49, 37)) 148 149 def testParsingAsctime(self): 150 parsed = parse_http_date('Sun Nov 6 08:49:37 1994') 151 self.assertEqual(datetime.utcfromtimestamp(parsed), 152 datetime(1994, 11, 06, 8, 49, 37)) -
tests/regressiontests/middleware/tests.py
3 3 from django.conf import settings 4 4 from django.http import HttpRequest 5 5 from django.middleware.common import CommonMiddleware 6 from django.middleware.http import ConditionalGetMiddleware 6 7 from django.test import TestCase 7 8 8 9 … … 247 248 self.assertEquals(r.status_code, 301) 248 249 self.assertEquals(r['Location'], 249 250 'http://www.testserver/middleware/customurlconf/slash/') 251 252 253 class ConditionalGetMiddlewareTest(TestCase): 254 urls = 'regressiontests.middleware.cond_get_urls' 255 def setUp(self): 256 self.req = HttpRequest() 257 self.req.META = { 258 'SERVER_NAME': 'testserver', 259 'SERVER_PORT': 80, 260 } 261 self.req.path = self.req.path_info = "/" 262 self.resp = self.client.get(self.req.path) 263 264 # Tests for the Date header 265 266 def test_date_header_added(self): 267 self.assertFalse('Date' in self.resp) 268 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 269 self.assertTrue('Date' in self.resp) 270 271 # Tests for the Content-Length header 272 273 def test_content_length_header_added(self): 274 content_length = len(self.resp.content) 275 self.assertFalse('Content-Length' in self.resp) 276 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 277 self.assertTrue('Content-Length' in self.resp) 278 self.assertEqual(int(self.resp['Content-Length']), content_length) 279 280 def test_content_length_header_not_changed(self): 281 bad_content_length = len(self.resp.content) + 10 282 self.resp['Content-Length'] = bad_content_length 283 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 284 self.assertEqual(int(self.resp['Content-Length']), bad_content_length) 285 286 # Tests for the ETag header 287 288 def test_if_none_match_and_no_etag(self): 289 self.req.META['HTTP_IF_NONE_MATCH'] = 'spam' 290 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 291 self.assertEquals(self.resp.status_code, 200) 292 293 def test_no_if_none_match_and_etag(self): 294 self.resp['ETag'] = 'eggs' 295 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 296 self.assertEquals(self.resp.status_code, 200) 297 298 def test_if_none_match_and_same_etag(self): 299 self.req.META['HTTP_IF_NONE_MATCH'] = self.resp['ETag'] = 'spam' 300 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 301 self.assertEquals(self.resp.status_code, 304) 302 303 def test_if_none_match_and_different_etag(self): 304 self.req.META['HTTP_IF_NONE_MATCH'] = 'spam' 305 self.resp['ETag'] = 'eggs' 306 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 307 self.assertEquals(self.resp.status_code, 200) 308 309 # Tests for the Last-Modified header 310 311 def test_if_modified_since_and_no_last_modified(self): 312 self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 313 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 314 self.assertEquals(self.resp.status_code, 200) 315 316 def test_no_if_modified_since_and_last_modified(self): 317 self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 318 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 319 self.assertEquals(self.resp.status_code, 200) 320 321 def test_if_modified_since_and_same_last_modified(self): 322 self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 323 self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 324 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 325 self.assertEquals(self.resp.status_code, 304) 326 327 def test_if_modified_since_and_last_modified_in_the_past(self): 328 self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 329 self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:35:44 GMT' 330 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 331 self.assertEquals(self.resp.status_code, 304) 332 333 def test_if_modified_since_and_last_modified_in_the_future(self): 334 self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT' 335 self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT' 336 self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp) 337 self.assertEquals(self.resp.status_code, 200) -
tests/regressiontests/middleware/cond_get_urls.py
1 from django.conf.urls.defaults import patterns 2 from django.http import HttpResponse 3 4 urlpatterns = patterns('', 5 (r'^$', lambda request: HttpResponse('root is here')), 6 )