Allow pre-1970 dates in If-[Un]Modified-Since
If I want to fetch an object only if it is newer than the first moon landing, I send a GET request with header: If-Modified-Since: Sun, 20 Jul 1969 20:18:00 UTC Since that date is older than Swift, I expect a 2xx response. However, I get a 412, which isn't even a valid thing to do for If-Modified-Since; it should either be 2xx or 304. This is because of two problems: (a) Swift treats pre-1970 dates as invalid, and (b) Swift returns 412 when a date is invalid instead of ignoring it. This commit makes it so any time between datetime.datetime.min and datetime.datetime.max is an acceptable value for If-Modified-Since and If-Unmodified-Since. Dates outside that date range are treated as invalid headers and thus are ignored, as RFC 2616 section 14.28 requires ("If the specified date is invalid, the header is ignored"). This only works for dates that the Python standard library can parse, which on my machine is 01 Jan 1 to 31 Dec 9999. Eliminating those restrictions would require implementing our own date parsing and comparison, and that's almost certainly not worth it. Change-Id: I4cb4903c4e5e3b6b3c9506c2cabbfbda62e82f35
This commit is contained in:
@@ -138,12 +138,9 @@ def _datetime_property(header):
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
try:
|
try:
|
||||||
parts = parsedate(self.headers[header])[:7]
|
parts = parsedate(self.headers[header])[:7]
|
||||||
date = datetime(*(parts + (UTC,)))
|
return datetime(*(parts + (UTC,)))
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
if date.year < 1970:
|
|
||||||
raise ValueError('Somehow an invalid year')
|
|
||||||
return date
|
|
||||||
|
|
||||||
def setter(self, value):
|
def setter(self, value):
|
||||||
if isinstance(value, (float, int, long)):
|
if isinstance(value, (float, int, long)):
|
||||||
|
@@ -481,21 +481,16 @@ class ObjectController(object):
|
|||||||
obj_size = int(metadata['Content-Length'])
|
obj_size = int(metadata['Content-Length'])
|
||||||
file_x_ts = metadata['X-Timestamp']
|
file_x_ts = metadata['X-Timestamp']
|
||||||
file_x_ts_flt = float(file_x_ts)
|
file_x_ts_flt = float(file_x_ts)
|
||||||
try:
|
|
||||||
if_unmodified_since = request.if_unmodified_since
|
|
||||||
except (OverflowError, ValueError):
|
|
||||||
# catches timestamps before the epoch
|
|
||||||
return HTTPPreconditionFailed(request=request)
|
|
||||||
file_x_ts_utc = datetime.fromtimestamp(file_x_ts_flt, UTC)
|
file_x_ts_utc = datetime.fromtimestamp(file_x_ts_flt, UTC)
|
||||||
|
|
||||||
|
if_unmodified_since = request.if_unmodified_since
|
||||||
if if_unmodified_since and file_x_ts_utc > if_unmodified_since:
|
if if_unmodified_since and file_x_ts_utc > if_unmodified_since:
|
||||||
return HTTPPreconditionFailed(request=request)
|
return HTTPPreconditionFailed(request=request)
|
||||||
try:
|
|
||||||
if_modified_since = request.if_modified_since
|
if_modified_since = request.if_modified_since
|
||||||
except (OverflowError, ValueError):
|
|
||||||
# catches timestamps before the epoch
|
|
||||||
return HTTPPreconditionFailed(request=request)
|
|
||||||
if if_modified_since and file_x_ts_utc <= if_modified_since:
|
if if_modified_since and file_x_ts_utc <= if_modified_since:
|
||||||
return HTTPNotModified(request=request)
|
return HTTPNotModified(request=request)
|
||||||
|
|
||||||
keep_cache = (self.keep_cache_private or
|
keep_cache = (self.keep_cache_private or
|
||||||
('X-Auth-Token' not in request.headers and
|
('X-Auth-Token' not in request.headers and
|
||||||
'X-Storage-Token' not in request.headers))
|
'X-Storage-Token' not in request.headers))
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
|
|
||||||
@@ -644,13 +645,18 @@ class TestRequest(unittest.TestCase):
|
|||||||
self.assertEquals(req.headers['If-Unmodified-Since'], 'something')
|
self.assertEquals(req.headers['If-Unmodified-Since'], 'something')
|
||||||
self.assertEquals(req.if_unmodified_since, None)
|
self.assertEquals(req.if_unmodified_since, None)
|
||||||
|
|
||||||
req.if_unmodified_since = -1
|
|
||||||
self.assertRaises(ValueError, lambda: req.if_unmodified_since)
|
|
||||||
|
|
||||||
self.assert_('If-Unmodified-Since' in req.headers)
|
self.assert_('If-Unmodified-Since' in req.headers)
|
||||||
req.if_unmodified_since = None
|
req.if_unmodified_since = None
|
||||||
self.assert_('If-Unmodified-Since' not in req.headers)
|
self.assert_('If-Unmodified-Since' not in req.headers)
|
||||||
|
|
||||||
|
too_big_date_list = list(datetime.datetime.max.timetuple())
|
||||||
|
too_big_date_list[0] += 1 # bump up the year
|
||||||
|
too_big_date = time.strftime(
|
||||||
|
"%a, %d %b %Y %H:%M:%S UTC", time.struct_time(too_big_date_list))
|
||||||
|
|
||||||
|
req.if_unmodified_since = too_big_date
|
||||||
|
self.assertEqual(req.if_unmodified_since, None)
|
||||||
|
|
||||||
def test_bad_range(self):
|
def test_bad_range(self):
|
||||||
req = swift.common.swob.Request.blank('/hi/there', body='hi')
|
req = swift.common.swob.Request.blank('/hi/there', body='hi')
|
||||||
req.range = 'bad range'
|
req.range = 'bad range'
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
"""Tests for swift.obj.server"""
|
"""Tests for swift.obj.server"""
|
||||||
|
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
|
import datetime
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
import mock
|
import mock
|
||||||
@@ -24,7 +25,7 @@ import unittest
|
|||||||
import math
|
import math
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from time import gmtime, strftime, time
|
from time import gmtime, strftime, time, struct_time
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
|
||||||
@@ -1911,16 +1912,16 @@ class TestObjectController(unittest.TestCase):
|
|||||||
headers={'If-Modified-Since': 'Not a valid date'})
|
headers={'If-Modified-Since': 'Not a valid date'})
|
||||||
resp = req.get_response(self.object_controller)
|
resp = req.get_response(self.object_controller)
|
||||||
self.assertEquals(resp.status_int, 200)
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
|
||||||
|
too_big_date_list = list(datetime.datetime.max.timetuple())
|
||||||
|
too_big_date_list[0] += 1 # bump up the year
|
||||||
|
too_big_date = strftime(
|
||||||
|
"%a, %d %b %Y %H:%M:%S UTC", struct_time(too_big_date_list))
|
||||||
req = Request.blank(
|
req = Request.blank(
|
||||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||||
headers={'If-Unmodified-Since': 'Sat, 29 Oct 1000 19:43:31 GMT'})
|
headers={'If-Unmodified-Since': too_big_date})
|
||||||
resp = req.get_response(self.object_controller)
|
resp = req.get_response(self.object_controller)
|
||||||
self.assertEquals(resp.status_int, 412)
|
self.assertEquals(resp.status_int, 200)
|
||||||
req = Request.blank(
|
|
||||||
'/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'If-Modified-Since': 'Sat, 29 Oct 1000 19:43:31 GMT'})
|
|
||||||
resp = req.get_response(self.object_controller)
|
|
||||||
self.assertEquals(resp.status_int, 412)
|
|
||||||
|
|
||||||
def test_content_encoding(self):
|
def test_content_encoding(self):
|
||||||
req = Request.blank(
|
req = Request.blank(
|
||||||
|
Reference in New Issue
Block a user