/* * Copyright 2010-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.services.s3.transfer.internal; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.Callable; import com.amazonaws.SdkClientException; import com.amazonaws.internal.SdkThreadLocalsRegistry; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.PartETag; import com.amazonaws.services.s3.model.UploadPartRequest; import com.amazonaws.util.BinaryUtils; public class UploadPartCallable implements Callable { private static final int MAX_SKIPS = 100; private static final ThreadLocal MD5_DIGEST; static { MD5_DIGEST = SdkThreadLocalsRegistry.register(new ThreadLocal() { @Override protected MessageDigest initialValue() { try { return MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new SdkClientException("Unable to get a digest instance for MD5!", e); } } }); } private final AmazonS3 s3; private final UploadPartRequest request; private final boolean calculateMd5; public UploadPartCallable(AmazonS3 s3, UploadPartRequest request) { this(s3, request, false); } public UploadPartCallable(AmazonS3 s3, UploadPartRequest request, boolean calculateMd5) { this.s3 = s3; this.request = request; this.calculateMd5 = calculateMd5; } public PartETag call() throws Exception { if (calculateMd5) { request.withMD5Digest(computedMd5()); } PartETag partETag = s3.uploadPart(request).getPartETag(); return partETag; } private String computedMd5() { FileInputStream fileStream = null; try { fileStream = new FileInputStream(request.getFile()); skipBytes(fileStream, request.getFileOffset()); return BinaryUtils.toBase64(computeMd5Bytes(fileStream, request.getPartSize())); } catch (IOException e) { throw new SdkClientException(e); } finally { if (fileStream != null) { try { fileStream.close(); } catch (IOException ignored) { } } } } private static byte[] computeMd5Bytes(InputStream is, long remaining) throws IOException { byte readBuff[] = new byte[4096]; MessageDigest digest = MD5_DIGEST.get(); digest.reset(); int read; while (remaining > 0 && (read = is.read(readBuff)) != -1) { int updateLen = (int) Math.min(remaining, read); digest.update(readBuff, 0, updateLen); remaining -= updateLen; } return digest.digest(); } private void skipBytes(FileInputStream fs, long n) throws IOException { long skippedSoFar = 0; for (int skips = 0; skips < MAX_SKIPS && skippedSoFar < n; ++skips) { skippedSoFar += fs.skip(n - skippedSoFar); } if (skippedSoFar != n) { throw new SdkClientException(String.format("Unable to skip to offset %d in file %s after %d attempts", n, request.getFile().getAbsolutePath(), MAX_SKIPS)); } } }