    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}