Browse code

Merge branch 'merge' of git://github.com/mdomsch/s3cmd

Michal Ludvig authored on 2013/12/02 13:08:21
Showing 12 changed files
... ...
@@ -179,6 +179,9 @@ Simple s3cmd HowTo
179 179
    account page. Be careful when copying them! They are 
180 180
    case sensitive and must be entered accurately or you'll 
181 181
    keep getting errors about invalid signatures or similar.
182
+   
183
+   Remember to add ListAllMyBuckets permissions to the keys
184
+   or you will get an AccessDenied error while testing access.
182 185
 
183 186
 3) Run "s3cmd ls" to list all your buckets.
184 187
    As you just started using S3 there are no buckets owned by 
... ...
@@ -146,7 +146,6 @@ class ACL(object):
146 146
         if self.hasGrant(name, permission):
147 147
             return
148 148
 
149
-        name = name.lower()
150 149
         permission = permission.upper()
151 150
 
152 151
         if "ALL" == permission:
... ...
@@ -159,12 +158,17 @@ class ACL(object):
159 159
         grantee.name = name
160 160
         grantee.permission = permission
161 161
 
162
-        if  name.find('@') <= -1: # ultra lame attempt to differenciate emails id from canonical ids
163
-            grantee.xsi_type = "CanonicalUser"
164
-            grantee.tag = "ID"
165
-        else:
162
+        if  name.find('@') > -1:
163
+            grantee.name = grantee.name.lower
166 164
             grantee.xsi_type = "AmazonCustomerByEmail"
167 165
             grantee.tag = "EmailAddress"
166
+        elif name.find('http://acs.amazonaws.com/groups/') > -1:
167
+            grantee.xsi_type = "Group"
168
+            grantee.tag = "URI"
169
+        else:
170
+            grantee.name = grantee.name.lower
171
+            grantee.xsi_type = "CanonicalUser"
172
+            grantee.tag = "ID"
168 173
 
169 174
         self.appendGrantee(grantee)
170 175
 
... ...
@@ -36,8 +36,11 @@ class Config(object):
36 36
     human_readable_sizes = False
37 37
     extra_headers = SortedDict(ignore_case = True)
38 38
     force = False
39
+    server_side_encryption = False
39 40
     enable = None
40 41
     get_continue = False
42
+    put_continue = False
43
+    upload_id = None
41 44
     skip_existing = False
42 45
     recursive = False
43 46
     acl_public = None
... ...
@@ -75,6 +78,7 @@ class Config(object):
75 75
     bucket_location = "US"
76 76
     default_mime_type = "binary/octet-stream"
77 77
     guess_mime_type = True
78
+    use_mime_magic = True
78 79
     mime_type = ""
79 80
     enable_multipart = True
80 81
     multipart_chunk_size_mb = 15    # MB
... ...
@@ -87,6 +91,7 @@ class Config(object):
87 87
     debug_exclude = {}
88 88
     debug_include = {}
89 89
     encoding = "utf-8"
90
+    add_content_encoding = True
90 91
     urlencoding_mode = "normal"
91 92
     log_target_prefix = ""
92 93
     reduced_redundancy = False
... ...
@@ -180,7 +185,7 @@ class Config(object):
180 180
                     if "key" in data:
181 181
                         Config().update_option(data["key"], data["value"])
182 182
                         if data["key"] in ("access_key", "secret_key", "gpg_passphrase"):
183
-                            print_value = (data["value"][:2]+"...%d_chars..."+data["value"][-1:]) % (len(data["value"]) - 3)
183
+                            print_value = ("%s...%d_chars...%s") % (data["value"][:2], len(data["value"]) - 3, data["value"][-1:])
184 184
                         else:
185 185
                             print_value = data["value"]
186 186
                         debug("env_Config: %s->%s" % (data["key"], print_value))
... ...
@@ -274,7 +279,7 @@ class ConfigParser(object):
274 274
                     data["value"] = data["value"][1:-1]
275 275
                 self.__setitem__(data["key"], data["value"])
276 276
                 if data["key"] in ("access_key", "secret_key", "gpg_passphrase"):
277
-                    print_value = (data["value"][:2]+"...%d_chars..."+data["value"][-1:]) % (len(data["value"]) - 3)
277
+                    print_value = ("%s...%d_chars...%s") % (data["value"][:2], len(data["value"]) - 3, data["value"][-1:])
278 278
                 else:
279 279
                     print_value = data["value"]
280 280
                 debug("ConfigParser: %s->%s" % (data["key"], print_value))
... ...
@@ -36,6 +36,7 @@ class FileDict(SortedDict):
36 36
         return md5
37 37
 
38 38
     def record_hardlink(self, relative_file, dev, inode, md5):
39
+        if dev == 0 or inode == 0: return # Windows
39 40
         if dev not in self.hardlinks:
40 41
             self.hardlinks[dev] = dict()
41 42
         if inode not in self.hardlinks[dev]:
... ...
@@ -17,6 +17,7 @@ import os
17 17
 import sys
18 18
 import glob
19 19
 import copy
20
+import re
20 21
 
21 22
 __all__ = ["fetch_local_list", "fetch_remote_list", "compare_filelists", "filter_exclude_include"]
22 23
 
... ...
@@ -250,7 +251,11 @@ def fetch_local_list(args, is_src = False, recursive = None):
250 250
         return loc_list, single_file
251 251
 
252 252
     def _maintain_cache(cache, local_list):
253
-        if cfg.cache_file:
253
+        # if getting the file list from files_from, it is going to be
254
+        # a subset of the actual tree.  We should not purge content
255
+        # outside of that subset as we don't know if it's valid or
256
+        # not.  Leave it to a non-files_from run to purge.
257
+        if cfg.cache_file and len(cfg.files_from) == 0:
254 258
             cache.mark_all_for_purge()
255 259
             for i in local_list.keys():
256 260
                 cache.unmark_for_purge(local_list[i]['dev'], local_list[i]['inode'], local_list[i]['mtime'], local_list[i]['size'])
... ...
@@ -363,7 +368,7 @@ def fetch_remote_list(args, require_attribs = False, recursive = None):
363 363
                 'dev' : None,
364 364
                 'inode' : None,
365 365
             }
366
-            if rem_list[key]['md5'].find("-"): # always get it for multipart uploads
366
+            if rem_list[key]['md5'].find("-") > 0: # always get it for multipart uploads
367 367
                 _get_remote_attribs(S3Uri(object_uri_str), rem_list[key])
368 368
             md5 = rem_list[key]['md5']
369 369
             rem_list.record_md5(key, md5)
... ...
@@ -398,16 +403,12 @@ def fetch_remote_list(args, require_attribs = False, recursive = None):
398 398
             uri_str = str(uri)
399 399
             ## Wildcards used in remote URI?
400 400
             ## If yes we'll need a bucket listing...
