This allows clients to download with human-friendly names and allows
browsers to properly interpret media like videos.
... | ... |
@@ -123,6 +123,8 @@ class Config(object): |
123 | 123 |
limitrate = 0 |
124 | 124 |
requester_pays = False |
125 | 125 |
stop_on_error = False |
126 |
+ content_disposition = None |
|
127 |
+ content_type = None |
|
126 | 128 |
|
127 | 129 |
## Creating a singleton |
128 | 130 |
def __new__(self, configfile = None, access_key=None, secret_key=None): |
... | ... |
@@ -55,15 +55,29 @@ __all__.append("sign_url_v2") |
55 | 55 |
|
56 | 56 |
def sign_url_base_v2(**parms): |
57 | 57 |
"""Shared implementation of sign_url methods. Takes a hash of 'bucket', 'object' and 'expiry' as args.""" |
58 |
+ content_disposition=Config.Config().content_disposition |
|
59 |
+ content_type=Config.Config().content_type |
|
58 | 60 |
parms['expiry']=time_to_epoch(parms['expiry']) |
59 | 61 |
parms['access_key']=Config.Config().access_key |
60 | 62 |
parms['host_base']=Config.Config().host_base |
61 | 63 |
debug("Expiry interpreted as epoch time %s", parms['expiry']) |
62 | 64 |
signtext = 'GET\n\n\n%(expiry)d\n/%(bucket)s/%(object)s' % parms |
65 |
+ param_separator = '?' |
|
66 |
+ if content_disposition is not None: |
|
67 |
+ signtext += param_separator + 'response-content-disposition=' + content_disposition |
|
68 |
+ param_separator = '&' |
|
69 |
+ if content_type is not None: |
|
70 |
+ signtext += param_separator + 'response-content-type=' + content_type |
|
71 |
+ param_separator = '&' |
|
63 | 72 |
debug("Signing plaintext: %r", signtext) |
64 | 73 |
parms['sig'] = urllib.quote_plus(sign_string_v2(signtext)) |
65 | 74 |
debug("Urlencoded signature: %s", parms['sig']) |
66 |
- return "http://%(bucket)s.%(host_base)s/%(object)s?AWSAccessKeyId=%(access_key)s&Expires=%(expiry)d&Signature=%(sig)s" % parms |
|
75 |
+ url = "http://%(bucket)s.%(host_base)s/%(object)s?AWSAccessKeyId=%(access_key)s&Expires=%(expiry)d&Signature=%(sig)s" % parms |
|
76 |
+ if content_disposition is not None: |
|
77 |
+ url += "&response-content-disposition=" + urllib.quote_plus(content_disposition) |
|
78 |
+ if content_type is not None: |
|
79 |
+ url += "&response-content-type=" + urllib.quote_plus(content_type) |
|
80 |
+ return url |
|
67 | 81 |
|
68 | 82 |
def sign(key, msg): |
69 | 83 |
return hmac.new(key, encode_to_s3(msg), sha256).digest() |
... | ... |
@@ -557,6 +557,7 @@ if have_wget: |
557 | 557 |
test_s3cmd("sign string", ['sign', 's3cmd'], must_find_re = ["Signature:"]) |
558 | 558 |
test_s3cmd("signurl time", ['signurl', '%s/copy/etc2/Logo.PNG' % pbucket(2), str(int(time.time()) + 60)], must_find_re = ["http://"]) |
559 | 559 |
test_s3cmd("signurl time offset", ['signurl', '%s/copy/etc2/Logo.PNG' % pbucket(2), '+60'], must_find_re = ["https?://"]) |
560 |
+test_s3cmd("signurl content disposition and type", ['signurl', '%s/copy/etc2/Logo.PNG' % pbucket(2), '+60', '--content-disposition=inline; filename=video.mp4', '--content-type=video/mp4'], must_find_re = [ 'response-content-disposition', 'response-content-type' ] ) |
|
560 | 561 |
|
561 | 562 |
## ====== Rename within S3 |
562 | 563 |
test_s3cmd("Rename within S3", ['mv', '%s/copy/etc2/Logo.PNG' % pbucket(2), '%s/copy/etc/logo.png' % pbucket(2)], |
... | ... |
@@ -2333,6 +2333,8 @@ def main(): |
2333 | 2333 |
optparser.add_option( "--requester-pays", dest="requester_pays", action="store_true", help="Set the REQUESTER PAYS flag for operations") |
2334 | 2334 |
optparser.add_option("-l", "--long-listing", dest="long_listing", action="store_true", help="Produce long listing [ls]") |
2335 | 2335 |
optparser.add_option( "--stop-on-error", dest="stop_on_error", action="store", type="string", help="stop if error in transfer") |
2336 |
+ optparser.add_option( "--content-disposition", dest="content_disposition", action="store", help="Provide a Content-Disposition for signed URLs, e.g., \"inline; filename=myvideo.mp4\"") |
|
2337 |
+ optparser.add_option( "--content-type", dest="content_type", action="store", help="Provide a Content-Type for signed URLs, e.g., \"video/mp4\"") |
|
2336 | 2338 |
|
2337 | 2339 |
optparser.set_usage(optparser.usage + " COMMAND [parameters]") |
2338 | 2340 |
optparser.set_description('S3cmd is a tool for managing objects in '+ |
... | ... |
@@ -2531,6 +2533,12 @@ def main(): |
2531 | 2531 |
if options.stop_on_error: |
2532 | 2532 |
cfg.stop_on_error = options.stop_on_error |
2533 | 2533 |
|
2534 |
+ if options.content_disposition: |
|
2535 |
+ cfg.content_disposition = options.content_disposition |
|
2536 |
+ |
|
2537 |
+ if options.content_type: |
|
2538 |
+ cfg.content_type = options.content_type |
|
2539 |
+ |
|
2534 | 2540 |
if len(args) < 1: |
2535 | 2541 |
optparser.print_help() |
2536 | 2542 |
sys.exit(EX_USAGE) |