diff --git a/swift/common/middleware/formpost.py b/swift/common/middleware/formpost.py index ba4de7af82..a50058191a 100644 --- a/swift/common/middleware/formpost.py +++ b/swift/common/middleware/formpost.py @@ -31,6 +31,13 @@ The format of the form is:: +Optionally, if you want the uploaded files to be temporary you can set +x-delete-at or x-delete-after attributes by adding one of these as a +form input:: + + + + The is the URL to the Swift desination, such as:: https://swift-cluster.example.com/v1/AUTH_account/container/object_prefix @@ -87,6 +94,8 @@ The key is the value of the X-Account-Meta-Temp-URL-Key header on the account. Be certain to use the full path, from the /v1/ onward. +Note the x_delete_at and x_delete_after attributes are not used in signature +generation as these are both considered optional attributes. The command line tool ``swift-form-signature`` may be used (mostly just when testing) to compute expires and signature. @@ -441,6 +450,19 @@ class FormPost(object): subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += attributes['filename'] or 'filename' + if 'x_delete_at' in attributes: + try: + subenv['HTTP_X_DELETE_AT'] = int(attributes['x_delete_at']) + except ValueError: + raise FormInvalid('x_delete_at not an integer: ' + 'Unix timestamp required.') + if 'x_delete_after' in attributes: + try: + subenv['HTTP_X_DELETE_AFTER'] = int( + attributes['x_delete_after']) + except ValueError: + raise FormInvalid('x_delete_after not an integer: ' + 'Number of seconds required.') if 'content-type' in attributes: subenv['CONTENT_TYPE'] = \ attributes['content-type'] or 'application/octet-stream' diff --git a/test/unit/common/middleware/test_formpost.py b/test/unit/common/middleware/test_formpost.py index 09eac11a91..4fa8caa894 100644 --- a/test/unit/common/middleware/test_formpost.py +++ b/test/unit/common/middleware/test_formpost.py @@ -1692,6 +1692,156 @@ class TestFormPost(unittest.TestCase): self.assertEquals(exc_info, None) self.assertTrue('FormPost: expired not an integer' in body) + def test_x_delete_at(self): + delete_at = int(time() + 100) + x_delete_body_part = [ + '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', + 'Content-Disposition: form-data; name="x_delete_at"', + '', + str(delete_at), + ] + key = 'abc' + sig, env, body = self._make_sig_env_body( + '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) + env['wsgi.input'] = StringIO('\r\n'.join(x_delete_body_part + body)) + env['swift.account/AUTH_test'] = self._fake_cache_env( + 'AUTH_test', [key]) + self.app = FakeApp(iter([('201 Created', {}, ''), + ('201 Created', {}, '')])) + self.auth = tempauth.filter_factory({})(self.app) + self.formpost = formpost.filter_factory({})(self.auth) + status = [None] + headers = [None] + exc_info = [None] + + def start_response(s, h, e=None): + status[0] = s + headers[0] = h + exc_info[0] = e + + body = ''.join(self.formpost(env, start_response)) + status = status[0] + headers = headers[0] + exc_info = exc_info[0] + self.assertEquals(status, '201 Created') + self.assertTrue('201 Created' in body) + self.assertEquals(len(self.app.requests), 2) + self.assertTrue("X-Delete-At" in self.app.requests[0].headers) + self.assertTrue("X-Delete-At" in self.app.requests[1].headers) + self.assertEquals(delete_at, + self.app.requests[0].headers["X-Delete-At"]) + self.assertEquals(delete_at, + self.app.requests[1].headers["X-Delete-At"]) + + def test_x_delete_at_not_int(self): + delete_at = "2014-07-16" + x_delete_body_part = [ + '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', + 'Content-Disposition: form-data; name="x_delete_at"', + '', + str(delete_at), + ] + key = 'abc' + sig, env, body = self._make_sig_env_body( + '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) + env['wsgi.input'] = StringIO('\r\n'.join(x_delete_body_part + body)) + env['swift.account/AUTH_test'] = self._fake_cache_env( + 'AUTH_test', [key]) + self.app = FakeApp(iter([('201 Created', {}, ''), + ('201 Created', {}, '')])) + self.auth = tempauth.filter_factory({})(self.app) + self.formpost = formpost.filter_factory({})(self.auth) + status = [None] + headers = [None] + exc_info = [None] + + def start_response(s, h, e=None): + status[0] = s + headers[0] = h + exc_info[0] = e + + body = ''.join(self.formpost(env, start_response)) + status = status[0] + headers = headers[0] + exc_info = exc_info[0] + self.assertEquals(status, '400 Bad Request') + self.assertTrue('FormPost: x_delete_at not an integer' in body) + + def test_x_delete_after(self): + delete_after = 100 + x_delete_body_part = [ + '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', + 'Content-Disposition: form-data; name="x_delete_after"', + '', + str(delete_after), + ] + key = 'abc' + sig, env, body = self._make_sig_env_body( + '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) + env['wsgi.input'] = StringIO('\r\n'.join(x_delete_body_part + body)) + env['swift.account/AUTH_test'] = self._fake_cache_env( + 'AUTH_test', [key]) + self.app = FakeApp(iter([('201 Created', {}, ''), + ('201 Created', {}, '')])) + self.auth = tempauth.filter_factory({})(self.app) + self.formpost = formpost.filter_factory({})(self.auth) + status = [None] + headers = [None] + exc_info = [None] + + def start_response(s, h, e=None): + status[0] = s + headers[0] = h + exc_info[0] = e + + body = ''.join(self.formpost(env, start_response)) + status = status[0] + headers = headers[0] + exc_info = exc_info[0] + self.assertEquals(status, '201 Created') + self.assertTrue('201 Created' in body) + self.assertEquals(len(self.app.requests), 2) + self.assertTrue("X-Delete-After" in self.app.requests[0].headers) + self.assertTrue("X-Delete-After" in self.app.requests[1].headers) + self.assertEqual(delete_after, + self.app.requests[0].headers["X-Delete-After"]) + self.assertEqual(delete_after, + self.app.requests[1].headers["X-Delete-After"]) + + def test_x_delete_after_not_int(self): + delete_after = "2 days" + x_delete_body_part = [ + '------WebKitFormBoundaryNcxTqxSlX7t4TDkR', + 'Content-Disposition: form-data; name="x_delete_after"', + '', + str(delete_after), + ] + key = 'abc' + sig, env, body = self._make_sig_env_body( + '/v1/AUTH_test/container', '', 1024, 10, int(time() + 86400), key) + env['wsgi.input'] = StringIO('\r\n'.join(x_delete_body_part + body)) + env['swift.account/AUTH_test'] = self._fake_cache_env( + 'AUTH_test', [key]) + self.app = FakeApp(iter([('201 Created', {}, ''), + ('201 Created', {}, '')])) + self.auth = tempauth.filter_factory({})(self.app) + self.formpost = formpost.filter_factory({})(self.auth) + status = [None] + headers = [None] + exc_info = [None] + + def start_response(s, h, e=None): + status[0] = s + headers[0] = h + exc_info[0] = e + + body = ''.join(self.formpost(env, start_response)) + status = status[0] + headers = headers[0] + exc_info = exc_info[0] + self.assertEquals(status, '400 Bad Request') + self.assertTrue('FormPost: x_delete_after not an integer' in body) + if __name__ == '__main__': unittest.main()