401
-            if uri_str.find('*') > -1 or uri_str.find('?') > -1:
402
-                first_wildcard = uri_str.find('*')
403
-                first_questionmark = uri_str.find('?')
404
-                if first_questionmark > -1 and first_questionmark < first_wildcard:
405
-                    first_wildcard = first_questionmark
406
-                prefix = uri_str[:first_wildcard]
407
-                rest = uri_str[first_wildcard+1:]
401
+            wildcard_split_result = re.split("\*|\?", uri_str, maxsplit=1)
402
+            if len(wildcard_split_result) == 2: # wildcards found
403
+                prefix, rest = wildcard_split_result
408 404
                 ## Only request recursive listing if the 'rest' of the URI,
409 405
                 ## i.e. the part after first wildcard, contains '/'
410
-                need_recursion = rest.find('/') > -1
406
+                need_recursion = '/' in rest
411 407
                 objectlist = _get_filelist_remote(S3Uri(prefix), recursive = need_recursion)
412 408
                 for key in objectlist:
413 409
                     ## Check whether the 'key' matches the requested wildcards
... ...
@@ -5,6 +5,7 @@ class HashCache(object):
5 5
         self.inodes = dict()
6 6
 
7 7
     def add(self, dev, inode, mtime, size, md5):
8
+        if dev == 0 or inode == 0: return # Windows
8 9
         if dev not in self.inodes:
9 10
             self.inodes[dev] = dict()
10 11
         if inode not in self.inodes[dev]:
... ...
@@ -27,7 +28,10 @@ class HashCache(object):
27 27
                     self.inodes[d][i][c]['purge'] = True
28 28
 
29 29
     def unmark_for_purge(self, dev, inode, mtime, size):
30
-        d = self.inodes[dev][inode][mtime]
30
+        try:
31
+            d = self.inodes[dev][inode][mtime]
32
+        except KeyError:
33
+            return
31 34
         if d['size'] == size and 'purge' in d:
32 35
             del self.inodes[dev][inode][mtime]['purge']
33 36
 
... ...
@@ -3,10 +3,12 @@
3 3
 ## License: GPL Version 2
4 4
 
5 5
 import os
6
+import sys
6 7
 from stat import ST_SIZE
7 8
 from logging import debug, info, warning, error
8
-from Utils import getTextFromXml, formatSize, unicodise
9
+from Utils import getTextFromXml, getTreeFromXml, formatSize, unicodise, calculateChecksum, parseNodes
9 10
 from Exceptions import S3UploadError
11
+from collections import defaultdict
10 12
 
11 13
 class MultiPartUpload(object):
12 14
 
... ...
@@ -22,15 +24,55 @@ class MultiPartUpload(object):
22 22
         self.headers_baseline = headers_baseline
23 23
         self.upload_id = self.initiate_multipart_upload()
24 24
 
25
+    def get_parts_information(self, uri, upload_id):
26
+        multipart_response = self.s3.list_multipart(uri, upload_id)
27
+        tree = getTreeFromXml(multipart_response['data'])
28
+
29
+        parts = defaultdict(lambda: None)
30
+        for elem in parseNodes(tree):
31
+            try:
32
+                parts[int(elem['PartNumber'])] = {'checksum': elem['ETag'], 'size': elem['Size']}
33
+            except KeyError:
34
+                pass
35
+
36
+        return parts
37
+
38
+    def get_unique_upload_id(self, uri):
39
+        upload_id = None
40
+        multipart_response = self.s3.get_multipart(uri)
41
+        tree = getTreeFromXml(multipart_response['data'])
42
+        for mpupload in parseNodes(tree):
43
+            try:
44
+                mp_upload_id = mpupload['UploadId']
45
+                mp_path = mpupload['Key']
46
+                info("mp_path: %s, object: %s" % (mp_path, uri.object()))
47
+                if mp_path == uri.object():
48
+                    if upload_id is not None:
49
+                        raise ValueError("More than one UploadId for URI %s.  Disable multipart upload, or use\n %s multipart %s\nto list the Ids, then pass a unique --upload-id into the put command." % (uri, sys.argv[0], uri))
50
+                    upload_id = mp_upload_id
51
+            except KeyError:
52
+                pass
53
+
54
+        return upload_id
55
+
25 56
     def initiate_multipart_upload(self):
26 57
         """
27 58
         Begin a multipart upload
28 59
         http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadInitiate.html
29 60
         """
30
-        request = self.s3.create_request("OBJECT_POST", uri = self.uri, headers = self.headers_baseline, extra = "?uploads")
31
-        response = self.s3.send_request(request)
32
-        data = response["data"]
33
-        self.upload_id = getTextFromXml(data, "UploadId")
61
+        if self.s3.config.upload_id is not None:
62
+            self.upload_id = self.s3.config.upload_id
63
+        elif self.s3.config.put_continue:
64
+            self.upload_id = self.get_unique_upload_id(self.uri)
65
+        else:
66
+            self.upload_id = None
67
+
68
+        if self.upload_id is None:
69
+            request = self.s3.create_request("OBJECT_POST", uri = self.uri, headers = self.headers_baseline, extra = "?uploads")
70
+            response = self.s3.send_request(request)
71
+            data = response["data"]
72
+            self.upload_id = getTextFromXml(data, "UploadId")
73
+
34 74
         return self.upload_id
35 75
 
36 76
     def upload_all_parts(self):
... ...
@@ -51,6 +93,10 @@ class MultiPartUpload(object):
51 51
         else:
52 52
             debug("MultiPart: Uploading from %s" % (self.file.name))
53 53
 
54
+        remote_statuses = defaultdict(lambda: None)
55
+        if self.s3.config.put_continue:
56
+            remote_statuses = self.get_parts_information(self.uri, self.upload_id)
57
+
54 58
         seq = 1
55 59
         if self.file.name != "<stdin>":
56 60
             while size_left > 0:
... ...
@@ -63,10 +109,10 @@ class MultiPartUpload(object):
63 63
                     'extra' : "[part %d of %d, %s]" % (seq, nr_parts, "%d%sB" % formatSize(current_chunk_size, human_readable = True))
64 64
                 }
65 65
                 try:
66
-                    self.upload_part(seq, offset, current_chunk_size, labels)
66
+                    self.upload_part(seq, offset, current_chunk_size, labels, remote_status = remote_statuses[seq])
67 67
                 except:
68
-                    error(u"Upload of '%s' part %d failed. Aborting multipart upload." % (self.file.name, seq))
69
-                    self.abort_upload()
68
+                    error(u"\nUpload of '%s' part %d failed. Use\n  %s abortmp %s %s\nto abort the upload, or\n  %s --upload-id %s put ...\nto continue the upload."
69
+                          % (self.file.name, seq, sys.argv[0], self.uri, self.upload_id, sys.argv[0], self.upload_id))
70 70
                     raise
71 71
                 seq += 1
72 72
         else:
... ...
@@ -82,22 +128,38 @@ class MultiPartUpload(object):
82 82
                 if len(buffer) == 0: # EOF
83 83
                     break
84 84
                 try:
85
-                    self.upload_part(seq, offset, current_chunk_size, labels, buffer)
85
+                    self.upload_part(seq, offset, current_chunk_size, labels, buffer, remote_status = remote_statuses[seq])
86 86
                 except:
