/* * Copyright 2010-2016 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.internal.crypto; import com.amazonaws.internal.SdkInputStream; import java.io.IOException; import java.io.InputStream; /** * Reads only a specific range of bytes from the underlying input stream. */ public class AdjustedRangeInputStream extends SdkInputStream { private InputStream decryptedContents; private long virtualAvailable; private boolean closed; /** * Creates a new DecryptedContentsInputStream object. * * @param objectContents The input stream containing the object contents * retrieved from S3 * @param rangeBeginning The position of the left-most byte desired by the * user * @param rangeEnd The position of the right-most byte desired by the user * @throws IOException If there are errors skipping to the left-most byte * desired by the user. */ public AdjustedRangeInputStream(InputStream objectContents, long rangeBeginning, long rangeEnd) throws IOException { this.decryptedContents = objectContents; this.closed = false; initializeForRead(rangeBeginning, rangeEnd); } /** * Skip to the start location of the range of bytes desired by the user. */ private void initializeForRead(long rangeBeginning, long rangeEnd) throws IOException { // To get to the left-most byte desired by a user, we must skip over the // 16 bytes of the // preliminary cipher block, and then possibly skip a few more bytes // into the next block // to where the the left-most byte is located. int numBytesToSkip; if (rangeBeginning < JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE) { numBytesToSkip = (int) rangeBeginning; } else { int offsetIntoBlock = (int) (rangeBeginning % JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE); numBytesToSkip = JceEncryptionConstants.SYMMETRIC_CIPHER_BLOCK_SIZE + offsetIntoBlock; } if (numBytesToSkip != 0) { // Skip to the left-most desired byte. The read() method is used // instead of the skip() method // since the skip() method will not block if the underlying input // stream is waiting for more input. while (numBytesToSkip > 0) { this.decryptedContents.read(); numBytesToSkip--; } } // The number of bytes the user may read is equal to the number of the // bytes in the range. // Note that the range includes the endpoints. this.virtualAvailable = (rangeEnd - rangeBeginning) + 1; } /* * (non-Javadoc) * @see java.io.InputStream#read() */ @Override public int read() throws IOException { abortIfNeeded(); int result = 0; // If there are no more available bytes, mark that we are at the end of // the stream. if (this.virtualAvailable <= 0) { result = -1; } else { // Otherwise, read a byte. result = this.decryptedContents.read(); } // If we have not reached the end of the stream, decrement the number of // available bytes. if (result != -1) { this.virtualAvailable--; } else { // If we are at the end of the stream, close it. close(); this.virtualAvailable = 0; } return result; } /* * (non-Javadoc) * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] buffer, int offset, int length) throws IOException { abortIfNeeded(); int numBytesRead; // If no more bytes are available, do not read any bytes into the buffer if (this.virtualAvailable <= 0) { numBytesRead = -1; } else { // If the desired read length is greater than the number of // available bytes, // shorten the read length to the number of available bytes. if (length > this.virtualAvailable) { // If the number of available bytes is greater than the maximum // value of a 32 bit int, then // read as many bytes as an int can. length = (this.virtualAvailable < Integer.MAX_VALUE) ? (int) this.virtualAvailable : Integer.MAX_VALUE; } // Read bytes into the buffer. numBytesRead = this.decryptedContents.read(buffer, offset, length); } // If we were able to read bytes, decrement the number of bytes // available to be read. if (numBytesRead != -1) { this.virtualAvailable -= numBytesRead; } else { // If we've reached the end of the stream, close it close(); this.virtualAvailable = 0; } return numBytesRead; } /* * (non-Javadoc) * @see java.io.InputStream#available() */ @Override public int available() throws IOException { abortIfNeeded(); int available = this.decryptedContents.available(); if (available < this.virtualAvailable) { return available; } else { // Limit the number of bytes available to the number // of bytes remaining in the range. return (int) this.virtualAvailable; } } /* * (non-Javadoc) * @see java.io.InputStream#close() */ @Override public void close() throws IOException { // If not already closed, then close the input stream. if (!this.closed) { this.closed = true; this.decryptedContents.close(); } abortIfNeeded(); } @Override protected InputStream getWrappedInputStream() { return decryptedContents; } }