Merge "TempURL filename options; bug fixes"
This commit is contained in:
@@ -69,6 +69,16 @@ locations in Swift.
|
|||||||
Note that changing the X-Account-Meta-Temp-URL-Key will invalidate
|
Note that changing the X-Account-Meta-Temp-URL-Key will invalidate
|
||||||
any previously generated temporary URLs within 60 seconds (the
|
any previously generated temporary URLs within 60 seconds (the
|
||||||
memcache time for the key).
|
memcache time for the key).
|
||||||
|
|
||||||
|
With GET TempURLs, a Content-Disposition header will be set on the
|
||||||
|
response so that browsers will interpret this as a file attachment to
|
||||||
|
be saved. The filename chosen is based on the object name, but you
|
||||||
|
can override this with a filename query parameter. Modifying the
|
||||||
|
above example::
|
||||||
|
|
||||||
|
https://swift-cluster.example.com/v1/AUTH_account/container/object?
|
||||||
|
temp_url_sig=da39a3ee5e6b4b0d3255bfef95601890afd80709&
|
||||||
|
temp_url_expires=1323479485&filename=My+Test+File.pdf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ['TempURL', 'filter_factory',
|
__all__ = ['TempURL', 'filter_factory',
|
||||||
@@ -83,7 +93,7 @@ from hashlib import sha1
|
|||||||
from os.path import basename
|
from os.path import basename
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from time import gmtime, strftime, time
|
from time import gmtime, strftime, time
|
||||||
from urllib import quote, unquote
|
from urllib import unquote, urlencode
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
|
|
||||||
from swift.common.wsgi import make_pre_authed_env
|
from swift.common.wsgi import make_pre_authed_env
|
||||||
@@ -224,7 +234,7 @@ class TempURL(object):
|
|||||||
:param start_response: The WSGI start_response hook.
|
:param start_response: The WSGI start_response hook.
|
||||||
:returns: Response as per WSGI.
|
:returns: Response as per WSGI.
|
||||||
"""
|
"""
|
||||||
temp_url_sig, temp_url_expires = self._get_temp_url_info(env)
|
temp_url_sig, temp_url_expires, filename = self._get_temp_url_info(env)
|
||||||
if temp_url_sig is None and temp_url_expires is None:
|
if temp_url_sig is None and temp_url_expires is None:
|
||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
if not temp_url_sig or not temp_url_expires:
|
if not temp_url_sig or not temp_url_expires:
|
||||||
@@ -251,19 +261,30 @@ class TempURL(object):
|
|||||||
env['swift.authorize'] = lambda req: None
|
env['swift.authorize'] = lambda req: None
|
||||||
env['swift.authorize_override'] = True
|
env['swift.authorize_override'] = True
|
||||||
env['REMOTE_USER'] = '.wsgi.tempurl'
|
env['REMOTE_USER'] = '.wsgi.tempurl'
|
||||||
|
qs = {'temp_url_sig': temp_url_sig,
|
||||||
|
'temp_url_expires': temp_url_expires}
|
||||||
|
if filename:
|
||||||
|
qs['filename'] = filename
|
||||||
|
env['QUERY_STRING'] = urlencode(qs)
|
||||||
|
|
||||||
def _start_response(status, headers, exc_info=None):
|
def _start_response(status, headers, exc_info=None):
|
||||||
headers = self._clean_outgoing_headers(headers)
|
headers = self._clean_outgoing_headers(headers)
|
||||||
if env['REQUEST_METHOD'] == 'GET':
|
if env['REQUEST_METHOD'] == 'GET' and status[0] == '2':
|
||||||
already = False
|
already = False
|
||||||
for h, v in headers:
|
for h, v in headers:
|
||||||
if h.lower() == 'content-disposition':
|
if h.lower() == 'content-disposition':
|
||||||
already = True
|
already = True
|
||||||
break
|
break
|
||||||
|
if already and filename:
|
||||||
|
headers = list((h, v) for h, v in headers
|
||||||
|
if h.lower() != 'content-disposition')
|
||||||
|
already = False
|
||||||
if not already:
|
if not already:
|
||||||
headers.append(
|
headers.append((
|
||||||
('Content-Disposition', 'attachment; filename=%s' %
|
'Content-Disposition',
|
||||||
(quote(basename(env['PATH_INFO'])))))
|
'attachment; filename="%s"' % (
|
||||||
|
filename or
|
||||||
|
basename(env['PATH_INFO'])).replace('"', '\\"')))
|
||||||
return start_response(status, headers, exc_info)
|
return start_response(status, headers, exc_info)
|
||||||
|
|
||||||
return self.app(env, _start_response)
|
return self.app(env, _start_response)
|
||||||
@@ -298,7 +319,7 @@ class TempURL(object):
|
|||||||
:param env: The WSGI environment for the request.
|
:param env: The WSGI environment for the request.
|
||||||
:returns: (sig, expires) as described above.
|
:returns: (sig, expires) as described above.
|
||||||
"""
|
"""
|
||||||
temp_url_sig = temp_url_expires = None
|
temp_url_sig = temp_url_expires = filename = None
|
||||||
qs = parse_qs(env.get('QUERY_STRING', ''))
|
qs = parse_qs(env.get('QUERY_STRING', ''))
|
||||||
if 'temp_url_sig' in qs:
|
if 'temp_url_sig' in qs:
|
||||||
temp_url_sig = qs['temp_url_sig'][0]
|
temp_url_sig = qs['temp_url_sig'][0]
|
||||||
@@ -309,7 +330,9 @@ class TempURL(object):
|
|||||||
temp_url_expires = 0
|
temp_url_expires = 0
|
||||||
if temp_url_expires < time():
|
if temp_url_expires < time():
|
||||||
temp_url_expires = 0
|
temp_url_expires = 0
|
||||||
return temp_url_sig, temp_url_expires
|
if 'filename' in qs:
|
||||||
|
filename = qs['filename'][0]
|
||||||
|
return temp_url_sig, temp_url_expires, filename
|
||||||
|
|
||||||
def _get_key(self, env, account):
|
def _get_key(self, env, account):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -93,6 +93,44 @@ class TestTempURL(unittest.TestCase):
|
|||||||
self.assertTrue('Temp URL invalid' not in resp.body)
|
self.assertTrue('Temp URL invalid' not in resp.body)
|
||||||
|
|
||||||
def test_get_valid(self):
|
def test_get_valid(self):
|
||||||
|
method = 'GET'
|
||||||
|
expires = int(time() + 86400)
|
||||||
|
path = '/v1/a/c/o'
|
||||||
|
key = 'abc'
|
||||||
|
hmac_body = '%s\n%s\n%s' % (method, expires, path)
|
||||||
|
sig = hmac.new(key, hmac_body, sha1).hexdigest()
|
||||||
|
req = self._make_request(path,
|
||||||
|
environ={'QUERY_STRING':
|
||||||
|
'temp_url_sig=%s&temp_url_expires=%s' % (sig, expires)})
|
||||||
|
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||||
|
self.tempurl.app = FakeApp(iter([('200 Ok', (), '123')]))
|
||||||
|
resp = req.get_response(self.tempurl)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertEquals(resp.headers['content-disposition'],
|
||||||
|
'attachment; filename="o"')
|
||||||
|
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||||
|
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
||||||
|
|
||||||
|
def test_get_valid_with_filename(self):
|
||||||
|
method = 'GET'
|
||||||
|
expires = int(time() + 86400)
|
||||||
|
path = '/v1/a/c/o'
|
||||||
|
key = 'abc'
|
||||||
|
hmac_body = '%s\n%s\n%s' % (method, expires, path)
|
||||||
|
sig = hmac.new(key, hmac_body, sha1).hexdigest()
|
||||||
|
req = self._make_request(path, environ={
|
||||||
|
'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
|
||||||
|
'filename=bob%%20%%22killer%%22.txt' % (sig, expires)})
|
||||||
|
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||||
|
self.tempurl.app = FakeApp(iter([('200 Ok', (), '123')]))
|
||||||
|
resp = req.get_response(self.tempurl)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertEquals(resp.headers['content-disposition'],
|
||||||
|
'attachment; filename="bob \\\"killer\\\".txt"')
|
||||||
|
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||||
|
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
||||||
|
|
||||||
|
def test_get_valid_but_404(self):
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
expires = int(time() + 86400)
|
expires = int(time() + 86400)
|
||||||
path = '/v1/a/c/o'
|
path = '/v1/a/c/o'
|
||||||
@@ -105,8 +143,7 @@ class TestTempURL(unittest.TestCase):
|
|||||||
req.environ['swift.cache'].set('temp-url-key/a', key)
|
req.environ['swift.cache'].set('temp-url-key/a', key)
|
||||||
resp = req.get_response(self.tempurl)
|
resp = req.get_response(self.tempurl)
|
||||||
self.assertEquals(resp.status_int, 404)
|
self.assertEquals(resp.status_int, 404)
|
||||||
self.assertEquals(resp.headers['content-disposition'],
|
self.assertFalse('content-disposition' in resp.headers)
|
||||||
'attachment; filename=o')
|
|
||||||
self.assertEquals(req.environ['swift.authorize_override'], True)
|
self.assertEquals(req.environ['swift.authorize_override'], True)
|
||||||
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
self.assertEquals(req.environ['REMOTE_USER'], '.wsgi.tempurl')
|
||||||
|
|
||||||
@@ -491,17 +528,21 @@ class TestTempURL(unittest.TestCase):
|
|||||||
s = 'f5d5051bddf5df7e27c628818738334f'
|
s = 'f5d5051bddf5df7e27c628818738334f'
|
||||||
e = int(time() + 86400)
|
e = int(time() + 86400)
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
||||||
'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, e))
|
'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, e, None))
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({}), (None, None))
|
self.assertEquals(self.tempurl._get_temp_url_info({
|
||||||
|
'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s&'
|
||||||
|
'filename=bobisyouruncle' % (s, e)}), (s, e, 'bobisyouruncle'))
|
||||||
|
self.assertEquals(self.tempurl._get_temp_url_info({}),
|
||||||
|
(None, None, None))
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
||||||
'temp_url_expires=%s' % e}), (None, e))
|
'temp_url_expires=%s' % e}), (None, e, None))
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
||||||
'temp_url_sig=%s' % s}), (s, None))
|
'temp_url_sig=%s' % s}), (s, None, None))
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
||||||
'temp_url_sig=%s&temp_url_expires=bad' % s}), (s, 0))
|
'temp_url_sig=%s&temp_url_expires=bad' % s}), (s, 0, None))
|
||||||
e = int(time() - 1)
|
e = int(time() - 1)
|
||||||
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
self.assertEquals(self.tempurl._get_temp_url_info({'QUERY_STRING':
|
||||||
'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, 0))
|
'temp_url_sig=%s&temp_url_expires=%s' % (s, e)}), (s, 0, None))
|
||||||
|
|
||||||
def test_get_key_memcache(self):
|
def test_get_key_memcache(self):
|
||||||
self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
self.app.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||||
|
|||||||
Reference in New Issue
Block a user