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    *      https://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   * Abstracts Base-N input streams.
31   *
32   * @param <C> A BaseNCodec subclass.
33   * @param <T> A BaseNCodecInputStream subclass.
34   * @param <B> A subclass.
35   * @see Base16InputStream
36   * @see Base32InputStream
37   * @see Base64InputStream
38   * @since 1.5
39   */
40  public class BaseNCodecInputStream<C extends BaseNCodec, T extends BaseNCodecInputStream<C, T, B>, B extends BaseNCodecInputStream.AbstracBuilder<T, C, B>>
41          extends FilterInputStream {
42  
43      /**
44       * Builds input stream instances in {@link BaseNCodec} format.
45       *
46       * @param <T> the input stream type to build.
47       * @param <C> A {@link BaseNCodec} subclass.
48       * @param <B> the builder subclass.
49       * @since 1.20.0
50       */
51      public abstract static class AbstracBuilder<T, C extends BaseNCodec, B extends AbstractBaseNCodecStreamBuilder<T, C, B>>
52          extends AbstractBaseNCodecStreamBuilder<T, C, B> {
53  
54          private InputStream inputStream;
55  
56          /**
57           * Constructs a new instance.
58           */
59          public AbstracBuilder() {
60              // super
61          }
62  
63          /**
64           * Gets the input stream.
65           *
66           * @return the input stream.
67           */
68          protected InputStream getInputStream() {
69              return inputStream;
70          }
71  
72          /**
73           * Sets the input stream.
74           *
75           * @param inputStream the input stream.
76           * @return {@code this} instance.
77           */
78          public B setInputStream(final InputStream inputStream) {
79              this.inputStream = inputStream;
80              return asThis();
81          }
82      }
83  
84      private final C baseNCodec;
85      private final boolean doEncode;
86      private final byte[] singleByte = new byte[1];
87      private final byte[] buf;
88      private final Context context = new Context();
89  
90      /**
91       * Constructs a new instance.
92       *
93       * @param builder A builder.
94       * @since 1.20.0
95       */
96      @SuppressWarnings("resource") // Caller closes.
97      protected BaseNCodecInputStream(final AbstracBuilder<T, C, B> builder) {
98          super(builder.getInputStream());
99          this.baseNCodec = builder.getBaseNCodec();
100         this.doEncode = builder.getEncode();
101         this.buf = new byte[doEncode ? 4096 : 8192];
102     }
103 
104     /**
105      * Constructs a new instance.
106      *
107      * @param inputStream the input stream.
108      * @param baseNCodec  the codec.
109      * @param doEncode    set to true to perform encoding, else decoding.
110      */
111     protected BaseNCodecInputStream(final InputStream inputStream, final C baseNCodec, final boolean doEncode) {
112         super(inputStream);
113         this.doEncode = doEncode;
114         this.baseNCodec = baseNCodec;
115         this.buf = new byte[doEncode ? 4096 : 8192];
116     }
117 
118     /**
119      * {@inheritDoc}
120      *
121      * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, {@code 1} otherwise.
122      * @since 1.7
123      */
124     @Override
125     public int available() throws IOException {
126         // Note: The logic is similar to the InflaterInputStream:
127         // as long as we have not reached EOF, indicate that there is more
128         // data available. As we do not know for sure how much data is left,
129         // just return 1 as a safe guess.
130         return context.eof ? 0 : 1;
131     }
132 
133     /**
134      * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
135      *
136      * <p>
137      * The default is false for lenient encoding. Decoding will compose trailing bits into 8-bit bytes and discard the remainder.
138      * </p>
139      *
140      * @return true if using strict decoding.
141      * @since 1.15
142      */
143     public boolean isStrictDecoding() {
144         return baseNCodec.isStrictDecoding();
145     }
146 
147     /**
148      * Marks the current position in this input stream.
149      * <p>
150      * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
151      * </p>
152      *
153      * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
154      * @see #markSupported()
155      * @since 1.7
156      */
157     @Override
158     public synchronized void mark(final int readLimit) {
159         // noop
160     }
161 
162     /**
163      * {@inheritDoc}
164      *
165      * @return Always returns {@code false}
166      */
167     @Override
168     public boolean markSupported() {
169         return false; // not an easy job to support marks
170     }
171 
172     /**
173      * Reads one {@code byte} from this input stream.
174      *
175      * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
176      * @throws IOException if an I/O error occurs.
177      */
178     @Override
179     public int read() throws IOException {
180         int r = read(singleByte, 0, 1);
181         while (r == 0) {
182             r = read(singleByte, 0, 1);
183         }
184         if (r > 0) {
185             final byte b = singleByte[0];
186             return b < 0 ? 256 + b : b;
187         }
188         return EOF;
189     }
190 
191     /**
192      * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} from this InputStream.
193      *
194      * @param array  destination byte array.
195      * @param offset where to start writing the bytes.
196      * @param len    maximum number of bytes to read.
197      * @return number of bytes read.
198      * @throws IOException               if an I/O error occurs.
199      * @throws NullPointerException      if the byte array parameter is null.
200      * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid.
201      */
202     @Override
203     public int read(final byte[] array, final int offset, final int len) throws IOException {
204         Objects.requireNonNull(array, "array");
205         if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
206             throw new IndexOutOfBoundsException();
207         }
208         if (len == 0) {
209             return 0;
210         }
211         int readLen = 0;
212         /*
213          * Rationale for while-loop on (readLen == 0): ----- Base32.readResults() usually returns > 0 or EOF (-1). In the rare case where it returns 0, we just
214          * keep trying.
215          *
216          * This is essentially an undocumented contract for InputStream implementors that want their code to work properly with java.io.InputStreamReader, since
217          * the latter hates it when InputStream.read(byte[]) returns a zero. Unfortunately our readResults() call must return 0 if a large amount of the data
218          * being decoded was non-base32, so this while-loop enables proper interop with InputStreamReader for that scenario. ----- This is a fix for CODEC-101
219          */
220         // Attempt to read the request length
221         while (readLen < len) {
222             if (!baseNCodec.hasData(context)) {
223                 // Obtain more data.
224                 // buf is reused across calls to read to avoid repeated allocations
225                 final int c = in.read(buf);
226                 if (doEncode) {
227                     baseNCodec.encode(buf, 0, c, context);
228                 } else {
229                     baseNCodec.decode(buf, 0, c, context);
230                 }
231             }
232             final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
233             if (read < 0) {
234                 // Return the amount read or EOF
235                 return readLen != 0 ? readLen : -1;
236             }
237             readLen += read;
238         }
239         return readLen;
240     }
241 
242     /**
243      * Repositions this stream to the position at the time the mark method was last called on this input stream.
244      * <p>
245      * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
246      * </p>
247      *
248      * @throws IOException if this method is invoked.
249      * @since 1.7
250      */
251     @Override
252     public synchronized void reset() throws IOException {
253         throw new IOException("mark/reset not supported");
254     }
255 
256     /**
257      * {@inheritDoc}
258      *
259      * @throws IllegalArgumentException if the provided skip length is negative.
260      * @since 1.7
261      */
262     @Override
263     public long skip(final long n) throws IOException {
264         if (n < 0) {
265             throw new IllegalArgumentException("Negative skip length: " + n);
266         }
267         // skip in chunks of 512 bytes
268         final byte[] b = new byte[512];
269         long todo = n;
270         while (todo > 0) {
271             int len = (int) Math.min(b.length, todo);
272             len = this.read(b, 0, len);
273             if (len == EOF) {
274                 break;
275             }
276             todo -= len;
277         }
278         return n - todo;
279     }
280 }