87
-                    error(u"Upload of '%s' part %d failed. Aborting multipart upload." % (self.file.name, seq))
88
-                    self.abort_upload()
87
+                    error(u"\nUpload of '%s' part %d failed. Use\n  %s abortmp %s %s\nto abort, or\n  %s --upload-id %s put ...\nto continue the upload."
88
+                          % (self.file.name, seq, self.uri, sys.argv[0], self.upload_id, sys.argv[0], self.upload_id))
89 89
                     raise
90 90
                 seq += 1
91 91
 
92 92
         debug("MultiPart: Upload finished: %d parts", seq - 1)
93 93
 
94
-    def upload_part(self, seq, offset, chunk_size, labels, buffer = ''):
94
+    def upload_part(self, seq, offset, chunk_size, labels, buffer = '', remote_status = None):
95 95
         """
96 96
         Upload a file chunk
97 97
         http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadUploadPart.html
98 98
         """
99 99
         # TODO implement Content-MD5
100 100
         debug("Uploading part %i of %r (%s bytes)" % (seq, self.upload_id, chunk_size))
101
+
102
+        if remote_status is not None:
103
+            if int(remote_status['size']) == chunk_size:
104
+                checksum = calculateChecksum(buffer, self.file, offset, chunk_size, self.s3.config.send_chunk)
105
+                remote_checksum = remote_status['checksum'].strip('"')
106
+                if remote_checksum == checksum:
107
+                    warning("MultiPart: size and md5sum match for %s part %d, skipping." % (self.uri, seq))
108
+                    self.parts[seq] = remote_status['checksum']
109
+                    return
110
+                else:
111
+                    warning("MultiPart: checksum (%s vs %s) does not match for %s part %d, reuploading."
112
+                            % (remote_checksum, checksum, self.uri, seq))
113
+            else:
114
+                warning("MultiPart: size (%d vs %d) does not match for %s part %d, reuploading."
115
+                        % (int(remote_status['size']), chunk_size, self.uri, seq))
116
+
101 117
         headers = { "content-length": chunk_size }
102 118
         query_string = "?partNumber=%i&uploadId=%s" % (seq, self.upload_id)
103 119
         request = self.s3.create_request("OBJECT_PUT", uri = self.uri, headers = headers, extra = query_string)
... ...
@@ -130,8 +192,19 @@ class MultiPartUpload(object):
130 130
         http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?mpUploadAbort.html
