1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.core.http;
17 
18 import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
19 
20 import java.util.Optional;
21 import java.util.zip.GZIPInputStream;
22 import software.amazon.awssdk.annotations.SdkProtectedApi;
23 import software.amazon.awssdk.core.internal.util.Crc32ChecksumValidatingInputStream;
24 import software.amazon.awssdk.http.AbortableInputStream;
25 import software.amazon.awssdk.http.SdkHttpFullResponse;
26 
27 /**
28  * Validate and decompress input data if necessary.
29  */
30 @SdkProtectedApi
31 public final class Crc32Validation {
32 
Crc32Validation()33     private Crc32Validation() {
34     }
35 
validate(boolean calculateCrc32FromCompressedData, SdkHttpFullResponse httpResponse)36     public static SdkHttpFullResponse validate(boolean calculateCrc32FromCompressedData,
37                                                SdkHttpFullResponse httpResponse) {
38 
39         if (!httpResponse.content().isPresent()) {
40             return httpResponse;
41         }
42 
43         return httpResponse.toBuilder().content(
44             process(calculateCrc32FromCompressedData, httpResponse,
45                     httpResponse.content().get())).build();
46     }
47 
process(boolean calculateCrc32FromCompressedData, SdkHttpFullResponse httpResponse, AbortableInputStream content)48     private static AbortableInputStream process(boolean calculateCrc32FromCompressedData,
49                                                 SdkHttpFullResponse httpResponse,
50                                                 AbortableInputStream content) {
51         Optional<Long> crc32Checksum = getCrc32Checksum(httpResponse);
52 
53         if (shouldDecompress(httpResponse)) {
54             if (calculateCrc32FromCompressedData && crc32Checksum.isPresent()) {
55                 return decompressing(crc32Validating(content, crc32Checksum.get()));
56             }
57 
58             if (crc32Checksum.isPresent()) {
59                 return crc32Validating(decompressing(content), crc32Checksum.get());
60             }
61 
62             return decompressing(content);
63 
64         }
65 
66         return crc32Checksum.map(aLong -> crc32Validating(content, aLong)).orElse(content);
67     }
68 
crc32Validating(AbortableInputStream source, long expectedChecksum)69     private static AbortableInputStream crc32Validating(AbortableInputStream source, long expectedChecksum) {
70         return AbortableInputStream.create(new Crc32ChecksumValidatingInputStream(source, expectedChecksum), source);
71     }
72 
getCrc32Checksum(SdkHttpFullResponse httpResponse)73     private static Optional<Long> getCrc32Checksum(SdkHttpFullResponse httpResponse) {
74         return httpResponse.firstMatchingHeader("x-amz-crc32")
75                            .map(Long::valueOf);
76     }
77 
shouldDecompress(SdkHttpFullResponse httpResponse)78     private static boolean shouldDecompress(SdkHttpFullResponse httpResponse) {
79         return httpResponse.firstMatchingHeader("Content-Encoding")
80                            .filter(e -> e.equals("gzip"))
81                            .isPresent();
82     }
83 
decompressing(AbortableInputStream source)84     private static AbortableInputStream decompressing(AbortableInputStream source) {
85         return AbortableInputStream.create(invokeSafely(() -> new GZIPInputStream(source)), source);
86     }
87 }
88