Repository: googleapis/java-storage (java)

Issue:
Add support for PublicAccessPrevention
We need to add support for a new field `publicAccessPrevention` in the bucket IAM configuration for storage. Full details are available in [this doc](https://docs.google.com/document/d/1o_ElQg4c8enEx8OYa3irrUYFKADmHFbRn7dC9g287sI/). This should include ability to create buckets with this field set, get the field, and set the field. We'd also like an integration test that verifies that the feature works as expected.

Please wait for approval from myself or @tritone before merging or releasing this change so we can coordinate around release dates.

Based on looking at the code, I _think_ the field should go here:
https://github.com/googleapis/java-storage/blob/ac65e5b0bd324d5726504bb3405c758675a56ddc/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java#L110


Pull request description:
feat: add support of public access prevention
Fixes #616 


Patch:
diff --git a/.github/readme/synth.metadata/synth.metadata b/.github/readme/synth.metadata/synth.metadata
index 25ec95aa..411acf73 100644
--- a/.github/readme/synth.metadata/synth.metadata
+++ b/.github/readme/synth.metadata/synth.metadata
@@ -4,14 +4,14 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/java-storage.git",
-        "sha": "0ec71e3fc2381af0858e8fc424465b0ef4e75260"
+        "sha": "45a2fff27cfe80b83fbfa24c6a490be502348db8"
       }
     },
     {
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "740366bbb9a7e0f4b77fc75dc26be1d3a376c3e0"
+        "sha": "9763f20e4b7bb1091082462b2f7970e965d0d414"
       }
     }
   ]
diff --git a/.github/release-please.yml b/.github/release-please.yml
index 22d5b382..db4c4ce5 100644
--- a/.github/release-please.yml
+++ b/.github/release-please.yml
@@ -10,4 +10,7 @@ branches:
     handleGHRelease: true
     releaseType: java-yoshi
     branch: 1.106.1-patch
-    
\ No newline at end of file
+  - bumpMinorPreMajor: true
+    handleGHRelease: true
+    releaseType: java-yoshi
+    branch: 1.111.3-patch
diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml
index 3b43692a..bd769aad 100644
--- a/.github/sync-repo-settings.yaml
+++ b/.github/sync-repo-settings.yaml
@@ -40,15 +40,31 @@ branchProtectionRules:
     requiresCodeOwnerReviews: true
     requiresStrictStatusChecks: false
     requiredStatusCheckContexts:
-    - dependencies (8)
-    - dependencies (11)
-    - lint
-    - clirr
-    - units (7)
-    - units (8)
-    - units (11)
-    - 'Kokoro - Test: Integration'
-    - cla/google
+      - dependencies (8)
+      - dependencies (11)
+      - lint
+      - clirr
+      - units (7)
+      - units (8)
+      - units (11)
+      - 'Kokoro - Test: Integration'
+      - cla/google
+  - pattern: 1.111.3-patch
+    isAdminEnforced: true
+    requiredApprovingReviewCount: 1
+    requiresCodeOwnerReviews: true
+    requiresStrictStatusChecks: false
+    requiredStatusCheckContexts:
+      - dependencies (8)
+      - dependencies (11)
+      - linkage-monitor
+      - lint
+      - clirr
+      - units (7)
+      - units (8)
+      - units (11)
+      - 'Kokoro - Test: Integration'
+      - cla/google
 permissionRules:
   - team: yoshi-admins
     permission: admin
diff --git a/.kokoro/coerce_logs.sh b/.kokoro/coerce_logs.sh
index 5cf7ba49..46edbf7f 100755
--- a/.kokoro/coerce_logs.sh
+++ b/.kokoro/coerce_logs.sh
@@ -28,7 +28,6 @@ job=$(basename ${KOKORO_JOB_NAME})
 echo "coercing sponge logs..."
 for xml in `find . -name *-sponge_log.xml`
 do
-  echo "processing ${xml}"
   class=$(basename ${xml} | cut -d- -f2)
   dir=$(dirname ${xml})/${job}/${class}
   text=$(dirname ${xml})/${class}-sponge_log.txt
diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh
index 59d2aafc..9030ba8f 100755
--- a/.kokoro/dependencies.sh
+++ b/.kokoro/dependencies.sh
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 set -eo pipefail
+shopt -s nullglob
 
 ## Get the directory of the build script
 scriptDir=$(realpath $(dirname "${BASH_SOURCE[0]}"))
