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 Base64InputStream
039 * @since 1.5
040 */
041public class BaseNCodecInputStream<C extends BaseNCodec, T extends BaseNCodecInputStream<C, T, B>, B extends BaseNCodecInputStream.AbstracBuilder<T, C, B>>
042        extends FilterInputStream {
043
044    /**
045     * Builds input stream instances in {@link BaseNCodec} format.
046     *
047     * @param <T> the input stream type to build.
048     * @param <C> A {@link BaseNCodec} subclass.
049     * @param <B> the builder subclass.
050     * @since 1.20.0
051     */
052    public abstract static class AbstracBuilder<T, C extends BaseNCodec, B extends AbstractBaseNCodecStreamBuilder<T, C, B>>
053        extends AbstractBaseNCodecStreamBuilder<T, C, B> {
054
055        private InputStream inputStream;
056
057        /**
058         * Constructs a new instance.
059         */
060        public AbstracBuilder() {
061            // super
062        }
063
064        /**
065         * Gets the input stream.
066         *
067         * @return the input stream.
068         */
069        protected InputStream getInputStream() {
070            return inputStream;
071        }
072
073        /**
074         * Sets the input bytes.
075         *
076         * @param inputBytes the input bytes.
077         * @return {@code this} instance.
078         * @since 1.22.0
079         */
080        public B setByteArray(final byte[] inputBytes) {
081            return setInputStream(inputBytes == null ? null : new ByteArrayInputStream(inputBytes));
082        }
083
084        /**
085         * Sets the input stream.
086         *
087         * @param inputStream the input stream.
088         * @return {@code this} instance.
089         */
090        public B setInputStream(final InputStream inputStream) {
091            this.inputStream = inputStream;
092            return asThis();
093        }
094    }
095
096    private final C baseNCodec;
097    private final boolean doEncode;
098    private final byte[] singleByte = new byte[1];
099    private final byte[] buf;
100    private final Context context = new Context();
101
102    /**
103     * Constructs a new instance.
104     *
105     * @param builder A builder.
106     * @since 1.20.0
107     */
108    @SuppressWarnings("resource") // Caller closes.
109    protected BaseNCodecInputStream(final AbstracBuilder<T, C, B> builder) {
110        super(builder.getInputStream());
111        this.baseNCodec = builder.getBaseNCodec();
112        this.doEncode = builder.getEncode();
113        this.buf = new byte[doEncode ? 4096 : 8192];
114    }
115
116    /**
117     * Constructs a new instance.
118     *
119     * @param inputStream the input stream.
120     * @param baseNCodec  the codec.
121     * @param doEncode    set to true to perform encoding, else decoding.
122     */
123    protected BaseNCodecInputStream(final InputStream inputStream, final C baseNCodec, final boolean doEncode) {
124        super(inputStream);
125        this.doEncode = doEncode;
126        this.baseNCodec = baseNCodec;
127        this.buf = new byte[doEncode ? 4096 : 8192];
128    }
129
130    /**
131     * {@inheritDoc}
132     *
133     * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, {@code 1} otherwise.
134     * @since 1.7
135     */
136    @Override
137    public int available() throws IOException {
138        // Note: The logic is similar to the InflaterInputStream:
139        // as long as we have not reached EOF, indicate that there is more
140        // data available. As we do not know for sure how much data is left,
141        // just return 1 as a safe guess.
142        return context.eof ? 0 : 1;
143    }
144
145    /**
146     * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
147     *
148     * <p>
149     * The default is false for lenient encoding. Decoding will compose trailing bits into 8-bit bytes and discard the remainder.
150     * </p>
151     *
152     * @return true if using strict decoding.
153     * @since 1.15
154     */
155    public boolean isStrictDecoding() {
156        return baseNCodec.isStrictDecoding();
157    }
158
159    /**
160     * Marks the current position in this input stream.
161     * <p>
162     * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
163     * </p>
164     *
165     * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
166     * @see #markSupported()
167     * @since 1.7
168     */
169    @Override
170    public synchronized void mark(final int readLimit) {
171        // noop
172    }
173
174    /**
175     * {@inheritDoc}
176     *
177     * @return Always returns {@code false}.
178     */
179    @Override
180    public boolean markSupported() {
181        return false; // not an easy job to support marks
182    }
183
184    /**
185     * Reads one {@code byte} from this input stream.
186     *
187     * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
188     * @throws IOException if an I/O error occurs.
189     */
190    @Override
191    public int read() throws IOException {
192        int r = read(singleByte, 0, 1);
193        while (r == 0) {
194            r = read(singleByte, 0, 1);
195        }
196        if (r > 0) {
197            final byte b = singleByte[0];
198            return b < 0 ? 256 + b : b;
199        }
200        return EOF;
201    }
202
203    /**
204     * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} from this InputStream.
205     *
206     * @param array  destination byte array.
207     * @param offset where to start writing the bytes.
208     * @param len    maximum number of bytes to read.
209     * @return number of bytes read.
210     * @throws IOException               if an I/O error occurs.
211     * @throws NullPointerException      if the byte array parameter is null.
212     * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid.
213     */
214    @Override
215    public int read(final byte[] array, final int offset, final int len) throws IOException {
216        Objects.requireNonNull(array, "array");
217        if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
218            throw new IndexOutOfBoundsException();
219        }
220        if (len == 0) {
221            return 0;
222        }
223        int readLen = 0;
224        /*
225         * 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
226         * keep trying.
227         *
228         * This is essentially an undocumented contract for InputStream implementers that want their code to work properly with java.io.InputStreamReader, since
229         * 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
230         * being decoded was non-base32, so this while-loop enables proper interop with InputStreamReader for that scenario. ----- This is a fix for CODEC-101
231         */
232        // Attempt to read the request length
233        while (readLen < len) {
234            if (!baseNCodec.hasData(context)) {
235                // Obtain more data.
236                // buf is reused across calls to read to avoid repeated allocations
237                final int c = in.read(buf);
238                if (doEncode) {
239                    baseNCodec.encode(buf, 0, c, context);
240                } else {
241                    baseNCodec.decode(buf, 0, c, context);
242                }
243            }
244            final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
245            if (read < 0) {
246                // Return the amount read or EOF
247                return readLen != 0 ? readLen : -1;
248            }
249            readLen += read;
250        }
251        return readLen;
252    }
253
254    /**
255     * Repositions this stream to the position at the time the mark method was last called on this input stream.
256     * <p>
257     * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
258     * </p>
259     *
260     * @throws IOException if this method is invoked.
261     * @since 1.7
262     */
263    @Override
264    public synchronized void reset() throws IOException {
265        throw new IOException("mark/reset not supported");
266    }
267
268    /**
269     * {@inheritDoc}
270     *
271     * @throws IllegalArgumentException if the provided skip length is negative.
272     * @since 1.7
273     */
274    @Override
275    public long skip(final long n) throws IOException {
276        if (n < 0) {
277            throw new IllegalArgumentException("Negative skip length: " + n);
278        }
279        // skip in chunks of 512 bytes
280        final byte[] b = new byte[512];
281        long todo = n;
282        while (todo > 0) {
283            int len = (int) Math.min(b.length, todo);
284            len = this.read(b, 0, len);
285            if (len == EOF) {
286                break;
287            }
288            todo -= len;
289        }
290        return n - todo;
291    }
292}