Ticket #717: 717.2.diff

File 717.2.diff, 17.8 KB (added by Aymeric Augustin, 13 years ago)
  • django/views/decorators/http.py

     
    99
    1010from calendar import timegm
    1111from datetime import timedelta
    12 from email.Utils import formatdate
    1312
    1413from django.utils.decorators import decorator_from_middleware, available_attrs
    15 from django.utils.http import parse_etags, quote_etag
     14from django.utils.http import http_date, parse_http_date_safe, parse_etags, quote_etag
    1615from django.utils.log import getLogger
    1716from django.middleware.http import ConditionalGetMiddleware
    1817from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse
     
    7978        def inner(request, *args, **kwargs):
    8079            # Get HTTP request headers
    8180            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)
    8283            if_none_match = request.META.get("HTTP_IF_NONE_MATCH")
    8384            if_match = request.META.get("HTTP_IF_MATCH")
    8485            if if_none_match or if_match:
     
    102103            if last_modified_func:
    103104                dt = last_modified_func(request, *args, **kwargs)
    104105                if dt:
    105                     res_last_modified = formatdate(timegm(dt.utctimetuple()))[:26] + 'GMT'
     106                    res_last_modified = timegm(dt.utctimetuple())
    106107                else:
    107108                    res_last_modified = None
    108109            else:
     
    116117                if ((if_none_match and (res_etag in etags or
    117118                        "*" in etags and res_etag)) and
    118119                        (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))):
    120122                    if request.method in ("GET", "HEAD"):
    121123                        response = HttpResponseNotModified()
    122124                    else:
     
    136138                        }
    137139                    )
    138140                    response = HttpResponse(status=412)
    139                 elif (not if_none_match and if_modified_since and
    140                         request.method == "GET" and
    141                         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):
    142144                    response = HttpResponseNotModified()
    143145
    144146            if response is None:
     
    146148
    147149            # Set relevant headers on the response if they don't already exist.
    148150            if res_last_modified and not response.has_header('Last-Modified'):
    149                 response['Last-Modified'] = res_last_modified
     151                response['Last-Modified'] = http_date(res_last_modified)
    150152            if res_etag and not response.has_header('ETag'):
    151153                response['ETag'] = quote_etag(res_etag)
    152154
  • django/views/static.py

     
    1010import stat
    1111import urllib
    1212import warnings
    13 from email.Utils import parsedate_tz, mktime_tz
    1413
    1514from django.template import loader
    1615from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
  • django/utils/http.py

     
     1import calendar
     2import datetime
    13import re
    24import sys
    35import urllib
     
    810
    911ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"')
    1012
     13MONTHS = '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})'
     20RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
     21RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
     22ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
     23
    1124def urlquote(url, safe='/'):
    1225    """
    1326    A version of Python's urllib.quote() function that can operate on unicode
     
    7083    rfcdate = formatdate(epoch_seconds)
    7184    return '%s GMT' % rfcdate[:25]
    7285
     86def 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
     119def 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
    73128# Base 36 functions: useful for generating compact URLs
    74129
    75130def base36_to_int(s):
  • django/contrib/staticfiles/views.py

     
    99import re
    1010import stat
    1111import urllib
    12 from email.Utils import parsedate_tz, mktime_tz
    1312
    1413from django.conf import settings
    1514from django.core.exceptions import ImproperlyConfigured
    1615from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
    1716from django.template import loader, Template, Context, TemplateDoesNotExist
    18 from django.utils.http import http_date
     17from django.utils.http import http_date, parse_http_date
    1918
    2019from django.contrib.staticfiles import finders, utils
    2120
     
    151150            raise ValueError
    152151        matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
    153152                           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))
    158154        header_len = matches.group(3)
    159155        if header_len and int(header_len) != size:
    160156            raise ValueError
  • django/middleware/http.py

     
    11from django.core.exceptions import MiddlewareNotUsed
    2 from django.utils.http import http_date
     2from django.utils.http import http_date, parse_http_date_safe
    33
    44class ConditionalGetMiddleware(object):
    55    """
     
    1515            response['Content-Length'] = str(len(response.content))
    1616
    1717        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')
    1919            if if_none_match == response['ETag']:
    2020                # Setting the status is enough here. The response handling path
    2121                # automatically removes content for this status code (in
     
    2323                response.status_code = 304
    2424
    2525        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
    3135
    3236        return response
  • tests/regressiontests/views/tests/static.py

     
    4848        file_name = 'file.txt'
    4949        response = self.client.get(
    5050            '/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'
    5252            # This is 24h before max Unix time. Remember to fix Django and
    5353            # update this test well before 2038 :)
    5454            )
  • tests/regressiontests/conditional_processing/models.py

     
    11# -*- coding:utf-8 -*-
    2 from datetime import datetime, timedelta
    3 from calendar import timegm
     2from datetime import datetime
    43
    54from django.test import TestCase
    6 from django.utils.http import parse_etags, quote_etag
     5from django.utils.http import parse_etags, quote_etag, parse_http_date
    76
    87FULL_RESPONSE = 'Test conditional get response'
    98LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47)
    109LAST_MODIFIED_STR = 'Sun, 21 Oct 2007 23:21:47 GMT'
     10LAST_MODIFIED_NEWER_STR = 'Mon, 18 Oct 2010 16:56:23 GMT'
     11LAST_MODIFIED_INVALID_STR = 'Mon, 32 Oct 2010 16:56:23 GMT'
    1112EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT'
    1213ETAG = 'b4246ffc4f62314ca13147c9d4f76974'
    1314EXPIRED_ETAG = '7fae4cd4b0f81e7d2914700043aa8ed6'
     
    3334        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
    3435        response = self.client.get('/condition/')
    3536        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)
    3643        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
    3744        response = self.client.get('/condition/')
    3845        self.assertFullResponse(response)
     
    118125        self.assertFullResponse(response, check_last_modified=False)
    119126
    120127
    121 class ETagProcesing(TestCase):
     128class ETagProcessing(TestCase):
    122129    def testParsing(self):
    123130        etags = parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"')
    124131        self.assertEquals(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak'])
     
    126133    def testQuoting(self):
    127134        quoted_etag = quote_etag(r'e\t"ag')
    128135        self.assertEquals(quoted_etag, r'"e\\t\"ag"')
     136
     137
     138class 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

     
    33from django.conf import settings
    44from django.http import HttpRequest
    55from django.middleware.common import CommonMiddleware
     6from django.middleware.http import ConditionalGetMiddleware
    67from django.test import TestCase
    78
    89
     
    247248      self.assertEquals(r.status_code, 301)
    248249      self.assertEquals(r['Location'],
    249250                        'http://www.testserver/middleware/customurlconf/slash/')
     251
     252
     253class 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

     
     1from django.conf.urls.defaults import patterns
     2from django.http import HttpResponse
     3
     4urlpatterns = patterns('',
     5    (r'^$', lambda request: HttpResponse('root is here')),
     6)
Back to Top