diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index 9c7f5b581023..3c29ca24a58a 100644
--- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -958,7 +958,8 @@ public static SignUrlOption withMd5() {
* Use it if signature should include the blob's canonicalized extended headers. When used,
* users of the signed URL should include the canonicalized extended headers with their request.
*
- * @see
+ * @see Request
+ * Headers
*/
public static SignUrlOption withExtHeaders(Map extHeaders) {
return new SignUrlOption(Option.EXT_HEADERS, extHeaders);
@@ -986,13 +987,16 @@ public static SignUrlOption withV4Signature() {
* get it from the environment.
*
* @see Service
- * account
+ * Accounts
*/
public static SignUrlOption signWith(ServiceAccountSigner signer) {
return new SignUrlOption(Option.SERVICE_ACCOUNT_CRED, signer);
}
- /** Use a different host name than the default host name 'storage.googleapis.com' */
+ /**
+ * Use a different host name than the default host name 'https://storage.googleapis.com'. This
+ * must also include the scheme component of the URI.
+ */
public static SignUrlOption withHostName(String hostName) {
return new SignUrlOption(Option.HOST_NAME, hostName);
}
diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index 6f127ac5fb0c..7547ff117e92 100644
--- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -50,6 +50,7 @@
import com.google.cloud.storage.Acl.Entity;
import com.google.cloud.storage.spi.v1.StorageRpc;
import com.google.cloud.storage.spi.v1.StorageRpc.RewriteResponse;
+import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
@@ -636,23 +637,25 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
getOptions().getClock().millisTime() + unit.toMillis(duration),
TimeUnit.MILLISECONDS);
- StringBuilder stPath = new StringBuilder();
- if (!blobInfo.getBucket().startsWith(PATH_DELIMITER)) {
- stPath.append(PATH_DELIMITER);
+ String storageXmlHostName =
+ optionMap.get(SignUrlOption.Option.HOST_NAME) != null
+ ? (String) optionMap.get(SignUrlOption.Option.HOST_NAME)
+ : STORAGE_XML_HOST_NAME;
+
+ // The bucket name itself should never contain a forward slash. However, parts already existed
+ // in the code to check for this, so we remove the forward slashes to be safe here.
+ String bucketName = CharMatcher.anyOf(PATH_DELIMITER).trimFrom(blobInfo.getBucket());
+ String escapedBlobName = "";
+ if (!Strings.isNullOrEmpty(blobInfo.getName())) {
+ escapedBlobName =
+ UrlEscapers.urlFragmentEscaper()
+ .escape(blobInfo.getName())
+ .replace("?", "%3F")
+ .replace(";", "%3B");
}
- stPath.append(blobInfo.getBucket());
- if (!blobInfo.getBucket().endsWith(PATH_DELIMITER)
- && !Strings.isNullOrEmpty(blobInfo.getName())) {
- stPath.append(PATH_DELIMITER);
- }
- if (blobInfo.getName().startsWith(PATH_DELIMITER)) {
- stPath.setLength(stPath.length() - 1);
- }
-
- String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName());
- stPath.append(escapedName.replace("?", "%3F").replace(";", "%3B"));
- URI path = URI.create(stPath.toString());
+ String stPath = constructResourceUriPath(bucketName, escapedBlobName);
+ URI path = URI.create(stPath);
try {
SignatureInfo signatureInfo =
@@ -660,11 +663,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
String unsignedPayload = signatureInfo.constructUnsignedPayload();
byte[] signatureBytes = credentials.sign(unsignedPayload.getBytes(UTF_8));
StringBuilder stBuilder = new StringBuilder();
- if (optionMap.get(SignUrlOption.Option.HOST_NAME) == null) {
- stBuilder.append(STORAGE_XML_HOST_NAME).append(path);
- } else {
- stBuilder.append(optionMap.get(SignUrlOption.Option.HOST_NAME)).append(path);
- }
+ stBuilder.append(storageXmlHostName).append(path);
if (isV4) {
BaseEncoding encoding = BaseEncoding.base16().lowerCase();
@@ -688,6 +687,19 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
}
}
+ private String constructResourceUriPath(String slashlessBucketName, String escapedBlobName) {
+ StringBuilder pathBuilder = new StringBuilder();
+ pathBuilder.append(PATH_DELIMITER).append(slashlessBucketName);
+ if (Strings.isNullOrEmpty(escapedBlobName)) {
+ return pathBuilder.toString();
+ }
+ if (!escapedBlobName.startsWith(PATH_DELIMITER)) {
+ pathBuilder.append(PATH_DELIMITER);
+ }
+ pathBuilder.append(escapedBlobName);
+ return pathBuilder.toString();
+ }
+
/**
* Builds signature info.
*