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.services.s3.internal.handlers; 17 18 import static software.amazon.awssdk.core.ClientType.SYNC; 19 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumConstant.CONTENT_LENGTH_HEADER; 20 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumsEnabledValidator.CHECKSUM; 21 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumsEnabledValidator.getObjectChecksumEnabledPerResponse; 22 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumsEnabledValidator.responseChecksumIsValid; 23 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumsEnabledValidator.shouldRecordChecksum; 24 import static software.amazon.awssdk.services.s3.internal.checksums.ChecksumsEnabledValidator.validatePutObjectChecksum; 25 import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; 26 27 import java.io.InputStream; 28 import java.util.Optional; 29 import software.amazon.awssdk.annotations.SdkInternalApi; 30 import software.amazon.awssdk.core.checksums.Md5Checksum; 31 import software.amazon.awssdk.core.checksums.SdkChecksum; 32 import software.amazon.awssdk.core.interceptor.Context; 33 import software.amazon.awssdk.core.interceptor.ExecutionAttribute; 34 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 35 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 36 import software.amazon.awssdk.core.sync.RequestBody; 37 import software.amazon.awssdk.http.ContentStreamProvider; 38 import software.amazon.awssdk.services.s3.internal.checksums.ChecksumCalculatingInputStream; 39 import software.amazon.awssdk.services.s3.internal.checksums.S3ChecksumValidatingInputStream; 40 import software.amazon.awssdk.services.s3.model.PutObjectResponse; 41 42 @SdkInternalApi 43 public final class SyncChecksumValidationInterceptor implements ExecutionInterceptor { 44 private static ExecutionAttribute<Boolean> SYNC_RECORDING_CHECKSUM = new ExecutionAttribute<>("syncRecordingChecksum"); 45 46 @Override modifyHttpContent(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes)47 public Optional<RequestBody> modifyHttpContent(Context.ModifyHttpRequest context, 48 ExecutionAttributes executionAttributes) { 49 if (shouldRecordChecksum(context.request(), SYNC, executionAttributes, context.httpRequest()) 50 && context.requestBody().isPresent()) { 51 SdkChecksum checksum = new Md5Checksum(); 52 executionAttributes.putAttribute(CHECKSUM, checksum); 53 executionAttributes.putAttribute(SYNC_RECORDING_CHECKSUM, true); 54 55 RequestBody requestBody = context.requestBody().get(); 56 57 ChecksumCalculatingStreamProvider streamProvider = 58 new ChecksumCalculatingStreamProvider(requestBody.contentStreamProvider(), checksum); 59 60 return Optional.of(RequestBody.fromContentProvider(streamProvider, 61 requestBody.contentLength(), 62 requestBody.contentType())); 63 } 64 65 return context.requestBody(); 66 } 67 68 @Override modifyHttpResponseContent(Context.ModifyHttpResponse context, ExecutionAttributes executionAttributes)69 public Optional<InputStream> modifyHttpResponseContent(Context.ModifyHttpResponse context, 70 ExecutionAttributes executionAttributes) { 71 if (getObjectChecksumEnabledPerResponse(context.request(), context.httpResponse()) 72 && context.responseBody().isPresent()) { 73 74 SdkChecksum checksum = new Md5Checksum(); 75 76 long contentLength = context.httpResponse() 77 .firstMatchingHeader(CONTENT_LENGTH_HEADER) 78 .map(Long::parseLong) 79 .orElse(0L); 80 81 if (contentLength > 0) { 82 return Optional.of(new S3ChecksumValidatingInputStream(context.responseBody().get(), checksum, contentLength)); 83 } 84 } 85 86 return context.responseBody(); 87 } 88 89 @Override afterUnmarshalling(Context.AfterUnmarshalling context, ExecutionAttributes executionAttributes)90 public void afterUnmarshalling(Context.AfterUnmarshalling context, ExecutionAttributes executionAttributes) { 91 boolean recordingChecksum = Boolean.TRUE.equals(executionAttributes.getAttribute(SYNC_RECORDING_CHECKSUM)); 92 boolean responseChecksumIsValid = responseChecksumIsValid(context.httpResponse()); 93 94 if (recordingChecksum && responseChecksumIsValid) { 95 validatePutObjectChecksum((PutObjectResponse) context.response(), executionAttributes); 96 } 97 } 98 99 static final class ChecksumCalculatingStreamProvider implements ContentStreamProvider { 100 private final SdkChecksum checksum; 101 private InputStream currentStream; 102 private final ContentStreamProvider underlyingInputStreamProvider; 103 ChecksumCalculatingStreamProvider(ContentStreamProvider underlyingInputStreamProvider, SdkChecksum checksum)104 ChecksumCalculatingStreamProvider(ContentStreamProvider underlyingInputStreamProvider, SdkChecksum checksum) { 105 this.underlyingInputStreamProvider = underlyingInputStreamProvider; 106 this.checksum = checksum; 107 } 108 109 @Override newStream()110 public InputStream newStream() { 111 closeCurrentStream(); 112 currentStream = invokeSafely(() -> new ChecksumCalculatingInputStream(underlyingInputStreamProvider.newStream(), 113 checksum)); 114 return currentStream; 115 } 116 closeCurrentStream()117 private void closeCurrentStream() { 118 checksum.reset(); 119 if (currentStream != null) { 120 invokeSafely(currentStream::close); 121 currentStream = null; 122 } 123 } 124 } 125 126 } 127