    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}