Fix the final boundary in resumable multipart media uploads.
Reviewed in http://codereview.appspot.com/5450107/.
diff --git a/apiclient/http.py b/apiclient/http.py
index 333461e..8ea5c73 100644
--- a/apiclient/http.py
+++ b/apiclient/http.py
@@ -251,7 +251,9 @@
# Pull the multipart boundary out of the content-type header.
major, minor, params = mimeparse.parse_mime_type(
headers.get('content-type', 'application/json'))
- self.multipart_boundary = params.get('boundary', '').strip('"')
+
+ # Terminating multipart boundary get a trailing '--' appended.
+ self.multipart_boundary = params.get('boundary', '').strip('"') + '--'
# If this was a multipart resumable, the size of the non-media part.
self.multipart_size = 0
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 3440c23..07df411 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -754,6 +754,8 @@
def _urlsafe_b64decode(b64string):
+ # Guard against unicode strings, which base64 can't handle.
+ b64string = b64string.encode('ascii')
padded = b64string + '=' * (4 - len(b64string) % 4)
return base64.urlsafe_b64decode(padded)
diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py
index 523c921..323345a 100644
--- a/oauth2client/crypt.py
+++ b/oauth2client/crypt.py
@@ -137,6 +137,8 @@
def _urlsafe_b64decode(b64string):
+ # Guard against unicode strings, which base64 can't handle.
+ b64string = b64string.encode('ascii')
padded = b64string + '=' * (4 - len(b64string) % 4)
return base64.urlsafe_b64decode(padded)
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 2995633..9e47e8f 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -389,7 +389,7 @@
self.assertEquals('image/png', request.resumable.mimetype())
- self.assertEquals(request.multipart_boundary, '')
+ self.assertEquals(request.multipart_boundary, '--')
self.assertEquals(request.body, None)
self.assertEquals(request.resumable_uri, None)
diff --git a/tests/test_http.py b/tests/test_http.py
index f502bac..b7054dc 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -109,7 +109,7 @@
self.assertEquals(new_req.body, '{}')
self.assertEquals(new_req.http, http)
self.assertEquals(new_req.resumable.to_json(), media_upload.to_json())
- self.assertEquals(new_req.multipart_boundary, '---flubber')
+ self.assertEquals(new_req.multipart_boundary, '---flubber--')
EXPECTED = """POST /someapi/v1/collection/?foo=bar HTTP/1.1
Content-Type: application/json
diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py
index 9acc9cf..8286eed 100644
--- a/tests/test_oauth2client.py
+++ b/tests/test_oauth2client.py
@@ -280,7 +280,8 @@
def test_exchange_id_token_fail(self):
body = {'foo': 'bar'}
payload = base64.urlsafe_b64encode(simplejson.dumps(body)).strip('=')
- jwt = 'stuff.' + payload + '.signature'
+ jwt = (base64.urlsafe_b64encode('stuff')+ '.' + payload + '.' +
+ base64.urlsafe_b64encode('signature'))
http = HttpMockSequence([
({'status': '200'}, """{ "access_token":"SlAV32hkKG",