131 131
         """
132 132
         debug("MultiPart: Aborting upload: %s" % self.upload_id)
133
-        request = self.s3.create_request("OBJECT_DELETE", uri = self.uri, extra = "?uploadId=%s" % (self.upload_id))
134
-        response = self.s3.send_request(request)
133
+        #request = self.s3.create_request("OBJECT_DELETE", uri = self.uri, extra = "?uploadId=%s" % (self.upload_id))
134
+        #response = self.s3.send_request(request)
135
+        response = None
135 136
         return response
136 137
 
137 138
 # vim:et:ts=4:sts=4:ai
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
... ...
@@ -79,7 +79,7 @@ class Progress(object):
79 79
             self._stdout.flush()
80 80
             return
81 81
 
82
-        rel_position = selfself.current_position * 100 / self.total_size
82
+        rel_position = self.current_position * 100 / self.total_size
83 83
         if rel_position >= self.last_milestone:
84 84
             self.last_milestone = (int(rel_position) / 5) * 5
85 85
             self._stdout.write("%d%% ", self.last_milestone)
... ...
@@ -403,12 +403,16 @@ class S3(object):
403 403
         headers = SortedDict(ignore_case = True)
404 404
         if extra_headers:
405 405
             headers.update(extra_headers)
406
-
406
+        
407
+        ## Set server side encryption
408
+        if self.config.server_side_encryption:
409
+            headers["x-amz-server-side-encryption"] = "AES256"
410
+    
407 411
         ## MIME-type handling
408 412
         content_type = self.config.mime_type
409 413
         content_encoding = None
410 414
         if filename != "-" and not content_type and self.config.guess_mime_type:
411
-            (content_type, content_encoding) = mime_magic(filename)
415
+            (content_type, content_encoding) = mime_magic(filename) if self.config.use_mime_magic else mimetypes.guess_type(filename)
412 416
         if not content_type:
413 417
             content_type = self.config.default_mime_type
414 418
 
... ...
@@ -417,7 +421,7 @@ class S3(object):
417 417
             content_type = content_type + "; charset=" + self.config.encoding.upper()
418 418
 
419 419
         headers["content-type"] = content_type
420
-        if content_encoding is not None:
420
+        if content_encoding is not None and self.config.add_content_encoding:
421 421
             headers["content-encoding"] = content_encoding
422 422
 
423 423
         ## Other Amazon S3 attributes
... ...
@@ -438,6 +442,31 @@ class S3(object):
438 438
             return self.send_file_multipart(file, headers, uri, size)
439 439
 
440 440
         ## Not multipart...
441
+        if self.config.put_continue:
442
+            # Note, if input was stdin, we would be performing multipart upload.
443
+            # So this will always work as long as the file already uploaded was
444
+            # not uploaded via MultiUpload, in which case its ETag will not be
445
+            # an md5.
446
+            try:
447
+                info = self.object_info(uri)
448
+            except:
449
+                info = None
450
+
451
+            if info is not None:
452
+                remote_size = int(info['headers']['content-length'])
453
+                remote_checksum = info['headers']['etag'].strip('"')
454
+                if size == remote_size:
455
+                    checksum = calculateChecksum('', file, 0, size, self.config.send_chunk)
456
+                    if remote_checksum == checksum:
457
+                        warning("Put: size and md5sum match for %s, skipping." % uri)
458
+                        return
459
+                    else:
460
+                        warning("MultiPart: checksum (%s vs %s) does not match for %s, reuploading."
461
+                                % (remote_checksum, checksum, uri))
462
+                else:
463
+                    warning("MultiPart: size (%d vs %d) does not match for %s, reuploading."
464
+                            % (remote_size, size, uri))
465
+
441 466
         headers["content-length"] = size
442 467
         request = self.create_request("OBJECT_PUT", uri = uri, headers = headers)
443 468
         labels = { 'source' : unicodise(filename), 'destination' : unicodise(uri.uri()), 'extra' : extra_label }
... ...
@@ -474,6 +503,11 @@ class S3(object):
474 474
             headers["x-amz-storage-class"] = "REDUCED_REDUNDANCY"
475 475
         # if extra_headers:
476 476
         #   headers.update(extra_headers)
477
+        
478
+        ## Set server side encryption
479
+        if self.config.server_side_encryption:
480
+            headers["x-amz-server-side-encryption"] = "AES256" 
481
+        
477 482
         request = self.create_request("OBJECT_PUT", uri = dst_uri, headers = headers)
478 483
         response = self.send_request(request)
479 484
         return response
... ...
@@ -535,6 +569,23 @@ class S3(object):
535 535
         response = self.send_request(request)
536 536
         return response
537 537
 
538
+    def get_multipart(self, uri):
539
+        request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?uploads")
540
+        response = self.send_request(request)
541
+        return response
542
+
543
+    def abort_multipart(self, uri, id):
544
+        request = self.create_request("OBJECT_DELETE", uri=uri,
545
+                                      extra = ("?uploadId=%s" % id))
546
+        response = self.send_request(request)
547
+        return response
548
+
549
+    def list_multipart(self, uri, id):
550
+        request = self.create_request("OBJECT_GET", uri=uri,
551
+                                      extra = ("?uploadId=%s" % id))
552
+        response = self.send_request(request)
553
+        return response
554
+
538 555
     def get_accesslog(self, uri):
539 556
         request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?logging")
540 557
         response = self.send_request(request)
... ...
@@ -739,6 +790,7 @@ class S3(object):
739 739
         if buffer == '':
740 740
             file.seek(offset)
741 741
         md5_hash = md5()
742
+
742 743
         try:
743 744
             while (size_left > 0):
744 745
                 #debug("SendFile: Reading up to %d bytes from '%s' - remaining bytes: %s" % (self.config.send_chunk, file.name, size_left))
... ...
@@ -746,6 +798,7 @@ class S3(object):
746 746
                     data = file.read(min(self.config.send_chunk, size_left))
747 747
                 else:
748 748
                     data = buffer
749
+
749 750
                 md5_hash.update(data)
750 751
                 conn.c.send(data)
751 752
                 if self.config.progress_meter:
... ...
@@ -754,6 +807,7 @@ class S3(object):
754 754
                 if throttle:
755 755
                     time.sleep(throttle)
756 756
             md5_computed = md5_hash.hexdigest()
757
+
757 758
             response = {}
758 759
             http_response = conn.c.getresponse()
759 760
             response["status"] = http_response.status
... ...
@@ -217,11 +217,11 @@ def mktmpsomething(prefix, randchars, createfunc):
217 217
     return dirname
218 218
 __all__.append("mktmpsomething")
219 219
 
220
-def mktmpdir(prefix = "/tmp/tmpdir-", randchars = 10):
220
+def mktmpdir(prefix = os.getenv('TMP','/tmp') + "/tmpdir-", randchars = 10):
221 221
     return mktmpsomething(prefix, randchars, os.mkdir)
222 222
 __all__.append("mktmpdir")
223 223
 
224
-def mktmpfile(prefix = "/tmp/tmpfile-", randchars = 20):
224
+def mktmpfile(prefix = os.getenv('TMP','/tmp') + "/tmpfile-", randchars = 20):
225 225
     createfunc = lambda filename : os.close(os.open(filename, os.O_CREAT | os.O_EXCL))
226 226
     return mktmpsomething(prefix, randchars, createfunc)
227 227
 __all__.append("mktmpfile")
... ...
@@ -383,7 +383,7 @@ def time_to_epoch(t):
383 383
         return int(time.mktime(t))
384 384
     elif hasattr(t, 'timetuple'):
385 385
         # Looks like a datetime object or compatible
386
-        return int(time.mktime(ex.timetuple()))
386
+        return int(time.mktime(t.timetuple()))
387 387
     elif hasattr(t, 'strftime'):
388 388
         # Looks like the object supports standard srftime()
389 389
         return int(t.strftime('%s'))
... ...
@@ -459,4 +459,22 @@ def getHostnameFromBucket(bucket):
459 459
     return Config.Config().host_bucket % { 'bucket' : bucket }
460 460
 __all__.append("getHostnameFromBucket")
461 461
 
462
+
463
+def calculateChecksum(buffer, mfile, offset, chunk_size, send_chunk):
464
+    md5_hash = md5()
465
+    size_left = chunk_size
466
+    if buffer == '':
467
+        mfile.seek(offset)
468
+        while size_left > 0:
469
+            data = mfile.read(min(send_chunk, size_left))
470
+            md5_hash.update(data)
471
+            size_left -= len(data)
472
+    else:
473
+        md5_hash.update(buffer)
474
+
475
+    return md5_hash.hexdigest()
476
+
477
+
478
+__all__.append("calculateChecksum")
479
+
462 480
 # vim:et:ts=4:sts=4:ai
... ...
@@ -339,14 +339,15 @@ def cmd_object_put(args):
339 339
         except InvalidFileError, e:
340 340
             warning(u"File can not be uploaded: %s" % e)
341 341
             continue
342
-        speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
343
-        if not Config().progress_meter:
344
-            output(u"File '%s' stored as '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
345
-                (unicodise(full_name_orig), uri_final, response["size"], response["elapsed"],
346
-                speed_fmt[0], speed_fmt[1], seq_label))
342
+        if response is not None:
343
+            speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
344
+            if not Config().progress_meter:
345
+                output(u"File '%s' stored as '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
346
+                       (unicodise(full_name_orig), uri_final, response["size"], response["elapsed"],
347
+                        speed_fmt[0], speed_fmt[1], seq_label))
347 348
         if Config().acl_public:
348 349
             output(u"Public URL of the object is: %s" %
349
-                (uri_final.public_url()))
350
+                   (uri_final.public_url()))
350 351
         if Config().encrypt and full_name != full_name_orig:
351 352
             debug(u"Removing temporary encrypted file: %s" % unicodise(full_name))
352 353
             os.remove(full_name)
... ...
@@ -625,6 +626,11 @@ def cmd_info(args):
625 625
                 except KeyError:
626 626
                     pass
627 627
                 output(u"   MD5 sum:   %s" % md5)
628
+                if 'x-amz-server-side-encryption' in info['headers']:
629
+                    output(u"   SSE:       %s" % info['headers']['x-amz-server-side-encryption'])
630
+                else:
631
+                    output(u"   SSE:       NONE")		
632
+
628 633
             else:
629 634
                 info = s3.bucket_info(uri)
630 635
                 output(u"%s (bucket):" % uri.uri())
... ...
@@ -751,7 +757,14 @@ def cmd_sync_remote2remote(args):
751 751
     seq = 0
752 752
     seq = _upload(src_list, seq, src_count + update_count)
753 753
     seq = _upload(update_list, seq, src_count + update_count)
754
-    n_copied, bytes_saved = remote_copy(s3, copy_pairs, destination_base)
754
+    n_copied, bytes_saved, failed_copy_files = remote_copy(s3, copy_pairs, destination_base)
755
+
756
+    #process files not copied
757
+    output("Process files that was not remote copied")
758
+    failed_copy_count = len (failed_copy_files) 
759
+    for key in failed_copy_files:
760
+        failed_copy_files[key]['target_uri'] = destination_base + key
761
+    seq = _upload(failed_copy_files, seq, failed_copy_count)
755 762
 
756 763
     total_elapsed = time.time() - timestamp_start
757 764
     outstr = "Done. Copied %d files in %0.1f seconds, %0.2f files/s" % (seq, total_elapsed, seq/total_elapsed)
... ...
@@ -797,6 +810,7 @@ def cmd_sync_remote2local(args):
797 797
 
798 798
     info(u"Summary: %d remote files to download, %d local files to delete, %d local files to hardlink" % (remote_count + update_count, local_count, copy_pairs_count))
799 799
 
800
+    empty_fname_re = re.compile(r'\A\s*\Z')
800 801
     def _set_local_filename(remote_list, destination_base):
801 802
         if len(remote_list) == 0:
802 803
             return
... ...
@@ -809,7 +823,12 @@ def cmd_sync_remote2local(args):
809 809
             if destination_base[-1] != os.path.sep:
810 810
                 destination_base += os.path.sep
811 811
             for key in remote_list:
812
-                local_filename = destination_base + key
812
+                local_basename = key
813
+                if empty_fname_re.match(key):
814
+                    # Objects may exist on S3 with empty names (''), which don't map so well to common filesystems.
815
+                    local_basename = '__AWS-EMPTY-OBJECT-NAME__'
816
+                    warning(u"Empty object name on S3 found, saving locally as %s" % (local_basename))
817
+                local_filename = destination_base + local_basename
813 818
                 if os.path.sep != "/":
814 819
                     local_filename = os.path.sep.join(local_filename.split("/"))
815 820
                 remote_list[key]['local_filename'] = deunicodise(local_filename)
... ...
@@ -887,7 +906,10 @@ def cmd_sync_remote2local(args):
887 887
                             mtime = attrs.has_key('mtime') and int(attrs['mtime']) or int(time.time())
888 888
                             atime = attrs.has_key('atime') and int(attrs['atime']) or int(time.time())
889 889
                             os.utime(dst_file, (atime, mtime))
890
-                        ## FIXME: uid/gid / uname/gname handling comes here! TODO
890
+                        if attrs.has_key('uid') and attrs.has_key('gid'):
891
+                            uid = int(attrs['uid'])
892
+                            gid = int(attrs['gid'])
893
+                            os.lchown(dst_file,uid,gid)				
891 894
                 except OSError, e:
892 895
                     try:
893 896
                         dst_stream.close()
... ...
@@ -986,6 +1008,7 @@ def local_copy(copy_pairs, destination_base):
986 986
 
987 987
 def remote_copy(s3, copy_pairs, destination_base):
988 988
     saved_bytes = 0
989
+    failed_copy_list = FileDict()
989 990
     for (src_obj, dst1, dst2) in copy_pairs:
990 991
         debug(u"Remote Copying from %s to %s" % (dst1, dst2))
991 992
         dst1_uri = S3Uri(destination_base + dst1)
... ...
@@ -997,8 +1020,9 @@ def remote_copy(s3, copy_pairs, destination_base):
997 997
             saved_bytes = saved_bytes + int(info['headers']['content-length'])
998 998
             output(u"remote copy: %s -> %s" % (dst1, dst2))
999 999
         except:
1000
-            raise
1001
-    return (len(copy_pairs), saved_bytes)
1000
+            warning(u'Unable to remote copy files %s -> %s' % (dst1_uri, dst2_uri))
1001
+            failed_copy_list[dst2] = src_obj    
1002
+    return (len(copy_pairs), saved_bytes, failed_copy_list)
1002 1003
 
1003 1004
 def _build_attr_header(local_list, src):
1004 1005
     import pwd, grp
... ...
@@ -1188,7 +1212,14 @@ def cmd_sync_local2remote(args):
1188 1188
         timestamp_start = time.time()
1189 1189
         n, total_size = _upload(local_list, 0, local_count, total_size)
1190 1190
         n, total_size = _upload(update_list, n, local_count, total_size)
1191
-        n_copies, saved_bytes = remote_copy(s3, copy_pairs, destination_base)
1191
+        n_copies, saved_bytes, failed_copy_files  = remote_copy(s3, copy_pairs, destination_base)
1192
+
1193
+        #upload file that could not be copied
1194
+        output("Process files that was not remote copied")
1195
+        failed_copy_count = len(failed_copy_files)
1196
+        _set_remote_uri(failed_copy_files, destination_base, single_file_local)
1197
+        n, total_size = _upload(failed_copy_files, n, failed_copy_count, total_size)
1198
+
1192 1199
         if cfg.delete_removed and cfg.delete_after:
1193 1200
             _do_deletes(s3, remote_list)
1194 1201
         total_elapsed = time.time() - timestamp_start
... ...
@@ -1331,6 +1362,50 @@ def cmd_delpolicy(args):
1331 1331
     output(u"%s: Policy deleted" % uri)
1332 1332
 
1333 1333
 
1334
+def cmd_multipart(args):
1335
+    s3 = S3(cfg)
1336
+    uri = S3Uri(args[0])
1337
+
1338
+    #id = ''
1339
+    #if(len(args) > 1): id = args[1]
1340
+
1341
+    response = s3.get_multipart(uri)
1342
+    debug(u"response - %s" % response['status'])
1343
+    output(u"%s" % uri)
1344
+    tree = getTreeFromXml(response['data'])
1345
+    debug(parseNodes(tree))
1346
+    output(u"Initiated\tPath\tId")
1347
+    for mpupload in parseNodes(tree):
1348
+        try:
1349
+            output("%s\t%s\t%s" % (mpupload['Initiated'], "s3://" + uri.bucket() + "/" + mpupload['Key'], mpupload['UploadId']))
1350
+        except KeyError:
1351
+            pass
1352
+
1353
+def cmd_abort_multipart(args):
1354
+    '''{"cmd":"abortmp",   "label":"abort a multipart upload", "param":"s3://BUCKET Id", "func":cmd_abort_multipart, "argc":2},'''
1355
+    s3 = S3(cfg)
1356
+    uri = S3Uri(args[0])
1357
+    id = args[1]
1358
+    response = s3.abort_multipart(uri, id)
1359
+    debug(u"response - %s" % response['status'])
1360
+    output(u"%s" % uri)
1361
+
1362
+def cmd_list_multipart(args):
1363
+    '''{"cmd":"abortmp",   "label":"list a multipart upload", "param":"s3://BUCKET Id", "func":cmd_list_multipart, "argc":2},'''
1364
+    s3 = S3(cfg)
1365
+    uri = S3Uri(args[0])
1366
+    id = args[1]
1367
+
1368
+    response = s3.list_multipart(uri, id)
1369
+    debug(u"response - %s" % response['status'])
1370
+    tree = getTreeFromXml(response['data'])
1371
+    output(u"LastModified\t\t\tPartNumber\tETag\tSize")
1372
+    for mpupload in parseNodes(tree):
1373
+        try:
1374
+            output("%s\t%s\t%s\t%s" % (mpupload['LastModified'], mpupload['PartNumber'], mpupload['ETag'], mpupload['Size']))
1375
+        except:
1376
+            pass
1377
+
1334 1378
 def cmd_accesslog(args):
1335 1379
     s3 = S3(cfg)
1336 1380
     bucket_uri = S3Uri(args.pop())
... ...
@@ -1587,6 +1662,8 @@ def run_configure(config_file, args):
1587 1587
 
1588 1588
                 except Exception, e:
1589 1589
                     error(u"Test failed: %s" % (e))
1590
+                    if e.find('403') != -1:
1591
+                        error(u"Are you sure your keys have ListAllMyBuckets permissions?")
1590 1592
                     val = raw_input("\nRetry configuration? [Y/n] ")
1591 1593
                     if val.lower().startswith("y") or val == "":
1592 1594
                         continue
... ...
@@ -1685,6 +1762,11 @@ def get_commands_list():
1685 1685
     {"cmd":"setpolicy", "label":"Modify Bucket Policy", "param":"FILE s3://BUCKET", "func":cmd_setpolicy, "argc":2},
1686 1686
     {"cmd":"delpolicy", "label":"Delete Bucket Policy", "param":"s3://BUCKET", "func":cmd_delpolicy, "argc":1},
1687 1687
 
1688
+    {"cmd":"multipart", "label":"show multipart uploads", "param":"s3://BUCKET [Id]", "func":cmd_multipart, "argc":1},
1689
+    {"cmd":"abortmp",   "label":"abort a multipart upload", "param":"s3://BUCKET/OBJECT Id", "func":cmd_abort_multipart, "argc":2},
1690
+
1691
+    {"cmd":"listmp",    "label":"list parts of a multipart upload", "param":"s3://BUCKET/OBJECT Id", "func":cmd_list_multipart, "argc":2},
1692
+
1688 1693
     {"cmd":"accesslog", "label":"Enable/disable bucket access logging", "param":"s3://BUCKET", "func":cmd_accesslog, "argc":1},
1689 1694
     {"cmd":"sign", "label":"Sign arbitrary string using the secret key", "param":"STRING-TO-SIGN", "func":cmd_sign, "argc":1},
1690 1695
     {"cmd":"signurl", "label":"Sign an S3 URL to provide limited public access with expiry", "param":"s3://BUCKET/OBJECT expiry_epoch", "func":cmd_signurl, "argc":2},
... ...
@@ -1813,7 +1895,7 @@ def main():
1813 1813
     optparser.set_defaults(config = config_file)
1814 1814
     optparser.set_defaults(verbosity = default_verbosity)
1815 1815
 
1816
-    optparser.add_option(      "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool. Optionally use as '--configure s3://come-bucket' to test access to a specific bucket instead of attempting to list them all.")
1816
+    optparser.add_option(      "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool. Optionally use as '--configure s3://some-bucket' to test access to a specific bucket instead of attempting to list them all.")
1817 1817
     optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to %default")
1818 1818
     optparser.add_option(      "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsing config files and command line options and exit.")
1819 1819
     optparser.add_option(      "--access_key", dest="access_key", help="AWS Access Key")
... ...
@@ -1825,6 +1907,8 @@ def main():
1825 1825
     optparser.add_option(      "--no-encrypt", dest="encrypt", action="store_false", help="Don't encrypt files.")
1826 1826
     optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.")
1827 1827
     optparser.add_option(      "--continue", dest="get_continue", action="store_true", help="Continue getting a partially downloaded file (only for [get] command).")
1828
+    optparser.add_option(      "--continue-put", dest="put_continue", action="store_true", help="Continue uploading partially uploaded files or multipart upload parts.  Restarts/parts files that don't have matching size and md5.  Skips files/parts that do.  Note: md5sum checks are not always sufficient to check (part) file equality.  Enable this at your own risk.")
1829
+    optparser.add_option(      "--upload-id", dest="upload_id", help="UploadId for Multipart Upload, in case you want continue an existing upload (equivalent to --continue-put) and there are multiple partial uploads.  Use s3cmd multipart [URI] to see what UploadIds are associated with the given URI.")
1828 1830
     optparser.add_option(      "--skip-existing", dest="skip_existing", action="store_true", help="Skip over files that exist at the destination (only for [get] and [sync] commands).")
1829 1831
     optparser.add_option("-r", "--recursive", dest="recursive", action="store_true", help="Recursive upload, download or removal.")
1830 1832
     optparser.add_option(      "--check-md5", dest="check_md5", action="store_true", help="Check MD5 sums when comparing files for [sync]. (default)")
... ...
@@ -1860,14 +1944,18 @@ def main():
1860 1860
     optparser.add_option(      "--access-logging-target-prefix", dest="log_target_prefix", help="Target prefix for access logs (S3 URI) (for [cfmodify] and [accesslog] commands)")
1861 1861
     optparser.add_option(      "--no-access-logging", dest="log_target_prefix", action="store_false", help="Disable access logging (for [cfmodify] and [accesslog] commands)")
1862 1862
 
1863
-    optparser.add_option(      "--default-mime-type", dest="default_mime_type", action="store_true", help="Default MIME-type for stored objects. Application default is binary/octet-stream.")
1863
+    optparser.add_option(      "--default-mime-type", dest="default_mime_type", type="mimetype", action="store", help="Default MIME-type for stored objects. Application default is binary/octet-stream.")
1864 1864
     optparser.add_option("-M", "--guess-mime-type", dest="guess_mime_type", action="store_true", help="Guess MIME-type of files by their extension or mime magic. Fall back to default MIME-Type as specified by --default-mime-type option")
1865 1865
     optparser.add_option(      "--no-guess-mime-type", dest="guess_mime_type", action="store_false", help="Don't guess MIME-type and use the default type instead.")
1866
+    optparser.add_option(      "--no-mime-magic", dest="use_mime_magic", action="store_false", help="Don't use mime magic when guessing MIME-type.")
1866 1867
     optparser.add_option("-m", "--mime-type", dest="mime_type", type="mimetype", metavar="MIME/TYPE", help="Force MIME-type. Override both --default-mime-type and --guess-mime-type.")
1867 1868
 
1868 1869
     optparser.add_option(      "--add-header", dest="add_header", action="append", metavar="NAME:VALUE", help="Add a given HTTP header to the upload request. Can be used multiple times. For instance set 'Expires' or 'Cache-Control' headers (or both) using this options if you like.")
1869 1870
 
1871
+    optparser.add_option(      "--server-side-encryption", dest="server_side_encryption", action="store_true", help="Specifies that server-side encryption will be used when putting objects.")
1872
+
1870 1873
     optparser.add_option(      "--encoding", dest="encoding", metavar="ENCODING", help="Override autodetected terminal and filesystem encoding (character set). Autodetected: %s" % preferred_encoding)
1874
+    optparser.add_option(      "--disable-content-encoding", dest="add_content_encoding", action="store_false", help="Don't include a Content-encoding header to the the uploaded objects")
1871 1875
     optparser.add_option(      "--add-encoding-exts", dest="add_encoding_exts", metavar="EXTENSIONs", help="Add encoding to these comma delimited extensions i.e. (css,js,html) when uploading to S3 )")
1872 1876
     optparser.add_option(      "--verbatim", dest="urlencoding_mode", action="store_const", const="verbatim", help="Use the S3 name as given on the command line. No pre-processing, encoding, etc. Use with caution!")
1873 1877
 
... ...
@@ -1906,7 +1994,7 @@ def main():
1906 1906
         '"buckets" and uploading, downloading and removing '+
1907 1907
         '"objects" from these buckets.')
1908 1908
     optparser.epilog = format_commands(optparser.get_prog_name(), commands_list)
1909
-    optparser.epilog += ("\nFor more informations see the progect homepage:\n%s\n" % PkgInfo.url)
1909
+    optparser.epilog += ("\nFor more information see the project homepage:\n%s\n" % PkgInfo.url)
1910 1910
     optparser.epilog += ("\nConsider a donation if you have found s3cmd useful:\n%s/donate\n" % PkgInfo.url)
1911 1911
 
1912 1912
     (options, args) = optparser.parse_args()
... ...
@@ -2017,6 +2105,14 @@ def main():
2017 2017
     if cfg.multipart_chunk_size_mb > MultiPartUpload.MAX_CHUNK_SIZE_MB:
2018 2018
         raise ParameterError("Chunk size %d MB is too large, must be <= %d MB. Please adjust --multipart-chunk-size-mb" % (cfg.multipart_chunk_size_mb, MultiPartUpload.MAX_CHUNK_SIZE_MB))
2019 2019
 
2020
+    ## If an UploadId was provided, set put_continue True
2021
+    if options.upload_id is not None:
2022
+        cfg.upload_id = options.upload_id
2023
+        cfg.put_continue = True
2024
+
2025
+    if cfg.upload_id and not cfg.multipart_chunk_size_mb:
2026
+        raise ParameterError("Must have --multipart-chunk-size-mb if using --put-continue or --upload-id")
2027
+
2020 2028
     ## CloudFront's cf_enable and Config's enable share the same --enable switch
2021 2029
     options.cf_enable = options.enable
2022 2030
 
... ...
@@ -1,4 +1,3 @@
1
-
2 1
 .TH s3cmd 1
3 2
 .SH NAME
4 3
 s3cmd \- tool for managing Amazon S3 storage space and Amazon CloudFront content delivery network
... ...
@@ -66,7 +65,7 @@ Delete Bucket Policy
66 66
 s3cmd \fBaccesslog\fR \fIs3://BUCKET\fR
67 67
 Enable/disable bucket access logging
68 68
 .TP
69
-s3cmd \fBsign\fR \fISTRING-TO-SIGN\fR
69
+s3cmd \fBsign\fR \fISTRING\-TO\-SIGN\fR
70 70
 Sign arbitrary string using the secret key
71 71
 .TP
72 72
 s3cmd \fBsignurl\fR \fIs3://BUCKET/OBJECT expiry_epoch\fR
... ...
@@ -79,13 +78,13 @@ Fix invalid file names in a bucket
79 79
 .PP
80 80
 Commands for static WebSites configuration
81 81
 .TP
82
-s3cmd \fBws-create\fR \fIs3://BUCKET\fR
82
+s3cmd \fBws\-create\fR \fIs3://BUCKET\fR
83 83
 Create Website from bucket
84 84
 .TP
85
-s3cmd \fBws-delete\fR \fIs3://BUCKET\fR
85
+s3cmd \fBws\-delete\fR \fIs3://BUCKET\fR
86 86
 Delete Website
87 87
 .TP
88
-s3cmd \fBws-info\fR \fIs3://BUCKET\fR
88
+s3cmd \fBws\-info\fR \fIs3://BUCKET\fR
89 89
 Info about Website
90 90
 
91 91
 
... ...
@@ -124,7 +123,7 @@ changes you like.
124 124
 show this help message and exit
125 125
 .TP
126 126
 \fB\-\-configure\fR
127
-Invoke interactive (re)configuration tool. Optionally use as '--configure s3://come-bucket' to test access to a specific bucket instead of attempting to list them all.
127
+Invoke interactive (re)configuration tool. Optionally use as '\-\-configure s3://come\-bucket' to test access to a specific bucket instead of attempting to list them all.
128 128
 .TP
129 129
 \fB\-c\fR FILE, \fB\-\-config\fR=FILE
130 130
 Config file name. Defaults to /home/mludvig/.s3cfg
... ...
@@ -208,32 +207,32 @@ Don't store FS attributes
208 208
 Filenames and paths matching GLOB will be excluded from sync
209 209
 .TP
210 210
 \fB\-\-exclude\-from\fR=FILE
211
-Read --exclude GLOBs from FILE
211
+Read \-\-exclude GLOBs from FILE
212 212
 .TP
213 213
 \fB\-\-rexclude\fR=REGEXP
214 214
 Filenames and paths matching REGEXP (regular expression) will be excluded from sync
215 215
 .TP
216 216
 \fB\-\-rexclude\-from\fR=FILE
217
-Read --rexclude REGEXPs from FILE
217
+Read \-\-rexclude REGEXPs from FILE
218 218
 .TP
219 219
 \fB\-\-include\fR=GLOB
220
-Filenames and paths matching GLOB will be included even if previously excluded by one of --(r)exclude(-from) patterns
220
+Filenames and paths matching GLOB will be included even if previously excluded by one of \-\-(r)exclude(\-from) patterns
221 221
 .TP
222 222
 \fB\-\-include\-from\fR=FILE
223
-Read --include GLOBs from FILE
223
+Read \-\-include GLOBs from FILE
224 224
 .TP
225 225
 \fB\-\-rinclude\fR=REGEXP
226
-Same as --include but uses REGEXP (regular expression) instead of GLOB
226
+Same as \-\-include but uses REGEXP (regular expression) instead of GLOB
227 227
 .TP
228 228
 \fB\-\-rinclude\-from\fR=FILE
229
-Read --rinclude REGEXPs from FILE
229
+Read \-\-rinclude REGEXPs from FILE
230 230
 .TP
231 231
 \fB\-\-files\-from\fR=FILE
232
-Read list of source-file names from FILE. Use - to read from stdin.
232
+Read list of source-file names from FILE. Use \- to read from stdin.
233 233
 May be repeated.
234 234
 .TP
235 235
 \fB\-\-bucket\-location\fR=BUCKET_LOCATION
236
-Datacentre to create bucket in. As of now the datacenters are: US (default), EU, ap-northeast-1, ap-southeast-1, sa-east-1, us-west-1 and us-west-2
236
+Datacentre to create bucket in. As of now the datacenters are: US (default), EU, ap\-northeast\-1, ap\-southeast\-1, sa\-east\-1, us\-west\-1 and us\-west\-2
237 237
 .TP
238 238
 \fB\-\-reduced\-redundancy\fR, \fB\-\-rr\fR
239 239
 Store object with 'Reduced redundancy'. Lower per-GB price. [put, cp, mv]
... ...
@@ -245,22 +244,28 @@ Target prefix for access logs (S3 URI) (for [cfmodify] and [accesslog] commands)
245 245
 Disable access logging (for [cfmodify] and [accesslog] commands)
246 246
 .TP
247 247
 \fB\-\-default\-mime\-type\fR
248
-Default MIME-type for stored objects. Application default is binary/octet-stream.
248
+Default MIME-type for stored objects. Application default is binary/octet\-stream.
249 249
 .TP
250 250
 \fB\-M\fR, \fB\-\-guess\-mime\-type\fR
251
-Guess MIME-type of files by their extension or mime magic. Fall back to default MIME-Type as specified by \fB--default-mime-type\fR option
251
+Guess MIME-type of files by their extension or mime magic. Fall back to default MIME-type as specified by \fB\-\-default\-mime\-type\fR option
252 252
 .TP
253 253
 \fB\-\-no\-guess\-mime\-type\fR
254 254
 Don't guess MIME-type and use the default type instead.
255 255
 .TP
256
+\fB\-\-no\-mime\-magic\fR
257
+Don't use mime magic when guessing MIME-type.
258
+.TP
256 259
 \fB\-m\fR MIME/TYPE, \fB\-\-mime\-type\fR=MIME/TYPE
257
-Force MIME-type. Override both \fB--default-mime-type\fR and \fB--guess-mime-type\fR.
260
+Force MIME-type. Override both \fB\-\-default\-mime\-type\fR and \fB\-\-guess\-mime\-type\fR.
258 261
 .TP
259 262
 \fB\-\-add\-header\fR=NAME:VALUE
260
-Add a given HTTP header to the upload request. Can be used multiple times. For instance set 'Expires' or 'Cache-Control' headers (or both) using this options if you like.
263
+Add a given HTTP header to the upload request. Can be used multiple times. For instance set 'Expires' or 'Cache\-Control' headers (or both) using this options if you like.
261 264
 .TP
262 265
 \fB\-\-encoding\fR=ENCODING
263
-Override autodetected terminal and filesystem encoding (character set). Autodetected: UTF-8
266
+Override autodetected terminal and filesystem encoding (character set). Autodetected: UTF\-8
267
+.TP
268
+\fB\-\-disable\-content\-encoding\fR
269
+Don't include a Content-encoding header to the the uploaded objects. Default: Off
264 270
 .TP
265 271
 \fB\-\-add\-encoding\-exts\fR=EXTENSIONs
266 272
 Add encoding to these comma delimited extensions i.e. (css,js,html) when uploading to S3 )
... ...
@@ -269,11 +274,11 @@ Add encoding to these comma delimited extensions i.e. (css,js,html) when uploadi
269 269
 Use the S3 name as given on the command line. No pre-processing, encoding, etc. Use with caution!
270 270
 .TP
271 271
 \fB\-\-disable\-multipart\fR
272
-Disable multipart upload on files bigger than --multipart-chunk-size-mb
272
+Disable multipart upload on files bigger than \-\-multipart\-chunk\-size\-mb
273 273
 .TP
274 274
 \fB\-\-multipart\-chunk\-size\-mb\fR=SIZE
275 275
 Size of each chunk of a multipart upload. Files bigger than SIZE are automatically uploaded as multithreaded-multipart, smaller files are uploaded using the traditional method. SIZE is in Mega-Bytes,
276
-default chunk size is noneMB, minimum allowed chunk size is 5MB, maximum is 5GB.
276
+default chunk size is 15MB, minimum allowed chunk size is 5MB, maximum is 5GB.
277 277
 .TP
278 278
 \fB\-\-list\-md5\fR
279 279
 Include MD5 sums in bucket listings (only for 'ls' command).
... ...
@@ -282,10 +287,10 @@ Include MD5 sums in bucket listings (only for 'ls' command).
282 282
 Print sizes in human readable form (eg 1kB instead of 1234).
283 283
 .TP
284 284
 \fB\-\-ws\-index\fR=WEBSITE_INDEX
285
-Name of index-document (only for [ws-create] command)
285
+Name of index-document (only for [ws\-create] command)
286 286
 .TP
287 287
 \fB\-\-ws\-error\fR=WEBSITE_ERROR
288
-Name of error-document (only for [ws-create] command)
288
+Name of error-document (only for [ws\-create] command)
289 289
 .TP
290 290
 \fB\-\-progress\fR
291 291
 Display progress meter (default on TTY).
... ...
@@ -347,11 +352,11 @@ synchronising complete directory trees to or from remote S3 storage. To some ext
347 347
 .PP
348 348
 Basic usage common in backup scenarios is as simple as:
349 349
 .nf
350
-	s3cmd sync /local/path/ s3://test-bucket/backup/
350
+	s3cmd sync /local/path/ s3://test\-bucket/backup/
351 351
 .fi
352 352
 .PP
353 353
 This command will find all files under /local/path directory and copy them 
354
-to corresponding paths under s3://test-bucket/backup on the remote side.
354
+to corresponding paths under s3://test\-bucket/backup on the remote side.
355 355
 For example:
356 356
 .nf
357 357
 	/local/path/\fBfile1.ext\fR         \->  s3://bucket/backup/\fBfile1.ext\fR
... ...
@@ -361,7 +366,7 @@ For example:
361 361
 However if the local path doesn't end with a slash the last directory's name
362 362
 is used on the remote side as well. Compare these with the previous example:
363 363
 .nf
364
-	s3cmd sync /local/path s3://test-bucket/backup/
364
+	s3cmd sync /local/path s3://test\-bucket/backup/
365 365
 .fi
366 366
 will sync:
367 367
 .nf
... ...
@@ -371,7 +376,7 @@ will sync:
371 371
 .PP
372 372
 To retrieve the files back from S3 use inverted syntax:
373 373
 .nf
374
-	s3cmd sync s3://test-bucket/backup/ /tmp/restore/
374
+	s3cmd sync s3://test\-bucket/backup/ /tmp/restore/
375 375
 .fi
376 376
 that will download files:
377 377
 .nf
... ...
@@ -382,7 +387,7 @@ that will download files:
382 382
 Without the trailing slash on source the behaviour is similar to 
383 383
 what has been demonstrated with upload:
384 384
 .nf
385
-	s3cmd sync s3://test-bucket/backup /tmp/restore/
385
+	s3cmd sync s3://test\-bucket/backup /tmp/restore/
386 386
 .fi
387 387
 will download the files as:
388 388
 .nf
... ...
@@ -414,9 +419,14 @@ about matching file names against exclude and include rules.
414 414
 .PP
415 415
 For example to exclude all files with ".jpg" extension except those beginning with a number use:
416 416
 .PP
417
-	\-\-exclude '*.jpg' \-\-rinclude '[0-9].*\.jpg'
417
+	\-\-exclude '*.jpg' \-\-rinclude '[0\-9].*\.jpg'
418
+
419
+.SH ENVIRONMENT
420
+.TP
421
+.B TMP
422
+Directory used to write temp files (/tmp by default)
418 423
 .SH SEE ALSO
419
-For the most up to date list of options run 
424
+For the most up to date list of options run
420 425
 .B s3cmd \-\-help
421 426
 .br
422 427
 For more info about usage, examples and other related info visit project homepage at
... ...
@@ -429,7 +439,7 @@ Please consider a donation if you have found s3cmd useful:
429 429
 .SH AUTHOR
430 430
 Written by Michal Ludvig <mludvig@logix.net.nz> and 15+ contributors
431 431
 .SH CONTACT, SUPPORT
432
-Prefered way to get support is our mailing list:
432
+Preferred way to get support is our mailing list:
433 433
 .I s3tools\-general@lists.sourceforge.net
434 434
 .SH REPORTING BUGS
435 435
 Report bugs to