1818
1919import com .google .api .client .http .HttpResponse ;
2020import com .google .api .core .InternalApi ;
21+ import com .google .common .hash .Hashing ;
22+ import com .google .common .hash .HashingOutputStream ;
2123import com .google .common .io .BaseEncoding ;
2224import com .google .common .primitives .Ints ;
2325import java .io .IOException ;
2426import java .io .OutputStream ;
2527import java .nio .ByteBuffer ;
26- import java .util .List ;
2728import java .util .Map ;
2829import java .util .function .Supplier ;
2930
3031/**
3132 * Internal utility class to perform client-side CRC32C checksum validation on downloaded data
3233 * specifically for the {@code HttpStorageRpc} transport layer.
33- *
34- * <p>Since this class resides in the {@code com.google.cloud.storage} package, it has full,
35- * package-private compile-time access to internal components (like {@link Hasher} and {@link
36- * Crc32cValue}) without leaking GCS internal types into public client API surfaces.
3734 */
3835@ InternalApi
3936public final class HttpStorageRpcHasherHelper {
@@ -50,10 +47,11 @@ private HttpStorageRpcHasherHelper() {
5047 * Returns a wrapping output stream that hashes the written content if validation is enabled, or
5148 * the original output stream otherwise.
5249 */
50+ @ SuppressWarnings ("UnstableApiUsage" )
5351 public OutputStream wrap (OutputStream out , boolean isChecksumValidationEnabled ) {
5452 boolean isHasherEnabled = !(hasher instanceof Hasher .NoOpHasher );
5553 return (isChecksumValidationEnabled && isHasherEnabled )
56- ? new Crc32cHashingOutputStream ( out )
54+ ? new HashingOutputStream ( Hashing . crc32c (), out )
5755 : out ;
5856 }
5957
@@ -63,7 +61,7 @@ public OutputStream wrap(OutputStream out, boolean isChecksumValidationEnabled)
6361 * @throws IOException if the checksums do not match.
6462 */
6563 public void validate (HttpResponse response , byte [] content ) throws IOException {
66- Map <String , String > hashes = extractHashesFromHeader (response );
64+ Map <String , String > hashes = ChecksumResponseParser . extractHashesFromHeader (response );
6765 String expectedCrc32cBase64 = hashes .get ("crc32c" );
6866 if (expectedCrc32cBase64 != null ) {
6967 validateCrc32c (expectedCrc32cBase64 , content );
@@ -76,13 +74,15 @@ public void validate(HttpResponse response, byte[] content) throws IOException {
7674 *
7775 * @throws IOException if the checksums do not match.
7876 */
77+ @ SuppressWarnings ("UnstableApiUsage" )
7978 public void validate (HttpResponse response , OutputStream activeStream ) throws IOException {
80- if (activeStream instanceof Crc32cHashingOutputStream ) {
81- Crc32cHashingOutputStream targetStream = (Crc32cHashingOutputStream ) activeStream ;
82- Map <String , String > hashes = extractHashesFromHeader (response );
79+ if (activeStream instanceof HashingOutputStream ) {
80+ HashingOutputStream targetStream = (HashingOutputStream ) activeStream ;
81+
82+ Map <String , String > hashes = ChecksumResponseParser .extractHashesFromHeader (response );
8383 String expectedCrc32cBase64 = hashes .get ("crc32c" );
8484 if (expectedCrc32cBase64 != null ) {
85- validateCrc32c (expectedCrc32cBase64 , targetStream .hash ());
85+ validateCrc32c (expectedCrc32cBase64 , targetStream .hash (). asInt () );
8686 }
8787 }
8888 }
@@ -127,47 +127,4 @@ public ByteBuffer get() {
127127 }
128128 });
129129 }
130-
131- @ SuppressWarnings ("UnstableApiUsage" )
132- private static class Crc32cHashingOutputStream extends java .io .FilterOutputStream {
133- private final com .google .common .hash .Hasher hasher ;
134-
135- Crc32cHashingOutputStream (OutputStream out ) {
136- super (out );
137- this .hasher = com .google .common .hash .Hashing .crc32c ().newHasher ();
138- }
139-
140- @ Override
141- public void write (int b ) throws IOException {
142- out .write (b );
143- hasher .putByte ((byte ) b );
144- }
145-
146- @ Override
147- public void write (byte [] b , int off , int len ) throws IOException {
148- out .write (b , off , len );
149- hasher .putBytes (b , off , len );
150- }
151-
152- int hash () {
153- return hasher .hash ().asInt ();
154- }
155- }
156-
157- private static Map <String , String > extractHashesFromHeader (HttpResponse response ) {
158- List <String > hashHeaders = response .getHeaders ().getHeaderStringValues ("x-goog-hash" );
159- if (hashHeaders == null || hashHeaders .isEmpty ()) {
160- return java .util .Collections .emptyMap ();
161- }
162-
163- return hashHeaders .stream ()
164- .flatMap (h -> java .util .Arrays .stream (h .split ("," )))
165- .map (String ::trim )
166- .filter (s -> !s .isEmpty ())
167- .map (s -> s .split ("=" , 2 ))
168- .filter (a -> a .length == 2 )
169- .filter (a -> "crc32c" .equalsIgnoreCase (a [0 ]) || "md5" .equalsIgnoreCase (a [0 ]))
170- .collect (
171- java .util .stream .Collectors .toMap (a -> a [0 ].toLowerCase (), a -> a [1 ], (v1 , v2 ) -> v1 ));
172- }
173130}
0 commit comments