Browse code

Support signed URL content disposition type

This allows clients to download with human-friendly names and allows
browsers to properly interpret media like videos.

Andrew Gaul authored on 2015/07/24 05:24:57
Showing 4 changed files
... ...
@@ -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)