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.FilterInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.util.Objects;
026
027import org.apache.commons.codec.binary.BaseNCodec.Context;
028
029/**
030 * Abstracts Base-N input streams.
031 *
032 * @param <C> A BaseNCodec subclass.
033 * @param <T> A BaseNCodecInputStream subclass.
034 * @param <B> A subclass.
035 * @see Base16InputStream
036 * @see Base32InputStream
037 * @see Base64InputStream
038 * @since 1.5
039 */
040public class BaseNCodecInputStream<C extends BaseNCodec, T extends BaseNCodecInputStream<C, T, B>, B extends BaseNCodecInputStream.AbstracBuilder<T, C, B>>
041        extends FilterInputStream {
042
043    /**
044     * Builds input stream instances in {@link BaseNCodec} format.
045     *
046     * @param <T> the input stream type to build.
047     * @param <C> A {@link BaseNCodec} subclass.
048     * @param <B> the builder subclass.
049     * @since 1.20.0
050     */
051    public abstract static class AbstracBuilder<T, C extends BaseNCodec, B extends AbstractBaseNCodecStreamBuilder<T, C, B>>
052        extends AbstractBaseNCodecStreamBuilder<T, C, B> {
053
054        private InputStream inputStream;
055
056        /**
057         * Constructs a new instance.
058         */
059        public AbstracBuilder() {
060            // super
061        }
062
063        /**
064         * Gets the input stream.
065         *
066         * @return the input stream.
067         */
068        protected InputStream getInputStream() {
069            return inputStream;
070        }
071
072        /**
073         * Sets the input stream.
074         *
075         * @param inputStream the input stream.
076         * @return {@code this} instance.
077         */
078        public B setInputStream(final InputStream inputStream) {
079            this.inputStream = inputStream;
080            return asThis();
081        }
082    }
083
084    private final C baseNCodec;
085    private final boolean doEncode;
086    private final byte[] singleByte = new byte[1];
087    private final byte[] buf;
088    private final Context context = new Context();
089
090    /**
091     * Constructs a new instance.
092     *
093     * @param builder A builder.
094     * @since 1.20.0
095     */
096    @SuppressWarnings("resource") // Caller closes.
097    protected BaseNCodecInputStream(final AbstracBuilder<T, C, B> builder) {
098        super(builder.getInputStream());
099        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}