@@ -46,7 +47,7 @@ function completenessCheck() {
   # This is stripped from the output as it is not present in the flattened pom.
   # Only dependencies with 'compile' or 'runtime' scope are included from original dependency list.
   msg "Generating dependency list using original pom..."
-  mvn dependency:list -f pom.xml -DincludeScope=runtime -Dsort=true | grep '\[INFO]    .*:.*:.*:.*:.*' | sed -e s/\\s--\\smodule.*// >.org-list.txt
+  mvn dependency:list -f pom.xml -DincludeScope=runtime -Dsort=true | grep '\[INFO]    .*:.*:.*:.*:.*' | sed -e 's/ --.*//' >.org-list.txt
 
   # Output dep list generated using the flattened pom (only 'compile' and 'runtime' scopes)
   msg "Generating dependency list using flattened pom..."
@@ -70,7 +71,7 @@ function completenessCheck() {
 set +e
 
 error_count=0
-for path in $(find -name ".flattened-pom.xml")
+for path in **/.flattened-pom.xml
 do
   # Check flattened pom in each dir that contains it for completeness
   dir=$(dirname "$path")
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 084115a7..c30126f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
 # Changelog
 
+## [1.116.0](https://www.github.com/googleapis/java-storage/compare/v1.115.0...v1.116.0) (2021-06-14)
+
+
+### Features
+
+* Add shouldReturnRawInputStream option to Get requests ([#872](https://www.github.com/googleapis/java-storage/issues/872)) ([474dfae](https://www.github.com/googleapis/java-storage/commit/474dfaec09d591455cecc77b08461efff1010c3a))
+
+
+### Bug Fixes
+
+* **ci:** remove linkage-monitor to pass 1.106.1 patch ci ([#862](https://www.github.com/googleapis/java-storage/issues/862)) ([94a9159](https://www.github.com/googleapis/java-storage/commit/94a915958f888cfbf4110d06a7f64be135dc141e))
+
+
+### Dependencies
+
+* update dependency com.google.cloud:google-cloud-shared-dependencies to v1.3.0 ([#863](https://www.github.com/googleapis/java-storage/issues/863)) ([37bfd5e](https://www.github.com/googleapis/java-storage/commit/37bfd5e3cf1c62767ff8033366cca66c2e8e6e4c))
+* update kms.version ([#860](https://www.github.com/googleapis/java-storage/issues/860)) ([f1430ff](https://www.github.com/googleapis/java-storage/commit/f1430ffea07696ea808369fcd287187c14afc9a2))
+* update kms.version to v0.89.3 ([#873](https://www.github.com/googleapis/java-storage/issues/873)) ([ee7c236](https://www.github.com/googleapis/java-storage/commit/ee7c2368928c050befb809a2d61bd6ffc92bdc88))
+
 ## [1.115.0](https://www.github.com/googleapis/java-storage/compare/v1.114.0...v1.115.0) (2021-06-01)
 
 
diff --git a/README.md b/README.md
index 0e577615..f39fc8f9 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
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>libraries-bom</artifactId>
-      <version>20.6.0</version>
+      <version>20.7.0</version>
       <type>pom</type>
       <scope>import</scope>
     </dependency>
@@ -38,25 +38,25 @@ If you are using Maven without BOM, add this to your dependencies:
 <dependency>
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage</artifactId>
-  <version>1.115.0</version>
+  <version>1.116.0</version>
 </dependency>
 
 ```
 
 If you are using Gradle 5.x or later, add this to your dependencies
 ```Groovy
-implementation platform('com.google.cloud:libraries-bom:20.6.0')
+implementation platform('com.google.cloud:libraries-bom:20.7.0')
 
 compile 'com.google.cloud:google-cloud-storage'
 ```
 If you are using Gradle without BOM, add this to your dependencies
 ```Groovy
-compile 'com.google.cloud:google-cloud-storage:1.115.0'
+compile 'com.google.cloud:google-cloud-storage:1.116.0'
 ```
 
 If you are using SBT, add this to your dependencies
 ```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.115.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "1.116.0"
 ```
 
 ## Authentication
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 8d3c1a9e..c80dfc16 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -2,7 +2,7 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <artifactId>google-cloud-storage</artifactId>
-  <version>1.115.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>1.116.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <packaging>jar</packaging>
   <name>Google Cloud Storage</name>
   <url>https://github.com/googleapis/java-storage</url>
@@ -12,11 +12,11 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-storage-parent</artifactId>
-    <version>1.115.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+    <version>1.116.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   </parent>
   <properties>
     <site.installationModule>google-cloud-storage</site.installationModule>
-    <kms.version>0.89.2</kms.version>
+    <kms.version>0.89.3</kms.version>
   </properties>
   <dependencies>
     <dependency>
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 6faef6c0..abc61eb1 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
@@ -117,6 +117,8 @@ public class Blob extends BlobInfo {
           return Storage.BlobSourceOption.decryptionKey((String) getValue());
         case USER_PROJECT:
           return Storage.BlobSourceOption.userProject((String) getValue());
+        case RETURN_RAW_INPUT_STREAM:
+          return Storage.BlobSourceOption.shouldReturnRawInputStream((boolean) getValue());
         default:
           throw new AssertionError("Unexpected enum value");
       }
@@ -136,6 +138,8 @@ public class Blob extends BlobInfo {
           return Storage.BlobGetOption.userProject((String) getValue());
         case CUSTOMER_SUPPLIED_KEY:
           return Storage.BlobGetOption.decryptionKey((String) getValue());
+        case RETURN_RAW_INPUT_STREAM:
+          return Storage.BlobGetOption.shouldReturnRawInputStream((boolean) getValue());
         default:
           throw new AssertionError("Unexpected enum value");
       }
@@ -200,6 +204,16 @@ public class Blob extends BlobInfo {
       return new BlobSourceOption(StorageRpc.Option.USER_PROJECT, userProject);
     }
 
+    /**
+     * Returns an option for whether the request should return the raw input stream, instead of
+     * automatically decompressing the content. By default, this is false for Blob.downloadTo(), but
+     * true for ReadChannel.read().
+     */
+    public static BlobSourceOption shouldReturnRawInputStream(boolean shouldReturnRawInputStream) {
+      return new BlobSourceOption(
+          StorageRpc.Option.RETURN_RAW_INPUT_STREAM, shouldReturnRawInputStream);
+    }
+
     static Storage.BlobSourceOption[] toSourceOptions(
         BlobInfo blobInfo, BlobSourceOption... options) {
       Storage.BlobSourceOption[] convertedOptions = new Storage.BlobSourceOption[options.length];
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
index 6a0953f9..599f45e2 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java
@@ -22,6 +22,7 @@ import com.google.api.services.storage.model.StorageObject;
 import com.google.common.base.MoreObjects;
 import java.io.Serializable;
 import java.util.Objects;
+import java.util.regex.Pattern;
 
 /**
  * Google Storage Object identifier. A {@code BlobId} object includes the name of the containing
@@ -56,6 +57,11 @@ public final class BlobId implements Serializable {
     return generation;
   }
 
+  /** Returns this blob's Storage url which can be used with gsutil */
+  public String toGsUtilUri() {
+    return "gs://" + bucket + "/" + name;
+  }
+
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this)
@@ -114,6 +120,23 @@ public final class BlobId implements Serializable {
     return new BlobId(checkNotNull(bucket), checkNotNull(name), generation);
   }
 
+  /**
+   * Creates a {@code BlobId} object.
+   *
+   * @param gsUtilUri the Storage url to create the blob from
+   */
+  public static BlobId fromGsUtilUri(String gsUtilUri) {
+    if (!Pattern.matches("gs://.*/.*", gsUtilUri)) {
+      throw new IllegalArgumentException(
+          gsUtilUri + " is not a valid gsutil URI (i.e. \"gs://bucket/blob\")");
+    }
+    int blobNameStartIndex = gsUtilUri.indexOf('/', 5);
+    String bucketName = gsUtilUri.substring(5, blobNameStartIndex);
+    String blobName = gsUtilUri.substring(blobNameStartIndex + 1);
+
+    return BlobId.of(bucketName, blobName);
+  }
+
   static BlobId fromPb(StorageObject storageObject) {
     return BlobId.of(
         storageObject.getBucket(), storageObject.getName(), storageObject.getGeneration());
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index c1339523..a5494872 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -105,17 +105,56 @@ public class BucketInfo implements Serializable {
 
   private static final Logger log = Logger.getLogger(BucketInfo.class.getName());
 
+  /**
+   * Public Access Prevention enum with expected values.
+   *
+   * @see <a
+   *     href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
+   */
+  public enum PublicAccessPrevention {
+    ENFORCED("enforced"),
+    /** Default value for Public Access Prevention */
+    UNSPECIFIED("unspecified"),
+    /**
+     * If the api returns a value that isn't defined in {@link PublicAccessPrevention} this value
+     * will be returned.
+     */
+    UNKNOWN(null);
+
+    private final String value;
+
+    PublicAccessPrevention(String value) {
+      this.value = value;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    public static PublicAccessPrevention parse(String value) {
+      String upper = value.toUpperCase();
+      try {
+        return valueOf(upper);
+      } catch (IllegalArgumentException ignore) {
+        return UNKNOWN;
+      }
+    }
+  }
+
   /**
    * The Bucket's IAM Configuration.
    *
    * @see <a href="https://cloud.google.com/storage/docs/uniform-bucket-level-access">uniform
    *     bucket-level access</a>
+   * @see <a
+   *     href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
    */
   public static class IamConfiguration implements Serializable {
     private static final long serialVersionUID = -8671736104909424616L;
 
-    private Boolean isUniformBucketLevelAccessEnabled;
-    private Long uniformBucketLevelAccessLockedTime;
+    private final Boolean isUniformBucketLevelAccessEnabled;
+    private final Long uniformBucketLevelAccessLockedTime;
+    private final PublicAccessPrevention publicAccessPrevention;
 
     @Override
     public boolean equals(Object o) {
@@ -129,12 +168,16 @@ public class BucketInfo implements Serializable {
 
     @Override
     public int hashCode() {
-      return Objects.hash(isUniformBucketLevelAccessEnabled, uniformBucketLevelAccessLockedTime);
+      return Objects.hash(
+          isUniformBucketLevelAccessEnabled,
+          uniformBucketLevelAccessLockedTime,
+          publicAccessPrevention);
     }
 
     private IamConfiguration(Builder builder) {
       this.isUniformBucketLevelAccessEnabled = builder.isUniformBucketLevelAccessEnabled;
       this.uniformBucketLevelAccessLockedTime = builder.uniformBucketLevelAccessLockedTime;
+      this.publicAccessPrevention = builder.publicAccessPrevention;
     }
 
     public static Builder newBuilder() {
@@ -145,6 +188,7 @@ public class BucketInfo implements Serializable {
       Builder builder = new Builder();
       builder.isUniformBucketLevelAccessEnabled = isUniformBucketLevelAccessEnabled;
       builder.uniformBucketLevelAccessLockedTime = uniformBucketLevelAccessLockedTime;
+      builder.publicAccessPrevention = publicAccessPrevention;
       return builder;
     }
 
@@ -168,6 +212,11 @@ public class BucketInfo implements Serializable {
       return uniformBucketLevelAccessLockedTime;
     }
 
+    /** Returns the Public Access Prevention. * */
+    public PublicAccessPrevention getPublicAccessPrevention() {
+      return publicAccessPrevention;
+    }
+
     Bucket.IamConfiguration toPb() {
       Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
 
@@ -180,6 +229,8 @@ public class BucketInfo implements Serializable {
               : new DateTime(uniformBucketLevelAccessLockedTime));
 
       iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
+      iamConfiguration.setPublicAccessPrevention(
+          publicAccessPrevention == null ? null : publicAccessPrevention.getValue());
 
       return iamConfiguration;
     }
@@ -188,10 +239,17 @@ public class BucketInfo implements Serializable {
       Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
           iamConfiguration.getUniformBucketLevelAccess();
       DateTime lockedTime = uniformBucketLevelAccess.getLockedTime();
+      String publicAccessPrevention = iamConfiguration.getPublicAccessPrevention();
+
+      PublicAccessPrevention publicAccessPreventionValue = null;
+      if (publicAccessPrevention != null) {
+        publicAccessPreventionValue = PublicAccessPrevention.parse(publicAccessPrevention);
+      }
 
       return newBuilder()
           .setIsUniformBucketLevelAccessEnabled(uniformBucketLevelAccess.getEnabled())
           .setUniformBucketLevelAccessLockedTime(lockedTime == null ? null : lockedTime.getValue())
+          .setPublicAccessPrevention(publicAccessPreventionValue)
           .build();
     }
 
@@ -199,6 +257,7 @@ public class BucketInfo implements Serializable {
     public static class Builder {
       private Boolean isUniformBucketLevelAccessEnabled;
       private Long uniformBucketLevelAccessLockedTime;
+      private PublicAccessPrevention publicAccessPrevention;
 
       /** Deprecated in favor of setIsUniformBucketLevelAccessEnabled(). */
       @Deprecated
@@ -239,6 +298,18 @@ public class BucketInfo implements Serializable {
         return this;
       }
 
+      /**
+       * Sets the bucket's Public Access Prevention configuration. Currently supported options are
+       * {@link PublicAccessPrevention#UNSPECIFIED} or {@link PublicAccessPrevention#ENFORCED}
+       *
+       * @see <a
+       *     href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
+       */
+      public Builder setPublicAccessPrevention(PublicAccessPrevention publicAccessPrevention) {
+        this.publicAccessPrevention = publicAccessPrevention;
+        return this;
+      }
+
       /** Builds an {@code IamConfiguration} object */
       public IamConfiguration build() {
         return new IamConfiguration(this);
@@ -341,6 +412,11 @@ public class BucketInfo implements Serializable {
    * Lifecycle rule for a bucket. Allows supported Actions, such as deleting and changing storage
    * class, to be executed when certain Conditions are met.
    *
+   * <p>Versions 1.50.0-1.111.2 of this library don’t support the CustomTimeBefore,
+   * DaysSinceCustomTime, DaysSinceNoncurrentTime and NoncurrentTimeBefore lifecycle conditions. To
+   * read GCS objects with those lifecycle conditions, update your Java client library to version
+   * 1.112.0 or higher.
+   *
    * @see <a href="https://cloud.google.com/storage/docs/lifecycle#actions">Object Lifecycle
    *     Management</a>
    */
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 08eec26a..02bdb8dd 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
@@ -847,6 +847,16 @@ public interface Storage extends Service<StorageOptions> {
     public static BlobSourceOption userProject(String userProject) {
       return new BlobSourceOption(StorageRpc.Option.USER_PROJECT, userProject);
     }
+
+    /**
+     * Returns an option for whether the request should return the raw input stream, instead of
+     * automatically decompressing the content. By default, this is false for Blob.downloadTo(), but
+     * true for ReadChannel.read().
+     */
+    public static BlobSourceOption shouldReturnRawInputStream(boolean shouldReturnRawInputStream) {
+      return new BlobSourceOption(
+          StorageRpc.Option.RETURN_RAW_INPUT_STREAM, shouldReturnRawInputStream);
+    }
   }
 
   /** Class for specifying blob get options. */
@@ -862,6 +872,10 @@ public interface Storage extends Service<StorageOptions> {
       super(rpcOption, value);
     }
 
+    private BlobGetOption(StorageRpc.Option rpcOption, boolean value) {
+      super(rpcOption, value);
+    }
+
     /**
      * Returns an option for blob's data generation match. If this option is used the request will
      * fail if blob's generation does not match. The generation value to compare with the actual
@@ -953,6 +967,16 @@ public interface Storage extends Service<StorageOptions> {
     public static BlobGetOption decryptionKey(String key) {
       return new BlobGetOption(StorageRpc.Option.CUSTOMER_SUPPLIED_KEY, key);
     }
+
+    /**
+     * Returns an option for whether the request should return the raw input stream, instead of
+     * automatically decompressing the content. By default, this is false for Blob.downloadTo(), but
+     * true for ReadChannel.read().
+     */
+    public static BlobGetOption shouldReturnRawInputStream(boolean shouldReturnRawInputStream) {
+      return new BlobGetOption(
+          StorageRpc.Option.RETURN_RAW_INPUT_STREAM, shouldReturnRawInputStream);
+    }
   }
 
   /** Class for specifying bucket list options. */
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 6df86cb6..23251edc 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
@@ -681,7 +681,6 @@ public class HttpStorageRpc implements StorageRpc {
             .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options))
             .setUserProject(Option.USER_PROJECT.getString(options));
     setEncryptionHeaders(req.getRequestHeaders(), ENCRYPTION_KEY_PREFIX, options);
-    req.setReturnRawInputStream(true);
     return req;
   }
 
@@ -692,9 +691,15 @@ public class HttpStorageRpc implements StorageRpc {
     Scope scope = tracer.withSpan(span);
     try {
       Get req = createReadRequest(from, options);
+      Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options);
+      if (shouldReturnRawInputStream != null) {
+        req.setReturnRawInputStream(shouldReturnRawInputStream);
+      } else {
+        req.setReturnRawInputStream(false);
+      }
       req.getMediaHttpDownloader().setBytesDownloaded(position);
       req.getMediaHttpDownloader().setDirectDownloadEnabled(true);
-      req.executeMediaAndDownloadTo(outputStream);
+      req.executeMedia().download(outputStream);
       return req.getMediaHttpDownloader().getNumBytesDownloaded();
     } catch (IOException ex) {
       span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage()));
@@ -717,6 +722,12 @@ public class HttpStorageRpc implements StorageRpc {
     try {
       checkArgument(position >= 0, "Position should be non-negative, is " + position);
       Get req = createReadRequest(from, options);
+      Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options);
+      if (shouldReturnRawInputStream != null) {
+        req.setReturnRawInputStream(shouldReturnRawInputStream);
+      } else {
+        req.setReturnRawInputStream(true);
+      }
       StringBuilder range = new StringBuilder();
       range.append("bytes=").append(position).append("-").append(position + bytes - 1);
       HttpHeaders requestHeaders = req.getRequestHeaders();
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
index cc4dd574..b695559b 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java
@@ -68,7 +68,8 @@ public interface StorageRpc extends ServiceRpc {
     SERVICE_ACCOUNT_EMAIL("serviceAccount"),
     SHOW_DELETED_KEYS("showDeletedKeys"),
     REQUESTED_POLICY_VERSION("optionsRequestedPolicyVersion"),
-    DETECT_CONTENT_TYPE("detectContentType");
+    DETECT_CONTENT_TYPE("detectContentType"),
+    RETURN_RAW_INPUT_STREAM("returnRawInputStream");
 
     private final String value;
 
diff --git a/pom.xml b/pom.xml
index c92a7fa2..32857d08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
   <groupId>com.google.cloud</groupId>
   <artifactId>google-cloud-storage-parent</artifactId>
   <packaging>pom</packaging>
-  <version>1.115.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
+  <version>1.116.1-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <name>Storage Parent</name>
   <url>https://github.com/googleapis/java-storage</url>
   <description>
@@ -79,7 +79,7 @@
       <dependency>
         <groupId>com.google.apis</groupId>
         <artifactId>google-api-services-storage</artifactId>
-        <version>v1-rev20210127-1.31.0</version>
+        <version>v1-rev20210127-1.31.5</version>
       </dependency>
       <dependency>
         <groupId>org.easymock</groupId>
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index db69bf56..c976a364 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -29,7 +29,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>1.115.0</version>
+      <version>1.116.0</version>
     </dependency>
     <!-- [END storage_install_without_bom] -->
 
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index e60966c3..3d52eae9 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -28,7 +28,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-storage</artifactId>
-      <version>1.115.0</version>
+      <version>1.116.0</version>
     </dependency>
 
     <dependency>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 0183d6d2..21cb1c4a 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -30,7 +30,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>20.6.0</version>
+        <version>20.7.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
diff --git a/synth.metadata b/synth.metadata
index 69d7a143..5ec465c3 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,14 +4,14 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/java-storage.git",
-        "sha": "340f393f81f5b17b8835166ac7a3da9e8ffea2f4"
+        "sha": "3c792b47487c4f3f83ebb5ffc0bcfc3f6cf0f297"
       }
     },
     {
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "8eae0234a16b26c2ff616d305dbd9786c8b10a47"
+        "sha": "87254ac89a9559864c0a245d6b150406439ce3d8"
       }
     }
   ],
@@ -79,6 +79,7 @@
     ".kokoro/trampoline.sh",
     "CODE_OF_CONDUCT.md",
     "LICENSE",
+    "SECURITY.md",
     "codecov.yaml",
     "java.header",
     "license-checks.xml",
diff --git a/versions.txt b/versions.txt
index 50d18142..b48886ce 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,4 +1,4 @@
 # Format:
 # module:released-version:current-version
 
-google-cloud-storage:1.115.0:1.115.1-SNAPSHOT
\ No newline at end of file
+google-cloud-storage:1.116.0:1.116.1-SNAPSHOT
\ No newline at end of file


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
index d503fb76..d90760a4 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java
@@ -31,6 +31,14 @@ public class BlobIdTest {
     assertEquals("n", blobId.getName());
   }
 
+  @Test
+  public void testToFromGsUtilUri() {
+    BlobId blobId = BlobId.fromGsUtilUri("gs://bucket/path/to/blob");
+    assertEquals("bucket", blobId.getBucket());
+    assertEquals("path/to/blob", blobId.getName());
+    assertEquals("gs://bucket/path/to/blob", blobId.toGsUtilUri());
+  }
+
   @Test
   public void testEquals() {
     compareBlobIds(BLOB, BlobId.of("b", "n"));
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
index d72901c1..c74625a1 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java
@@ -18,10 +18,13 @@ package com.google.cloud.storage;
 
 import static com.google.cloud.storage.Acl.Project.ProjectRole.VIEWERS;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import com.google.api.client.json.JsonGenerator;
+import com.google.api.client.json.jackson2.JacksonFactory;
 import com.google.api.client.util.DateTime;
 import com.google.api.services.storage.model.Bucket;
 import com.google.api.services.storage.model.Bucket.Lifecycle;
@@ -33,14 +36,18 @@ import com.google.cloud.storage.BucketInfo.AgeDeleteRule;
 import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule;
 import com.google.cloud.storage.BucketInfo.DeleteRule;
 import com.google.cloud.storage.BucketInfo.DeleteRule.Type;
+import com.google.cloud.storage.BucketInfo.IamConfiguration;
 import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule;
 import com.google.cloud.storage.BucketInfo.LifecycleRule;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction;
 import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition;
 import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule;
+import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
 import com.google.cloud.storage.BucketInfo.RawDeleteRule;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -79,6 +86,7 @@ public class BucketInfoTest {
       BucketInfo.IamConfiguration.newBuilder()
           .setIsUniformBucketLevelAccessEnabled(true)
           .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
+          .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
           .build();
   private static final BucketInfo.Logging LOGGING =
       BucketInfo.Logging.newBuilder()
@@ -363,11 +371,45 @@ public class BucketInfoTest {
         BucketInfo.IamConfiguration.newBuilder()
             .setIsUniformBucketLevelAccessEnabled(true)
             .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
+            .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
             .build()
             .toPb();
 
     assertEquals(Boolean.TRUE, iamConfiguration.getUniformBucketLevelAccess().getEnabled());
     assertNotNull(iamConfiguration.getUniformBucketLevelAccess().getLockedTime());
+    assertEquals(
+        BucketInfo.PublicAccessPrevention.ENFORCED.getValue(),
+        iamConfiguration.getPublicAccessPrevention());
+  }
+
+  @Test
+  public void testPublicAccessPrevention_ensureAbsentWhenUnknown() throws IOException {
+    StringWriter stringWriter = new StringWriter();
+    JsonGenerator jsonGenerator =
+        JacksonFactory.getDefaultInstance().createJsonGenerator(stringWriter);
+
+    jsonGenerator.serialize(
+        BucketInfo.IamConfiguration.newBuilder()
+            .setIsUniformBucketLevelAccessEnabled(true)
+            .setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
+            .setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN)
+            .build()
+            .toPb());
+    jsonGenerator.flush();
+
+    assertFalse(stringWriter.getBuffer().toString().contains("publicAccessPrevention"));
+  }
+
+  @Test
+  public void testPapValueOfIamConfiguration() {
+    Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
+    Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
+        new Bucket.IamConfiguration.UniformBucketLevelAccess();
+    iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
+    iamConfiguration.setPublicAccessPrevention("random-string");
+    IamConfiguration fromPb = IamConfiguration.fromPb(iamConfiguration);
+
+    assertEquals(PublicAccessPrevention.UNKNOWN, fromPb.getPublicAccessPrevention());
   }
 
   @Test
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
index f696a285..30558020 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java
@@ -104,6 +104,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -127,6 +128,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
 import javax.crypto.spec.SecretKeySpec;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpPost;
@@ -802,6 +804,40 @@ public class ITStorageTest {
     }
   }
 
+  @Test
+  public void testGetBlobRawInput() throws IOException {
+    Path file = File.createTempFile("temp", ".txt").toPath();
+    Files.write(file, "hello world".getBytes());
+
+    File gzippedFile = File.createTempFile("temp", ".gz");
+
+    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(gzippedFile));
+    Files.copy(file, gzipOutputStream);
+    gzipOutputStream.close();
+
+    String blobName = "zipped_blob";
+    BlobId blobId = BlobId.of(BUCKET, blobName);
+    BlobInfo blobInfo =
+        BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build();
+
+    storage.createFrom(blobInfo, gzippedFile.toPath());
+
+    Path rawInputGzippedFile = File.createTempFile("rawinputgzippedfile", ".txt").toPath();
+    Blob blob = storage.get(blobId);
+
+    blob.downloadTo(rawInputGzippedFile, Blob.BlobSourceOption.shouldReturnRawInputStream(true));
+
+    assertArrayEquals(
+        Files.readAllBytes(gzippedFile.toPath()), Files.readAllBytes(rawInputGzippedFile));
+
+    Path unzippedFile = File.createTempFile("unzippedfile", ".txt").toPath();
+    storage
+        .get(blobId)
+        .downloadTo(unzippedFile, Blob.BlobSourceOption.shouldReturnRawInputStream(false));
+
+    assertArrayEquals("hello world".getBytes(), Files.readAllBytes(unzippedFile));
+  }
+
   @Test(timeout = 5000)
   public void testListBlobsSelectedFields() throws InterruptedException {
     String[] blobNames = {
@@ -3235,6 +3271,151 @@ public class ITStorageTest {
     }
   }
 
+  private Bucket generatePublicAccessPreventionBucket(String bucketName, boolean enforced) {
+    return storage.create(
+        Bucket.newBuilder(bucketName)
+            .setIamConfiguration(
+                BucketInfo.IamConfiguration.newBuilder()
+                    .setPublicAccessPrevention(
+                        enforced
+                            ? BucketInfo.PublicAccessPrevention.ENFORCED
+                            : BucketInfo.PublicAccessPrevention.UNSPECIFIED)
+                    .build())
+            .build());
+  }
+
+  @Test
+  public void testEnforcedPublicAccessPreventionOnBucket() throws Exception {
+    String papBucket = RemoteStorageHelper.generateBucketName();
+    try {
+      Bucket bucket = generatePublicAccessPreventionBucket(papBucket, true);
+      // Making bucket public should fail.
+      try {
+        storage.setIamPolicy(
+            papBucket,
+            Policy.newBuilder()
+                .setVersion(3)
+                .setBindings(
+                    ImmutableList.<com.google.cloud.Binding>of(
+                        com.google.cloud.Binding.newBuilder()
+                            .setRole("roles/storage.objectViewer")
+                            .addMembers("allUsers")
+                            .build()))
+                .build());
+        fail("pap: expected adding allUsers policy to bucket should fail");
+      } catch (StorageException storageException) {
+        // Creating a bucket with roles/storage.objectViewer is not
+        // allowed when publicAccessPrevention is enabled.
+        assertEquals(storageException.getCode(), 412);
+      }
+
+      // Making object public via ACL should fail.
+      try {
+        // Create a public object
+        bucket.create(
+            "pap-test-object",
+            "".getBytes(),
+            Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
+        fail("pap: expected adding allUsers ACL to object should fail");
+      } catch (StorageException storageException) {
+        // Creating an object with allUsers roles/storage.viewer permission
+        // is not allowed. When Public Access Prevention is enabled.
+        assertEquals(storageException.getCode(), 412);
+      }
+    } finally {
+      RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
+    }
+  }
+
+  @Test
+  public void testUnspecifiedPublicAccessPreventionOnBucket() throws Exception {
+    String papBucket = RemoteStorageHelper.generateBucketName();
+    try {
+      Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);
+
+      // Now, making object public or making bucket public should succeed.
+      try {
+        // Create a public object
+        bucket.create(
+            "pap-test-object",
+            "".getBytes(),
+            Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
+      } catch (StorageException storageException) {
+        fail("pap: expected adding allUsers ACL to object to succeed");
+      }
+
+      // Now, making bucket public should succeed.
+      try {
+        storage.setIamPolicy(
+            papBucket,
+            Policy.newBuilder()
+                .setVersion(3)
+                .setBindings(
+                    ImmutableList.<com.google.cloud.Binding>of(
+                        com.google.cloud.Binding.newBuilder()
+                            .setRole("roles/storage.objectViewer")
+                            .addMembers("allUsers")
+                            .build()))
+                .build());
+      } catch (StorageException storageException) {
+        fail("pap: expected adding allUsers policy to bucket to succeed");
+      }
+    } finally {
+      RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
+    }
+  }
+
+  @Test
+  public void testUBLAWithPublicAccessPreventionOnBucket() throws Exception {
+    String papBucket = RemoteStorageHelper.generateBucketName();
+    try {
+      Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);
+      assertEquals(
+          bucket.getIamConfiguration().getPublicAccessPrevention(),
+          BucketInfo.PublicAccessPrevention.UNSPECIFIED);
+      assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
+      assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
+
+      // Update PAP setting to ENFORCED and should not affect UBLA setting.
+      bucket
+          .toBuilder()
+          .setIamConfiguration(
+              bucket
+                  .getIamConfiguration()
+                  .toBuilder()
+                  .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
+                  .build())
+          .build()
+          .update();
+      bucket = storage.get(papBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION));
+      assertEquals(
+          bucket.getIamConfiguration().getPublicAccessPrevention(),
+          BucketInfo.PublicAccessPrevention.ENFORCED);
+      assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
+      assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
+
+      // Updating UBLA should not affect PAP setting.
+      bucket =
+          bucket
+              .toBuilder()
+              .setIamConfiguration(
+                  bucket
+                      .getIamConfiguration()
+                      .toBuilder()
+                      .setIsUniformBucketLevelAccessEnabled(true)
+                      .build())
+              .build()
+              .update();
+      assertTrue(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
+      assertTrue(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
+      assertEquals(
+          bucket.getIamConfiguration().getPublicAccessPrevention(),
+          BucketInfo.PublicAccessPrevention.ENFORCED);
+    } finally {
+      RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
+    }
+  }
+
   @Test
   public void testUploadUsingSignedURL() throws Exception {
     String blobName = "test-signed-url-upload";