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.internal.util; 17 18 import java.nio.ByteBuffer; 19 import java.nio.charset.StandardCharsets; 20 import software.amazon.awssdk.annotations.SdkInternalApi; 21 import software.amazon.awssdk.core.checksums.Algorithm; 22 import software.amazon.awssdk.core.exception.SdkClientException; 23 24 @SdkInternalApi 25 public final class ChunkContentUtils { 26 27 public static final String HEADER_COLON_SEPARATOR = ":"; 28 public static final String ZERO_BYTE = "0"; 29 public static final String CRLF = "\r\n"; 30 31 public static final String LAST_CHUNK = ZERO_BYTE + CRLF; 32 public static final long LAST_CHUNK_LEN = LAST_CHUNK.length(); 33 ChunkContentUtils()34 private ChunkContentUtils() { 35 } 36 37 /** 38 * The chunk format is: chunk-size CRLF chunk-data CRLF. 39 * 40 * @param originalContentLength Original Content length. 41 * @return the length of this chunk 42 */ calculateChunkLength(long originalContentLength)43 public static long calculateChunkLength(long originalContentLength) { 44 if (originalContentLength == 0) { 45 return 0; 46 } 47 return Long.toHexString(originalContentLength).length() 48 + CRLF.length() 49 + originalContentLength 50 + CRLF.length(); 51 } 52 53 /** 54 * Calculates the content length for data that is divided into chunks. 55 * 56 * @param originalLength original content length. 57 * @param chunkSize chunk size 58 * @return Content length of the trailer that will be appended at the end. 59 */ calculateStreamContentLength(long originalLength, long chunkSize)60 public static long calculateStreamContentLength(long originalLength, long chunkSize) { 61 if (originalLength < 0 || chunkSize == 0) { 62 throw new IllegalArgumentException(originalLength + ", " + chunkSize + "Args <= 0 not expected"); 63 } 64 65 long maxSizeChunks = originalLength / chunkSize; 66 long remainingBytes = originalLength % chunkSize; 67 68 long allChunks = maxSizeChunks * calculateChunkLength(chunkSize); 69 long remainingInChunk = remainingBytes > 0 ? calculateChunkLength(remainingBytes) : 0; 70 // last byte is composed of a "0" and "\r\n" 71 long lastByteSize = 1 + (long) CRLF.length(); 72 73 return allChunks + remainingInChunk + lastByteSize; 74 } 75 76 /** 77 * Calculates the content length for a given algorithm and header name. 78 * 79 * @param algorithm Algorithm used. 80 * @param headerName Header name. 81 * @return Content length of the trailer that will be appended at the end. 82 */ calculateChecksumTrailerLength(Algorithm algorithm, String headerName)83 public static long calculateChecksumTrailerLength(Algorithm algorithm, String headerName) { 84 return headerName.length() 85 + HEADER_COLON_SEPARATOR.length() 86 + algorithm.base64EncodedLength().longValue() 87 + CRLF.length() 88 + CRLF.length(); 89 } 90 91 /** 92 * Creates Chunk encoded checksum trailer for a computedChecksum which is in Base64 encoded. 93 * @param computedChecksum Base64 encoded computed checksum. 94 * @param trailerHeader Header for the checksum data in the trailer. 95 * @return Chunk encoded checksum trailer with given header. 96 */ createChecksumTrailer(String computedChecksum, String trailerHeader)97 public static ByteBuffer createChecksumTrailer(String computedChecksum, String trailerHeader) { 98 StringBuilder headerBuilder = new StringBuilder(trailerHeader) 99 .append(HEADER_COLON_SEPARATOR) 100 .append(computedChecksum) 101 .append(CRLF) 102 .append(CRLF); 103 return ByteBuffer.wrap(headerBuilder.toString().getBytes(StandardCharsets.UTF_8)); 104 } 105 106 /** 107 * Creates ChunkEncoded data for an given chunk data. 108 * @param chunkData chunk data that needs to be converted to chunk encoded format. 109 * @param isLastByte if true then additional CRLF will not be appended. 110 * @return Chunk encoded format of a given data. 111 */ createChunk(ByteBuffer chunkData, boolean isLastByte)112 public static ByteBuffer createChunk(ByteBuffer chunkData, boolean isLastByte) { 113 int chunkLength = chunkData.remaining(); 114 StringBuilder chunkHeader = new StringBuilder(Integer.toHexString(chunkLength)); 115 chunkHeader.append(CRLF); 116 try { 117 byte[] header = chunkHeader.toString().getBytes(StandardCharsets.UTF_8); 118 byte[] trailer = !isLastByte ? CRLF.getBytes(StandardCharsets.UTF_8) 119 : "".getBytes(StandardCharsets.UTF_8); 120 ByteBuffer chunkFormattedBuffer = ByteBuffer.allocate(header.length + chunkLength + trailer.length); 121 chunkFormattedBuffer.put(header).put(chunkData).put(trailer); 122 chunkFormattedBuffer.flip(); 123 return chunkFormattedBuffer; 124 } catch (Exception e) { 125 throw SdkClientException.builder() 126 .message("Unable to create chunked data. " + e.getMessage()) 127 .cause(e) 128 .build(); 129 } 130 } 131 }