Add X-Delete-At/After support to FormPost
This change adds the ability to specify a X-Delete-At or X-Delete-After attribute when using the FormPost middleware. Here is an example of what you need to add to the form: <input type="hidden" name="x_delete_at" value="<unix-timestamp>"/> <input type="hidden" name="x_delete_after" value="<seconds>"/> To be inline with the other form imput names, x-delete-at/after has changed to x_delete_at/after. DocImpact Change-Id: Ib1cc0bcf1dd7d2b689f2f26d100f9bab36880c81 Closes-Bug: #1065522
This commit is contained in:

committed by
Samuel Merritt

parent
0a5b003345
commit
90272a0564
@@ -31,6 +31,13 @@ The format of the form is::
|
|||||||
<input type="submit" />
|
<input type="submit" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
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::
|
||||||
|
|
||||||
|
<input type="hidden" name="x_delete_at" value="<unix-timestamp>" />
|
||||||
|
<input type="hidden" name="x_delete_after" value="<seconds>" />
|
||||||
|
|
||||||
The <swift-url> is the URL to the Swift desination, such as::
|
The <swift-url> is the URL to the Swift desination, such as::
|
||||||
|
|
||||||
https://swift-cluster.example.com/v1/AUTH_account/container/object_prefix
|
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.
|
account.
|
||||||
|
|
||||||
Be certain to use the full path, from the /v1/ onward.
|
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
|
The command line tool ``swift-form-signature`` may be used (mostly
|
||||||
just when testing) to compute expires and signature.
|
just when testing) to compute expires and signature.
|
||||||
@@ -441,6 +450,19 @@ class FormPost(object):
|
|||||||
subenv['PATH_INFO'].count('/') < 4:
|
subenv['PATH_INFO'].count('/') < 4:
|
||||||
subenv['PATH_INFO'] += '/'
|
subenv['PATH_INFO'] += '/'
|
||||||
subenv['PATH_INFO'] += attributes['filename'] or 'filename'
|
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:
|
if 'content-type' in attributes:
|
||||||
subenv['CONTENT_TYPE'] = \
|
subenv['CONTENT_TYPE'] = \
|
||||||
attributes['content-type'] or 'application/octet-stream'
|
attributes['content-type'] or 'application/octet-stream'
|
||||||
|
@@ -1692,6 +1692,156 @@ class TestFormPost(unittest.TestCase):
|
|||||||
self.assertEquals(exc_info, None)
|
self.assertEquals(exc_info, None)
|
||||||
self.assertTrue('FormPost: expired not an integer' in body)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Reference in New Issue
Block a user