001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.utils;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.nio.ByteBuffer;
024
025/**
026 * NIO backed bounded input stream for reading a predefined amount of data.
027 *
028 * @ThreadSafe this base class is thread safe but implementations must not be.
029 * @since 1.21
030 */
031public abstract class BoundedArchiveInputStream extends InputStream {
032
033    private final long end;
034    private ByteBuffer singleByteBuffer;
035    private long loc;
036
037    /**
038     * Constructs a new bounded input stream.
039     *
040     * @param start     position in the stream from where the reading of this bounded stream starts.
041     * @param remaining amount of bytes which are allowed to read from the bounded stream.
042     */
043    public BoundedArchiveInputStream(final long start, final long remaining) {
044        this.end = start + remaining;
045        if (this.end < start) {
046            // check for potential vulnerability due to overflow
047            throw new IllegalArgumentException("Invalid length of stream at offset=" + start + ", length=" + remaining);
048        }
049        loc = start;
050    }
051
052    @Override
053    public synchronized int read() throws IOException {
054        if (loc >= end) {
055            return -1;
056        }
057        if (singleByteBuffer == null) {
058            singleByteBuffer = ByteBuffer.allocate(1);
059        } else {
060            singleByteBuffer.rewind();
061        }
062        final int read = read(loc, singleByteBuffer);
063        if (read < 1) {
064            return -1;
065        }
066        loc++;
067        return singleByteBuffer.get() & 0xff;
068    }
069
070    @Override
071    public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
072        if (loc >= end) {
073            return -1;
074        }
075        final long maxLen = Math.min(len, end - loc);
076        if (maxLen <= 0) {
077            return 0;
078        }
079        if (off < 0 || off > b.length || maxLen > b.length - off) {
080            throw new IndexOutOfBoundsException("offset or len are out of bounds");
081        }
082
083        final ByteBuffer buf = ByteBuffer.wrap(b, off, (int) maxLen);
084        final int ret = read(loc, buf);
085        if (ret > 0) {
086            loc += ret;
087        }
088        return ret;
089    }
090
091    /**
092     * Reads content of the stream into a {@link ByteBuffer}.
093     *
094     * @param pos position to start the read.
095     * @param buf buffer to add the read content.
096     * @return number of read bytes.
097     * @throws IOException if I/O fails.
098     */
099    protected abstract int read(long pos, ByteBuffer buf) throws IOException;
100}