diff options
| author | Luke Plant <L.Plant.98@cantab.net> | 2011-03-01 14:34:03 +0000 |
|---|---|---|
| committer | Luke Plant <L.Plant.98@cantab.net> | 2011-03-01 14:34:03 +0000 |
| commit | 58bd2492839f2b1c7463aee34edbd2b009910e7d (patch) | |
| tree | 1182b1c1fab2ce70d5f1075a74c5aae966cda3db /django/utils/http.py | |
| parent | 8d3ee2984796336285961a8f9a00e1ceb77f2dd4 (diff) | |
[1.2.X] Fixed #717 - If-Modified-Since handling should compare dates according to RFC 2616
Thanks to Maniac for the report, julienb for the initial patch, and
especially to aaugustin for the final patch and tests.
Backport of [15696] from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@15697 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/utils/http.py')
| -rw-r--r-- | django/utils/http.py | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/django/utils/http.py b/django/utils/http.py index 49d067e57c..61623fc696 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -1,3 +1,5 @@ +import calendar +import datetime import re import sys import urllib @@ -8,6 +10,17 @@ from django.utils.functional import allow_lazy ETAG_MATCH = re.compile(r'(?:W/)?"((?:\\.|[^"])*)"') +MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split() +__D = r'(?P<day>\d{2})' +__D2 = r'(?P<day>[ \d]\d)' +__M = r'(?P<mon>\w{3})' +__Y = r'(?P<year>\d{4})' +__Y2 = r'(?P<year>\d{2})' +__T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})' +RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T)) +RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T)) +ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y)) + def urlquote(url, safe='/'): """ A version of Python's urllib.quote() function that can operate on unicode @@ -70,6 +83,48 @@ def http_date(epoch_seconds=None): rfcdate = formatdate(epoch_seconds) return '%s GMT' % rfcdate[:25] +def parse_http_date(date): + """ + Parses a date format as specified by HTTP RFC2616 section 3.3.1. + + The three formats allowed by the RFC are accepted, even if only the first + one is still in widespread use. + + Returns an floating point number expressed in seconds since the epoch, in + UTC. + """ + # emails.Util.parsedate does the job for RFC1123 dates; unfortunately + # RFC2616 makes it mandatory to support RFC850 dates too. So we roll + # our own RFC-compliant parsing. + for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE: + m = regex.match(date) + if m is not None: + break + else: + raise ValueError("%r is not in a valid HTTP date format" % date) + try: + year = int(m.group('year')) + if year < 100: + year += 2000 if year < 70 else 1900 + month = MONTHS.index(m.group('mon').lower()) + 1 + day = int(m.group('day')) + hour = int(m.group('hour')) + min = int(m.group('min')) + sec = int(m.group('sec')) + result = datetime.datetime(year, month, day, hour, min, sec) + return calendar.timegm(result.utctimetuple()) + except Exception: + raise ValueError("%r is not a valid date" % date) + +def parse_http_date_safe(date): + """ + Same as parse_http_date, but returns None if the input is invalid. + """ + try: + return parse_http_date(date) + except Exception: + pass + # Base 36 functions: useful for generating compact URLs def base36_to_int(s): |
