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. *