Browse code

* Merge from trunk, revisions 218, 226, 233, 234: * s3cmd, S3/S3.py, S3/Config.py: Allow access to upper-case named buckets again with --use-old-connect-method (uses http://s3.amazonaws.com/bucket/object instead of http://bucket.s3.amazonaws.com/object) * s3cmd, S3/S3.py, S3/Config.py: Removed --use-old-connect-method again. Autodetect the need for old connect method instead. * S3/S3.py: "s3cmd mb" can create upper-case buckets again in US. Non-US (e.g. EU) bucket names must conform to strict DNS-rules. * S3/S3Uri.py: Display public URLs correctly for non-DNS buckets.

git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/branches/0.9.8.x@236 830e0280-6d2a-0410-9c65-932aecc39d9d

Michal Ludvig authored on 2008/09/15 13:29:47
Showing 3 changed files
... ...
@@ -1,5 +1,19 @@
1 1
 2008-09-15  Michal Ludvig  <michal@logix.cz>
2 2
 
3
+	* Merge from trunk, revisions 218, 226, 233, 234:
4
+	* s3cmd, S3/S3.py, S3/Config.py: Allow access to upper-case
5
+	  named buckets again with --use-old-connect-method 
6
+	  (uses http://s3.amazonaws.com/bucket/object instead of
7
+	  http://bucket.s3.amazonaws.com/object)
8
+	* s3cmd, S3/S3.py, S3/Config.py: Removed --use-old-connect-method
9
+	  again. Autodetect the need for old connect method instead.
10
+	* S3/S3.py: "s3cmd mb" can create upper-case buckets again
11
+	  in US. Non-US (e.g. EU) bucket names must conform to strict
12
+	  DNS-rules.
13
+	* S3/S3Uri.py: Display public URLs correctly for non-DNS buckets.
14
+
15
+2008-09-15  Michal Ludvig  <michal@logix.cz>
16
+
3 17
 	* Merge from trunk, revisions 227, 230:
4 18
 
5 19
 	* r227: s3cmd: Rework UTF-8 output to keep sys.stdout untouched 
... ...
@@ -71,7 +71,7 @@ class S3(object):
71 71
 				return httplib.HTTPConnection(self.get_hostname(bucket))
72 72
 
73 73
 	def get_hostname(self, bucket):
74
-		if bucket:
74
+		if bucket and self.check_bucket_name_dns_conformity(bucket):
75 75
 			if self.redir_map.has_key(bucket):
76 76
 				host = self.redir_map[bucket]
77 77
 			else:
... ...
@@ -85,10 +85,12 @@ class S3(object):
85 85
 		self.redir_map[bucket] = redir_hostname
86 86
 
87 87
 	def format_uri(self, resource):
88
-		if self.config.proxy_host != "":
89
-			uri = "http://%s%s" % (self.get_hostname(resource['bucket']), resource['uri'])
88
+		if resource['bucket'] and not self.check_bucket_name_dns_conformity(resource['bucket']):
89
+			uri = "/%s%s" % (resource['bucket'], resource['uri'])
90 90
 		else:
91 91
 			uri = resource['uri']
92
+		if self.config.proxy_host != "":
93
+			uri = "http://%s%s" % (self.get_hostname(resource['bucket']), uri)
92 94
 		debug('format_uri(): ' + uri)
93 95
 		return uri
94 96
 
... ...
@@ -124,7 +126,6 @@ class S3(object):
124 124
 		return response
125 125
 
126 126
 	def bucket_create(self, bucket, bucket_location = None):
127
-		self.check_bucket_name(bucket)
128 127
 		headers = SortedDict()
129 128
 		body = ""
130 129
 		if bucket_location and bucket_location.strip().upper() != "US":
... ...
@@ -132,6 +133,9 @@ class S3(object):
132 132
 			body += bucket_location.strip().upper()
133 133
 			body += "</LocationConstraint></CreateBucketConfiguration>"
134 134
 			debug("bucket_location: " + body)
135
+			self.check_bucket_name(bucket, dns_strict = True)
136
+		else:
137
+			self.check_bucket_name(bucket, dns_strict = False)
135 138
 		headers["content-length"] = len(body)
136 139
 		if self.config.acl_public:
137 140
 			headers["x-amz-acl"] = "public-read"
... ...
@@ -475,13 +479,37 @@ class S3(object):
475 475
 		debug("SignHeaders: " + repr(h))
476 476
 		return base64.encodestring(hmac.new(self.config.secret_key, h, sha).digest()).strip()
477 477
 
478
-	def check_bucket_name(self, bucket):
479
-		invalid = re.compile("([^a-z0-9\._-])").search(bucket)
480
-		if invalid:
481
-			raise ParameterError("Bucket name '%s' contains disallowed character '%s'. The only supported ones are: lowercase us-ascii letters (a-z), digits (0-9), dot (.), hyphen (-) and underscore (_)." % (bucket, invalid.groups()[0]))
478
+	@staticmethod
479
+	def check_bucket_name(bucket, dns_strict = True):
480
+		if dns_strict:
481
+			invalid = re.search("([^a-z0-9\.-])", bucket)
482
+			if invalid:
483
+				raise ParameterError("Bucket name '%s' contains disallowed character '%s'. The only supported ones are: lowercase us-ascii letters (a-z), digits (0-9), dot (.) and hyphen (-)." % (bucket, invalid.groups()[0]))
484
+		else:
485
+			invalid = re.search("([^A-Za-z0-9\._-])", bucket)
486
+			if invalid:
487
+				raise ParameterError("Bucket name '%s' contains disallowed character '%s'. The only supported ones are: us-ascii letters (a-z, A-Z), digits (0-9), dot (.), hyphen (-) and underscore (_)." % (bucket, invalid.groups()[0]))
488
+
482 489
 		if len(bucket) < 3:
483 490
 			raise ParameterError("Bucket name '%s' is too short (min 3 characters)" % bucket)
484 491
 		if len(bucket) > 255:
485 492
 			raise ParameterError("Bucket name '%s' is too long (max 255 characters)" % bucket)
493
+		if dns_strict:
494
+			if len(bucket) > 63:
495
+				raise ParameterError("Bucket name '%s' is too long (max 63 characters)" % bucket)
496
+			if re.search("-\.", bucket):
497
+				raise ParameterError("Bucket name '%s' must not contain sequence '-.' for DNS compatibility" % bucket)
498
+			if re.search("\.\.", bucket):
499
+				raise ParameterError("Bucket name '%s' must not contain sequence '..' for DNS compatibility" % bucket)
500
+			if not re.search("^[0-9a-z]", bucket):
501
+				raise ParameterError("Bucket name '%s' must start with a letter or a digit" % bucket)
502
+			if not re.search("[0-9a-z]$", bucket):
503
+				raise ParameterError("Bucket name '%s' must end with a letter or a digit" % bucket)
486 504
 		return True
487 505
 
506
+	@staticmethod
507
+	def check_bucket_name_dns_conformity(bucket):
508
+		try:
509
+			return S3.check_bucket_name(bucket, dns_strict = True)
510
+		except ParameterError:
511
+			return False
... ...
@@ -7,6 +7,7 @@ import re
7 7
 import sys
8 8
 from BidirMap import BidirMap
9 9
 from logging import debug
10
+from S3 import S3
10 11
 
11 12
 class S3Uri(object):
12 13
 	type = None
... ...
@@ -79,12 +80,15 @@ class S3UriS3(S3Uri):
79 79
 		return "/".join(["s3:/", self._bucket, self._object])
80 80
 	
81 81
 	def public_url(self):
82
-		return "http://%s.s3.amazonaws.com/%s" % (self._bucket, self._object)
82
+		if S3.check_bucket_name_dns_conformity(self._bucket):
83
+			return "http://%s.s3.amazonaws.com/%s" % (self._bucket, self._object)
84
+		else:
85
+			return "http://s3.amazonaws.com/%s/%s" % (self._bucket, self._object)
83 86
 
84 87
 	@staticmethod
85 88
 	def compose_uri(bucket, object = ""):
86 89
 		return "s3://%s/%s" % (bucket, object)
87
-	
90
+
88 91
 class S3UriS3FS(S3Uri):
89 92
 	type = "s3fs"
90 93
 	_re = re.compile("^s3fs://([^/]*)/?(.*)", re.IGNORECASE)