Skip to content

Commit ac786de

Browse files
ostronommziccard
authored andcommitted
Fix generation of signed urls for blobs containing spaces and other special chars (#1277)
1 parent 947f03e commit ac786de

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.google.common.hash.Hashing;
5656
import com.google.common.io.BaseEncoding;
5757
import com.google.common.primitives.Ints;
58+
import com.google.common.net.UrlEscapers;
5859

5960
import java.io.ByteArrayInputStream;
6061
import java.io.InputStream;
@@ -524,7 +525,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
524525
if (blobInfo.name().startsWith("/")) {
525526
path.setLength(path.length() - 1);
526527
}
527-
path.append(blobInfo.name());
528+
path.append(UrlEscapers.urlPathSegmentEscaper().escape(blobInfo.name()));
528529
stBuilder.append(path);
529530
try {
530531
byte[] signatureBytes = authCredentials.sign(stBuilder.toString().getBytes(UTF_8));

google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.google.common.collect.ImmutableMap;
5151
import com.google.common.collect.Iterables;
5252
import com.google.common.io.BaseEncoding;
53+
import com.google.common.net.UrlEscapers;
5354

5455
import org.easymock.Capture;
5556
import org.easymock.EasyMock;
@@ -1252,16 +1253,17 @@ public void testSignUrlLeadingSlash() throws NoSuchAlgorithmException, InvalidKe
12521253
ServiceAccountAuthCredentials.createFor(ACCOUNT, privateKey);
12531254
storage = options.toBuilder().authCredentials(authCredentials).build().service();
12541255
URL url = storage.signUrl(BlobInfo.builder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS);
1256+
String escapedBlobName = UrlEscapers.urlPathSegmentEscaper().escape(blobName);
12551257
String stringUrl = url.toString();
12561258
String expectedUrl = new StringBuilder("https://storage.googleapis.com/").append(BUCKET_NAME1)
1257-
.append(blobName).append("?GoogleAccessId=").append(ACCOUNT).append("&Expires=")
1259+
.append(escapedBlobName).append("?GoogleAccessId=").append(ACCOUNT).append("&Expires=")
12581260
.append(42L + 1209600).append("&Signature=").toString();
12591261
assertTrue(stringUrl.startsWith(expectedUrl));
12601262
String signature = stringUrl.substring(expectedUrl.length());
12611263

12621264
StringBuilder signedMessageBuilder = new StringBuilder();
12631265
signedMessageBuilder.append(HttpMethod.GET).append("\n\n\n").append(42L + 1209600).append("\n/")
1264-
.append(BUCKET_NAME1).append(blobName);
1266+
.append(BUCKET_NAME1).append(escapedBlobName);
12651267

12661268
Signature signer = Signature.getInstance("SHA256withRSA");
12671269
signer.initVerify(publicKey);
@@ -1299,6 +1301,42 @@ public void testSignUrlWithOptions() throws NoSuchAlgorithmException, InvalidKey
12991301
URLDecoder.decode(signature, UTF_8.name()))));
13001302
}
13011303

1304+
@Test
1305+
public void testSignUrlForBlobWithSpecialChars() throws NoSuchAlgorithmException,
1306+
InvalidKeyException, SignatureException, UnsupportedEncodingException {
1307+
// List of chars under test were taken from
1308+
// https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
1309+
char[] specialChars =
1310+
new char[]{'!','#','$','&','\'','(',')','*','+',',',':',';','=','?','@','[',']'};
1311+
EasyMock.replay(storageRpcMock);
1312+
ServiceAccountAuthCredentials authCredentials =
1313+
ServiceAccountAuthCredentials.createFor(ACCOUNT, privateKey);
1314+
storage = options.toBuilder().authCredentials(authCredentials).build().service();
1315+
1316+
for (char specialChar : specialChars) {
1317+
String blobName = "/a" + specialChar + "b";
1318+
URL url =
1319+
storage.signUrl(BlobInfo.builder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS);
1320+
String escapedBlobName = UrlEscapers.urlPathSegmentEscaper().escape(blobName);
1321+
String stringUrl = url.toString();
1322+
String expectedUrl = new StringBuilder("https://storage.googleapis.com/").append(BUCKET_NAME1)
1323+
.append(escapedBlobName).append("?GoogleAccessId=").append(ACCOUNT).append("&Expires=")
1324+
.append(42L + 1209600).append("&Signature=").toString();
1325+
assertTrue(stringUrl.startsWith(expectedUrl));
1326+
String signature = stringUrl.substring(expectedUrl.length());
1327+
1328+
StringBuilder signedMessageBuilder = new StringBuilder();
1329+
signedMessageBuilder.append(HttpMethod.GET).append("\n\n\n").append(42L + 1209600)
1330+
.append("\n/").append(BUCKET_NAME1).append(escapedBlobName);
1331+
1332+
Signature signer = Signature.getInstance("SHA256withRSA");
1333+
signer.initVerify(publicKey);
1334+
signer.update(signedMessageBuilder.toString().getBytes(UTF_8));
1335+
assertTrue(signer.verify(BaseEncoding.base64().decode(
1336+
URLDecoder.decode(signature, UTF_8.name()))));
1337+
}
1338+
}
1339+
13021340
@Test
13031341
public void testGetAllArray() {
13041342
BlobId blobId1 = BlobId.of(BUCKET_NAME1, BLOB_NAME1);

0 commit comments

Comments
 (0)