change the last-modified header value with valid one
the Last-Modified header in Response didn't have a suitable value - an integer part of object's timestamp. This leads that the the if-[un]modified-since header with the value from last-modified is always earlier than timestamp and results the content is always newer than value of these conditional headers. Patched code returns math.ceil() of object's timestamp in Last-Modified header so the later conditional header works correctly Closes-Bug: #1248818 Change-Id: I1ece7d008551bf989da74d23f0ed6307c45c5436
This commit is contained in:
parent
deaddf003b
commit
d69e013519
@ -21,6 +21,7 @@ import os
|
||||
import time
|
||||
import traceback
|
||||
import socket
|
||||
import math
|
||||
from datetime import datetime
|
||||
from swift import gettext_ as _
|
||||
from hashlib import md5
|
||||
@ -483,7 +484,7 @@ class ObjectController(object):
|
||||
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)
|
||||
keep_cache = (self.keep_cache_private or
|
||||
('X-Auth-Token' not in request.headers and
|
||||
@ -499,7 +500,7 @@ class ObjectController(object):
|
||||
key.lower() in self.allowed_headers:
|
||||
response.headers[key] = value
|
||||
response.etag = metadata['ETag']
|
||||
response.last_modified = file_x_ts_flt
|
||||
response.last_modified = math.ceil(file_x_ts_flt)
|
||||
response.content_length = obj_size
|
||||
try:
|
||||
response.content_encoding = metadata[
|
||||
@ -541,7 +542,7 @@ class ObjectController(object):
|
||||
response.headers[key] = value
|
||||
response.etag = metadata['ETag']
|
||||
ts = metadata['X-Timestamp']
|
||||
response.last_modified = float(ts)
|
||||
response.last_modified = math.ceil(float(ts))
|
||||
# Needed for container sync feature
|
||||
response.headers['X-Timestamp'] = ts
|
||||
response.content_length = int(metadata['Content-Length'])
|
||||
|
@ -28,6 +28,7 @@ import itertools
|
||||
import mimetypes
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
from datetime import datetime
|
||||
from swift import gettext_ as _
|
||||
from urllib import unquote, quote
|
||||
@ -1169,7 +1170,7 @@ class ObjectController(Controller):
|
||||
resp.headers['X-Copied-From-Last-Modified'] = \
|
||||
source_resp.headers['last-modified']
|
||||
copy_headers_into(req, resp)
|
||||
resp.last_modified = float(req.headers['X-Timestamp'])
|
||||
resp.last_modified = math.ceil(float(req.headers['X-Timestamp']))
|
||||
return resp
|
||||
|
||||
@public
|
||||
|
@ -697,7 +697,8 @@ class File(Base):
|
||||
else:
|
||||
raise RuntimeError
|
||||
|
||||
def write(self, data='', hdrs={}, parms={}, callback=None, cfg={}):
|
||||
def write(self, data='', hdrs={}, parms={}, callback=None, cfg={},
|
||||
return_resp=False):
|
||||
block_size = 2 ** 20
|
||||
|
||||
if isinstance(data, file):
|
||||
@ -736,6 +737,9 @@ class File(Base):
|
||||
|
||||
self.md5 = self.compute_md5sum(data)
|
||||
|
||||
if return_resp:
|
||||
return self.conn.response
|
||||
|
||||
return True
|
||||
|
||||
def write_random(self, size=None, hdrs={}, parms={}, cfg={}):
|
||||
@ -744,3 +748,12 @@ class File(Base):
|
||||
raise ResponseError(self.conn.response)
|
||||
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
|
||||
return data
|
||||
|
||||
def write_random_return_resp(self, size=None, hdrs={}, parms={}, cfg={}):
|
||||
data = self.random_data(size)
|
||||
resp = self.write(data, hdrs=hdrs, parms=parms, cfg=cfg,
|
||||
return_resp=True)
|
||||
if not resp:
|
||||
raise ResponseError(self.conn.response)
|
||||
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
|
||||
return resp
|
||||
|
@ -1620,6 +1620,28 @@ class TestFileComparison(Base):
|
||||
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
|
||||
self.assert_status(412)
|
||||
|
||||
def testLastModified(self):
|
||||
file_name = Utils.create_name()
|
||||
content_type = Utils.create_name()
|
||||
|
||||
file = self.env.container.file(file_name)
|
||||
file.content_type = content_type
|
||||
resp = file.write_random_return_resp(self.env.file_size)
|
||||
put_last_modified = resp.getheader('last-modified')
|
||||
|
||||
file = self.env.container.file(file_name)
|
||||
info = file.info()
|
||||
self.assert_('last_modified' in info)
|
||||
last_modified = info['last_modified']
|
||||
self.assertEqual(put_last_modified, info['last_modified'])
|
||||
|
||||
hdrs = {'If-Modified-Since': last_modified}
|
||||
self.assertRaises(ResponseError, file.read, hdrs=hdrs)
|
||||
self.assert_status(304)
|
||||
|
||||
hdrs = {'If-Unmodified-Since': last_modified}
|
||||
self.assert_(file.read(hdrs=hdrs))
|
||||
|
||||
|
||||
class TestFileComparisonUTF8(Base2, TestFileComparison):
|
||||
set_up = False
|
||||
|
@ -21,6 +21,7 @@ import operator
|
||||
import os
|
||||
import mock
|
||||
import unittest
|
||||
import math
|
||||
from shutil import rmtree
|
||||
from StringIO import StringIO
|
||||
from time import gmtime, strftime, time
|
||||
@ -672,7 +673,8 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
||||
self.assertEquals(
|
||||
resp.headers['last-modified'],
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp))))
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(math.ceil(float(timestamp)))))
|
||||
self.assertEquals(resp.headers['etag'],
|
||||
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
||||
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
||||
@ -774,7 +776,8 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEquals(resp.headers['content-type'], 'application/x-test')
|
||||
self.assertEquals(
|
||||
resp.headers['last-modified'],
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp))))
|
||||
strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(math.ceil(float(timestamp)))))
|
||||
self.assertEquals(resp.headers['etag'],
|
||||
'"0b4c12d7e0a73840c1c4f148fda3b037"')
|
||||
self.assertEquals(resp.headers['x-object-meta-1'], 'One')
|
||||
@ -976,6 +979,37 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
since = resp.headers['Last-Modified']
|
||||
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(math.ceil(float(timestamp)))))
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'If-Modified-Since': since})
|
||||
resp = self.object_controller.GET(req)
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
|
||||
timestamp = normalize_timestamp(int(time()))
|
||||
req = Request.blank('/sda1/p/a/c/o2',
|
||||
environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={
|
||||
'X-Timestamp': timestamp,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Length': '4'})
|
||||
req.body = 'test'
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 201)
|
||||
|
||||
since = strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(float(timestamp)))
|
||||
req = Request.blank('/sda1/p/a/c/o2',
|
||||
environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'If-Modified-Since': since})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 304)
|
||||
|
||||
def test_GET_if_unmodified_since(self):
|
||||
timestamp = normalize_timestamp(time())
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
@ -1012,6 +1046,18 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o',
|
||||
environ={'REQUEST_METHOD': 'HEAD'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
since = resp.headers['Last-Modified']
|
||||
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
|
||||
gmtime(math.ceil(float(timestamp)))))
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'If-Unmodified-Since': since})
|
||||
resp = self.object_controller.GET(req)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
|
||||
def test_GET_quarantine(self):
|
||||
# Test swift.obj.server.ObjectController.GET
|
||||
timestamp = normalize_timestamp(time())
|
||||
|
@ -998,6 +998,56 @@ class TestObjectController(unittest.TestCase):
|
||||
finally:
|
||||
swift.proxy.controllers.obj.MAX_FILE_SIZE = MAX_FILE_SIZE
|
||||
|
||||
def test_PUT_last_modified(self):
|
||||
prolis = _test_sockets[0]
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||
'Host: localhost\r\nConnection: close\r\n'
|
||||
'X-Storage-Token: t\r\nContent-Length: 0\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
lm_hdr = 'Last-Modified: '
|
||||
self.assertEqual(headers[:len(exp)], exp)
|
||||
|
||||
last_modified_put = [line for line in headers.split('\r\n')
|
||||
if lm_hdr in line][0][len(lm_hdr):]
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('HEAD /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||
'Host: localhost\r\nConnection: close\r\n'
|
||||
'X-Storage-Token: t\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEqual(headers[:len(exp)], exp)
|
||||
last_modified_head = [line for line in headers.split('\r\n')
|
||||
if lm_hdr in line][0][len(lm_hdr):]
|
||||
self.assertEqual(last_modified_put, last_modified_head)
|
||||
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||
'Host: localhost\r\nConnection: close\r\n'
|
||||
'If-Modified-Since: %s\r\n'
|
||||
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 304'
|
||||
self.assertEqual(headers[:len(exp)], exp)
|
||||
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
|
||||
'Host: localhost\r\nConnection: close\r\n'
|
||||
'If-Unmodified-Since: %s\r\n'
|
||||
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEqual(headers[:len(exp)], exp)
|
||||
|
||||
def test_expirer_DELETE_on_versioned_object(self):
|
||||
test_errors = []
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user