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/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 8b3771d2c2cf..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 @@ -1035,7 +1035,9 @@ enum Option { EXT_HEADERS, SERVICE_ACCOUNT_CRED, SIGNATURE_VERSION, - HOST_NAME + HOST_NAME, + PATH_STYLE, + VIRTUAL_HOSTED_STYLE } enum SignatureVersion { @@ -1122,11 +1124,41 @@ 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. 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 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. + * + * @see Request Endpoints + */ + public static SignUrlOption withVirtualHostedStyle() { + return new SignUrlOption(Option.VIRTUAL_HOSTED_STYLE, ""); + } + + /** + * Generate a path-style URL, which places the bucket name in the path portion of the URL + * 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 + */ + public static SignUrlOption withPathStyle() { + return new SignUrlOption(Option.PATH_STYLE, ""); + } } /** @@ -1556,14 +1588,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) @@ -1583,8 +1615,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); @@ -1604,8 +1636,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)); @@ -1627,8 +1659,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); @@ -1654,8 +1686,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(); @@ -1665,8 +1697,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)); * @@ -1693,7 +1725,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)); @@ -1716,7 +1748,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()); * }@@ -1736,8 +1768,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)); @@ -1757,8 +1789,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)); @@ -1772,8 +1804,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)); @@ -1789,8 +1821,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); * }@@ -1826,7 +1858,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/"; * Pageblobs = storage.list(bucketName, BlobListOption.currentDirectory(), * BlobListOption.prefix(directory)); @@ -1850,7 +1882,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); * }@@ -1871,8 +1903,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()); @@ -1892,8 +1924,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"; * MapnewMetadata = new HashMap<>(); * newMetadata.put("keyToAddOrUpdate", "value"); * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName) @@ -1904,8 +1936,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"; * MapnewMetadata = new HashMap<>(); * newMetadata.put("keyToRemove", null); * Blob blob = storage.update(BlobInfo.newBuilder(bucketName, blobName) @@ -1928,7 +1960,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)); @@ -1951,8 +1983,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)); @@ -1978,8 +2010,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)); @@ -2001,8 +2033,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) { @@ -2026,8 +2058,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); @@ -2061,8 +2093,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)) @@ -2074,8 +2106,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)) @@ -2091,8 +2123,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); @@ -2118,8 +2150,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)); @@ -2137,8 +2169,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); @@ -2147,8 +2179,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)); @@ -2165,9 +2197,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); @@ -2196,8 +2228,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) { @@ -2227,8 +2259,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); @@ -2252,8 +2284,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(); @@ -2276,12 +2308,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)); * } @@ -2314,36 +2349,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 @@ -2353,8 +2412,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} @@ -2373,9 +2431,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); * Listblobs = storage.get(firstBlob, secondBlob); @@ -2394,9 +2452,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"; * ListblobIds = new LinkedList<>(); * blobIds.add(BlobId.of(bucketName, blobName1)); * blobIds.add(BlobId.of(bucketName, blobName2)); @@ -2419,9 +2477,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); * ListupdatedBlobs = storage.update( @@ -2445,9 +2503,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); * Listblobs = new LinkedList<>(); @@ -2469,9 +2527,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); * Listdeleted = storage.delete(firstBlob, secondBlob); @@ -2491,9 +2549,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"; * ListblobIds = new LinkedList<>(); * blobIds.add(BlobId.of(bucketName, blobName1)); * blobIds.add(BlobId.of(bucketName, blobName2)); @@ -2515,7 +2573,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()); * }* @@ -2523,7 +2581,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); @@ -2545,7 +2603,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 @@ -2558,7 +2616,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); * }@@ -2580,14 +2638,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")); * }@@ -2608,14 +2666,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")); * }@@ -2636,7 +2694,7 @@ Blob create( *Example of listing the ACL entries for a blob. * *
{@code - * String bucketName = "my_unique_bucket"; + * String bucketName = "my-unique-bucket"; * Listacls = storage.listAcls(bucketName); * for (Acl acl : acls) { * // do something with ACL entry @@ -2647,7 +2705,7 @@ Blob create( * option. * * {@code - * String bucketName = "my_unique_bucket"; + * String bucketName = "my-unique-bucket"; * Listacls = storage.listAcls(bucketName, BucketSourceOption.userProject("myProject")); * for (Acl acl : acls) { * // do something with ACL entry @@ -2673,7 +2731,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()); * }* @@ -2690,7 +2748,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 @@ -2713,7 +2771,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)); * }@@ -2731,7 +2789,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)); * }@@ -2749,7 +2807,7 @@ Blob create( *Example of listing the default ACL entries for a blob. * *
{@code - * String bucketName = "my_unique_bucket"; + * String bucketName = "my-unique-bucket"; * Listacls = storage.listDefaultAcls(bucketName); * for (Acl acl : acls) { * // do something with ACL entry @@ -2767,8 +2825,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()); @@ -2777,8 +2835,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)); @@ -2794,8 +2852,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()); @@ -2817,8 +2875,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)); @@ -2827,8 +2885,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)); @@ -2844,8 +2902,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)); @@ -2861,8 +2919,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); * Listacls = storage.listAcls(blobId); @@ -2987,7 +3045,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); * }* @@ -3004,7 +3062,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( @@ -3028,7 +3086,7 @@ HmacKeyMetadata updateHmacKeyState( *Example of testing permissions on a bucket. * *
{@code - * String bucketName = "my_unique_bucket"; + * String bucketName = "my-unique-bucket"; * Listresponse = * storage.testIamPermissions( * bucket, 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..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 @@ -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,9 +637,10 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio optionMap.put(option.getOption(), option.getValue()); } + boolean isV2 = + 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); @@ -655,14 +658,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.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE) + && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)), + "Cannot specify both the VIRTUAL_HOSTED_STYLE and PATH_STYLE 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 +673,28 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio .replace(";", "%3B"); } - String stPath = constructResourceUriPath(bucketName, escapedBlobName); + boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap); + + String storageXmlHostName = + usePathStyle + ? STORAGE_XML_URI_SCHEME + "://" + getBaseStorageHostName(optionMap) + : STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap); + + String stPath = + 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. + 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 +722,30 @@ 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 = + 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) { + pathBuilder.append(PATH_DELIMITER); + } return pathBuilder.toString(); } if (!escapedBlobName.startsWith(PATH_DELIMITER)) { @@ -718,6 +755,28 @@ private String constructResourceUriPath(String slashlessBucketName, String escap 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. * @@ -760,14 +819,44 @@ 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)); + 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)) { + extHeaders.putAll((Map ) optionMap.get(SignUrlOption.Option.EXT_HEADERS)); + } - return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + 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()); + } + + /** 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 STORAGE_XML_URI_HOST_NAME; } @Override 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