diff --git a/swift/common/constraints.py b/swift/common/constraints.py index f661a6989d..1d4d80cf05 100644 --- a/swift/common/constraints.py +++ b/swift/common/constraints.py @@ -303,13 +303,16 @@ def valid_timestamp(request): def check_delete_headers(request): """ Check that 'x-delete-after' and 'x-delete-at' headers have valid values. - Values should be positive integers and correspond to a time greater than or - equal to the request timestamp. + Values should be positive integers and correspond to a time greater than + the request timestamp. + + If the 'x-delete-after' header is found then its value is used to compute + an 'x-delete-at' value which takes precedence over any existing + 'x-delete-at' header. :param request: the swob request object - - :returns: HTTPBadRequest in case of invalid values - or None if values are ok + :raises: HTTPBadRequest in case of invalid values + :returns: the swob request object """ now = float(valid_timestamp(request)) if 'x-delete-after' in request.headers: @@ -321,7 +324,7 @@ def check_delete_headers(request): body='Non-integer X-Delete-After') actual_del_time = utils.normalize_delete_at_timestamp( now + x_delete_after) - if int(actual_del_time) < now: + if int(actual_del_time) <= now: raise HTTPBadRequest(request=request, content_type='text/plain', body='X-Delete-After in past') @@ -336,7 +339,7 @@ def check_delete_headers(request): raise HTTPBadRequest(request=request, content_type='text/plain', body='Non-integer X-Delete-At') - if x_delete_at < now and not utils.config_true_value( + if x_delete_at <= now and not utils.config_true_value( request.headers.get('x-backend-replication', 'f')): raise HTTPBadRequest(request=request, content_type='text/plain', body='X-Delete-At in past') diff --git a/test/unit/common/test_constraints.py b/test/unit/common/test_constraints.py index a11d222653..093e089524 100644 --- a/test/unit/common/test_constraints.py +++ b/test/unit/common/test_constraints.py @@ -291,6 +291,16 @@ class TestConstraints(unittest.TestCase): self.assertEqual(cm.exception.status_int, HTTP_BAD_REQUEST) self.assertIn('X-Delete-After in past', cm.exception.body) + # x-delete-after = 0 disallowed when it results in x-delete-at equal to + # the timestamp + headers = {'X-Delete-After': '0', + 'X-Timestamp': utils.Timestamp(int(ts)).internal} + with self.assertRaises(HTTPException) as cm: + constraints.check_delete_headers( + Request.blank('/', headers=headers)) + self.assertEqual(cm.exception.status_int, HTTP_BAD_REQUEST) + self.assertIn('X-Delete-After in past', cm.exception.body) + # X-Delete-At delete_at = str(int(ts) + 100) headers = {'X-Delete-At': delete_at, @@ -327,6 +337,16 @@ class TestConstraints(unittest.TestCase): self.assertEqual(cm.exception.status_int, HTTP_BAD_REQUEST) self.assertIn('X-Delete-At in past', cm.exception.body) + # x-delete-at disallowed when exactly equal to timestamp + delete_at = str(int(ts)) + headers = {'X-Delete-At': delete_at, + 'X-Timestamp': utils.Timestamp(int(ts)).internal} + with self.assertRaises(HTTPException) as cm: + constraints.check_delete_headers( + Request.blank('/', headers=headers)) + self.assertEqual(cm.exception.status_int, HTTP_BAD_REQUEST) + self.assertIn('X-Delete-At in past', cm.exception.body) + def test_check_delete_headers_removes_delete_after(self): t = time.time() headers = {'Content-Length': '0',