2222import uuid
2323from . import const
2424from .logger import INFO
25- from .util import quote
25+ from .util import quote , unquote_plus , parse_url
2626
2727
2828class Signer (object ):
29-
3029 ignored_headers = ['authorization' , 'user-agent' ]
3130
3231 def __init__ (self , logger ):
@@ -36,38 +35,43 @@ def sign(self, method, service, region, uri, headers, data, credential, security
3635 uri_dict = self .__url_path_to_dict (uri )
3736 host = uri_dict ['host' ]
3837 port = uri_dict ['port' ]
38+ path = uri_dict ['path' ]
3939 query = uri_dict ['query' ]
40- canonical_uri = quote (uri_dict ['path' ])
4140
4241 if port and port not in ['80' , '443' ]:
4342 full_host = host + ':' + port
4443 else :
4544 full_host = host
45+ return self .signV3 (method , service , region , full_host , path , query , headers , data , credential , security_token )
4646
47+ def signV3 (self , method , service , region , host , path , query , headers , data , credential , security_token ):
48+ canonical_host = self .__build_canonical_host (host )
4749 now = self .__now ()
48- jdcloud_date = now .strftime ('%Y%m%dT%H%M%SZ' )
49- datestamp = now .strftime ('%Y%m%d' ) # Date w/o time, used in credential scope
5050 nonce = str (uuid .uuid4 ())
51- headers [const .JDCLOUD_DATE ] = jdcloud_date
52- headers [const .JDCLOUD_NONCE ] = nonce
51+ jdcloud_date = now .strftime ('%Y%m%dT%H%M%SZ' )
52+ if const .JDCLOUD_DATE in headers :
53+ jdcloud_date = headers [const .JDCLOUD_DATE ]
54+ if const .JDCLOUD_NONCE in headers :
55+ nonce = headers [const .JDCLOUD_NONCE ]
56+ date_str = jdcloud_date [:8 ]
5357
5458 canonical_querystring = self .__normalize_query_string (query )
55- canonical_headers , signed_headers = self .__build_canonical_headers (headers , security_token , full_host )
59+ canonical_headers , signed_headers = self .__build_canonical_headers (headers , security_token , canonical_host )
5660
5761 payload_hash = self .__sha256_hash (data )
5862
5963 canonical_request = (method + '\n ' +
60- canonical_uri + '\n ' +
64+ self . __build_canonical_uri ( path ) + '\n ' +
6165 canonical_querystring + '\n ' +
6266 canonical_headers + '\n ' +
6367 signed_headers + '\n ' +
6468 payload_hash )
6569
66- algorithm = const .JDCLOUD_ALGORITHM
67- credential_scope = (datestamp + '/' +
70+ algorithm = const .JDCLOUD3_ALGORITHM
71+ credential_scope = (date_str + '/' +
6872 region + '/' +
6973 service + '/' +
70- const .JDCLOUD_REQUEST )
74+ const .JDCLOUD3_REQUEST )
7175 string_to_sign = (algorithm + '\n ' +
7276 jdcloud_date + '\n ' +
7377 credential_scope + '\n ' +
@@ -76,35 +80,62 @@ def sign(self, method, service, region, uri, headers, data, credential, security
7680 self .__logger .log (INFO , '---canonical_request---\n ' + canonical_request )
7781 self .__logger .log (INFO , '----string_to_sign---\n ' + string_to_sign )
7882
79- signing_key = self .__get_signature_key (credential .secret_key , datestamp , region , service )
83+ signing_key = self .__get_signature_key (credential .secret_key , date_str , region , service )
8084 encoded = string_to_sign .encode ('utf-8' )
8185 signature = hmac .new (signing_key , encoded , hashlib .sha256 ).hexdigest ()
8286
8387 authorization_header = (
84- algorithm + ' ' +
85- 'Credential=' + credential .access_key + '/' + credential_scope + ', ' +
86- 'SignedHeaders=' + signed_headers + ', ' +
87- 'Signature=' + signature
88+ algorithm + ' ' +
89+ 'Credential=' + credential .access_key + '/' + credential_scope + ', ' +
90+ 'SignedHeaders=' + signed_headers + ', ' +
91+ 'Signature=' + signature
8892 )
8993
9094 headers .update ({
9195 const .JDCLOUD_AUTH : authorization_header ,
9296 const .JDCLOUD_DATE : jdcloud_date ,
9397 const .JDCLOUD_CONTENT_SHA256 : payload_hash ,
94- const .JDCLOUD_ALGORITHM : const .JDCLOUD_ALGORITHM ,
98+ const .JDCLOUD3_ALGORITHM : const .JDCLOUD3_ALGORITHM ,
9599 const .JDCLOUD_NONCE : nonce
96100 })
97101
98102 if security_token :
99103 headers .update ({const .JDCLOUD_SECURITY_TOKEN : security_token })
100104
101- def __normalize_query_string (self , query ):
102- params = (list (map (str .strip , s .split ("=" )))
103- for s in query .split ('&' )
104- if len (s ) > 0 )
105+ def __build_canonical_host (self , full_host ):
106+ if full_host .lower ().find ('http://' ) == 0 :
107+ full_host = full_host [7 :]
108+ elif full_host .lower ().find ('https://' ) == 0 :
109+ full_host = full_host [8 :]
110+ return full_host
105111
106- normalized = '&' .join ('%s=%s' % (p [0 ], p [1 ] if len (p ) == 2 else ('%3D' .join (p [1 :]) if len (p ) > 2 else '' ))
107- for p in sorted (params ))
112+ def __normalize_query_string (self , query ):
113+ params = []
114+ if isinstance (query , str ):
115+ for s in query .split ('&' ):
116+ if len (s ) <= 0 :
117+ continue
118+ list = []
119+ for val in s .split ('=' ):
120+ list .append (self .__urlencode (self .__urldecode (val )))
121+ params .append (list )
122+ elif isinstance (query , dict ):
123+ for key in query .keys ():
124+ list = []
125+ list .append (self .__urlencode (self .__urldecode (key )))
126+ list .append (self .__urlencode (self .__urldecode (query [key ])))
127+ params .append (list )
128+
129+ normalized = ''
130+ for p in sorted (params ):
131+ if p [0 ] == '' :
132+ continue
133+ elif len (p ) == 2 :
134+ normalized += '%s=%s&' % (p [0 ], p [1 ])
135+ elif len (p ) > 2 :
136+ normalized += '%3D' .join (p [1 :]) + '&'
137+ if normalized .endswith ('&' ):
138+ normalized = normalized [:normalized .__len__ ()- 1 ]
108139 return normalized
109140
110141 def __now (self ):
@@ -113,39 +144,58 @@ def __now(self):
113144 def __url_path_to_dict (self , path ):
114145 """http://stackoverflow.com/a/17892757/142207"""
115146
116- pattern = (r'^'
117- r'((?P<schema>.+?)://)?'
118- r'((?P<user>.+?)(:(?P<password>.*?))?@)?'
119- r'(?P<host>.*?)'
120- r'(:(?P<port>\d+?))?'
121- r'(?P<path>/.*?)?'
122- r'(\?(?P<query>.*?))?'
123- r'$' )
124- regex = re .compile (pattern )
125- match = regex .match (path )
126- group_dict = match .groupdict () if match is not None else None
127-
128- if group_dict ['path' ] is None :
129- group_dict ['path' ] = '/'
130-
131- if group_dict ['query' ] is None :
132- group_dict ['query' ] = ''
133-
134- return group_dict
147+ # pattern = (r'^'
148+ # r'((?P<schema>.+?)://)?'
149+ # r'((?P<user>.+?)(:(?P<password>.*?))?@)?'
150+ # r'(?P<host>.*?)'
151+ # r'(:(?P<port>\d+?))?'
152+ # r'(?P<path>/.*?)?'
153+ # r'(\?(?P<query>.*?))?'
154+ # r'$')
155+ # regex = re.compile(pattern)
156+ # match = regex.match(path)
157+ # group_dict = match.groupdict() if match is not None else None
158+ #
159+ # if group_dict['path'] is None:
160+ # group_dict['path'] = '/'
161+ #
162+ # if group_dict['query'] is None:
163+ # group_dict['query'] = ''
164+ # return group_dict
165+ return parse_url ( path )
135166
136167 def __sign (self , key , msg ):
137168 return hmac .new (key , msg .encode ('utf-8' ), hashlib .sha256 ).digest ()
138169
139170 def __get_signature_key (self , key , date_stamp , region_name , service_name ):
140- k_date = self .__sign ((const .JDCLOUD2 + key ).encode ('utf-8' ), date_stamp )
171+ k_date = self .__sign ((const .JDCLOUD3 + key ).encode ('utf-8' ), date_stamp )
141172 k_region = self .__sign (k_date , region_name )
142173 k_service = self .__sign (k_region , service_name )
143- k_signing = self .__sign (k_service , const .JDCLOUD_REQUEST )
174+ k_signing = self .__sign (k_service , const .JDCLOUD3_REQUEST )
144175 return k_signing
145176
146177 def __sha256_hash (self , val ):
147178 return hashlib .sha256 (val .encode ('utf-8' )).hexdigest ()
148179
180+ def __build_canonical_uri (self , path ):
181+ reg = re .compile ('/+' )
182+ decoded_path = reg .sub ('/' , self .__urldecode (path ))
183+ if decoded_path == '' :
184+ return '/'
185+ encoded_path = self .__urlencode_ignore_slashes (decoded_path )
186+ if encoded_path .startswith ('/' ):
187+ return encoded_path
188+ return '/' + encoded_path
189+
190+ def __urlencode (self , value ):
191+ return quote (value , '~' )
192+
193+ def __urlencode_ignore_slashes (self , value ):
194+ return quote (value , '/~' )
195+
196+ def __urldecode (self , value ):
197+ return unquote_plus (value )
198+
149199 def __build_canonical_headers (self , req_headers , security_token , full_host ):
150200 headers = ['host' ] # add host header first
151201 signed_values = {}
@@ -166,9 +216,18 @@ def __build_canonical_headers(self, req_headers, security_token, full_host):
166216 canonical_values = []
167217 for key in headers :
168218 if key == 'host' :
169- canonical_values .append ('host:' + full_host )
219+ canonical_values .append ('host:' + full_host . strip () )
170220 else :
171- canonical_values .append (key + ':' + signed_values [key ])
221+ # canonical_values.append(key + ':' + )
222+ header_value = signed_values [key ]
223+ if isinstance (header_value , str ):
224+ canonical_values .append (key + ':' + header_value .strip ())
225+ elif isinstance (header_value , list ):
226+ arr = []
227+ for val in header_value :
228+ arr .append (val .strip ())
229+ canonical_values .append (key + ':' + ',' .join (arr ))
230+
172231
173232 canonical_headers = '\n ' .join (canonical_values ) + '\n '
174233
0 commit comments