From e112b08aa0945e09d1811ca3b21661010edee611 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Fri, 9 Aug 2019 00:19:48 +0000 Subject: [PATCH 1/9] Add virtual hosted-style support for signurl gen. Adds new methods to produce a SignUrlOption that results in generated signed URLs using virtual-hosted-style URLs (i.e. mybucket.storage.googleapis.com instead of storage.googleapis.com/mybucket). One option allows specifying the virtual hostname explicitly (for the case where someone might have a custom subdomain, with a bucket of the same name, CNAME'd to c.storage.googleapis.com), while the other will implicitly construct the hostname using the bucket from the passed-in BlobInfo. Addresses part of https://issuetracker.google.com/issues/130190655. --- .../com/google/cloud/storage/Storage.java | 34 +++++- .../com/google/cloud/storage/StorageImpl.java | 114 +++++++++++++++--- 2 files changed, 128 insertions(+), 20 deletions(-) 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 8b3771d2c2cf..311dc7cdb53a 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 @@ -1035,7 +1035,8 @@ enum Option { EXT_HEADERS, SERVICE_ACCOUNT_CRED, SIGNATURE_VERSION, - HOST_NAME + HOST_NAME, + VIRTUAL_HOST_NAME } enum SignatureVersion { @@ -1122,11 +1123,40 @@ public static SignUrlOption signWith(ServiceAccountSigner signer) { /** * Use a different host name than the default host name 'https://storage.googleapis.com'. This - * must also include the scheme component of the URI. + * must also include the scheme component of the URI. Note that this cannot be used alongside + * {@code withVirtualHostName()}. */ public static SignUrlOption withHostName(String hostName) { return new SignUrlOption(Option.HOST_NAME, hostName); } + + /** + * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI + * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. This must also include + * the scheme component of the URI. Note that this cannot be used alongside {@code + * withHostName()}. For V4 signing, this also sets the "host" header in the canonicalized + * extension headers to the specified value, minus the "http[s]://", unless that header is + * supplied via the {@code withExtHeaders()} method. + * + * @see Request Endpoints + */ + public static SignUrlOption withVirtualHostName(String virtualHostName) { + return new SignUrlOption(Option.VIRTUAL_HOST_NAME, virtualHostName); + } + + /** + * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI + * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. The bucket name will be + * obtained from the resource passed in. Note that this cannot be used alongside {@code + * withHostName()}. For V4 signing, this also sets the "host" header in the canonicalized + * extension headers to the virtual hosted-style host, unless that header is supplied via the + * {@code withExtHeaders()} method. + * + * @see Request Endpoints + */ + public static SignUrlOption withVirtualHostName() { + return new SignUrlOption(Option.VIRTUAL_HOST_NAME, ""); + } } /** 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 8b6ccf3b16b3..b5673bf42976 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 @@ -88,7 +88,9 @@ final class StorageImpl extends BaseService implements Storage { private static final String EMPTY_BYTE_ARRAY_CRC32C = "AAAAAA=="; private static final String PATH_DELIMITER = "/"; /** Signed URLs are only supported through the GCS XML API endpoint. */ - private static final String STORAGE_XML_HOST_NAME = "https://storage.googleapis.com"; + private static final String STORAGE_XML_URI_SCHEME = "https"; + + private static final String STORAGE_XML_URI_HOST_NAME = "storage.googleapis.com"; private static final Function, Boolean> DELETE_FUNCTION = new Function, Boolean>() { @@ -635,6 +637,9 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio optionMap.put(option.getOption(), option.getValue()); } + boolean isV2 = + SignUrlOption.SignatureVersion.V2.equals( + optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); boolean isV4 = SignUrlOption.SignatureVersion.V4.equals( optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); @@ -655,14 +660,12 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); - String storageXmlHostName = - optionMap.get(SignUrlOption.Option.HOST_NAME) != null - ? (String) optionMap.get(SignUrlOption.Option.HOST_NAME) - : STORAGE_XML_HOST_NAME; + checkArgument( + !(optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null + && optionMap.get(SignUrlOption.Option.HOST_NAME) != null), + "Cannot specify both the VIRTUAL_HOST_NAME and HOST_NAME SignUrlOptions together."); - // 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 bucketName = slashlessBucketNameFromBlobInfo(blobInfo); String escapedBlobName = ""; if (!Strings.isNullOrEmpty(blobInfo.getName())) { escapedBlobName = @@ -672,12 +675,35 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio .replace(";", "%3B"); } - String stPath = constructResourceUriPath(bucketName, escapedBlobName); + String storageXmlHostName; + boolean useBucketInPath = true; + if (optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null) { + // In virtual hosted-style endpoints, the bucket is included in the host portion of the URI + // instead of in the path. + useBucketInPath = false; + storageXmlHostName = + virtualHostFromOptionValue( + (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME), bucketName); + } else if (optionMap.get(SignUrlOption.Option.HOST_NAME) != null) { + storageXmlHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME); + } else { + storageXmlHostName = STORAGE_XML_URI_SCHEME + "://" + STORAGE_XML_URI_HOST_NAME; + } + + String stPath = + useBucketInPath + ? constructResourceUriPath(bucketName, escapedBlobName, optionMap) + : constructResourceUriPath("", escapedBlobName, optionMap); URI path = URI.create(stPath); + // For V2 signing, even if we don't specify the bucket in the URI path, we still need the + // canonical resource string that we'll sign to include the bucket. + URI pathForSigning = + isV2 ? URI.create(constructResourceUriPath(bucketName, escapedBlobName, optionMap)) : path; try { SignatureInfo signatureInfo = - buildSignatureInfo(optionMap, blobInfo, expiration, path, credentials.getAccount()); + buildSignatureInfo( + optionMap, blobInfo, expiration, pathForSigning, credentials.getAccount()); String unsignedPayload = signatureInfo.constructUnsignedPayload(); byte[] signatureBytes = credentials.sign(unsignedPayload.getBytes(UTF_8)); StringBuilder stBuilder = new StringBuilder(); @@ -705,10 +731,31 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio } } - private String constructResourceUriPath(String slashlessBucketName, String escapedBlobName) { + private String constructResourceUriPath( + String slashlessBucketName, + String escapedBlobName, + EnumMap optionMap) { + if (Strings.isNullOrEmpty(slashlessBucketName)) { + if (Strings.isNullOrEmpty(escapedBlobName)) { + return PATH_DELIMITER; + } + if (escapedBlobName.startsWith(PATH_DELIMITER)) { + return escapedBlobName; + } + return PATH_DELIMITER + escapedBlobName; + } + StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(PATH_DELIMITER).append(slashlessBucketName); if (Strings.isNullOrEmpty(escapedBlobName)) { + boolean isV2 = + SignUrlOption.SignatureVersion.V2.equals( + optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); + // If using virtual-hosted style URLs with V2 signing, the path string for a bucket resource + // must end with a forward slash. + if (optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null && isV2) { + pathBuilder.append(PATH_DELIMITER); + } return pathBuilder.toString(); } if (!escapedBlobName.startsWith(PATH_DELIMITER)) { @@ -760,14 +807,45 @@ private SignatureInfo buildSignatureInfo( signatureInfoBuilder.setTimestamp(getOptions().getClock().millisTime()); - @SuppressWarnings("unchecked") - Map extHeaders = - (Map) - (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) - ? (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) - : Collections.emptyMap()); + ImmutableMap.Builder extHeaders = new ImmutableMap.Builder(); + + boolean isV4 = + SignUrlOption.SignatureVersion.V4.equals( + optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); + // V2 signing requires that the header not include the bucket, but V4 signing requires that + // the host name used in the URI must match the "host" header. + boolean setHostHeaderToVirtualHost = + optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOST_NAME) && isV4; + // Add this host first if needed, allowing it to be overridden in the EXT_HEADERS option below. + if (setHostHeaderToVirtualHost) { + String vhost = + virtualHostFromOptionValue( + (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME), + slashlessBucketNameFromBlobInfo(blobInfo)); + vhost = vhost.replaceFirst("http(s)?://", ""); + extHeaders.put("host", vhost); + } - return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + if (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS)) { + extHeaders.putAll((Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS)); + } + + return signatureInfoBuilder + .setCanonicalizedExtensionHeaders((Map) extHeaders.build()) + .build(); + } + + private String slashlessBucketNameFromBlobInfo(BlobInfo blobInfo) { + // 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. + return CharMatcher.anyOf(PATH_DELIMITER).trimFrom(blobInfo.getBucket()); + } + + private String virtualHostFromOptionValue(String vhostOptionValue, String bucketName) { + if (Strings.isNullOrEmpty(vhostOptionValue)) { + return STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + STORAGE_XML_URI_HOST_NAME; + } + return vhostOptionValue; } @Override From c91d831782b11daf932ab69d1bf21e6bc2b7151e Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Thu, 12 Sep 2019 22:54:16 +0000 Subject: [PATCH 2/9] Add `withPathStyle()` method for new SignUrlOption. This allows creating a signed URL in path-style (bucket in the path component of the URI instead of in the hostname, i.e. virtual hosted-style). Note that this was already the default behavior, but adding this method allows us to more easily switch the default behavior in the future; virtual hosted-style is considered best practice. --- .../com/google/cloud/storage/Storage.java | 11 ++++ .../com/google/cloud/storage/StorageImpl.java | 66 +++++++++++++------ 2 files changed, 56 insertions(+), 21 deletions(-) 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 311dc7cdb53a..b5e17db3d08c 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 @@ -1036,6 +1036,7 @@ enum Option { SERVICE_ACCOUNT_CRED, SIGNATURE_VERSION, HOST_NAME, + PATH_STYLE, VIRTUAL_HOST_NAME } @@ -1157,6 +1158,16 @@ public static SignUrlOption withVirtualHostName(String virtualHostName) { public static SignUrlOption withVirtualHostName() { return new SignUrlOption(Option.VIRTUAL_HOST_NAME, ""); } + + /** + * Generate a path-style URL, which places the bucket name in the path portion of the URL + * instead of in the hostname. Note that this cannot be used alongside {@code + * withVirtualHostName()}. Virtual hosted-style URLs, which can be used via the {@code + * withVirtualHostName()} method, should generally be preferred instead. + */ + public static SignUrlOption withPathStyle() { + return new SignUrlOption(Option.PATH_STYLE, ""); + } } /** 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 b5673bf42976..508e103bc21a 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 @@ -660,10 +660,19 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); + // Only allow specifying HOST_NAME along with VIRTUAL_HOST_NAME if the user did not provide + // the virtual hostname, i.e. they used withVirtualHostName() instead of + // withVirtualHostName(String). In this case, we can construct the virtual hosted-style URL + // using the bucket name and the supplied base hostname. + checkArgument( + Strings.isNullOrEmpty((String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME)) + || optionMap.get(SignUrlOption.Option.HOST_NAME) == null, + "Cannot specify HOST_NAME option when also supplying explicit VIRTUAL_HOST_NAME."); + checkArgument( !(optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null - && optionMap.get(SignUrlOption.Option.HOST_NAME) != null), - "Cannot specify both the VIRTUAL_HOST_NAME and HOST_NAME SignUrlOptions together."); + && optionMap.get(SignUrlOption.Option.PATH_STYLE) != null), + "Cannot specify both the VIRTUAL_HOST_NAME and PATH_STYLE SignUrlOptions together."); String bucketName = slashlessBucketNameFromBlobInfo(blobInfo); String escapedBlobName = ""; @@ -675,25 +684,25 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio .replace(";", "%3B"); } - String storageXmlHostName; - boolean useBucketInPath = true; + boolean usePathStyle; + // TODO: If we decide to change the default style used to generate URLs, switch this logic to + // set usePathStyle to false unless PATH_STYLE was specified. if (optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null) { - // In virtual hosted-style endpoints, the bucket is included in the host portion of the URI - // instead of in the path. - useBucketInPath = false; - storageXmlHostName = - virtualHostFromOptionValue( - (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME), bucketName); - } else if (optionMap.get(SignUrlOption.Option.HOST_NAME) != null) { - storageXmlHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME); + usePathStyle = false; } else { - storageXmlHostName = STORAGE_XML_URI_SCHEME + "://" + STORAGE_XML_URI_HOST_NAME; + usePathStyle = true; } + String storageXmlHostName = + usePathStyle + ? STORAGE_XML_URI_SCHEME + "://" + getBaseStorageHostName(optionMap) + : virtualHostRootUrlFromOpts(optionMap, bucketName); + String stPath = - useBucketInPath + usePathStyle ? constructResourceUriPath(bucketName, escapedBlobName, optionMap) : constructResourceUriPath("", escapedBlobName, optionMap); + URI path = URI.create(stPath); // For V2 signing, even if we don't specify the bucket in the URI path, we still need the // canonical resource string that we'll sign to include the bucket. @@ -819,9 +828,7 @@ private SignatureInfo buildSignatureInfo( // Add this host first if needed, allowing it to be overridden in the EXT_HEADERS option below. if (setHostHeaderToVirtualHost) { String vhost = - virtualHostFromOptionValue( - (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME), - slashlessBucketNameFromBlobInfo(blobInfo)); + virtualHostRootUrlFromOpts(optionMap, slashlessBucketNameFromBlobInfo(blobInfo)); vhost = vhost.replaceFirst("http(s)?://", ""); extHeaders.put("host", vhost); } @@ -841,11 +848,28 @@ private String slashlessBucketNameFromBlobInfo(BlobInfo blobInfo) { return CharMatcher.anyOf(PATH_DELIMITER).trimFrom(blobInfo.getBucket()); } - private String virtualHostFromOptionValue(String vhostOptionValue, String bucketName) { - if (Strings.isNullOrEmpty(vhostOptionValue)) { - return STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + STORAGE_XML_URI_HOST_NAME; + /** + * Returns the root URL (scheme and hostname) to be used in a virtual hosted-style URL. + */ + private String virtualHostRootUrlFromOpts( + Map optionMap, String bucketName) { + String vhostOptVal = (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME); + if (!Strings.isNullOrEmpty(vhostOptVal)) { + return vhostOptVal; + } + + return STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap); + } + + /** + * Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". + */ + private String getBaseStorageHostName(Map optionMap) { + String specifiedBaseHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME); + if (!Strings.isNullOrEmpty(specifiedBaseHostName)) { + return specifiedBaseHostName.replaceFirst("http(s)?://", ""); } - return vhostOptionValue; + return STORAGE_XML_URI_HOST_NAME; } @Override From 34bef2ce7baa8033a9f0d84778d8525b15b6a461 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Mon, 16 Sep 2019 20:35:37 +0000 Subject: [PATCH 3/9] Run code formatter. --- .../main/java/com/google/cloud/storage/StorageImpl.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 508e103bc21a..05a4e491898b 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 @@ -848,9 +848,7 @@ private String slashlessBucketNameFromBlobInfo(BlobInfo blobInfo) { return CharMatcher.anyOf(PATH_DELIMITER).trimFrom(blobInfo.getBucket()); } - /** - * Returns the root URL (scheme and hostname) to be used in a virtual hosted-style URL. - */ + /** Returns the root URL (scheme and hostname) to be used in a virtual hosted-style URL. */ private String virtualHostRootUrlFromOpts( Map optionMap, String bucketName) { String vhostOptVal = (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME); @@ -861,9 +859,7 @@ private String virtualHostRootUrlFromOpts( return STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap); } - /** - * Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". - */ + /** Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". */ private String getBaseStorageHostName(Map optionMap) { String specifiedBaseHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME); if (!Strings.isNullOrEmpty(specifiedBaseHostName)) { From 1517686b98033f12fe358b593499488669a9a0d9 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Fri, 20 Sep 2019 01:26:15 +0000 Subject: [PATCH 4/9] Remove unnecessary override, rename methods. Per code review comments. --- .../com/google/cloud/storage/Storage.java | 20 ++------- .../com/google/cloud/storage/StorageImpl.java | 41 +++++-------------- 2 files changed, 13 insertions(+), 48 deletions(-) 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 b5e17db3d08c..067bd3ef944f 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 @@ -1037,7 +1037,7 @@ enum Option { SIGNATURE_VERSION, HOST_NAME, PATH_STYLE, - VIRTUAL_HOST_NAME + VIRTUAL_HOSTED_STYLE } enum SignatureVersion { @@ -1131,20 +1131,6 @@ public static SignUrlOption withHostName(String hostName) { return new SignUrlOption(Option.HOST_NAME, hostName); } - /** - * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI - * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. This must also include - * the scheme component of the URI. Note that this cannot be used alongside {@code - * withHostName()}. For V4 signing, this also sets the "host" header in the canonicalized - * extension headers to the specified value, minus the "http[s]://", unless that header is - * supplied via the {@code withExtHeaders()} method. - * - * @see Request Endpoints - */ - public static SignUrlOption withVirtualHostName(String virtualHostName) { - return new SignUrlOption(Option.VIRTUAL_HOST_NAME, virtualHostName); - } - /** * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. The bucket name will be @@ -1155,8 +1141,8 @@ public static SignUrlOption withVirtualHostName(String virtualHostName) { * * @see Request Endpoints */ - public static SignUrlOption withVirtualHostName() { - return new SignUrlOption(Option.VIRTUAL_HOST_NAME, ""); + public static SignUrlOption withVirtualHostedStyle() { + return new SignUrlOption(Option.VIRTUAL_HOSTED_STYLE, ""); } /** 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 05a4e491898b..fd9644365948 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 @@ -660,19 +660,10 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); - // Only allow specifying HOST_NAME along with VIRTUAL_HOST_NAME if the user did not provide - // the virtual hostname, i.e. they used withVirtualHostName() instead of - // withVirtualHostName(String). In this case, we can construct the virtual hosted-style URL - // using the bucket name and the supplied base hostname. checkArgument( - Strings.isNullOrEmpty((String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME)) - || optionMap.get(SignUrlOption.Option.HOST_NAME) == null, - "Cannot specify HOST_NAME option when also supplying explicit VIRTUAL_HOST_NAME."); - - checkArgument( - !(optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null - && optionMap.get(SignUrlOption.Option.PATH_STYLE) != null), - "Cannot specify both the VIRTUAL_HOST_NAME and PATH_STYLE SignUrlOptions together."); + !(optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) + && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)), + "Cannot specify both the VIRTUAL_HOSTED_STYLE and PATH_STYLE SignUrlOptions together."); String bucketName = slashlessBucketNameFromBlobInfo(blobInfo); String escapedBlobName = ""; @@ -687,7 +678,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio boolean usePathStyle; // TODO: If we decide to change the default style used to generate URLs, switch this logic to // set usePathStyle to false unless PATH_STYLE was specified. - if (optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null) { + if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) { usePathStyle = false; } else { usePathStyle = true; @@ -696,7 +687,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio String storageXmlHostName = usePathStyle ? STORAGE_XML_URI_SCHEME + "://" + getBaseStorageHostName(optionMap) - : virtualHostRootUrlFromOpts(optionMap, bucketName); + : STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap); String stPath = usePathStyle @@ -762,7 +753,7 @@ private String constructResourceUriPath( optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); // If using virtual-hosted style URLs with V2 signing, the path string for a bucket resource // must end with a forward slash. - if (optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME) != null && isV2) { + if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) && isV2) { pathBuilder.append(PATH_DELIMITER); } return pathBuilder.toString(); @@ -824,13 +815,12 @@ private SignatureInfo buildSignatureInfo( // V2 signing requires that the header not include the bucket, but V4 signing requires that // the host name used in the URI must match the "host" header. boolean setHostHeaderToVirtualHost = - optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOST_NAME) && isV4; + optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) && isV4; // Add this host first if needed, allowing it to be overridden in the EXT_HEADERS option below. if (setHostHeaderToVirtualHost) { - String vhost = - virtualHostRootUrlFromOpts(optionMap, slashlessBucketNameFromBlobInfo(blobInfo)); - vhost = vhost.replaceFirst("http(s)?://", ""); - extHeaders.put("host", vhost); + extHeaders.put( + "host", + slashlessBucketNameFromBlobInfo(blobInfo) + "." + getBaseStorageHostName(optionMap)); } if (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS)) { @@ -848,17 +838,6 @@ private String slashlessBucketNameFromBlobInfo(BlobInfo blobInfo) { return CharMatcher.anyOf(PATH_DELIMITER).trimFrom(blobInfo.getBucket()); } - /** Returns the root URL (scheme and hostname) to be used in a virtual hosted-style URL. */ - private String virtualHostRootUrlFromOpts( - Map optionMap, String bucketName) { - String vhostOptVal = (String) optionMap.get(SignUrlOption.Option.VIRTUAL_HOST_NAME); - if (!Strings.isNullOrEmpty(vhostOptVal)) { - return vhostOptVal; - } - - return STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap); - } - /** Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". */ private String getBaseStorageHostName(Map optionMap) { String specifiedBaseHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME); From 95787a215bf7f09903dd5a762e7cf7b0de8e7334 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Tue, 24 Sep 2019 19:45:29 +0000 Subject: [PATCH 5/9] Correctly populate+sign host header for sigV4 URLs The Signature V2 doesn't require us to sign the "host" header, but the Signature V4 process requires it. We need to ensure we're populating and signing the same host header value as is used in the request URL. --- .../google/cloud/storage/SignatureInfo.java | 1 + .../com/google/cloud/storage/StorageImpl.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index 7346390b3840..baf4989f1311 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -65,6 +65,7 @@ private SignatureInfo(Builder builder) { this.accountEmail = builder.accountEmail; this.timestamp = builder.timestamp; + // The "host" header only needs to be present and signed if using V4. if (Storage.SignUrlOption.SignatureVersion.V4.equals(signatureVersion) && (!builder.canonicalizedExtensionHeaders.containsKey("host"))) { canonicalizedExtensionHeaders = 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 fd9644365948..1e14e797b9f8 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 @@ -812,15 +812,15 @@ private SignatureInfo buildSignatureInfo( boolean isV4 = SignUrlOption.SignatureVersion.V4.equals( optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION)); - // V2 signing requires that the header not include the bucket, but V4 signing requires that - // the host name used in the URI must match the "host" header. - boolean setHostHeaderToVirtualHost = - optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) && isV4; - // Add this host first if needed, allowing it to be overridden in the EXT_HEADERS option below. - if (setHostHeaderToVirtualHost) { - extHeaders.put( - "host", - slashlessBucketNameFromBlobInfo(blobInfo) + "." + getBaseStorageHostName(optionMap)); + if (isV4) { // We don't sign the host header for V2 signed URLs; only do this for V4. + // Add the host here first, allowing it to be overridden in the EXT_HEADERS option below. + if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) { + extHeaders.put( + "host", + slashlessBucketNameFromBlobInfo(blobInfo) + "." + getBaseStorageHostName(optionMap)); + } else if (optionMap.containsKey(SignUrlOption.Option.HOST_NAME)) { + extHeaders.put("host", getBaseStorageHostName(optionMap)); + } } if (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS)) { From 41252dd1e0eaa06adcacae2eb5429ea17784adf2 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Thu, 26 Sep 2019 18:12:14 +0000 Subject: [PATCH 6/9] Also change the method names in the docstrings --- .../java/com/google/cloud/storage/Storage.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 067bd3ef944f..f5109b3e7d72 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 @@ -1124,8 +1124,9 @@ public static SignUrlOption signWith(ServiceAccountSigner signer) { /** * Use a different host name than the default host name 'https://storage.googleapis.com'. This - * must also include the scheme component of the URI. Note that this cannot be used alongside - * {@code withVirtualHostName()}. + * must also include the scheme component of the URI. Note that if using this with the {@code + * withVirtualHostedStyle()} method, you should omit the bucket name from the hostname, as it + * automatically gets prepended to the hostname for virtual hosted-style URLs. */ public static SignUrlOption withHostName(String hostName) { return new SignUrlOption(Option.HOST_NAME, hostName); @@ -1134,10 +1135,9 @@ public static SignUrlOption withHostName(String hostName) { /** * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. The bucket name will be - * obtained from the resource passed in. Note that this cannot be used alongside {@code - * withHostName()}. For V4 signing, this also sets the "host" header in the canonicalized - * extension headers to the virtual hosted-style host, unless that header is supplied via the - * {@code withExtHeaders()} method. + * obtained from the resource passed in. For V4 signing, this also sets the "host" header in the + * canonicalized extension headers to the virtual hosted-style host, unless that header is + * supplied via the {@code withExtHeaders()} method. * * @see Request Endpoints */ @@ -1148,8 +1148,8 @@ public static SignUrlOption withVirtualHostedStyle() { /** * Generate a path-style URL, which places the bucket name in the path portion of the URL * instead of in the hostname. Note that this cannot be used alongside {@code - * withVirtualHostName()}. Virtual hosted-style URLs, which can be used via the {@code - * withVirtualHostName()} method, should generally be preferred instead. + * withVirtualHostedStyle()}. Virtual hosted-style URLs, which can be used via the {@code + * withVirtualHostedStyle()} method, should generally be preferred instead. */ public static SignUrlOption withPathStyle() { return new SignUrlOption(Option.PATH_STYLE, ""); From 0f29d2f17214c824ef4b8d6bffefc9dd22e8f244 Mon Sep 17 00:00:00 2001 From: Matt Houglum Date: Thu, 26 Sep 2019 19:43:08 +0000 Subject: [PATCH 7/9] Update docs with examples. --- .../com/google/cloud/storage/Storage.java | 304 ++++++++++-------- 1 file changed, 167 insertions(+), 137 deletions(-) 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 f5109b3e7d72..ba9e31056048 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 @@ -1124,16 +1124,18 @@ public static SignUrlOption signWith(ServiceAccountSigner signer) { /** * Use a different host name than the default host name 'https://storage.googleapis.com'. This - * must also include the scheme component of the URI. Note that if using this with the {@code - * withVirtualHostedStyle()} method, you should omit the bucket name from the hostname, as it - * automatically gets prepended to the hostname for virtual hosted-style URLs. + * must also include the scheme component of the URI. This option is particularly useful for + * developers to point requests to an alternate endpoint (e.g. a staging environment or sending + * requests through VPC). Note that if using this with the {@code withVirtualHostedStyle()} + * method, you should omit the bucket name from the hostname, as it automatically gets prepended + * to the hostname for virtual hosted-style URLs. */ public static SignUrlOption withHostName(String hostName) { return new SignUrlOption(Option.HOST_NAME, hostName); } /** - * Use a virtual hosted-style hostname, which includes the bucket in the host portion of the URI + * Use a virtual hosted-style hostname, which adds the bucket into the host portion of the URI * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. The bucket name will be * obtained from the resource passed in. For V4 signing, this also sets the "host" header in the * canonicalized extension headers to the virtual hosted-style host, unless that header is @@ -1149,7 +1151,9 @@ public static SignUrlOption withVirtualHostedStyle() { * Generate a path-style URL, which places the bucket name in the path portion of the URL * instead of in the hostname. Note that this cannot be used alongside {@code * withVirtualHostedStyle()}. Virtual hosted-style URLs, which can be used via the {@code - * withVirtualHostedStyle()} method, should generally be preferred instead. + * withVirtualHostedStyle()} method, should generally be preferred instead of path-style URLs. + * + * @see Request Endpoints */ public static SignUrlOption withPathStyle() { return new SignUrlOption(Option.PATH_STYLE, ""); @@ -1583,14 +1587,14 @@ public static Builder newBuilder() { *

Example of creating a bucket. * *

{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * Bucket bucket = storage.create(BucketInfo.of(bucketName));
    * }
* *

Example of creating a bucket with storage class and location. * *

{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * Bucket bucket = storage.create(BucketInfo.newBuilder(bucketName)
    *     // See here for possible values: http://g.co/cloud/storage/docs/storage-classes
    *     .setStorageClass(StorageClass.COLDLINE)
@@ -1610,8 +1614,8 @@ public static Builder newBuilder() {
    * 

Example of creating a blob with no content. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
    * Blob blob = storage.create(blobInfo);
@@ -1631,8 +1635,8 @@ public static Builder newBuilder() {
    * 

Example of creating a blob from a byte array. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
    * Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8));
@@ -1654,8 +1658,8 @@ public static Builder newBuilder() {
    * 

Example of creating a blob from a byte array. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
    * Blob blob = storage.create(blobInfo, "Hello, World!".getBytes(UTF_8), 7, 5);
@@ -1681,8 +1685,8 @@ Blob create(
    * 

Example of creating a blob from an input stream. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * InputStream content = new ByteArrayInputStream("Hello, World!".getBytes(UTF_8));
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
@@ -1692,8 +1696,8 @@ Blob create(
    * 

Example of uploading an encrypted blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String encryptionKey = "my_encryption_key";
    * InputStream content = new ByteArrayInputStream("Hello, World!".getBytes(UTF_8));
    *
@@ -1720,7 +1724,7 @@ Blob create(
    * otherwise a {@link StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * long bucketMetageneration = 42;
    * Bucket bucket = storage.get(bucketName,
    *     BucketGetOption.metagenerationMatch(bucketMetageneration));
@@ -1743,7 +1747,7 @@ Blob create(
    * matches the bucket's service metageneration otherwise a {@link StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * Bucket bucket = storage.get(bucketName, BucketGetOption.fields(BucketField.METAGENERATION));
    * storage.lockRetentionPolicy(bucket, BucketTargetOption.metagenerationMatch());
    * }
@@ -1763,8 +1767,8 @@ Blob create( * otherwise a {@link StorageException} is thrown. * *
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobMetageneration = 42;
    * Blob blob = storage.get(bucketName, blobName,
    *     BlobGetOption.metagenerationMatch(blobMetageneration));
@@ -1784,8 +1788,8 @@ Blob create(
    * otherwise a {@link StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobMetageneration = 42;
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * Blob blob = storage.get(blobId, BlobGetOption.metagenerationMatch(blobMetageneration));
@@ -1799,8 +1803,8 @@ Blob create(
    *     href="https://cloud.google.com/storage/docs/encryption/customer-supplied-keys#encrypted-elements">Encrypted
    *     Elements
    *     
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String blobEncryptionKey = "";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * Blob blob = storage.get(blobId, BlobGetOption.decryptionKey(blobEncryptionKey));
@@ -1816,8 +1820,8 @@ Blob create(
    * 

Example of getting information on a blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * Blob blob = storage.get(blobId);
    * }
@@ -1853,7 +1857,7 @@ Blob create( *

Example of listing blobs in a provided directory. * *

{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * String directory = "my_directory/";
    * Page blobs = storage.list(bucketName, BlobListOption.currentDirectory(),
    *     BlobListOption.prefix(directory));
@@ -1877,7 +1881,7 @@ Blob create(
    * 

Example of updating bucket information. * *

{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * BucketInfo bucketInfo = BucketInfo.newBuilder(bucketName).setVersioningEnabled(true).build();
    * Bucket bucket = storage.update(bucketInfo);
    * }
@@ -1898,8 +1902,8 @@ Blob create( * {@link StorageException} is thrown. * *
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * Blob blob = storage.get(bucketName, blobName);
    * BlobInfo updatedInfo = blob.toBuilder().setContentType("text/plain").build();
    * storage.update(updatedInfo, BlobTargetOption.metagenerationMatch());
@@ -1919,8 +1923,8 @@ Blob create(
    * 

Example of adding new metadata values or updating existing ones. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * Map newMetadata = new HashMap<>();
    * newMetadata.put("keyToAddOrUpdate", "value");
    * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
@@ -1931,8 +1935,8 @@ Blob create(
    * 

Example of removing metadata values. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * Map newMetadata = new HashMap<>();
    * newMetadata.put("keyToRemove", null);
    * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName)
@@ -1955,7 +1959,7 @@ Blob create(
    * StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
+   * String bucketName = "my-unique-bucket";
    * long bucketMetageneration = 42;
    * boolean deleted = storage.delete(bucketName,
    *     BucketSourceOption.metagenerationMatch(bucketMetageneration));
@@ -1978,8 +1982,8 @@ Blob create(
    * StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobGeneration = 42;
    * boolean deleted = storage.delete(bucketName, blobName,
    *     BlobSourceOption.generationMatch(blobGeneration));
@@ -2005,8 +2009,8 @@ Blob create(
    * StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobGeneration = 42;
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * boolean deleted = storage.delete(blobId, BlobSourceOption.generationMatch(blobGeneration));
@@ -2028,8 +2032,8 @@ Blob create(
    * 

Example of deleting a blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * boolean deleted = storage.delete(blobId);
    * if (deleted) {
@@ -2053,8 +2057,8 @@ Blob create(
    * 

Example of composing two blobs. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String sourceBlob1 = "source_blob_1";
    * String sourceBlob2 = "source_blob_2";
    * BlobId blobId = BlobId.of(bucketName, blobName);
@@ -2088,8 +2092,8 @@ Blob create(
    * 

Example of copying a blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String copyBlobName = "copy_blob_name";
    * CopyRequest request = CopyRequest.newBuilder()
    *     .setSource(BlobId.of(bucketName, blobName))
@@ -2101,8 +2105,8 @@ Blob create(
    * 

Example of copying a blob in chunks. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String copyBlobName = "copy_blob_name";
    * CopyRequest request = CopyRequest.newBuilder()
    *     .setSource(BlobId.of(bucketName, blobName))
@@ -2118,8 +2122,8 @@ Blob create(
    * 

Example of rotating the encryption key of a blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String oldEncryptionKey = "old_encryption_key";
    * String newEncryptionKey = "new_encryption_key";
    * BlobId blobId = BlobId.of(bucketName, blobName);
@@ -2145,8 +2149,8 @@ Blob create(
    * StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobGeneration = 42";
    * byte[] content = storage.readAllBytes(bucketName, blobName,
    *     BlobSourceOption.generationMatch(blobGeneration));
@@ -2164,8 +2168,8 @@ Blob create(
    * StorageException} is thrown.
    *
    * 
{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * long blobGeneration = 42;
    * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
    * byte[] content = storage.readAllBytes(blobId);
@@ -2174,8 +2178,8 @@ Blob create(
    * 

Example of reading all bytes of an encrypted blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * String decryptionKey = "my_encryption_key";
    * byte[] content = storage.readAllBytes(
    *     bucketName, blobName, BlobSourceOption.decryptionKey(decryptionKey));
@@ -2192,9 +2196,9 @@ Blob create(
    * 

Example of using a batch request to delete, update and get a blob. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName1 = "my_blob_name1";
-   * String blobName2 = "my_blob_name2";
+   * String bucketName = "my-unique-bucket";
+   * String blobName1 = "my-blob-name1";
+   * String blobName2 = "my-blob-name2";
    * StorageBatch batch = storage.batch();
    * BlobId firstBlob = BlobId.of(bucketName, blobName1);
    * BlobId secondBlob = BlobId.of(bucketName, blobName2);
@@ -2223,8 +2227,8 @@ Blob create(
    * 

Example of reading a blob's content through a reader. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * try (ReadChannel reader = storage.reader(bucketName, blobName)) {
    *   ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
    *   while (reader.read(bytes) > 0) {
@@ -2254,8 +2258,8 @@ Blob create(
    * 

Example of reading a blob's content through a reader. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * try (ReadChannel reader = storage.reader(blobId)) {
    *   ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
@@ -2279,8 +2283,8 @@ Blob create(
    * 

Example of writing a blob's content through a writer. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * byte[] content = "Hello, World!".getBytes(UTF_8);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
@@ -2303,12 +2307,15 @@ Blob create(
    * 

Example of writing content through a writer using signed URL. * *

{@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
+   * String bucketName = "my-unique-bucket";
+   * String blobName = "my-blob-name";
    * BlobId blobId = BlobId.of(bucketName, blobName);
    * byte[] content = "Hello, World!".getBytes(UTF_8);
    * BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build();
-   * URL signedURL = storage.signUrl(blobInfo, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST));
+   * URL signedURL = storage.signUrl(
+   *     blobInfo,
+   *     1, TimeUnit.HOURS,
+   *     Storage.SignUrlOption.httpMethod(HttpMethod.POST));
    * try (WriteChannel writer = storage.writer(signedURL)) {
    *    writer.write(ByteBuffer.wrap(content, 0, content.length));
    * }
@@ -2341,36 +2348,60 @@ Blob create(
    *   
  • The default credentials, if no credentials were passed to {@link StorageOptions} * * - *

    Example of creating a signed URL that is valid for 2 weeks, using the default credentials - * for signing the URL. + *

    Example of creating a signed URL that is valid for 1 week, using the default credentials for + * signing the URL, the default signing method (V2), and the default URL style (path-style): * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    -   * URL signedUrl = storage.signUrl(BlobInfo.newBuilder(bucketName, blobName).build(), 14,
    -   *     TimeUnit.DAYS);
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
    +   * URL signedUrl = storage.signUrl(
    +   *     BlobInfo.newBuilder(bucketName, blobName).build(),
    +   *     7, TimeUnit.DAYS);
        * }
    * *

    Example of creating a signed URL passing the {@link SignUrlOption#withV4Signature()} option, - * which enables V4 signing. + * which enables V4 signing: + * + *

    {@code
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
    +   * URL signedUrl = storage.signUrl(
    +   *     BlobInfo.newBuilder(bucketName, blobName).build(),
    +   *     7, TimeUnit.DAYS,
    +   *     Storage.SignUrlOption.withV4Signature());
    +   * }
    + * + *

    Example of creating a signed URL passing the {@link SignUrlOption#withVirtualHostedStyle()} + * option, which specifies the bucket name in the hostname of the URI, rather than in the path: + * + *

    {@code
    +   * URL signedUrl = storage.signUrl(
    +   *     BlobInfo.newBuilder(bucketName, blobName).build(),
    +   *     1, TimeUnit.DAYS,
    +   *     Storage.SignUrlOption.withVirtualHostedStyle());
    +   * }
    + * + *

    Example of creating a signed URL passing the {@link SignUrlOption#withPathStyle()} option, + * which specifies the bucket name in path portion of the URI, rather than in the hostname: * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    -   * URL signedUrl = storage.signUrl(BlobInfo.newBuilder(bucketName, blobName).build(),
    -   *     7, TimeUnit.DAYS, Storage.SignUrlOption.withV4Signature());
    +   * URL signedUrl = storage.signUrl(
    +   *     BlobInfo.newBuilder(bucketName, blobName).build(),
    +   *     1, TimeUnit.DAYS,
    +   *     Storage.SignUrlOption.withPathStyle());
        * }
    * *

    Example of creating a signed URL passing the {@link * SignUrlOption#signWith(ServiceAccountSigner)} option, that will be used for signing the URL. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    -   * String keyPath = "/path/to/key.json";
    -   * URL signedUrl = storage.signUrl(BlobInfo.newBuilder(bucketName, blobName).build(),
    -   *     14, TimeUnit.DAYS, SignUrlOption.signWith(
    -   *         ServiceAccountCredentials.fromStream(new FileInputStream(keyPath))));
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
    +   * String kfPath = "/path/to/keyfile.json";
    +   * URL signedUrl = storage.signUrl(
    +   *     BlobInfo.newBuilder(bucketName, blobName).build(),
    +   *     7, TimeUnit.DAYS,
    +   *     SignUrlOption.signWith(ServiceAccountCredentials.fromStream(new FileInputStream(kfPath))));
        * }
    * *

    Note that the {@link ServiceAccountSigner} may require additional configuration to enable @@ -2380,8 +2411,7 @@ Blob create( * @param duration time until the signed URL expires, expressed in {@code unit}. The finest * granularity supported is 1 second, finer granularities will be truncated * @param unit time unit of the {@code duration} parameter - * @param options optional URL signing options {@code SignUrlOption.withHostName()} option to set - * a custom host name instead of using https://storage.googleapis.com. + * @param options optional URL signing options * @throws IllegalStateException if {@link SignUrlOption#signWith(ServiceAccountSigner)} was not * used and no implementation of {@link ServiceAccountSigner} was provided to {@link * StorageOptions} @@ -2400,9 +2430,9 @@ Blob create( *

    Example of getting information on several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * BlobId firstBlob = BlobId.of(bucketName, blobName1);
        * BlobId secondBlob = BlobId.of(bucketName, blobName2);
        * List blobs = storage.get(firstBlob, secondBlob);
    @@ -2421,9 +2451,9 @@ Blob create(
        * 

    Example of getting information on several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * List blobIds = new LinkedList<>();
        * blobIds.add(BlobId.of(bucketName, blobName1));
        * blobIds.add(BlobId.of(bucketName, blobName2));
    @@ -2446,9 +2476,9 @@ Blob create(
        * 

    Example of updating information on several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * Blob firstBlob = storage.get(bucketName, blobName1);
        * Blob secondBlob = storage.get(bucketName, blobName2);
        * List updatedBlobs = storage.update(
    @@ -2472,9 +2502,9 @@ Blob create(
        * 

    Example of updating information on several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * Blob firstBlob = storage.get(bucketName, blobName1);
        * Blob secondBlob = storage.get(bucketName, blobName2);
        * List blobs = new LinkedList<>();
    @@ -2496,9 +2526,9 @@ Blob create(
        * 

    Example of deleting several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * BlobId firstBlob = BlobId.of(bucketName, blobName1);
        * BlobId secondBlob = BlobId.of(bucketName, blobName2);
        * List deleted = storage.delete(firstBlob, secondBlob);
    @@ -2518,9 +2548,9 @@ Blob create(
        * 

    Example of deleting several blobs using a single batch request. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName1 = "my_blob_name1";
    -   * String blobName2 = "my_blob_name2";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName1 = "my-blob-name1";
    +   * String blobName2 = "my-blob-name2";
        * List blobIds = new LinkedList<>();
        * blobIds.add(BlobId.of(bucketName, blobName1));
        * blobIds.add(BlobId.of(bucketName, blobName2));
    @@ -2542,7 +2572,7 @@ Blob create(
        * 

    Example of getting the ACL entry for an entity on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.getAcl(bucketName, User.ofAllAuthenticatedUsers());
        * }
    * @@ -2550,7 +2580,7 @@ Blob create( * user_project option. * *
    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * String userEmail = "google-cloud-java-tests@java-docs-samples-tests.iam.gserviceaccount.com";
        * BucketSourceOption userProjectOption = BucketSourceOption.userProject("myProject");
        * Acl acl = storage.getAcl(bucketName, new User(userEmail), userProjectOption);
    @@ -2572,7 +2602,7 @@ Blob create(
        * 

    Example of deleting the ACL entry for an entity on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * boolean deleted = storage.deleteAcl(bucketName, User.ofAllAuthenticatedUsers());
        * if (deleted) {
        *   // the acl entry was deleted
    @@ -2585,7 +2615,7 @@ Blob create(
        * user_project option.
        *
        * 
    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * BucketSourceOption userProject = BucketSourceOption.userProject("myProject");
        * boolean deleted = storage.deleteAcl(bucketName, User.ofAllAuthenticatedUsers(), userProject);
        * }
    @@ -2607,14 +2637,14 @@ Blob create( *

    Example of creating a new ACL entry on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.createAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.READER));
        * }
    * *

    Example of creating a new ACL entry on a requester_pays bucket with a user_project option. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.createAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.READER),
        *     BucketSourceOption.userProject("myProject"));
        * }
    @@ -2635,14 +2665,14 @@ Blob create( *

    Example of updating a new ACL entry on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.updateAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.OWNER));
        * }
    * *

    Example of updating a new ACL entry on a requester_pays bucket with a user_project option. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.updateAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.OWNER),
        *     BucketSourceOption.userProject("myProject"));
        * }
    @@ -2663,7 +2693,7 @@ Blob create( *

    Example of listing the ACL entries for a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * List acls = storage.listAcls(bucketName);
        * for (Acl acl : acls) {
        *   // do something with ACL entry
    @@ -2674,7 +2704,7 @@ Blob create(
        * option.
        *
        * 
    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * List acls = storage.listAcls(bucketName, BucketSourceOption.userProject("myProject"));
        * for (Acl acl : acls) {
        *   // do something with ACL entry
    @@ -2700,7 +2730,7 @@ Blob create(
        * 

    Example of getting the default ACL entry for an entity on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl = storage.getDefaultAcl(bucketName, User.ofAllAuthenticatedUsers());
        * }
    * @@ -2717,7 +2747,7 @@ Blob create( *

    Example of deleting the default ACL entry for an entity on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * boolean deleted = storage.deleteDefaultAcl(bucketName, User.ofAllAuthenticatedUsers());
        * if (deleted) {
        *   // the acl entry was deleted
    @@ -2740,7 +2770,7 @@ Blob create(
        * 

    Example of creating a new default ACL entry on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl =
        *     storage.createDefaultAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.READER));
        * }
    @@ -2758,7 +2788,7 @@ Blob create( *

    Example of updating a new default ACL entry on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Acl acl =
        *     storage.updateDefaultAcl(bucketName, Acl.of(User.ofAllAuthenticatedUsers(), Role.OWNER));
        * }
    @@ -2776,7 +2806,7 @@ Blob create( *

    Example of listing the default ACL entries for a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * List acls = storage.listDefaultAcls(bucketName);
        * for (Acl acl : acls) {
        *   // do something with ACL entry
    @@ -2794,8 +2824,8 @@ Blob create(
        * 

    Example of getting the ACL entry for an entity on a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * Acl acl = storage.getAcl(blobId, User.ofAllAuthenticatedUsers());
    @@ -2804,8 +2834,8 @@ Blob create(
        * 

    Example of getting the ACL entry for a specific user on a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * String userEmail = "google-cloud-java-tests@java-docs-samples-tests.iam.gserviceaccount.com";
        * BlobId blobId = BlobId.of(bucketName, blobName);
        * Acl acl = storage.getAcl(blobId, new User(userEmail));
    @@ -2821,8 +2851,8 @@ Blob create(
        * 

    Example of deleting the ACL entry for an entity on a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * boolean deleted = storage.deleteAcl(blobId, User.ofAllAuthenticatedUsers());
    @@ -2844,8 +2874,8 @@ Blob create(
        * 

    Example of creating a new ACL entry on a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * Acl acl = storage.createAcl(blobId, Acl.of(User.ofAllAuthenticatedUsers(), Role.READER));
    @@ -2854,8 +2884,8 @@ Blob create(
        * 

    Example of updating a blob to be public-read. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * Acl acl = storage.createAcl(blobId, Acl.of(User.ofAllUsers(), Role.READER));
    @@ -2871,8 +2901,8 @@ Blob create(
        * 

    Example of updating a new ACL entry on a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * Acl acl = storage.updateAcl(blobId, Acl.of(User.ofAllAuthenticatedUsers(), Role.OWNER));
    @@ -2888,8 +2918,8 @@ Blob create(
        * 

    Example of listing the ACL entries for a blob. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    -   * String blobName = "my_blob_name";
    +   * String bucketName = "my-unique-bucket";
    +   * String blobName = "my-blob-name";
        * long blobGeneration = 42;
        * BlobId blobId = BlobId.of(bucketName, blobName, blobGeneration);
        * List acls = storage.listAcls(blobId);
    @@ -3014,7 +3044,7 @@ HmacKeyMetadata updateHmacKeyState(
        * 

    Example of getting the IAM policy for a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Policy policy = storage.getIamPolicy(bucketName);
        * }
    * @@ -3031,7 +3061,7 @@ HmacKeyMetadata updateHmacKeyState( * *
    {@code
        * // We want to make all objects in our bucket publicly readable.
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * Policy currentPolicy = storage.getIamPolicy(bucketName);
        * Policy updatedPolicy =
        *     storage.setIamPolicy(
    @@ -3055,7 +3085,7 @@ HmacKeyMetadata updateHmacKeyState(
        * 

    Example of testing permissions on a bucket. * *

    {@code
    -   * String bucketName = "my_unique_bucket";
    +   * String bucketName = "my-unique-bucket";
        * List response =
        *     storage.testIamPermissions(
        *         bucket,
    
    From 4cda39534269c7430d961543af2bd642533c927b Mon Sep 17 00:00:00 2001
    From: Matt Houglum 
    Date: Thu, 26 Sep 2019 23:38:11 +0000
    Subject: [PATCH 8/9] Add tests for withVirtualHostedStyle, fix bug.
    
    I discovered a bug with one of my previous commits, where I was
    incorrectly determining whether to use Signature Version 2. This commit
    makes two new (private) methods that determine the correct default
    option to use, taking default values into account.
    ---
     .../com/google/cloud/storage/StorageImpl.java | 40 +++++---
     .../cloud/storage/it/ITStorageTest.java       | 99 ++++++++++++-------
     2 files changed, 88 insertions(+), 51 deletions(-)
    
    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 1e14e797b9f8..78db9fc9e283 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
    @@ -638,11 +638,9 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
         }
     
         boolean isV2 =
    -        SignUrlOption.SignatureVersion.V2.equals(
    -            optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION));
    +        getPreferredSignatureVersion(optionMap).equals(SignUrlOption.SignatureVersion.V2);
         boolean isV4 =
    -        SignUrlOption.SignatureVersion.V4.equals(
    -            optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION));
    +        getPreferredSignatureVersion(optionMap).equals(SignUrlOption.SignatureVersion.V4);
     
         ServiceAccountSigner credentials =
             (ServiceAccountSigner) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED);
    @@ -675,14 +673,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
                   .replace(";", "%3B");
         }
     
    -    boolean usePathStyle;
    -    // TODO: If we decide to change the default style used to generate URLs, switch this logic to
    -    // set usePathStyle to false unless PATH_STYLE was specified.
    -    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) {
    -      usePathStyle = false;
    -    } else {
    -      usePathStyle = true;
    -    }
    +    boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap);
     
         String storageXmlHostName =
             usePathStyle
    @@ -749,8 +740,7 @@ private String constructResourceUriPath(
         pathBuilder.append(PATH_DELIMITER).append(slashlessBucketName);
         if (Strings.isNullOrEmpty(escapedBlobName)) {
           boolean isV2 =
    -          SignUrlOption.SignatureVersion.V2.equals(
    -              optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION));
    +          getPreferredSignatureVersion(optionMap).equals(SignUrlOption.SignatureVersion.V2);
           // If using virtual-hosted style URLs with V2 signing, the path string for a bucket resource
           // must end with a forward slash.
           if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) && isV2) {
    @@ -765,6 +755,28 @@ private String constructResourceUriPath(
         return pathBuilder.toString();
       }
     
    +  private SignUrlOption.SignatureVersion getPreferredSignatureVersion(
    +      EnumMap optionMap) {
    +    // Check for an explicitly specified version in the map.
    +    for (SignUrlOption.SignatureVersion version : SignUrlOption.SignatureVersion.values()) {
    +      if (version.equals(optionMap.get(SignUrlOption.Option.SIGNATURE_VERSION))) {
    +        return version;
    +      }
    +    }
    +    // TODO(#6362): V2 is the default, and thus can be specified either explicitly or implicitly
    +    // Change this to V4 once we make it the default.
    +    return SignUrlOption.SignatureVersion.V2;
    +  }
    +
    +  private boolean shouldUsePathStyleForSignedUrl(EnumMap optionMap) {
    +    // TODO(#6362): If we decide to change the default style used to generate URLs, switch this
    +    // logic to return false unless PATH_STYLE was explicitly specified.
    +    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) {
    +      return false;
    +    }
    +    return true;
    +  }
    +
       /**
        * Builds signature info.
        *
    diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
    index ce4fa12f30ed..305dd17e1cc6 100644
    --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
    +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
    @@ -1819,17 +1819,21 @@ public void testGetSignedUrl() throws IOException {
         if (storage.getOptions().getCredentials() != null) {
           assumeTrue(storage.getOptions().getCredentials() instanceof ServiceAccountSigner);
         }
    -
         String blobName = "test-get-signed-url-blob/with/slashes/and?special=!#$&'()*+,:;=?@[]";
         BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build();
         Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
         assertNotNull(remoteBlob);
    -    URL url = storage.signUrl(blob, 1, TimeUnit.HOURS);
    -    URLConnection connection = url.openConnection();
    -    byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
    -    try (InputStream responseStream = connection.getInputStream()) {
    -      assertEquals(BLOB_BYTE_CONTENT.length, responseStream.read(readBytes));
    -      assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
    +    for (Storage.SignUrlOption urlStyle :
    +        Arrays.asList(
    +            Storage.SignUrlOption.withPathStyle(),
    +            Storage.SignUrlOption.withVirtualHostedStyle())) {
    +      URL url = storage.signUrl(blob, 1, TimeUnit.HOURS, urlStyle);
    +      URLConnection connection = url.openConnection();
    +      byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
    +      try (InputStream responseStream = connection.getInputStream()) {
    +        assertEquals(BLOB_BYTE_CONTENT.length, responseStream.read(readBytes));
    +        assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
    +      }
         }
       }
     
    @@ -1841,15 +1845,22 @@ public void testPostSignedUrl() throws IOException {
         String blobName = "test-post-signed-url-blob";
         BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build();
         assertNotNull(storage.create(blob));
    -    URL url =
    -        storage.signUrl(blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST));
    -    URLConnection connection = url.openConnection();
    -    connection.setDoOutput(true);
    -    connection.connect();
    -    Blob remoteBlob = storage.get(BUCKET, blobName);
    -    assertNotNull(remoteBlob);
    -    assertEquals(blob.getBucket(), remoteBlob.getBucket());
    -    assertEquals(blob.getName(), remoteBlob.getName());
    +    for (Storage.SignUrlOption urlStyle :
    +        Arrays.asList(
    +            Storage.SignUrlOption.withPathStyle(),
    +            Storage.SignUrlOption.withVirtualHostedStyle())) {
    +
    +      URL url =
    +          storage.signUrl(
    +              blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST), urlStyle);
    +      URLConnection connection = url.openConnection();
    +      connection.setDoOutput(true);
    +      connection.connect();
    +      Blob remoteBlob = storage.get(BUCKET, blobName);
    +      assertNotNull(remoteBlob);
    +      assertEquals(blob.getBucket(), remoteBlob.getBucket());
    +      assertEquals(blob.getName(), remoteBlob.getName());
    +    }
       }
     
       @Test
    @@ -1862,12 +1873,20 @@ public void testV4SignedUrl() throws IOException {
         BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build();
         Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT);
         assertNotNull(remoteBlob);
    -    URL url = storage.signUrl(blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.withV4Signature());
    -    URLConnection connection = url.openConnection();
    -    byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
    -    try (InputStream responseStream = connection.getInputStream()) {
    -      assertEquals(BLOB_BYTE_CONTENT.length, responseStream.read(readBytes));
    -      assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
    +    for (Storage.SignUrlOption urlStyle :
    +        Arrays.asList(
    +            Storage.SignUrlOption.withPathStyle(),
    +            Storage.SignUrlOption.withVirtualHostedStyle())) {
    +
    +      URL url =
    +          storage.signUrl(
    +              blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.withV4Signature(), urlStyle);
    +      URLConnection connection = url.openConnection();
    +      byte[] readBytes = new byte[BLOB_BYTE_CONTENT.length];
    +      try (InputStream responseStream = connection.getInputStream()) {
    +        assertEquals(BLOB_BYTE_CONTENT.length, responseStream.read(readBytes));
    +        assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
    +      }
         }
       }
     
    @@ -2661,23 +2680,29 @@ public void testUploadUsingSignedURL() throws Exception {
         String blobName = "test-signed-url-upload";
         BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build();
         assertNotNull(storage.create(blob));
    -    URL signUrl =
    -        storage.signUrl(blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST));
    -    byte[] bytesArrayToUpload = BLOB_STRING_CONTENT.getBytes();
    -    try (WriteChannel writer = storage.writer(signUrl)) {
    -      writer.write(ByteBuffer.wrap(bytesArrayToUpload, 0, bytesArrayToUpload.length));
    -    }
    +    for (Storage.SignUrlOption urlStyle :
    +        Arrays.asList(
    +            Storage.SignUrlOption.withPathStyle(),
    +            Storage.SignUrlOption.withVirtualHostedStyle())) {
    +      URL signUrl =
    +          storage.signUrl(
    +              blob, 1, TimeUnit.HOURS, Storage.SignUrlOption.httpMethod(HttpMethod.POST), urlStyle);
    +      byte[] bytesArrayToUpload = BLOB_STRING_CONTENT.getBytes();
    +      try (WriteChannel writer = storage.writer(signUrl)) {
    +        writer.write(ByteBuffer.wrap(bytesArrayToUpload, 0, bytesArrayToUpload.length));
    +      }
     
    -    int lengthOfDownLoadBytes = -1;
    -    BlobId blobId = BlobId.of(BUCKET, blobName);
    -    Blob blobToRead = storage.get(blobId);
    -    try (ReadChannel reader = blobToRead.reader()) {
    -      ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
    -      lengthOfDownLoadBytes = reader.read(bytes);
    -    }
    +      int lengthOfDownLoadBytes = -1;
    +      BlobId blobId = BlobId.of(BUCKET, blobName);
    +      Blob blobToRead = storage.get(blobId);
    +      try (ReadChannel reader = blobToRead.reader()) {
    +        ByteBuffer bytes = ByteBuffer.allocate(64 * 1024);
    +        lengthOfDownLoadBytes = reader.read(bytes);
    +      }
     
    -    assertEquals(bytesArrayToUpload.length, lengthOfDownLoadBytes);
    -    assertTrue(storage.delete(BUCKET, blobName));
    +      assertEquals(bytesArrayToUpload.length, lengthOfDownLoadBytes);
    +      assertTrue(storage.delete(BUCKET, blobName));
    +    }
       }
     
       @Test
    
    From fc6e31ee9a83784d3ceaea553cac5ddab82cf2a4 Mon Sep 17 00:00:00 2001
    From: Matt Houglum 
    Date: Mon, 30 Sep 2019 23:56:04 +0000
    Subject: [PATCH 9/9] Add example URL to docstring.
    
    ---
     .../java/com/google/cloud/storage/Storage.java    | 15 ++++++++-------
     1 file changed, 8 insertions(+), 7 deletions(-)
    
    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 ba9e31056048..b0a3e9137a0b 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
    @@ -1136,10 +1136,10 @@ public static SignUrlOption withHostName(String hostName) {
     
         /**
          * Use a virtual hosted-style hostname, which adds the bucket into the host portion of the URI
    -     * rather than the path, e.g. 'https://mybucket.storage.googleapis.com'. The bucket name will be
    -     * obtained from the resource passed in. For V4 signing, this also sets the "host" header in the
    -     * canonicalized extension headers to the virtual hosted-style host, unless that header is
    -     * supplied via the {@code withExtHeaders()} method.
    +     * rather than the path, e.g. 'https://mybucket.storage.googleapis.com/...'. The bucket name
    +     * will be obtained from the resource passed in. For V4 signing, this also sets the "host"
    +     * header in the canonicalized extension headers to the virtual hosted-style host, unless that
    +     * header is supplied via the {@code withExtHeaders()} method.
          *
          * @see Request Endpoints
          */
    @@ -1149,9 +1149,10 @@ public static SignUrlOption withVirtualHostedStyle() {
     
         /**
          * Generate a path-style URL, which places the bucket name in the path portion of the URL
    -     * instead of in the hostname. Note that this cannot be used alongside {@code
    -     * withVirtualHostedStyle()}. Virtual hosted-style URLs, which can be used via the {@code
    -     * withVirtualHostedStyle()} method, should generally be preferred instead of path-style URLs.
    +     * instead of in the hostname, e.g 'https://storage.googleapis.com/mybucket/...'. Note that this
    +     * cannot be used alongside {@code withVirtualHostedStyle()}. Virtual hosted-style URLs, which
    +     * can be used via the {@code withVirtualHostedStyle()} method, should generally be preferred
    +     * instead of path-style URLs.
          *
          * @see Request Endpoints
          */