View Javadoc
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  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.nio.ByteBuffer;
24  
25  /**
26   * NIO backed bounded input stream for reading a predefined amount of data.
27   *
28   * @ThreadSafe this base class is thread safe but implementations must not be.
29   * @since 1.21
30   */
31  public abstract class BoundedArchiveInputStream extends InputStream {
32  
33      private final long end;
34      private ByteBuffer singleByteBuffer;
35      private long loc;
36  
37      /**
38       * Constructs a new bounded input stream.
39       *
40       * @param start     position in the stream from where the reading of this bounded stream starts.
41       * @param remaining amount of bytes which are allowed to read from the bounded stream.
42       */
43      public BoundedArchiveInputStream(final long start, final long remaining) {
44          this.end = start + remaining;
45          if (this.end < start) {
46              // check for potential vulnerability due to overflow
47              throw new IllegalArgumentException("Invalid length of stream at offset=" + start + ", length=" + remaining);
48          }
49          loc = start;
50      }
51  
52      @Override
53      public synchronized int read() throws IOException {
54          if (loc >= end) {
55              return -1;
56          }
57          if (singleByteBuffer == null) {
58              singleByteBuffer = ByteBuffer.allocate(1);
59          } else {
60              singleByteBuffer.rewind();
61          }
62          final int read = read(loc, singleByteBuffer);
63          if (read < 1) {
64              return -1;
65          }
66          loc++;
67          return singleByteBuffer.get() & 0xff;
68      }
69  
70      @Override
71      public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
72          if (loc >= end) {
73              return -1;
74          }
75          final long maxLen = Math.min(len, end - loc);
76          if (maxLen <= 0) {
77              return 0;
78          }
79          if (off < 0 || off > b.length || maxLen > b.length - off) {
80              throw new IndexOutOfBoundsException("offset or len are out of bounds");
81          }
82  
83          final ByteBuffer buf = ByteBuffer.wrap(b, off, (int) maxLen);
84          final int ret = read(loc, buf);
85          if (ret > 0) {
86              loc += ret;
87          }
88          return ret;
89      }
90  
91      /**
92       * Reads content of the stream into a {@link ByteBuffer}.
93       *
94       * @param pos position to start the read.
95       * @param buf buffer to add the read content.
96       * @return number of read bytes.
97       * @throws IOException if I/O fails.
98       */
99      protected abstract int read(long pos, ByteBuffer buf) throws IOException;
100 }