001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.binary;
019
020import static org.apache.commons.codec.binary.BaseNCodec.EOF;
021
022import java.io.ByteArrayInputStream;
023import java.io.FilterInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.util.Objects;
027
028import org.apache.commons.codec.binary.BaseNCodec.Context;
029
030/**
031 * Abstracts Base-N input streams.
032 *
033 * @param <C> A BaseNCodec subclass.
034 * @param <T> A BaseNCodecInputStream subclass.
035 * @param <B> A subclass.
036 * @see Base16InputStream
037 * @see Base32InputStream
038 * @see Base58InputStream
039 * @see Base64InputStream
040 * @since 1.5
041 */
042public class BaseNCodecInputStream<C extends BaseNCodec, T extends BaseNCodecInputStream<C, T, B>, B extends BaseNCodecInputStream.AbstracBuilder<T, C, B>>
043        extends FilterInputStream {
044
045    /**
046     * Builds input stream instances in {@link BaseNCodec} format.
047     *
048     * @param <T> The input stream type to build.
049     * @param <C> A {@link BaseNCodec} subclass.
050     * @param <B> The builder subclass.
051     * @since 1.20.0
052     */
053    public abstract static class AbstracBuilder<T, C extends BaseNCodec, B extends AbstractBaseNCodecStreamBuilder<T, C, B>>
054        extends AbstractBaseNCodecStreamBuilder<T, C, B> {
055
056        private InputStream inputStream;
057
058        /**
059         * Constructs a new instance.
060         */
061        public AbstracBuilder() {
062            // super
063        }
064
065        /**
066         * Gets the input stream.
067         *
068         * @return The input stream.
069         */
070        protected InputStream getInputStream() {
071            return inputStream;
072        }
073
074        /**
075         * Sets the input bytes.
076         *
077         * @param inputBytes The input bytes.
078         * @return {@code this} instance.
079         * @since 1.22.0
080         */
081        public B setByteArray(final byte[] inputBytes) {
082            return setInputStream(inputBytes == null ? null : new ByteArrayInputStream(inputBytes));
083        }
084
085        /**
086         * Sets the input stream.
087         *
088         * @param inputStream The input stream.
089         * @return {@code this} instance.
090         */
091        public B setInputStream(final InputStream inputStream) {
092            this.inputStream = inputStream;
093            return asThis();
094        }
095    }
096
097    private final C baseNCodec;
098    private final boolean doEncode;
099    private final byte[] singleByte = new byte[1];
100    private final byte[] buf;
101    private final Context context = new Context();
102
103    /**
104     * Constructs a new instance.
105     *
106     * @param builder A builder.
107     * @since 1.20.0
108     */
109    @SuppressWarnings("resource") // Caller closes.
110    protected BaseNCodecInputStream(final AbstracBuilder<T, C, B> builder) {
111        super(builder.getInputStream());
112        this.baseNCodec = builder.getBaseNCodec();
113        this.doEncode = builder.getEncode();
114        this.buf = new byte[doEncode ? 4096 : 8192];
115    }
116
117    /**
118     * Constructs a new instance.
119     *
120     * @param inputStream The input stream.
121     * @param baseNCodec  The codec.
122     * @param doEncode    set to true to perform encoding, else decoding.
123     */
124    protected BaseNCodecInputStream(final InputStream inputStream, final C baseNCodec, final boolean doEncode) {
125        super(inputStream);
126        this.doEncode = doEncode;
127        this.baseNCodec = baseNCodec;
128        this.buf = new byte[doEncode ? 4096 : 8192];
129    }
130
131    /**
132     * {@inheritDoc}
133     *
134     * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, {@code 1} otherwise.
135     * @throws IOException Thrown if an I/O error occurs.
136     * @since 1.7
137     */
138    @Override
139    public int available() throws IOException {
140        // Note: The logic is similar to the InflaterInputStream:
141        // as long as we have not reached EOF, indicate that there is more
142        // data available. As we do not know for sure how much data is left,
143        // just return 1 as a safe guess.
144        return context.eof ? 0 : 1;
145    }
146
147    /**
148     * Tests whether decoding behavior is strict.
149     *
150     * <p>
151     * Strict decoding rejects invalid trailing bits and, for Base32 and Base64, noncanonical input. Decoding errors are reported as {@link IOException}.
152     * To complete validation, consume this stream to EOF. Decoded bytes can be emitted before a later validation error.
153     * </p>
154     *
155     * @return true if using strict decoding.
156     * @since 1.15
157     */
158    public boolean isStrictDecoding() {
159        return baseNCodec.isStrictDecoding();
160    }
161
162    /**
163     * Marks the current position in this input stream.
164     * <p>
165     * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
166     * </p>
167     *
168     * @param readLimit The maximum limit of bytes that can be read before the mark position becomes invalid.
169     * @see #markSupported()
170     * @since 1.7
171     */
172    @Override
173    public synchronized void mark(final int readLimit) {
174        // noop
175    }
176
177    /**
178     * {@inheritDoc}
179     *
180     * @return Always returns {@code false}.
181     */
182    @Override
183    public boolean markSupported() {
184        return false; // not an easy job to support marks
185    }
186
187    /**
188     * Reads one {@code byte} from this input stream.
189     *
190     * @return The byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
191     * @throws IOException Thrown if an I/O error occurs.
192     */
193    @Override
194    public int read() throws IOException {
195        int r = read(singleByte, 0, 1);
196        while (r == 0) {
197            r = read(singleByte, 0, 1);
198        }
199        if (r > 0) {
200            final byte b = singleByte[0];
201            return b < 0 ? 256 + b : b;
202        }
203        return EOF;
204    }
205
206    /**
207     * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} from this InputStream.
208     *
209     * @param array  destination byte array.
210     * @param offset where to start writing the bytes.
211     * @param len    maximum number of bytes to read.
212     * @return number of bytes read.
213     * @throws IOException               Thrown if an I/O error occurs.
214     * @throws NullPointerException      Thrown if the byte array parameter is null.
215     * @throws IndexOutOfBoundsException Thrown if the offset, length, or buffer size is invalid.
216     */
217    @Override
218    public int read(final byte[] array, final int offset, final int len) throws IOException {
219        Objects.requireNonNull(array, "array");
220        if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
221            throw new IndexOutOfBoundsException();
222        }
223        if (len == 0) {
224            return 0;
225        }
226        int readLen = 0;
227        /*
228         * 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
229         * keep trying.
230         *
231         * This is essentially an undocumented contract for InputStream implementers that want their code to work properly with java.io.InputStreamReader, since
232         * 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
233         * being decoded was non-base32, so this while-loop enables proper interop with InputStreamReader for that scenario. ----- This is a fix for CODEC-101
234         */
235        // Attempt to read the request length
236        while (readLen < len) {
237            if (!baseNCodec.hasData(context)) {
238                // Obtain more data.
239                // buf is reused across calls to read to avoid repeated allocations
240                BaseNCodec.code(doEncode, baseNCodec, buf, 0, in.read(buf), context);
241            }
242            final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
243            if (read < 0) {
244                // Return the amount read or EOF
245                return readLen != 0 ? readLen : -1;
246            }
247            readLen += read;
248        }
249        return readLen;
250    }
251
252    /**
253     * Always throws {@link IOException} because this stream does not support resetting.
254     *
255     * @throws IOException Thrown if this method is invoked.
256     * @since 1.7
257     */
258    @Override
259    public synchronized void reset() throws IOException {
260        throw new IOException("mark/reset not supported");
261    }
262
263    /**
264     * {@inheritDoc}
265     *
266     * @throws IllegalArgumentException Thrown if the provided skip length is negative.
267     * @throws IOException Thrown if an I/O error occurs.
268     * @since 1.7
269     */
270    @Override
271    public long skip(final long n) throws IOException {
272        if (n < 0) {
273            throw new IllegalArgumentException("Negative skip length: " + n);
274        }
275        // skip in chunks of 512 bytes
276        final byte[] b = new byte[512];
277        long todo = n;
278        while (todo > 0) {
279            int len = (int) Math.min(b.length, todo);
280            len = this.read(b, 0, len);
281            if (len == EOF) {
282                break;
283            }
284            todo -= len;
285        }
286        return n - todo;
287    }
288}