From 8602b81eae95868e184fd4ab290396707bd21a8e Mon Sep 17 00:00:00 2001
From: Dmitry <58846611+dmitry-fa@users.noreply.github.com>
Date: Fri, 21 Aug 2020 20:21:29 +0300
Subject: [PATCH] fix: PostPolicyV4 classes could be improved (#442)
* fix: PostPolicyV4 classes could be improved
* fix: javadoc
* fix: javadoc
* fix: javadoc
* test: PostPolicyV4Test unit tests
* test: PostPolicyV4Test unit tests
* test: PostPolicyV4Test unit tests
* test: PostPolicyV4Test unit tests
* fix: better URL syntax check
---
.../google/cloud/storage/PostPolicyV4.java | 192 +++++-
.../com/google/cloud/storage/Storage.java | 18 +-
.../cloud/storage/PostPolicyV4Test.java | 592 ++++++++++++++++++
3 files changed, 765 insertions(+), 37 deletions(-)
create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/PostPolicyV4Test.java
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
index cc9b413a2..96afca06e 100644
--- 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
@@ -18,56 +18,125 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
- * Presigned V4 post policy.
+ * Presigned V4 post policy. Instances of {@code PostPolicyV4} include a URL and a map of fields
+ * that can be specified in an HTML form to submit a POST request to upload an object.
*
- * @see POST Object
+ *
See POST Object for
+ * details of upload by using HTML forms.
+ *
+ *
See {@link Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit,
+ * PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)} for
+ * example of usage.
*/
public final class PostPolicyV4 {
private final String url;
private final Map fields;
private PostPolicyV4(String url, Map fields) {
+ try {
+ if (!new URI(url).isAbsolute()) {
+ throw new IllegalArgumentException(url + " is not an absolute URL");
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ PostFieldsV4.validateFields(fields);
+
this.url = url;
- this.fields = fields;
+ this.fields = Collections.unmodifiableMap(fields);
}
+ /**
+ * Constructs {@code PostPolicyV4} instance of the given URL and fields map.
+ *
+ * @param url URL for the HTTP POST request
+ * @param fields HTML form fields
+ * @return constructed object
+ * @throws IllegalArgumentException if URL is malformed or fields are not valid
+ */
public static PostPolicyV4 of(String url, Map fields) {
return new PostPolicyV4(url, fields);
}
+ /** Returns the URL for the HTTP POST request */
public String getUrl() {
return url;
}
+ /** Returns the HTML form fields */
public Map getFields() {
return fields;
}
/**
- * Class representing which fields to specify in a V4 POST request.
+ * A helper class to define fields to be specified in a V4 POST request. Instance of this class
+ * helps to construct {@code PostPolicyV4} objects. Used in: {@link
+ * Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
+ * PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see POST
* Object Form fields
*/
public static final class PostFieldsV4 {
private final Map fieldsMap;
+ private static final List VALID_FIELDS =
+ Arrays.asList(
+ "acl",
+ "bucket",
+ "cache-control",
+ "content-disposition",
+ "content-encoding",
+ "content-type",
+ "expires",
+ "file",
+ "key",
+ "policy",
+ "success_action_redirect",
+ "success_action_status",
+ "x-goog-algorithm",
+ "x-goog-credential",
+ "x-goog-date",
+ "x-goog-signature");
+
+ private static void validateFields(Map fields) {
+ for (String key : fields.keySet()) {
+ if (!VALID_FIELDS.contains(key.toLowerCase())
+ && !key.startsWith(Builder.CUSTOM_FIELD_PREFIX)) {
+ throw new IllegalArgumentException("Invalid key: " + key);
+ }
+ }
+ }
private PostFieldsV4(Builder builder) {
- this.fieldsMap = builder.fieldsMap;
+ this(builder.fieldsMap);
}
private PostFieldsV4(Map fields) {
- this.fieldsMap = fields;
+ validateFields(fields);
+ this.fieldsMap = Collections.unmodifiableMap(fields);
}
+ /**
+ * Constructs {@code PostPolicyV4.PostFieldsV4} object of the given field map.
+ *
+ * @param fields a map of the HTML form fields
+ * @return constructed object
+ * @throws IllegalArgumentException if an unsupported field is specified
+ */
public static PostFieldsV4 of(Map fields) {
return new PostFieldsV4(fields);
}
@@ -112,8 +181,14 @@ public Builder setContentEncoding(String contentEncoding) {
return this;
}
+ /**
+ * @deprecated Invocation of this method has no effect, because all valid HTML form fields
+ * except Content-Length can use exact matching. Use {@link
+ * PostPolicyV4.PostConditionsV4.Builder#addContentLengthRangeCondition(int, int)} to
+ * specify a range for the content-length.
+ */
+ @Deprecated
public Builder setContentLength(int contentLength) {
- fieldsMap.put("content-length", "" + contentLength);
return this;
}
@@ -122,7 +197,7 @@ public Builder setContentType(String contentType) {
return this;
}
- /** @deprecated use {@link #setExpires(String)} */
+ /** @deprecated Use {@link #setExpires(String)}. */
@Deprecated
public Builder Expires(String expires) {
return setExpires(expires);
@@ -143,7 +218,7 @@ public Builder setSuccessActionStatus(int successActionStatus) {
return this;
}
- /** @deprecated use {@link #setCustomMetadataField(String, String)} */
+ /** @deprecated Use {@link #setCustomMetadataField(String, String)}. */
@Deprecated
public Builder AddCustomMetadataField(String field, String value) {
return setCustomMetadataField(field, value);
@@ -151,7 +226,7 @@ public Builder AddCustomMetadataField(String field, String value) {
public Builder setCustomMetadataField(String field, String value) {
if (!field.startsWith(CUSTOM_FIELD_PREFIX)) {
- field = CUSTOM_FIELD_PREFIX + value;
+ field = CUSTOM_FIELD_PREFIX + field;
}
fieldsMap.put(field, value);
return this;
@@ -160,7 +235,9 @@ public Builder setCustomMetadataField(String field, String value) {
}
/**
- * Class for specifying conditions in a V4 POST Policy document.
+ * A helper class for specifying conditions in a V4 POST Policy document. Used in: {@link
+ * Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
+ * PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see
* Policy document
@@ -183,14 +260,14 @@ public static Builder newBuilder() {
}
public Set getConditions() {
- return conditions;
+ return Collections.unmodifiableSet(conditions);
}
public static class Builder {
- Set conditions;
+ private final Set conditions;
private Builder() {
- this.conditions = new LinkedHashSet<>();
+ this(new LinkedHashSet());
}
private Builder(Set conditions) {
@@ -206,64 +283,93 @@ public PostConditionsV4 build() {
}
public Builder addAclCondition(ConditionV4Type type, String acl) {
+ checkType(type, "acl");
conditions.add(new ConditionV4(type, "acl", acl));
return this;
}
public Builder addBucketCondition(ConditionV4Type type, String bucket) {
+ checkType(type, "bucket");
conditions.add(new ConditionV4(type, "bucket", bucket));
return this;
}
public Builder addCacheControlCondition(ConditionV4Type type, String cacheControl) {
+ checkType(type, "cache-control");
conditions.add(new ConditionV4(type, "cache-control", cacheControl));
return this;
}
public Builder addContentDispositionCondition(
ConditionV4Type type, String contentDisposition) {
+ checkType(type, "content-disposition");
conditions.add(new ConditionV4(type, "content-disposition", contentDisposition));
return this;
}
public Builder addContentEncodingCondition(ConditionV4Type type, String contentEncoding) {
+ checkType(type, "content-encoding");
conditions.add(new ConditionV4(type, "content-encoding", contentEncoding));
return this;
}
+ /**
+ * @deprecated Invocation of this method has no effect. Use {@link
+ * #addContentLengthRangeCondition(int, int)} to specify a range for the content-length.
+ */
public Builder addContentLengthCondition(ConditionV4Type type, int contentLength) {
- conditions.add(new ConditionV4(type, "content-length", "" + contentLength));
return this;
}
public Builder addContentTypeCondition(ConditionV4Type type, String contentType) {
+ checkType(type, "content-type");
conditions.add(new ConditionV4(type, "content-type", contentType));
return this;
}
+ /** @deprecated Use {@link #addExpiresCondition(long)} */
+ @Deprecated
public Builder addExpiresCondition(ConditionV4Type type, long expires) {
- conditions.add(new ConditionV4(type, "expires", dateFormat.format(expires)));
- return this;
+ return addExpiresCondition(expires);
}
+ /** @deprecated Use {@link #addExpiresCondition(String)} */
+ @Deprecated
public Builder addExpiresCondition(ConditionV4Type type, String expires) {
- conditions.add(new ConditionV4(type, "expires", expires));
+ return addExpiresCondition(expires);
+ }
+
+ public Builder addExpiresCondition(long expires) {
+ return addExpiresCondition(dateFormat.format(expires));
+ }
+
+ public Builder addExpiresCondition(String expires) {
+ conditions.add(new ConditionV4(ConditionV4Type.MATCHES, "expires", expires));
return this;
}
public Builder addKeyCondition(ConditionV4Type type, String key) {
+ checkType(type, "key");
conditions.add(new ConditionV4(type, "key", key));
return this;
}
public Builder addSuccessActionRedirectUrlCondition(
ConditionV4Type type, String successActionRedirectUrl) {
+ checkType(type, "success_action_redirect");
conditions.add(new ConditionV4(type, "success_action_redirect", successActionRedirectUrl));
return this;
}
+ /** @deprecated Use {@link #addSuccessActionStatusCondition(int)} */
+ @Deprecated
public Builder addSuccessActionStatusCondition(ConditionV4Type type, int status) {
- conditions.add(new ConditionV4(type, "success_action_status", "" + status));
+ return addSuccessActionStatusCondition(status);
+ }
+
+ public Builder addSuccessActionStatusCondition(int status) {
+ conditions.add(
+ new ConditionV4(ConditionV4Type.MATCHES, "success_action_status", "" + status));
return this;
}
@@ -276,11 +382,17 @@ Builder addCustomCondition(ConditionV4Type type, String field, String value) {
conditions.add(new ConditionV4(type, field, value));
return this;
}
+
+ private void checkType(ConditionV4Type type, String field) {
+ if (type != ConditionV4Type.MATCHES && type != ConditionV4Type.STARTS_WITH) {
+ throw new IllegalArgumentException("Field " + field + " can't use " + type);
+ }
+ }
}
}
/**
- * Class for a V4 POST Policy document.
+ * Class for a V4 POST Policy document. Used by Storage to construct {@code PostPolicyV4} objects.
*
* @see
* Policy document
@@ -367,9 +479,20 @@ public String toJson() {
}
public enum ConditionV4Type {
- MATCHES,
- STARTS_WITH,
- CONTENT_LENGTH_RANGE
+ MATCHES("eq"),
+ STARTS_WITH("starts-with"),
+ CONTENT_LENGTH_RANGE("content-length-range");
+
+ private final String name;
+
+ ConditionV4Type(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
}
/**
@@ -378,12 +501,12 @@ public enum ConditionV4Type {
* @see
* Policy document
*/
- static final class ConditionV4 {
- final ConditionV4Type type;
- final String operand1;
- final String operand2;
+ public static final class ConditionV4 {
+ public final ConditionV4Type type;
+ public final String operand1;
+ public final String operand2;
- private ConditionV4(ConditionV4Type type, String operand1, String operand2) {
+ ConditionV4(ConditionV4Type type, String operand1, String operand2) {
this.type = type;
this.operand1 = operand1;
this.operand2 = operand2;
@@ -401,5 +524,18 @@ public boolean equals(Object other) {
public int hashCode() {
return Objects.hash(type, operand1, operand2);
}
+
+ /**
+ * Examples of returned strings: {@code ["eq", "$key", "test-object"]}, {@code ["starts-with",
+ * "$acl", "public"]}, {@code ["content-length-range", 246, 266]}.
+ */
+ @Override
+ public String toString() {
+ String body =
+ type == ConditionV4Type.CONTENT_LENGTH_RANGE
+ ? operand1 + ", " + operand2
+ : "\"$" + operand1 + "\", \"" + operand2 + "\"";
+ return "[\"" + type + "\", " + body + "]";
+ }
}
}
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 82ba17508..6b8fc4990 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
@@ -2843,6 +2843,8 @@ Blob createFrom(
* }
*
* @param blobInfo the blob uploaded in the form
+ * @param duration time before expiration
+ * @param unit duration time unit
* @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
@@ -2861,9 +2863,8 @@ PostPolicyV4 generateSignedPostPolicyV4(
/**
* 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.
+ * conditions. See full documentation for {@link #generateSignedPostPolicyV4(BlobInfo, long,
+ * TimeUnit, PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
@@ -2874,9 +2875,8 @@ PostPolicyV4 generateSignedPostPolicyV4(
/**
* 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.
+ * See full documentation for {@link #generateSignedPostPolicyV4(BlobInfo, long, TimeUnit,
+ * PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
@@ -2887,9 +2887,9 @@ PostPolicyV4 generateSignedPostPolicyV4(
/**
* 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.
+ * required fields and conditions. See full documentation for {@link
+ * #generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
+ * PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options);
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PostPolicyV4Test.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PostPolicyV4Test.java
new file mode 100644
index 000000000..8213f6093
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PostPolicyV4Test.java
@@ -0,0 +1,592 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+
+public class PostPolicyV4Test {
+ private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+ private void assertNotSameButEqual(Map expected, Map returned) {
+ assertNotSame(expected, returned);
+ assertEquals("map sizes", expected.size(), returned.size());
+ for (String key : expected.keySet()) {
+ assertEquals("value of $" + key, expected.get(key), returned.get(key));
+ }
+ }
+
+ private static final String[] VALID_FIELDS = {
+ "acl",
+ "bucket",
+ "cache-control",
+ "content-disposition",
+ "content-encoding",
+ "content-type",
+ "expires",
+ "file",
+ "key",
+ "policy",
+ "success_action_redirect",
+ "success_action_status",
+ "x-goog-algorithm",
+ "x-goog-credential",
+ "x-goog-date",
+ "x-goog-signature",
+ };
+
+ private static final String CUSTOM_PREFIX = "x-goog-meta-";
+
+ private static Map initAllFields() {
+ Map fields = new HashMap<>();
+ for (String key : VALID_FIELDS) {
+ fields.put(key, "value of " + key);
+ }
+ fields.put(CUSTOM_PREFIX + "custom", "value of custom field");
+ return Collections.unmodifiableMap(fields);
+ }
+
+ private static final Map ALL_FIELDS = initAllFields();
+
+ @Test
+ public void testPostPolicyV4_of() {
+ String url = "http://example.com";
+ PostPolicyV4 policy = PostPolicyV4.of(url, ALL_FIELDS);
+ assertEquals(url, policy.getUrl());
+ assertNotSameButEqual(ALL_FIELDS, policy.getFields());
+ }
+
+ @Test
+ public void testPostPolicyV4_ofMalformedURL() {
+ try {
+ PostPolicyV4.of("example.com", new HashMap());
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("example.com is not an absolute URL", e.getMessage());
+ }
+
+ try {
+ PostPolicyV4.of("Scio nescio", new HashMap());
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "java.net.URISyntaxException: Illegal character in path at index 4: Scio nescio",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testPostPolicyV4_ofInvalidField() {
+ Map fields = new HashMap<>(ALL_FIELDS);
+ fields.put("$file", "file.txt");
+ try {
+ PostPolicyV4.of("http://google.com", fields);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid key: $file", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testPostFieldsV4_of() {
+ PostPolicyV4.PostFieldsV4 fields = PostPolicyV4.PostFieldsV4.of(ALL_FIELDS);
+ assertNotSameButEqual(ALL_FIELDS, fields.getFieldsMap());
+ }
+
+ @Test
+ public void testPostFieldsV4_ofInvalidField() {
+ Map map = new HashMap<>();
+ map.put("$file", "file.txt");
+ try {
+ PostPolicyV4.PostFieldsV4.of(map);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid key: $file", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testPostPolicyV4_builder() {
+ PostPolicyV4.PostFieldsV4.Builder builder = PostPolicyV4.PostFieldsV4.newBuilder();
+ builder.setAcl("acl");
+ builder.setCacheControl("cache-control");
+ builder.setContentDisposition("content-disposition");
+ builder.setContentType("content-type");
+ builder.setExpires("expires");
+ builder.setSuccessActionRedirect("success_action_redirect");
+ Map map = builder.build().getFieldsMap();
+ assertEquals("map size", 6, map.size());
+ for (String key : map.keySet()) {
+ assertEquals("value of $" + key, key, map.get(key));
+ }
+
+ Map expectedUpdated = new HashMap<>(map);
+ builder.setCustomMetadataField("xxx", "XXX");
+ builder.setCustomMetadataField(CUSTOM_PREFIX + "yyy", "YYY");
+ builder.setAcl(null);
+ builder.setContentType("new-content-type");
+ builder.setSuccessActionStatus(42);
+ expectedUpdated.put(CUSTOM_PREFIX + "xxx", "XXX");
+ expectedUpdated.put(CUSTOM_PREFIX + "yyy", "YYY");
+ expectedUpdated.put("acl", null);
+ expectedUpdated.put("content-type", "new-content-type");
+ expectedUpdated.put("success_action_status", "42");
+ Map updated = builder.build().getFieldsMap();
+ assertNotSameButEqual(expectedUpdated, updated);
+ }
+
+ @Test
+ public void testPostPolicyV4_setContentLength() {
+ PostPolicyV4.PostFieldsV4.Builder builder = PostPolicyV4.PostFieldsV4.newBuilder();
+ builder.setContentLength(12345);
+ assertTrue(builder.build().getFieldsMap().isEmpty());
+ }
+
+ @Test
+ public void testPostConditionsV4_builder() {
+ PostPolicyV4.PostConditionsV4.Builder builder = PostPolicyV4.PostConditionsV4.newBuilder();
+ assertTrue(builder.build().getConditions().isEmpty());
+
+ builder.addAclCondition(PostPolicyV4.ConditionV4Type.STARTS_WITH, "public");
+ builder.addBucketCondition(PostPolicyV4.ConditionV4Type.MATCHES, "travel-maps");
+ builder.addContentLengthRangeCondition(0, 100000);
+
+ PostPolicyV4.PostConditionsV4 postConditionsV4 = builder.build();
+ Set conditions = postConditionsV4.getConditions();
+ assertEquals(3, conditions.size());
+
+ try {
+ conditions.clear();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+
+ PostPolicyV4.PostConditionsV4 postConditionsV4Extended =
+ postConditionsV4
+ .toBuilder()
+ .addCustomCondition(PostPolicyV4.ConditionV4Type.STARTS_WITH, "key", "")
+ .build();
+ assertEquals(4, postConditionsV4Extended.getConditions().size());
+ }
+
+ interface ConditionTest {
+ /**
+ * Calls one of addCondition method on the given builder and returns expected ConditionV4
+ * object.
+ */
+ PostPolicyV4.ConditionV4 addCondition(PostPolicyV4.PostConditionsV4.Builder builder);
+ }
+
+ @Test
+ public void testPostConditionsV4_addCondition() {
+ // shortcuts
+ final PostPolicyV4.ConditionV4Type eq = PostPolicyV4.ConditionV4Type.MATCHES;
+ final PostPolicyV4.ConditionV4Type startsWith = PostPolicyV4.ConditionV4Type.STARTS_WITH;
+ final PostPolicyV4.ConditionV4Type range = PostPolicyV4.ConditionV4Type.CONTENT_LENGTH_RANGE;
+
+ ConditionTest[] cases = {
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addContentLengthRangeCondition(123, 456);
+ return new PostPolicyV4.ConditionV4(range, "123", "456");
+ }
+
+ @Override
+ public String toString() {
+ return "addContentLengthRangeCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ long date = 2000000000000L;
+ builder.addExpiresCondition(date);
+ return new PostPolicyV4.ConditionV4(eq, "expires", dateFormat.format(date));
+ }
+
+ @Override
+ public String toString() {
+ return "addExpiresCondition(long)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addExpiresCondition("2030-Dec-31");
+ return new PostPolicyV4.ConditionV4(eq, "expires", "2030-Dec-31");
+ }
+
+ @Override
+ public String toString() {
+ return "addExpiresCondition(String)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addExpiresCondition(range, 0);
+ return new PostPolicyV4.ConditionV4(eq, "expires", dateFormat.format(0));
+ }
+
+ @Override
+ public String toString() {
+ return "@deprecated addExpiresCondition(type,long)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addExpiresCondition(startsWith, "2030-Dec-31");
+ return new PostPolicyV4.ConditionV4(eq, "expires", "2030-Dec-31");
+ }
+
+ @Override
+ public String toString() {
+ return "@deprecated addExpiresCondition(type,String)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addSuccessActionStatusCondition(202);
+ return new PostPolicyV4.ConditionV4(eq, "success_action_status", "202");
+ }
+
+ @Override
+ public String toString() {
+ return "addSuccessActionStatusCondition(int)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addSuccessActionStatusCondition(startsWith, 202);
+ return new PostPolicyV4.ConditionV4(eq, "success_action_status", "202");
+ }
+
+ @Override
+ public String toString() {
+ return "@deprecated addSuccessActionStatusCondition(type,int)";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addAclCondition(startsWith, "read");
+ return new PostPolicyV4.ConditionV4(startsWith, "acl", "read");
+ }
+
+ @Override
+ public String toString() {
+ return "addAclCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addBucketCondition(eq, "my-bucket");
+ return new PostPolicyV4.ConditionV4(eq, "bucket", "my-bucket");
+ }
+
+ @Override
+ public String toString() {
+ return "addBucketCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addCacheControlCondition(eq, "false");
+ return new PostPolicyV4.ConditionV4(eq, "cache-control", "false");
+ }
+
+ @Override
+ public String toString() {
+ return "addCacheControlCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addContentDispositionCondition(startsWith, "gzip");
+ return new PostPolicyV4.ConditionV4(startsWith, "content-disposition", "gzip");
+ }
+
+ @Override
+ public String toString() {
+ return "addContentDispositionCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addContentEncodingCondition(eq, "koi8");
+ return new PostPolicyV4.ConditionV4(eq, "content-encoding", "koi8");
+ }
+
+ @Override
+ public String toString() {
+ return "addContentEncodingCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addContentTypeCondition(startsWith, "application/");
+ return new PostPolicyV4.ConditionV4(startsWith, "content-type", "application/");
+ }
+
+ @Override
+ public String toString() {
+ return "addContentTypeCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addKeyCondition(startsWith, "");
+ return new PostPolicyV4.ConditionV4(startsWith, "key", "");
+ }
+
+ @Override
+ public String toString() {
+ return "addKeyCondition()";
+ }
+ },
+ new ConditionTest() {
+ @Override
+ public PostPolicyV4.ConditionV4 addCondition(
+ PostPolicyV4.PostConditionsV4.Builder builder) {
+ builder.addSuccessActionRedirectUrlCondition(eq, "fail");
+ return new PostPolicyV4.ConditionV4(eq, "success_action_redirect", "fail");
+ }
+
+ @Override
+ public String toString() {
+ return "addSuccessActionRedirectUrlCondition()";
+ }
+ },
+ };
+
+ for (ConditionTest testCase : cases) {
+ PostPolicyV4.PostConditionsV4.Builder builder = PostPolicyV4.PostConditionsV4.newBuilder();
+ PostPolicyV4.ConditionV4 expected = testCase.addCondition(builder);
+ Set conditions = builder.build().getConditions();
+ assertEquals("size", 1, conditions.size());
+ PostPolicyV4.ConditionV4 actual = conditions.toArray(new PostPolicyV4.ConditionV4[1])[0];
+ assertEquals(testCase.toString(), expected, actual);
+ }
+ }
+
+ @Test
+ public void testPostConditionsV4_addConditionFail() {
+ final PostPolicyV4.PostConditionsV4.Builder builder =
+ PostPolicyV4.PostConditionsV4.newBuilder();
+ final PostPolicyV4.ConditionV4Type range = PostPolicyV4.ConditionV4Type.CONTENT_LENGTH_RANGE;
+
+ Callable[] cases = {
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addAclCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "acl";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addBucketCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "bucket";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addCacheControlCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "cache-control";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addContentDispositionCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "content-disposition";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addContentEncodingCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "content-encoding";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addContentTypeCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "content-type";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addKeyCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "key";
+ }
+ },
+ new Callable() {
+ @Override
+ public Void call() {
+ builder.addSuccessActionRedirectUrlCondition(range, "");
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "success_action_redirect";
+ }
+ },
+ };
+
+ for (Callable testCase : cases) {
+ try {
+ testCase.call();
+ fail();
+ } catch (Exception e) {
+ String expected =
+ "java.lang.IllegalArgumentException: Field "
+ + testCase
+ + " can't use content-length-range";
+ assertEquals(expected, e.toString());
+ }
+ }
+ assertTrue(builder.build().getConditions().isEmpty());
+ }
+
+ @Test
+ public void testPostConditionsV4_toString() {
+ PostPolicyV4.PostConditionsV4.Builder builder = PostPolicyV4.PostConditionsV4.newBuilder();
+ builder.addKeyCondition(PostPolicyV4.ConditionV4Type.MATCHES, "test-object");
+ builder.addAclCondition(PostPolicyV4.ConditionV4Type.STARTS_WITH, "public");
+ builder.addContentLengthRangeCondition(246, 266);
+
+ Set toStringSet = new HashSet<>();
+ for (PostPolicyV4.ConditionV4 conditionV4 : builder.build().getConditions()) {
+ toStringSet.add(conditionV4.toString());
+ }
+ assertEquals(3, toStringSet.size());
+
+ String[] expectedStrings = {
+ "[\"eq\", \"$key\", \"test-object\"]",
+ "[\"starts-with\", \"$acl\", \"public\"]",
+ "[\"content-length-range\", 246, 266]"
+ };
+
+ for (String expected : expectedStrings) {
+ assertTrue(expected + "/" + toStringSet, toStringSet.contains(expected));
+ }
+ }
+
+ @Test
+ public void testPostPolicyV4Document_of_toJson() {
+ PostPolicyV4.PostConditionsV4 emptyConditions =
+ PostPolicyV4.PostConditionsV4.newBuilder().build();
+ PostPolicyV4.PostPolicyV4Document emptyDocument =
+ PostPolicyV4.PostPolicyV4Document.of("", emptyConditions);
+ String emptyJson = emptyDocument.toJson();
+ assertEquals(emptyJson, "{\"conditions\":[],\"expiration\":\"\"}");
+
+ PostPolicyV4.PostConditionsV4 postConditionsV4 =
+ PostPolicyV4.PostConditionsV4.newBuilder()
+ .addBucketCondition(PostPolicyV4.ConditionV4Type.MATCHES, "my-bucket")
+ .addKeyCondition(PostPolicyV4.ConditionV4Type.STARTS_WITH, "")
+ .addContentLengthRangeCondition(1, 1000)
+ .build();
+
+ String expiration = dateFormat.format(System.currentTimeMillis());
+ PostPolicyV4.PostPolicyV4Document document =
+ PostPolicyV4.PostPolicyV4Document.of(expiration, postConditionsV4);
+ String json = document.toJson();
+ assertEquals(
+ json,
+ "{\"conditions\":[{\"bucket\":\"my-bucket\"},[\"starts-with\",\"$key\",\"\"],[\"content-length-range\",1,1000]],\"expiration\":\""
+ + expiration
+ + "\"}");
+ }
+}