BoundedArchiveInputStream.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *   https://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.compress.utils;

  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.nio.ByteBuffer;

  23. /**
  24.  * NIO backed bounded input stream for reading a predefined amount of data.
  25.  *
  26.  * @ThreadSafe this base class is thread safe but implementations must not be.
  27.  * @since 1.21
  28.  */
  29. public abstract class BoundedArchiveInputStream extends InputStream {

  30.     private final long end;
  31.     private ByteBuffer singleByteBuffer;
  32.     private long loc;

  33.     /**
  34.      * Constructs a new bounded input stream.
  35.      *
  36.      * @param start     position in the stream from where the reading of this bounded stream starts.
  37.      * @param remaining amount of bytes which are allowed to read from the bounded stream.
  38.      */
  39.     public BoundedArchiveInputStream(final long start, final long remaining) {
  40.         this.end = start + remaining;
  41.         if (this.end < start) {
  42.             // check for potential vulnerability due to overflow
  43.             throw new IllegalArgumentException("Invalid length of stream at offset=" + start + ", length=" + remaining);
  44.         }
  45.         loc = start;
  46.     }

  47.     @Override
  48.     public synchronized int read() throws IOException {
  49.         if (loc >= end) {
  50.             return -1;
  51.         }
  52.         if (singleByteBuffer == null) {
  53.             singleByteBuffer = ByteBuffer.allocate(1);
  54.         } else {
  55.             singleByteBuffer.rewind();
  56.         }
  57.         final int read = read(loc, singleByteBuffer);
  58.         if (read < 1) {
  59.             return -1;
  60.         }
  61.         loc++;
  62.         return singleByteBuffer.get() & 0xff;
  63.     }

  64.     @Override
  65.     public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
  66.         if (loc >= end) {
  67.             return -1;
  68.         }
  69.         final long maxLen = Math.min(len, end - loc);
  70.         if (maxLen <= 0) {
  71.             return 0;
  72.         }
  73.         if (off < 0 || off > b.length || maxLen > b.length - off) {
  74.             throw new IndexOutOfBoundsException("offset or len are out of bounds");
  75.         }

  76.         final ByteBuffer buf = ByteBuffer.wrap(b, off, (int) maxLen);
  77.         final int ret = read(loc, buf);
  78.         if (ret > 0) {
  79.             loc += ret;
  80.         }
  81.         return ret;
  82.     }

  83.     /**
  84.      * Reads content of the stream into a {@link ByteBuffer}.
  85.      *
  86.      * @param pos position to start the read.
  87.      * @param buf buffer to add the read content.
  88.      * @return number of read bytes.
  89.      * @throws IOException if I/O fails.
  90.      */
  91.     protected abstract int read(long pos, ByteBuffer buf) throws IOException;
  92. }