diff --git a/.kokoro/common.sh b/.kokoro/common.sh
index 8f09de5d3..a8d0ea04d 100644
--- a/.kokoro/common.sh
+++ b/.kokoro/common.sh
@@ -52,3 +52,8 @@ function retry_with_backoff {
return $exit_code
}
+
+## Helper functionss
+function now() { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n'; }
+function msg() { println "$*" >&2; }
+function println() { printf '%s\n' "$(now) $*"; }
\ No newline at end of file
diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh
index 0aade871c..cf3bb4347 100755
--- a/.kokoro/dependencies.sh
+++ b/.kokoro/dependencies.sh
@@ -36,3 +36,51 @@ retry_with_backoff 3 10 \
-Dclirr.skip=true
mvn -B dependency:analyze -DfailOnWarning=true
+
+echo "****************** DEPENDENCY LIST COMPLETENESS CHECK *******************"
+## Run dependency list completeness check
+function completenessCheck() {
+ # Output dep list with compile scope generated using the original pom
+ msg "Generating dependency list using original pom..."
+ mvn dependency:list -f pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' | grep -v ':test$' >.org-list.txt
+
+ # Output dep list generated using the flattened pom (test scope deps are ommitted)
+ msg "Generating dependency list using flattened pom..."
+ mvn dependency:list -f .flattened-pom.xml -Dsort=true | grep '\[INFO] .*:.*:.*:.*:.*' >.new-list.txt
+
+ # Compare two dependency lists
+ msg "Comparing dependency lists..."
+ diff .org-list.txt .new-list.txt >.diff.txt
+ if [[ $? == 0 ]]
+ then
+ msg "Success. No diff!"
+ else
+ msg "Diff found. See below: "
+ msg "You can also check .diff.txt file located in $1."
+ cat .diff.txt
+ return 1
+ fi
+}
+
+# Allow failures to continue running the script
+set +e
+
+error_count=0
+for path in $(find -name ".flattened-pom.xml")
+do
+ # Check flattened pom in each dir that contains it for completeness
+ dir=$(dirname "$path")
+ pushd "$dir"
+ completenessCheck "$dir"
+ error_count=$(($error_count + $?))
+ popd
+done
+
+if [[ $error_count == 0 ]]
+then
+ msg "All checks passed."
+ exit 0
+else
+ msg "Errors found. See log statements above."
+ exit 1
+fi
diff --git a/.kokoro/nightly/integration.cfg b/.kokoro/nightly/integration.cfg
index ca0274800..40c4abb7b 100644
--- a/.kokoro/nightly/integration.cfg
+++ b/.kokoro/nightly/integration.cfg
@@ -10,20 +10,25 @@ env_vars: {
key: "JOB_TYPE"
value: "integration"
}
-
+# TODO: remove this after we've migrated all tests and scripts
env_vars: {
key: "GCLOUD_PROJECT"
value: "gcloud-devel"
}
+env_vars: {
+ key: "GOOGLE_CLOUD_PROJECT"
+ value: "gcloud-devel"
+}
+
env_vars: {
key: "ENABLE_BUILD_COP"
value: "true"
}
env_vars: {
- key: "GOOGLE_APPLICATION_CREDENTIALS"
- value: "keystore/73713_java_it_service_account"
+ key: "GOOGLE_APPLICATION_CREDENTIALS"
+ value: "keystore/73713_java_it_service_account"
}
before_action {
diff --git a/.kokoro/nightly/samples.cfg b/.kokoro/nightly/samples.cfg
index b4b051cd0..20aabd55d 100644
--- a/.kokoro/nightly/samples.cfg
+++ b/.kokoro/nightly/samples.cfg
@@ -11,9 +11,15 @@ env_vars: {
value: "samples"
}
+# TODO: remove this after we've migrated all tests and scripts
env_vars: {
key: "GCLOUD_PROJECT"
- value: "gcloud-devel"
+ value: "java-docs-samples-testing"
+}
+
+env_vars: {
+ key: "GOOGLE_CLOUD_PROJECT"
+ value: "java-docs-samples-testing"
}
env_vars: {
diff --git a/.kokoro/presubmit/samples.cfg b/.kokoro/presubmit/samples.cfg
index fa7b493d0..1171aead0 100644
--- a/.kokoro/presubmit/samples.cfg
+++ b/.kokoro/presubmit/samples.cfg
@@ -11,14 +11,20 @@ env_vars: {
value: "samples"
}
+# TODO: remove this after we've migrated all tests and scripts
env_vars: {
- key: "GCLOUD_PROJECT"
- value: "gcloud-devel"
+ key: "GCLOUD_PROJECT"
+ value: "java-docs-samples-testing"
}
env_vars: {
- key: "GOOGLE_APPLICATION_CREDENTIALS"
- value: "keystore/73713_java_it_service_account"
+ key: "GOOGLE_CLOUD_PROJECT"
+ value: "java-docs-samples-testing"
+}
+
+env_vars: {
+ key: "GOOGLE_APPLICATION_CREDENTIALS"
+ value: "keystore/73713_java_it_service_account"
}
before_action {
diff --git a/.readme-partials.yaml b/.readme-partials.yaml
index 4b9e2ad49..3a56b30ca 100644
--- a/.readme-partials.yaml
+++ b/.readme-partials.yaml
@@ -118,4 +118,9 @@ custom_content: |
- [`StorageExample`](https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-examples/src/main/java/com/google/cloud/examples/storage/StorageExample.java) is a simple command line interface that provides some of Cloud Storage's functionality. Read more about using the application on the [`StorageExample` docs page](https://github.com/googleapis/google-cloud-java/blob/master/google-cloud-examples/README.md).
- [`Bookshelf`](https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/bookshelf) - An App Engine application that manages a virtual bookshelf.
- This app uses `google-cloud` to interface with Cloud Datastore and Cloud Storage. It also uses Cloud SQL, another Google Cloud Platform service.
- - [`Flexible Environment/Storage example`](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/flexible/cloudstorage) - An app that uploads files to a public Cloud Storage bucket on the App Engine Flexible Environment runtime.
\ No newline at end of file
+ - [`Flexible Environment/Storage example`](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/flexible/cloudstorage) - An app that uploads files to a public Cloud Storage bucket on the App Engine Flexible Environment runtime.
+
+versioning: |
+ This library follows [Semantic Versioning](http://semver.org/), but does update [Storage interface](src/main/java/com.google.cloud.storage/Storage.java)
+ to introduce new methods which can break your implementations if you implement this interface for testing purposes.
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3a855760..2ccff73a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## [1.108.0](https://www.github.com/googleapis/java-storage/compare/v1.107.0...v1.108.0) (2020-04-30)
+
+
+### Features
+
+* add mockito dependency ([#284](https://www.github.com/googleapis/java-storage/issues/284)) ([58692dd](https://www.github.com/googleapis/java-storage/commit/58692dd8eeb2d228d14c896e563184d723b25df1))
+* V4 POST policy ([#177](https://www.github.com/googleapis/java-storage/issues/177)) ([32d8ffa](https://www.github.com/googleapis/java-storage/commit/32d8fface1a994cb5ac928f08c0467edc3c9aab1))
+
+
+### Bug Fixes
+
+* Documentation for Blob.update() and Storage.update() methods is confusing/incorrect ([#261](https://www.github.com/googleapis/java-storage/issues/261)) ([876405f](https://www.github.com/googleapis/java-storage/commit/876405f81cf195f5619b353be8d1e8efcbf5e0b3)), closes [#252](https://www.github.com/googleapis/java-storage/issues/252)
+
+
+### Dependencies
+
+* pin mockito version to work with java 7 ([#292](https://www.github.com/googleapis/java-storage/issues/292)) ([8eb2fff](https://www.github.com/googleapis/java-storage/commit/8eb2fff3f51c90af7f76f74d40ed1d6d6b4320b7))
+* update dependency com.google.api.grpc:grpc-google-cloud-kms-v1 to v0.85.1 ([#273](https://www.github.com/googleapis/java-storage/issues/273)) ([7b5e7d1](https://www.github.com/googleapis/java-storage/commit/7b5e7d173cdac6b2de802c568e3a60b915d39d1c))
+* update dependency com.google.api.grpc:proto-google-cloud-kms-v1 to v0.85.1 ([#274](https://www.github.com/googleapis/java-storage/issues/274)) ([0ab4304](https://www.github.com/googleapis/java-storage/commit/0ab4304ea4e5e5668c05c67d2c96c6056f8c19c2))
+* update dependency com.google.cloud:google-cloud-conformance-tests to v0.0.10 ([#281](https://www.github.com/googleapis/java-storage/issues/281)) ([f3dee7e](https://www.github.com/googleapis/java-storage/commit/f3dee7ea0d0e305f0bc0c980aa65e538f7bf890c))
+* update dependency com.google.http-client:google-http-client-bom to v1.35.0 ([#282](https://www.github.com/googleapis/java-storage/issues/282)) ([1c1c1be](https://www.github.com/googleapis/java-storage/commit/1c1c1bee0d6382e76e74f9a00dca8e527cc390c6))
+* update dependency io.grpc:grpc-bom to v1.28.1 ([#250](https://www.github.com/googleapis/java-storage/issues/250)) ([b35e81c](https://www.github.com/googleapis/java-storage/commit/b35e81ce19fa72672aefe8bd956959bfa954194c))
+* update dependency io.grpc:grpc-bom to v1.29.0 ([#275](https://www.github.com/googleapis/java-storage/issues/275)) ([9b241b4](https://www.github.com/googleapis/java-storage/commit/9b241b468d4f3a73b81c5bc67c085c6fe7c6ea1e))
+* update dependency org.threeten:threetenbp to v1.4.4 ([#278](https://www.github.com/googleapis/java-storage/issues/278)) ([7bae49f](https://www.github.com/googleapis/java-storage/commit/7bae49f16ba5de0eeac8301a6a11b85bd4406ed5))
+
+
+### Documentation
+
+* label legacy storage classes in documentation ([#267](https://www.github.com/googleapis/java-storage/issues/267)) ([50e5938](https://www.github.com/googleapis/java-storage/commit/50e5938147f7bb2594b9a142e8087c6e555f4979)), closes [#254](https://www.github.com/googleapis/java-storage/issues/254)
+
## [1.107.0](https://www.github.com/googleapis/java-storage/compare/v1.106.0...v1.107.0) (2020-04-14)
diff --git a/README.md b/README.md
index 074a1de9e..193fa6606 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file
com.google.cloudlibraries-bom
- 4.4.1
+ 5.3.0pomimport
@@ -38,7 +38,7 @@ If you are using Maven without BOM, add this to your dependencies:
com.google.cloudgoogle-cloud-storage
- 1.106.0
+ 1.107.0
```
@@ -47,11 +47,11 @@ If you are using Maven without BOM, add this to your dependencies:
If you are using Gradle, add this to your dependencies
```Groovy
-compile 'com.google.cloud:google-cloud-storage:1.107.0'
+compile 'com.google.cloud:google-cloud-storage:1.108.0'
```
If you are using SBT, add this to your dependencies
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.107.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.108.0"
```
[//]: # ({x-version-update-end})
@@ -207,16 +207,15 @@ display on your webpage.
+
## Troubleshooting
To get help, follow the instructions in the [shared Troubleshooting document][troubleshooting].
-
## Java Versions
Java 7 or above is required for using this client.
-
## Versioning
This library follows [Semantic Versioning](http://semver.org/), but does update [Storage interface](src/main/java/com.google.cloud.storage/Storage.java)
diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml
new file mode 100644
index 000000000..ae8d781db
--- /dev/null
+++ b/google-cloud-storage/clirr-ignored-differences.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ com/google/cloud/storage/Storage
+ com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostFieldsV4, com.google.cloud.storage.PostPolicyV4$PostConditionsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[])
+ 7012
+
+
+ com/google/cloud/storage/Storage
+ com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostFieldsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[])
+ 7012
+
+
+ com/google/cloud/storage/Storage
+ com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.PostPolicyV4$PostConditionsV4, com.google.cloud.storage.Storage$PostPolicyV4Option[])
+ 7012
+
+
+ com/google/cloud/storage/Storage
+ com.google.cloud.storage.PostPolicyV4 generateSignedPostPolicyV4(com.google.cloud.storage.BlobInfo, long, java.util.concurrent.TimeUnit, com.google.cloud.storage.Storage$PostPolicyV4Option[])
+ 7012
+
+
\ No newline at end of file
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 94cbf8a78..ce9a1145d 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -2,7 +2,7 @@
4.0.0google-cloud-storage
- 1.107.0
+ 1.108.0jarGoogle Cloud Storagehttps://github.com/googleapis/java-storage
@@ -12,7 +12,7 @@
com.google.cloudgoogle-cloud-storage-parent
- 1.107.0
+ 1.108.0google-cloud-storage
@@ -40,6 +40,10 @@
com.google.apisgoogle-api-services-storage
+
+ com.google.code.gson
+ gson
+ com.google.cloudgoogle-cloud-core
@@ -126,13 +130,13 @@
com.google.api.grpcgrpc-google-cloud-kms-v1
- 0.83.1
+ 0.85.1testcom.google.api.grpcproto-google-cloud-kms-v1
- 0.83.1
+ 0.85.1test
@@ -156,6 +160,11 @@
easymocktest
+
+ org.mockito
+ mockito-core
+ test
+ org.objenesisobjenesis
@@ -169,7 +178,16 @@
org.apache.httpcomponentshttpclient
- 4.5.12
+ test
+
+
+ org.apache.httpcomponents
+ httpmime
+ test
+
+
+ org.apache.httpcomponents
+ httpcoretest
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
index 3cb99e61d..743f3e7b4 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java
@@ -546,45 +546,27 @@ public Blob reload(BlobSourceOption... options) {
}
/**
- * Updates the blob's information. Bucket or blob's name cannot be changed by this method. If you
- * want to rename the blob or move it to a different bucket use the {@link #copyTo} and {@link
- * #delete} operations. A new {@code Blob} object is returned. By default no checks are made on
- * the metadata generation of the current blob. If you want to update the information only if the
- * current blob metadata are at their latest version use the {@code metagenerationMatch} option:
- * {@code newBlob.update(BlobTargetOption.metagenerationMatch())}.
+ * Updates the blob properties. The {@code options} parameter contains the preconditions for
+ * applying the update. To update the properties call {@link #toBuilder()}, set the properties you
+ * want to change, build the new {@code Blob} instance, and then call {@link
+ * #update(BlobTargetOption...)}.
*
- *
Original metadata are merged with metadata in the provided {@code blobInfo}. If the original
- * metadata already contains a key specified in the provided {@code blobInfo's} metadata map, it
- * will be replaced by the new value. Removing metadata can be done by setting that metadata's
- * value to {@code null}.
+ *
The property update details are described in {@link Storage#update(BlobInfo)}. {@link
+ * Storage#update(BlobInfo, BlobTargetOption...)} describes how to specify preconditions.
*
- *
Example of adding new metadata values or updating existing ones.
+ *
*
- * @param options update options
- * @return a {@code Blob} object with updated information
+ * @param options preconditions to apply the update
+ * @return the updated {@code Blob}
* @throws StorageException upon failure
+ * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update
*/
public Blob update(BlobTargetOption... options) {
return storage.update(this, options);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java
new file mode 100644
index 000000000..df2936dc9
--- /dev/null
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.storage;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Presigned V4 post policy.
+ *
+ * @see POST Object
+ */
+public final class PostPolicyV4 {
+ private String url;
+ private Map fields;
+
+ private PostPolicyV4(String url, Map fields) {
+ this.url = url;
+ this.fields = fields;
+ }
+
+ public static PostPolicyV4 of(String url, Map fields) {
+ return new PostPolicyV4(url, fields);
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public Map getFields() {
+ return fields;
+ }
+
+ /**
+ * Class representing which fields to specify in a V4 POST request.
+ *
+ * @see POST
+ * Object Form fields
+ */
+ public static final class PostFieldsV4 {
+ private Map fieldsMap;
+
+ private PostFieldsV4(Builder builder) {
+ this.fieldsMap = builder.fieldsMap;
+ }
+
+ private PostFieldsV4(Map fields) {
+ this.fieldsMap = fields;
+ }
+
+ public static PostFieldsV4 of(Map fields) {
+ return new PostFieldsV4(fields);
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public Map getFieldsMap() {
+ return fieldsMap;
+ }
+
+ public static class Builder {
+ private Map fieldsMap;
+
+ private Builder() {
+ fieldsMap = new HashMap<>();
+ }
+
+ public PostFieldsV4 build() {
+ return new PostFieldsV4(this);
+ }
+
+ public Builder setAcl(String acl) {
+ fieldsMap.put("acl", acl);
+ return this;
+ }
+
+ public Builder setCacheControl(String cacheControl) {
+ fieldsMap.put("cache-control", cacheControl);
+ return this;
+ }
+
+ public Builder setContentDisposition(String contentDisposition) {
+ fieldsMap.put("content-disposition", contentDisposition);
+ return this;
+ }
+
+ public Builder setContentEncoding(String contentEncoding) {
+ fieldsMap.put("content-encoding", contentEncoding);
+ return this;
+ }
+
+ public Builder setContentLength(int contentLength) {
+ fieldsMap.put("content-length", "" + contentLength);
+ return this;
+ }
+
+ public Builder setContentType(String contentType) {
+ fieldsMap.put("content-type", contentType);
+ return this;
+ }
+
+ public Builder Expires(String expires) {
+ fieldsMap.put("expires", expires);
+ return this;
+ }
+
+ public Builder setSuccessActionRedirect(String successActionRedirect) {
+ fieldsMap.put("success_action_redirect", successActionRedirect);
+ return this;
+ }
+
+ public Builder setSuccessActionStatus(int successActionStatus) {
+ fieldsMap.put("success_action_status", "" + successActionStatus);
+ return this;
+ }
+
+ public Builder AddCustomMetadataField(String field, String value) {
+ fieldsMap.put("x-goog-meta-" + field, value);
+ return this;
+ }
+ }
+ }
+
+ /**
+ * Class for specifying conditions in a V4 POST Policy document.
+ *
+ * @see
+ * Policy document
+ */
+ public static final class PostConditionsV4 {
+ private Set conditions;
+
+ private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+ public PostConditionsV4(Builder builder) {
+ this.conditions = builder.conditions;
+ }
+
+ public Builder toBuilder() {
+ return new Builder(conditions);
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public Set getConditions() {
+ return conditions;
+ }
+
+ public static class Builder {
+ Set conditions;
+
+ private Builder() {
+ this.conditions = new LinkedHashSet<>();
+ }
+
+ private Builder(Set conditions) {
+ this.conditions = conditions;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public PostConditionsV4 build() {
+ return new PostConditionsV4(this);
+ }
+
+ public Builder addAclCondition(ConditionV4Type type, String acl) {
+ conditions.add(new ConditionV4(type, "acl", acl));
+ return this;
+ }
+
+ public Builder addBucketCondition(ConditionV4Type type, String bucket) {
+ conditions.add(new ConditionV4(type, "bucket", bucket));
+ return this;
+ }
+
+ public Builder addCacheControlCondition(ConditionV4Type type, String cacheControl) {
+ conditions.add(new ConditionV4(type, "cache-control", cacheControl));
+ return this;
+ }
+
+ public Builder addContentDispositionCondition(
+ ConditionV4Type type, String contentDisposition) {
+ conditions.add(new ConditionV4(type, "content-disposition", contentDisposition));
+ return this;
+ }
+
+ public Builder addContentEncodingCondition(ConditionV4Type type, String contentEncoding) {
+ conditions.add(new ConditionV4(type, "content-encoding", contentEncoding));
+ return this;
+ }
+
+ public Builder addContentLengthCondition(ConditionV4Type type, int contentLength) {
+ conditions.add(new ConditionV4(type, "content-length", "" + contentLength));
+ return this;
+ }
+
+ public Builder addContentTypeCondition(ConditionV4Type type, String contentType) {
+ conditions.add(new ConditionV4(type, "content-type", contentType));
+ return this;
+ }
+
+ public Builder addExpiresCondition(ConditionV4Type type, long expires) {
+ conditions.add(new ConditionV4(type, "expires", dateFormat.format(expires)));
+ return this;
+ }
+
+ public Builder addExpiresCondition(ConditionV4Type type, String expires) {
+ conditions.add(new ConditionV4(type, "expires", expires));
+ return this;
+ }
+
+ public Builder addKeyCondition(ConditionV4Type type, String key) {
+ conditions.add(new ConditionV4(type, "key", key));
+ return this;
+ }
+
+ public Builder addSuccessActionRedirectUrlCondition(
+ ConditionV4Type type, String successActionRedirectUrl) {
+ conditions.add(new ConditionV4(type, "success_action_redirect", successActionRedirectUrl));
+ return this;
+ }
+
+ public Builder addSuccessActionStatusCondition(ConditionV4Type type, int status) {
+ conditions.add(new ConditionV4(type, "success_action_status", "" + status));
+ return this;
+ }
+
+ public Builder addContentLengthRangeCondition(int min, int max) {
+ conditions.add(new ConditionV4(ConditionV4Type.CONTENT_LENGTH_RANGE, "" + min, "" + max));
+ return this;
+ }
+
+ Builder addCustomCondition(ConditionV4Type type, String field, String value) {
+ conditions.add(new ConditionV4(type, field, value));
+ return this;
+ }
+ }
+ }
+
+ /**
+ * Class for a V4 POST Policy document.
+ *
+ * @see
+ * Policy document
+ */
+ public static final class PostPolicyV4Document {
+ private String expiration;
+ private PostConditionsV4 conditions;
+
+ private PostPolicyV4Document(String expiration, PostConditionsV4 conditions) {
+ this.expiration = expiration;
+ this.conditions = conditions;
+ }
+
+ public static PostPolicyV4Document of(String expiration, PostConditionsV4 conditions) {
+ return new PostPolicyV4Document(expiration, conditions);
+ }
+
+ public String toJson() {
+ JsonObject object = new JsonObject();
+ JsonArray conditions = new JsonArray();
+ for (ConditionV4 condition : this.conditions.conditions) {
+ switch (condition.type) {
+ case MATCHES:
+ JsonObject match = new JsonObject();
+ match.addProperty(condition.operand1, condition.operand2);
+ conditions.add(match);
+ break;
+ case STARTS_WITH:
+ JsonArray startsWith = new JsonArray();
+ startsWith.add("starts-with");
+ startsWith.add("$" + condition.operand1);
+ startsWith.add(condition.operand2);
+ conditions.add(startsWith);
+ break;
+ case CONTENT_LENGTH_RANGE:
+ JsonArray contentLengthRange = new JsonArray();
+ contentLengthRange.add("content-length-range");
+ contentLengthRange.add(Integer.parseInt(condition.operand1));
+ contentLengthRange.add(Integer.parseInt(condition.operand2));
+ conditions.add(contentLengthRange);
+ break;
+ }
+ }
+ object.add("conditions", conditions);
+ object.addProperty("expiration", expiration);
+
+ String json = object.toString();
+ StringBuilder escapedJson = new StringBuilder();
+
+ // Certain characters in a policy must be escaped
+ for (char c : json.toCharArray()) {
+ if (c >= 128) { // is a unicode character
+ escapedJson.append(String.format("\\u%04x", (int) c));
+ } else {
+ switch (c) {
+ case '\\':
+ escapedJson.append("\\\\");
+ break;
+ case '\b':
+ escapedJson.append("\\b");
+ break;
+ case '\f':
+ escapedJson.append("\\f");
+ break;
+ case '\n':
+ escapedJson.append("\\n");
+ break;
+ case '\r':
+ escapedJson.append("\\r");
+ break;
+ case '\t':
+ escapedJson.append("\\t");
+ break;
+ case '\u000b':
+ escapedJson.append("\\v");
+ break;
+ default:
+ escapedJson.append(c);
+ }
+ }
+ }
+ return escapedJson.toString();
+ }
+ }
+
+ public enum ConditionV4Type {
+ MATCHES,
+ STARTS_WITH,
+ CONTENT_LENGTH_RANGE
+ }
+
+ /**
+ * Class for a specific POST policy document condition.
+ *
+ * @see
+ * Policy document
+ */
+ static final class ConditionV4 {
+ ConditionV4Type type;
+ String operand1;
+ String operand2;
+
+ private ConditionV4(ConditionV4Type type, String operand1, String operand2) {
+ this.type = type;
+ this.operand1 = operand1;
+ this.operand2 = operand2;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ ConditionV4 condition = (ConditionV4) other;
+ return this.type == condition.type
+ && this.operand1.equals(condition.operand1)
+ && this.operand2.equals(condition.operand2);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, operand1, operand2);
+ }
+ }
+}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index a9bb589a2..38794cd4b 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -32,6 +32,8 @@
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Acl.Entity;
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
+import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
+import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
import com.google.cloud.storage.spi.v1.StorageRpc;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -1041,7 +1043,7 @@ public static BlobListOption userProject(String userProject) {
/**
* If set to {@code true}, lists all versions of a blob. The default is {@code false}.
*
- * @see Object Versioning
+ * @see Object Versioning
*/
public static BlobListOption versions(boolean versions) {
return new BlobListOption(StorageRpc.Option.VERSIONS, versions);
@@ -1060,6 +1062,107 @@ public static BlobListOption fields(BlobField... fields) {
}
}
+ /** Class for specifying Post Policy V4 options. * */
+ class PostPolicyV4Option implements Serializable {
+ private static final long serialVersionUID = 8150867146534084543L;
+ private final PostPolicyV4Option.Option option;
+ private final Object value;
+
+ enum Option {
+ PATH_STYLE,
+ VIRTUAL_HOSTED_STYLE,
+ BUCKET_BOUND_HOST_NAME,
+ SERVICE_ACCOUNT_CRED
+ }
+
+ private PostPolicyV4Option(Option option, Object value) {
+ this.option = option;
+ this.value = value;
+ }
+
+ PostPolicyV4Option.Option getOption() {
+ return option;
+ }
+
+ Object getValue() {
+ return value;
+ }
+
+ /**
+ * Provides a service account signer to sign the policy. If not provided an attempt is made to
+ * get it from the environment.
+ *
+ * @see Service
+ * Accounts
+ */
+ public static PostPolicyV4Option signWith(ServiceAccountSigner signer) {
+ return new PostPolicyV4Option(PostPolicyV4Option.Option.SERVICE_ACCOUNT_CRED, signer);
+ }
+
+ /**
+ * 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 is
+ * obtained from the resource passed in.
+ *
+ * @see Request Endpoints
+ */
+ public static PostPolicyV4Option withVirtualHostedStyle() {
+ return new PostPolicyV4Option(PostPolicyV4Option.Option.VIRTUAL_HOSTED_STYLE, "");
+ }
+
+ /**
+ * Generates 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 PostPolicyV4Option withPathStyle() {
+ return new PostPolicyV4Option(PostPolicyV4Option.Option.PATH_STYLE, "");
+ }
+
+ /**
+ * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+ * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+ * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+ * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This
+ * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code
+ * withBucketBoundHostname("...", UriScheme.HTTP).}
+ *
+ * @see CNAME
+ * Redirects
+ * @see
+ * GCLB Redirects
+ */
+ public static PostPolicyV4Option withBucketBoundHostname(String bucketBoundHostname) {
+ return withBucketBoundHostname(bucketBoundHostname, Storage.UriScheme.HTTP);
+ }
+
+ /**
+ * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+ * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+ * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+ * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. The
+ * bucket name itself should not include the URI scheme (http or https), so it is specified via
+ * a local enum.
+ *
+ * @see CNAME
+ * Redirects
+ * @see
+ * GCLB Redirects
+ */
+ public static PostPolicyV4Option withBucketBoundHostname(
+ String bucketBoundHostname, Storage.UriScheme uriScheme) {
+ return new PostPolicyV4Option(
+ PostPolicyV4Option.Option.BUCKET_BOUND_HOST_NAME,
+ uriScheme.getScheme() + "://" + bucketBoundHostname);
+ }
+ }
+
/** Class for specifying signed URL options. */
class SignUrlOption implements Serializable {
@@ -1154,8 +1257,8 @@ public static SignUrlOption withV4Signature() {
}
/**
- * Provides a service account signer to sign the URL. If not provided an attempt will be made to
- * get it from the environment.
+ * Provides a service account signer to sign the URL. If not provided an attempt is made to get
+ * it from the environment.
*
* @see Service
* Accounts
@@ -1167,7 +1270,7 @@ public static SignUrlOption signWith(ServiceAccountSigner signer) {
/**
* Use a different host name than the default host name 'storage.googleapis.com'. 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
+ * environment or sending requests through VPC). 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.
*/
@@ -1177,10 +1280,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 is
+ * 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
*/
@@ -1189,11 +1292,11 @@ 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, 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.
+ * Generates 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/...'. 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
*/
@@ -1204,9 +1307,9 @@ public static SignUrlOption withPathStyle() {
/**
* Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
* a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
- * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
- * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This
- * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code
+ * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. This cannot be
+ * used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This method
+ * signature uses HTTP for the URI scheme, and is equivalent to calling {@code
* withBucketBoundHostname("...", UriScheme.HTTP).}
*
* @see CNAME
@@ -1896,6 +1999,7 @@ Blob create(
* only if supplied Decrpytion Key decrypts the blob successfully, otherwise a {@link
* StorageException} is thrown. For more information review
*
+ * @throws StorageException upon failure
* @see Encrypted
* Elements
@@ -1989,60 +2093,73 @@ Blob create(
Bucket update(BucketInfo bucketInfo, BucketTargetOption... options);
/**
- * Updates blob information. Original metadata are merged with metadata in the provided {@code
- * blobInfo}. To replace metadata instead you first have to unset them. Unsetting metadata can be
- * done by setting the provided {@code blobInfo}'s metadata to {@code null}. Accepts an optional
- * userProject {@link BlobTargetOption} option which defines the project id to assign operational
- * costs.
+ * Updates the blob properties if the preconditions specified by {@code options} are met. The
+ * property update works as described in {@link #update(BlobInfo)}.
+ *
+ *
{@code options} parameter can contain the preconditions for applying the update. E.g. update
+ * of the blob properties might be required only if the properties have not been updated
+ * externally. {@code StorageException} with the code {@code 412} is thrown if preconditions fail.
*
- *
Example of udating a blob, only if the blob's metageneration matches a value, otherwise a
- * {@link StorageException} is thrown.
+ *
Example of updating the content type only if the properties are not updated externally:
*
*
*
+ * @param blobInfo information to update
+ * @param options preconditions to apply the update
* @return the updated blob
* @throws StorageException upon failure
+ * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update
*/
Blob update(BlobInfo blobInfo, BlobTargetOption... options);
/**
- * Updates blob information. Original metadata are merged with metadata in the provided {@code
- * blobInfo}. If the original metadata already contains a key specified in the provided {@code
- * blobInfo's} metadata map, it will be replaced by the new value. Removing metadata can be done
- * by setting that metadata's value to {@code null}.
+ * Updates the properties of the blob. This method issues an RPC request to merge the current blob
+ * properties with the properties in the provided {@code blobInfo}. Properties not defined in
+ * {@code blobInfo} will not be updated. To unset a blob property this property in {@code
+ * blobInfo} should be explicitly set to {@code null}.
*
- *
Example of adding new metadata values or updating existing ones.
+ *
Bucket or blob's name cannot be changed by this method. If you want to rename the blob or
+ * move it to a different bucket use the {@link Blob#copyTo} and {@link #delete} operations.
*
- *
*
+ * @param blobInfo information to update
* @return the updated blob
* @throws StorageException upon failure
+ * @see https://cloud.google.com/storage/docs/json_api/v1/objects/update
*/
Blob update(BlobInfo blobInfo);
@@ -2534,6 +2651,96 @@ Blob create(
*/
URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options);
+ /**
+ * Generates a URL and a map of fields that can be specified in an HTML form to submit a POST
+ * request. The returned map includes a signature which must be provided with the request.
+ * Generating a presigned POST policy requires a service account signer. If an instance of {@link
+ * com.google.auth.ServiceAccountSigner} was passed to {@link StorageOptions}' builder via {@code
+ * setCredentials(Credentials)} or the default credentials are being used and the environment
+ * variable {@code GOOGLE_APPLICATION_CREDENTIALS} is set, generatPresignedPostPolicyV4 will use
+ * that credentials to sign the URL. If the credentials passed to {@link StorageOptions} do not
+ * implement {@link ServiceAccountSigner} (this is the case, for instance, for Google Cloud SDK
+ * credentials) then {@code signUrl} will throw an {@link IllegalStateException} unless an
+ * implementation of {@link ServiceAccountSigner} is passed using the {@link
+ * PostPolicyV4Option#signWith(ServiceAccountSigner)} option.
+ *
+ *
Example of generating a presigned post policy which has the condition that only jpeg images
+ * can be uploaded, and applies the public read acl to each image uploaded, and making the POST
+ * request:
+ *
+ *
+ *
+ * @param blobInfo the blob uploaded in the form
+ * @param fields the fields specified in the form
+ * @param conditions which conditions every upload must satisfy
+ * @param duration how long until the form expires, in milliseconds
+ * @param options optional post policy options
+ * @see POST
+ * Object
+ */
+ PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostFieldsV4 fields,
+ PostConditionsV4 conditions,
+ PostPolicyV4Option... options);
+
+ /**
+ * Generates a presigned post policy without any conditions. Automatically creates required
+ * conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long
+ * duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions,
+ * PostPolicyV4Option... options) above.
+ */
+ PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostFieldsV4 fields,
+ PostPolicyV4Option... options);
+
+ /**
+ * Generates a presigned post policy without any fields. Automatically creates required fields.
+ * See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long duration,
+ * TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions, PostPolicyV4Option... options)
+ * above.
+ */
+ PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostConditionsV4 conditions,
+ PostPolicyV4Option... options);
+
+ /**
+ * Generates a presigned post policy without any fields or conditions. Automatically creates
+ * required fields and conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo
+ * blobInfo, long duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions,
+ * PostPolicyV4Option... options) above.
+ */
+ PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options);
+
/**
* Gets the requested blobs. A batch request is used to perform this call.
*
@@ -2578,10 +2785,10 @@ Blob create(
List get(Iterable blobIds);
/**
- * Updates the requested blobs. A batch request is used to perform this call. Original metadata
- * are merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead
- * you first have to unset them. Unsetting metadata can be done by setting the provided {@code
- * BlobInfo} objects metadata to {@code null}. See {@link #update(BlobInfo)} for a code example.
+ * Updates the requested blobs. A batch request is used to perform this call. The original
+ * properties are merged with the properties in the provided {@code BlobInfo} objects. Unsetting a
+ * property can be done by setting the property of the provided {@code BlobInfo} objects to {@code
+ * null}. See {@link #update(BlobInfo)} for a code example.
*
*
Example of updating information on several blobs using a single batch request.
*
@@ -2604,10 +2811,10 @@ Blob create(
List update(BlobInfo... blobInfos);
/**
- * Updates the requested blobs. A batch request is used to perform this call. Original metadata
- * are merged with metadata in the provided {@code BlobInfo} objects. To replace metadata instead
- * you first have to unset them. Unsetting metadata can be done by setting the provided {@code
- * BlobInfo} objects metadata to {@code null}. See {@link #update(BlobInfo)} for a code example.
+ * Updates the requested blobs. A batch request is used to perform this call. The original
+ * properties are merged with the properties in the provided {@code BlobInfo} objects. Unsetting a
+ * property can be done by setting the property of the provided {@code BlobInfo} objects to {@code
+ * null}. See {@link #update(BlobInfo)} for a code example.
*
*
Example of updating information on several blobs using a single batch request.
*
@@ -3148,6 +3355,7 @@ HmacKeyMetadata updateHmacKeyState(
final HmacKeyMetadata hmacKeyMetadata,
final HmacKey.HmacKeyState state,
UpdateHmacKeyOption... options);
+
/**
* Gets the IAM policy for the provided bucket.
*
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java
index a345a31fc..5cf979878 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java
@@ -20,8 +20,9 @@
import com.google.cloud.StringEnumValue;
/**
- * Enums for the storage classes. See https://cloud.google.com/storage/docs/storage-classes for
- * details.
+ * Enums for the storage classes. See https://cloud.google.com/storage/docs/storage-classes
+ * for details.
*/
public final class StorageClass extends StringEnumValue {
private static final long serialVersionUID = -6938125060419556331L;
@@ -42,42 +43,61 @@ public StorageClass apply(String constant) {
new StringEnumType(StorageClass.class, CONSTRUCTOR);
/**
- * Standard storage class. See: https://cloud.google.com/storage/docs/storage-classes for details
+ * Standard storage class.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#standard
*/
public static final StorageClass STANDARD = type.createAndRegister("STANDARD");
/**
- * Nearline storage class. See: https://cloud.google.com/storage/docs/storage-classes for details
+ * Nearline storage class.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#nearline
*/
public static final StorageClass NEARLINE = type.createAndRegister("NEARLINE");
/**
- * Coldline storage class. See: https://cloud.google.com/storage/docs/storage-classes for details
+ * Coldline storage class.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#coldline
*/
public static final StorageClass COLDLINE = type.createAndRegister("COLDLINE");
/**
- * Archive storage class. See: https://cloud.google.com/storage/docs/storage-classes for details
+ * Archive storage class.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#archive
*/
public static final StorageClass ARCHIVE = type.createAndRegister("ARCHIVE");
/**
- * Regional storage class. This is supported as a legacy storage class and will be deprecated in
- * the future. See: https://cloud.google.com/storage/docs/storage-classes for details
+ * Legacy Regional storage class, use {@link #STANDARD} instead. This class will be deprecated in
+ * the future.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#legacy
*/
public static final StorageClass REGIONAL = type.createAndRegister("REGIONAL");
/**
- * Multi-regional storage class. This is supported as a legacy storage class and will be
- * deprecated in the future. See: https://cloud.google.com/storage/docs/storage-classes for
- * details
+ * Legacy Multi-regional storage class, use {@link #STANDARD} instead. This class will be
+ * deprecated in the future.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#legacy
*/
public static final StorageClass MULTI_REGIONAL = type.createAndRegister("MULTI_REGIONAL");
/**
- * Durable Reduced Availability storage class. This is supported as a legacy storage class and
- * will be deprecated in the future. See: https://cloud.google.com/storage/docs/storage-classes
- * for details
+ * Legacy Durable Reduced Availability storage class, use {@link #STANDARD} instead. This class
+ * will be deprecated in the future.
+ *
+ * @see https://cloud.google.com/storage/docs/storage-classes#legacy
*/
public static final StorageClass DURABLE_REDUCED_AVAILABILITY =
type.createAndRegister("DURABLE_REDUCED_AVAILABILITY");
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index 0f38e3827..0e24521eb 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -50,6 +50,10 @@
import com.google.cloud.Tuple;
import com.google.cloud.storage.Acl.Entity;
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
+import com.google.cloud.storage.PostPolicyV4.ConditionV4Type;
+import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
+import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
+import com.google.cloud.storage.PostPolicyV4.PostPolicyV4Document;
import com.google.cloud.storage.spi.v1.StorageRpc;
import com.google.cloud.storage.spi.v1.StorageRpc.RewriteResponse;
import com.google.common.base.CharMatcher;
@@ -72,12 +76,15 @@
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
@@ -733,6 +740,139 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio
}
}
+ @Override
+ public PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostFieldsV4 fields,
+ PostConditionsV4 conditions,
+ PostPolicyV4Option... options) {
+ EnumMap optionMap = Maps.newEnumMap(SignUrlOption.Option.class);
+ // Convert to a map of SignUrlOptions so we can re-use some utility methods
+ for (PostPolicyV4Option option : options) {
+ optionMap.put(SignUrlOption.Option.valueOf(option.getOption().name()), option.getValue());
+ }
+
+ optionMap.put(SignUrlOption.Option.SIGNATURE_VERSION, SignUrlOption.SignatureVersion.V4);
+
+ ServiceAccountSigner credentials =
+ (ServiceAccountSigner) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED);
+ if (credentials == null) {
+ checkState(
+ this.getOptions().getCredentials() instanceof ServiceAccountSigner,
+ "Signing key was not provided and could not be derived");
+ credentials = (ServiceAccountSigner) this.getOptions().getCredentials();
+ }
+
+ checkArgument(
+ !(optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)
+ && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)
+ && optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)),
+ "Only one of VIRTUAL_HOSTED_STYLE, PATH_STYLE, or BUCKET_BOUND_HOST_NAME SignUrlOptions can be"
+ + " specified.");
+
+ String bucketName = slashlessBucketNameFromBlobInfo(blobInfo);
+
+ boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap);
+
+ String url;
+
+ if (usePathStyle) {
+ url = STORAGE_XML_URI_SCHEME + "://" + STORAGE_XML_URI_HOST_NAME + "/" + bucketName + "/";
+ } else {
+ url = STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + STORAGE_XML_URI_HOST_NAME + "/";
+ }
+
+ if (optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
+ url = optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME) + "/";
+ }
+
+ SimpleDateFormat googDateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
+ SimpleDateFormat yearMonthDayFormat = new SimpleDateFormat("yyyyMMdd");
+ SimpleDateFormat expirationFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ googDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ yearMonthDayFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ expirationFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ long timestamp = getOptions().getClock().millisTime();
+ String date = googDateFormat.format(timestamp);
+ String signingCredential =
+ credentials.getAccount()
+ + "/"
+ + yearMonthDayFormat.format(timestamp)
+ + "/auto/storage/goog4_request";
+
+ Map policyFields = new HashMap<>();
+
+ PostConditionsV4.Builder conditionsBuilder = conditions.toBuilder();
+
+ for (Map.Entry entry : fields.getFieldsMap().entrySet()) {
+ // Every field needs a corresponding policy condition, so add them if they're missing
+ conditionsBuilder.addCustomCondition(
+ ConditionV4Type.MATCHES, entry.getKey(), entry.getValue());
+
+ policyFields.put(entry.getKey(), entry.getValue());
+ }
+
+ PostConditionsV4 v4Conditions =
+ conditionsBuilder
+ .addBucketCondition(ConditionV4Type.MATCHES, blobInfo.getBucket())
+ .addKeyCondition(ConditionV4Type.MATCHES, blobInfo.getName())
+ .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-date", date)
+ .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-credential", signingCredential)
+ .addCustomCondition(ConditionV4Type.MATCHES, "x-goog-algorithm", "GOOG4-RSA-SHA256")
+ .build();
+ PostPolicyV4Document document =
+ PostPolicyV4Document.of(
+ expirationFormat.format(timestamp + unit.toMillis(duration)), v4Conditions);
+ String policy = BaseEncoding.base64().encode(document.toJson().getBytes());
+ String signature =
+ BaseEncoding.base16().encode(credentials.sign(policy.getBytes())).toLowerCase();
+
+ for (PostPolicyV4.ConditionV4 condition : v4Conditions.getConditions()) {
+ if (condition.type == ConditionV4Type.MATCHES) {
+ policyFields.put(condition.operand1, condition.operand2);
+ }
+ }
+ policyFields.put("key", blobInfo.getName());
+ policyFields.put("x-goog-credential", signingCredential);
+ policyFields.put("x-goog-algorithm", "GOOG4-RSA-SHA256");
+ policyFields.put("x-goog-date", date);
+ policyFields.put("x-goog-signature", signature);
+ policyFields.put("policy", policy);
+
+ policyFields.remove("bucket");
+
+ return PostPolicyV4.of(url, policyFields);
+ }
+
+ public PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostFieldsV4 fields,
+ PostPolicyV4Option... options) {
+ return generateSignedPostPolicyV4(
+ blobInfo, duration, unit, fields, PostConditionsV4.newBuilder().build(), options);
+ }
+
+ public PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo,
+ long duration,
+ TimeUnit unit,
+ PostConditionsV4 conditions,
+ PostPolicyV4Option... options) {
+ return generateSignedPostPolicyV4(
+ blobInfo, duration, unit, PostFieldsV4.newBuilder().build(), conditions, options);
+ }
+
+ public PostPolicyV4 generateSignedPostPolicyV4(
+ BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options) {
+ return generateSignedPostPolicyV4(
+ blobInfo, duration, unit, PostFieldsV4.newBuilder().build(), options);
+ }
+
private String constructResourceUriPath(
String slashlessBucketName,
String escapedBlobName,
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
index c677cbdb6..e211c0277 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java
@@ -109,8 +109,6 @@ public HttpStorageRpc(StorageOptions options) {
censusHttpModule = new CensusHttpModule(tracer, true);
initializer = censusHttpModule.getHttpRequestInitializer(initializer);
batchRequestInitializer = censusHttpModule.getHttpRequestInitializer(null);
- HttpStorageRpcSpans.registerAllSpanNamesForCollection();
-
storage =
new Storage.Builder(transport, new JacksonFactory(), initializer)
.setRootUrl(options.getHost())
@@ -205,7 +203,7 @@ public void submit() {
throw translate(ex);
} finally {
scope.close();
- span.end();
+ span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS);
}
}
}
@@ -272,7 +270,7 @@ public Bucket create(Bucket bucket, Map