Ticket #717: 717.3.diff

File 717.3.diff, 17.6 KB (added by Aymeric Augustin, 14 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

     
    99import re
    1010import stat
    1111import urllib
    12 from email.Utils import parsedate_tz, mktime_tz
    1312
    1413from django.template import loader
    1514from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
    1615from django.template import Template, Context, TemplateDoesNotExist
    17 from django.utils.http import http_date
     16from django.utils.http import http_date, parse_http_date
    1817
    1918def serve(request, path, document_root=None, show_indexes=False):
    2019    """
     
    128127            raise ValueError
    129128        matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header,
    130129                           re.IGNORECASE)
    131         header_date = parsedate_tz(matches.group(1))
    132         if header_date is None:
    133             raise ValueError
    134         header_mtime = mktime_tz(header_date)
     130        header_mtime = parse_http_date(matches.group(1))
    135131        header_len = matches.group(3)
    136132        if header_len and int(header_len) != size:
    137133            raise ValueError
  • 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/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

     
    5151        file_name = 'file.txt'
    5252        response = self.client.get(
    5353            '/views/%s/%s' % (self.prefix, file_name),
    54             HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 UTC'
     54            HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 GMT'
    5555            # This is 24h before max Unix time. Remember to fix Django and
    5656            # update this test well before 2038 :)
    5757            )
  • 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 import unittest
     6from django.utils.http import parse_etags, quote_etag, parse_http_date
    77
    88FULL_RESPONSE = 'Test conditional get response'
    99LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47)
    1010LAST_MODIFIED_STR = 'Sun, 21 Oct 2007 23:21:47 GMT'
     11LAST_MODIFIED_NEWER_STR = 'Mon, 18 Oct 2010 16:56:23 GMT'
     12LAST_MODIFIED_INVALID_STR = 'Mon, 32 Oct 2010 16:56:23 GMT'
    1113EXPIRED_LAST_MODIFIED_STR = 'Sat, 20 Oct 2007 23:21:47 GMT'
    1214ETAG = 'b4246ffc4f62314ca13147c9d4f76974'
    1315EXPIRED_ETAG = '7fae4cd4b0f81e7d2914700043aa8ed6'
    1416
     17
    1518class ConditionalGet(TestCase):
    1619    def assertFullResponse(self, response, check_last_modified=True, check_etag=True):
    1720        self.assertEquals(response.status_code, 200)
     
    3336        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_STR
    3437        response = self.client.get('/condition/')
    3538        self.assertNotModified(response)
     39        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_NEWER_STR
     40        response = self.client.get('/condition/')
     41        self.assertNotModified(response)
     42        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = LAST_MODIFIED_INVALID_STR
     43        response = self.client.get('/condition/')
     44        self.assertFullResponse(response)
    3645        self.client.defaults['HTTP_IF_MODIFIED_SINCE'] = EXPIRED_LAST_MODIFIED_STR
    3746        response = self.client.get('/condition/')
    3847        self.assertFullResponse(response)
     
    118127        self.assertFullResponse(response, check_last_modified=False)
    119128
    120129
    121 class ETagProcesing(TestCase):
     130class ETagProcessing(unittest.TestCase):
    122131    def testParsing(self):
    123132        etags = parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"')
    124133        self.assertEquals(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak'])
     
    126135    def testQuoting(self):
    127136        quoted_etag = quote_etag(r'e\t"ag')
    128137        self.assertEquals(quoted_etag, r'"e\\t\"ag"')
     138
     139
     140class HttpDateProcessing(unittest.TestCase):
     141    def testParsingRfc1123(self):
     142        parsed = parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT')
     143        self.assertEqual(datetime.utcfromtimestamp(parsed),
     144                         datetime(1994, 11, 06, 8, 49, 37))
     145
     146    def testParsingRfc850(self):
     147        parsed = parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT')
     148        self.assertEqual(datetime.utcfromtimestamp(parsed),
     149                         datetime(1994, 11, 06, 8, 49, 37))
     150
     151    def testParsingAsctime(self):
     152        parsed = parse_http_date('Sun Nov  6 08:49:37 1994')
     153        self.assertEqual(datetime.utcfromtimestamp(parsed),
     154                         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
     252class ConditionalGetMiddlewareTest(TestCase):
     253    urls = 'regressiontests.middleware.cond_get_urls'
     254    def setUp(self):
     255        self.req = HttpRequest()
     256        self.req.META = {
     257            'SERVER_NAME': 'testserver',
     258            'SERVER_PORT': 80,
     259        }
     260        self.req.path = self.req.path_info = "/"
     261        self.resp = self.client.get(self.req.path)
     262
     263    # Tests for the Date header
     264
     265    def test_date_header_added(self):
     266        self.assertFalse('Date' in self.resp)
     267        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     268        self.assertTrue('Date' in self.resp)
     269
     270    # Tests for the Content-Length header
     271
     272    def test_content_length_header_added(self):
     273        content_length = len(self.resp.content)
     274        self.assertFalse('Content-Length' in self.resp)
     275        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     276        self.assertTrue('Content-Length' in self.resp)
     277        self.assertEqual(int(self.resp['Content-Length']), content_length)
     278
     279    def test_content_length_header_not_changed(self):
     280        bad_content_length = len(self.resp.content) + 10
     281        self.resp['Content-Length'] = bad_content_length
     282        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     283        self.assertEqual(int(self.resp['Content-Length']), bad_content_length)
     284
     285    # Tests for the ETag header
     286
     287    def test_if_none_match_and_no_etag(self):
     288        self.req.META['HTTP_IF_NONE_MATCH'] = 'spam'
     289        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     290        self.assertEquals(self.resp.status_code, 200)
     291
     292    def test_no_if_none_match_and_etag(self):
     293        self.resp['ETag'] = 'eggs'
     294        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     295        self.assertEquals(self.resp.status_code, 200)
     296
     297    def test_if_none_match_and_same_etag(self):
     298        self.req.META['HTTP_IF_NONE_MATCH'] = self.resp['ETag'] = 'spam'
     299        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     300        self.assertEquals(self.resp.status_code, 304)
     301
     302    def test_if_none_match_and_different_etag(self):
     303        self.req.META['HTTP_IF_NONE_MATCH'] = 'spam'
     304        self.resp['ETag'] = 'eggs'
     305        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     306        self.assertEquals(self.resp.status_code, 200)
     307
     308    # Tests for the Last-Modified header
     309
     310    def test_if_modified_since_and_no_last_modified(self):
     311        self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     312        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     313        self.assertEquals(self.resp.status_code, 200)
     314
     315    def test_no_if_modified_since_and_last_modified(self):
     316        self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     317        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     318        self.assertEquals(self.resp.status_code, 200)
     319
     320    def test_if_modified_since_and_same_last_modified(self):
     321        self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     322        self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     323        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     324        self.assertEquals(self.resp.status_code, 304)
     325
     326    def test_if_modified_since_and_last_modified_in_the_past(self):
     327        self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     328        self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:35:44 GMT'
     329        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     330        self.assertEquals(self.resp.status_code, 304)
     331
     332    def test_if_modified_since_and_last_modified_in_the_future(self):
     333        self.req.META['HTTP_IF_MODIFIED_SINCE'] = 'Sat, 12 Feb 2011 17:38:44 GMT'
     334        self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT'
     335        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
     336        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