View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.apache.commons.codec.binary.BaseNCodec.EOF;
21  
22  import java.io.FilterInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.util.Objects;
26  
27  import org.apache.commons.codec.binary.BaseNCodec.Context;
28  
29  /**
30   * Abstract superclass for Base-N input streams.
31   *
32   * @since 1.5
33   */
34  public class BaseNCodecInputStream extends FilterInputStream {
35  
36      private final BaseNCodec baseNCodec;
37  
38      private final boolean doEncode;
39  
40      private final byte[] singleByte = new byte[1];
41  
42      private final byte[] buf;
43  
44      private final Context context = new Context();
45  
46      /**
47       * Create an instance.
48       *
49       * @param inputStream the input stream
50       * @param baseNCodec the codec
51       * @param doEncode set to true to perform encoding, else decoding
52       */
53      protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) {
54          super(inputStream);
55          this.doEncode = doEncode;
56          this.baseNCodec = baseNCodec;
57          this.buf = new byte[doEncode ? 4096 : 8192];
58      }
59  
60      /**
61       * {@inheritDoc}
62       *
63       * @return {@code 0} if the {@link InputStream} has reached {@code EOF},
64       * {@code 1} otherwise
65       * @since 1.7
66       */
67      @Override
68      public int available() throws IOException {
69          // Note: the logic is similar to the InflaterInputStream:
70          //       as long as we have not reached EOF, indicate that there is more
71          //       data available. As we do not know for sure how much data is left,
72          //       just return 1 as a safe guess.
73  
74          return context.eof ? 0 : 1;
75      }
76  
77      /**
78       * Returns true if decoding behavior is strict. Decoding will raise an
79       * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
80       *
81       * <p>
82       * The default is false for lenient encoding. Decoding will compose trailing bits
83       * into 8-bit bytes and discard the remainder.
84       * </p>
85       *
86       * @return true if using strict decoding
87       * @since 1.15
88       */
89      public boolean isStrictDecoding() {
90          return baseNCodec.isStrictDecoding();
91      }
92  
93      /**
94       * Marks the current position in this input stream.
95       * <p>
96       * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
97       * </p>
98       *
99       * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
100      * @see #markSupported()
101      * @since 1.7
102      */
103     @Override
104     public synchronized void mark(final int readLimit) {
105         // noop
106     }
107 
108     /**
109      * {@inheritDoc}
110      *
111      * @return Always returns {@code false}
112      */
113     @Override
114     public boolean markSupported() {
115         return false; // not an easy job to support marks
116     }
117 
118     /**
119      * Reads one {@code byte} from this input stream.
120      *
121      * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
122      * @throws IOException
123      *             if an I/O error occurs.
124      */
125     @Override
126     public int read() throws IOException {
127         int r = read(singleByte, 0, 1);
128         while (r == 0) {
129             r = read(singleByte, 0, 1);
130         }
131         if (r > 0) {
132             final byte b = singleByte[0];
133             return b < 0 ? 256 + b : b;
134         }
135         return EOF;
136     }
137 
138     /**
139      * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset}
140      * from this InputStream.
141      *
142      * @param array
143      *            destination byte array
144      * @param offset
145      *            where to start writing the bytes
146      * @param len
147      *            maximum number of bytes to read
148      *
149      * @return number of bytes read
150      * @throws IOException
151      *             if an I/O error occurs.
152      * @throws NullPointerException
153      *             if the byte array parameter is null
154      * @throws IndexOutOfBoundsException
155      *             if offset, len or buffer size are invalid
156      */
157     @Override
158     public int read(final byte[] array, final int offset, final int len) throws IOException {
159         Objects.requireNonNull(array, "array");
160         if (offset < 0 || len < 0) {
161             throw new IndexOutOfBoundsException();
162         }
163         if (offset > array.length || offset + len > array.length) {
164             throw new IndexOutOfBoundsException();
165         }
166         if (len == 0) {
167             return 0;
168         }
169         int readLen = 0;
170         /*
171          Rationale for while-loop on (readLen == 0):
172          -----
173          Base32.readResults() usually returns > 0 or EOF (-1).  In the
174          rare case where it returns 0, we just keep trying.
175 
176          This is essentially an undocumented contract for InputStream
177          implementors that want their code to work properly with
178          java.io.InputStreamReader, since the latter hates it when
179          InputStream.read(byte[]) returns a zero.  Unfortunately our
180          readResults() call must return 0 if a large amount of the data
181          being decoded was non-base32, so this while-loop enables proper
182          interop with InputStreamReader for that scenario.
183          -----
184          This is a fix for CODEC-101
185         */
186         // Attempt to read the request length
187         while (readLen < len) {
188             if (!baseNCodec.hasData(context)) {
189                 // Obtain more data.
190                 // buf is reused across calls to read to avoid repeated allocations
191                 final int c = in.read(buf);
192                 if (doEncode) {
193                     baseNCodec.encode(buf, 0, c, context);
194                 } else {
195                     baseNCodec.decode(buf, 0, c, context);
196                 }
197             }
198             final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
199             if (read < 0) {
200                 // Return the amount read or EOF
201                 return readLen != 0 ? readLen : -1;
202             }
203             readLen += read;
204         }
205         return readLen;
206     }
207 
208     /**
209      * Repositions this stream to the position at the time the mark method was last called on this input stream.
210      * <p>
211      * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
212      *
213      * @throws IOException if this method is invoked
214      * @since 1.7
215      */
216     @Override
217     public synchronized void reset() throws IOException {
218         throw new IOException("mark/reset not supported");
219     }
220 
221     /**
222      * {@inheritDoc}
223      *
224      * @throws IllegalArgumentException if the provided skip length is negative
225      * @since 1.7
226      */
227     @Override
228     public long skip(final long n) throws IOException {
229         if (n < 0) {
230             throw new IllegalArgumentException("Negative skip length: " + n);
231         }
232 
233         // skip in chunks of 512 bytes
234         final byte[] b = new byte[512];
235         long todo = n;
236 
237         while (todo > 0) {
238             int len = (int) Math.min(b.length, todo);
239             len = this.read(b, 0, len);
240             if (len == EOF) {
241                 break;
242             }
243             todo -= len;
244         }
245 
246         return n - todo;
247     }
248 }