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
33     private Crc32Validation() {
34     }
35
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
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
69     private static AbortableInputStream crc32Validating(AbortableInputStream source, long expectedChecksum) {
70         return AbortableInputStream.create(new Crc32ChecksumValidatingInputStream(source, expectedChecksum), source);
71     }
72
73     private static Optional<Long> getCrc32Checksum(SdkHttpFullResponse httpResponse) {
74         return httpResponse.firstMatchingHeader("x-amz-crc32")
75                            .map(Long::valueOf);
76     }
77
78     private static boolean shouldDecompress(SdkHttpFullResponse httpResponse) {
79         return httpResponse.firstMatchingHeader("Content-Encoding")
80                            .filter(e -> e.equals("gzip"))
81                            .isPresent();
82     }
83
84     private static AbortableInputStream decompressing(AbortableInputStream source) {
85         return AbortableInputStream.create(invokeSafely(() -> new GZIPInputStream(source)), source);
86     }
87 }
88