18
18
import collections
19
19
import datetime
20
20
import hashlib
21
- import re
22
21
import json
23
22
24
23
import six
31
30
32
31
33
32
NOW = datetime .datetime .utcnow # To be replaced by tests.
34
- MULTIPLE_SPACES_RE = r"\s+"
35
- MULTIPLE_SPACES = re .compile (MULTIPLE_SPACES_RE )
36
33
37
34
SERVICE_ACCOUNT_URL = (
38
35
"https://googleapis.dev/python/google-api-core/latest/"
@@ -192,7 +189,7 @@ def get_canonical_headers(headers):
192
189
normalized = collections .defaultdict (list )
193
190
for key , val in headers :
194
191
key = key .lower ().strip ()
195
- val = MULTIPLE_SPACES . sub ( " " , val .strip ())
192
+ val = " " . join ( val .split ())
196
193
normalized [key ].append (val )
197
194
198
195
ordered_headers = sorted ((key , "," .join (val )) for key , val in normalized .items ())
@@ -206,8 +203,8 @@ def get_canonical_headers(headers):
206
203
)
207
204
208
205
209
- def canonicalize (method , resource , query_parameters , headers ):
210
- """Canonicalize method, resource
206
+ def canonicalize_v2 (method , resource , query_parameters , headers ):
207
+ """Canonicalize method, resource per the V2 spec.
211
208
212
209
:type method: str
213
210
:param method: The HTTP verb that will be used when requesting the URL.
@@ -301,6 +298,7 @@ def generate_signed_url_v2(
301
298
:type resource: str
302
299
:param resource: A pointer to a specific resource
303
300
(typically, ``/bucket-name/path/to/blob.txt``).
301
+ Caller should have already URL-encoded the value.
304
302
305
303
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
306
304
:param expiration: Point in time when the signed URL should expire.
@@ -368,7 +366,7 @@ def generate_signed_url_v2(
368
366
"""
369
367
expiration_stamp = get_expiration_seconds_v2 (expiration )
370
368
371
- canonical = canonicalize (method , resource , query_parameters , headers )
369
+ canonical = canonicalize_v2 (method , resource , query_parameters , headers )
372
370
373
371
# Generate the string to sign.
374
372
elements_to_sign = [
@@ -462,6 +460,7 @@ def generate_signed_url_v4(
462
460
:type resource: str
463
461
:param resource: A pointer to a specific resource
464
462
(typically, ``/bucket-name/path/to/blob.txt``).
463
+ Caller should have already URL-encoded the value.
465
464
466
465
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
467
466
:param expiration: Point in time when the signed URL should expire.
@@ -589,13 +588,20 @@ def generate_signed_url_v4(
589
588
ordered_query_parameters = sorted (query_parameters .items ())
590
589
canonical_query_string = six .moves .urllib .parse .urlencode (ordered_query_parameters )
591
590
591
+ lowercased_headers = dict (ordered_headers )
592
+
593
+ if "x-goog-content-sha256" in lowercased_headers :
594
+ payload = lowercased_headers ["x-goog-content-sha256" ]
595
+ else :
596
+ payload = "UNSIGNED-PAYLOAD"
597
+
592
598
canonical_elements = [
593
599
method ,
594
600
resource ,
595
601
canonical_query_string ,
596
602
canonical_header_string ,
597
603
signed_headers ,
598
- "UNSIGNED-PAYLOAD" ,
604
+ payload ,
599
605
]
600
606
canonical_request = "\n " .join (canonical_elements )
601
607
0 commit comments