BaseNCodecInputStream.java

  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. package org.apache.commons.codec.binary;

  18. import static org.apache.commons.codec.binary.BaseNCodec.EOF;

  19. import java.io.FilterInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.util.Objects;

  23. import org.apache.commons.codec.binary.BaseNCodec.Context;

  24. /**
  25.  * Abstract superclass for Base-N input streams.
  26.  *
  27.  * @since 1.5
  28.  */
  29. public class BaseNCodecInputStream extends FilterInputStream {

  30.     private final BaseNCodec baseNCodec;

  31.     private final boolean doEncode;

  32.     private final byte[] singleByte = new byte[1];

  33.     private final byte[] buf;

  34.     private final Context context = new Context();

  35.     /**
  36.      * Constructs a new instance.
  37.      *
  38.      * @param inputStream the input stream
  39.      * @param baseNCodec the codec
  40.      * @param doEncode set to true to perform encoding, else decoding
  41.      */
  42.     protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) {
  43.         super(inputStream);
  44.         this.doEncode = doEncode;
  45.         this.baseNCodec = baseNCodec;
  46.         this.buf = new byte[doEncode ? 4096 : 8192];
  47.     }

  48.     /**
  49.      * {@inheritDoc}
  50.      *
  51.      * @return {@code 0} if the {@link InputStream} has reached {@code EOF},
  52.      * {@code 1} otherwise
  53.      * @since 1.7
  54.      */
  55.     @Override
  56.     public int available() throws IOException {
  57.         // Note: The logic is similar to the InflaterInputStream:
  58.         //       as long as we have not reached EOF, indicate that there is more
  59.         //       data available. As we do not know for sure how much data is left,
  60.         //       just return 1 as a safe guess.
  61.         return context.eof ? 0 : 1;
  62.     }

  63.     /**
  64.      * Returns true if decoding behavior is strict. Decoding will raise an
  65.      * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding.
  66.      *
  67.      * <p>
  68.      * The default is false for lenient encoding. Decoding will compose trailing bits
  69.      * into 8-bit bytes and discard the remainder.
  70.      * </p>
  71.      *
  72.      * @return true if using strict decoding
  73.      * @since 1.15
  74.      */
  75.     public boolean isStrictDecoding() {
  76.         return baseNCodec.isStrictDecoding();
  77.     }

  78.     /**
  79.      * Marks the current position in this input stream.
  80.      * <p>
  81.      * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing.
  82.      * </p>
  83.      *
  84.      * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
  85.      * @see #markSupported()
  86.      * @since 1.7
  87.      */
  88.     @Override
  89.     public synchronized void mark(final int readLimit) {
  90.         // noop
  91.     }

  92.     /**
  93.      * {@inheritDoc}
  94.      *
  95.      * @return Always returns {@code false}
  96.      */
  97.     @Override
  98.     public boolean markSupported() {
  99.         return false; // not an easy job to support marks
  100.     }

  101.     /**
  102.      * Reads one {@code byte} from this input stream.
  103.      *
  104.      * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached.
  105.      * @throws IOException
  106.      *             if an I/O error occurs.
  107.      */
  108.     @Override
  109.     public int read() throws IOException {
  110.         int r = read(singleByte, 0, 1);
  111.         while (r == 0) {
  112.             r = read(singleByte, 0, 1);
  113.         }
  114.         if (r > 0) {
  115.             final byte b = singleByte[0];
  116.             return b < 0 ? 256 + b : b;
  117.         }
  118.         return EOF;
  119.     }

  120.     /**
  121.      * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset}
  122.      * from this InputStream.
  123.      *
  124.      * @param array
  125.      *            destination byte array
  126.      * @param offset
  127.      *            where to start writing the bytes
  128.      * @param len
  129.      *            maximum number of bytes to read
  130.      *
  131.      * @return number of bytes read
  132.      * @throws IOException
  133.      *             if an I/O error occurs.
  134.      * @throws NullPointerException
  135.      *             if the byte array parameter is null
  136.      * @throws IndexOutOfBoundsException
  137.      *             if offset, len or buffer size are invalid
  138.      */
  139.     @Override
  140.     public int read(final byte[] array, final int offset, final int len) throws IOException {
  141.         Objects.requireNonNull(array, "array");
  142.         if (offset < 0 || len < 0 || offset > array.length || offset + len > array.length) {
  143.             throw new IndexOutOfBoundsException();
  144.         }
  145.         if (len == 0) {
  146.             return 0;
  147.         }
  148.         int readLen = 0;
  149.         /*
  150.          Rationale for while-loop on (readLen == 0):
  151.          -----
  152.          Base32.readResults() usually returns > 0 or EOF (-1).  In the
  153.          rare case where it returns 0, we just keep trying.

  154.          This is essentially an undocumented contract for InputStream
  155.          implementors that want their code to work properly with
  156.          java.io.InputStreamReader, since the latter hates it when
  157.          InputStream.read(byte[]) returns a zero.  Unfortunately our
  158.          readResults() call must return 0 if a large amount of the data
  159.          being decoded was non-base32, so this while-loop enables proper
  160.          interop with InputStreamReader for that scenario.
  161.          -----
  162.          This is a fix for CODEC-101
  163.         */
  164.         // Attempt to read the request length
  165.         while (readLen < len) {
  166.             if (!baseNCodec.hasData(context)) {
  167.                 // Obtain more data.
  168.                 // buf is reused across calls to read to avoid repeated allocations
  169.                 final int c = in.read(buf);
  170.                 if (doEncode) {
  171.                     baseNCodec.encode(buf, 0, c, context);
  172.                 } else {
  173.                     baseNCodec.decode(buf, 0, c, context);
  174.                 }
  175.             }
  176.             final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context);
  177.             if (read < 0) {
  178.                 // Return the amount read or EOF
  179.                 return readLen != 0 ? readLen : -1;
  180.             }
  181.             readLen += read;
  182.         }
  183.         return readLen;
  184.     }

  185.     /**
  186.      * Repositions this stream to the position at the time the mark method was last called on this input stream.
  187.      * <p>
  188.      * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}.
  189.      * </p>
  190.      *
  191.      * @throws IOException if this method is invoked
  192.      * @since 1.7
  193.      */
  194.     @Override
  195.     public synchronized void reset() throws IOException {
  196.         throw new IOException("mark/reset not supported");
  197.     }

  198.     /**
  199.      * {@inheritDoc}
  200.      *
  201.      * @throws IllegalArgumentException if the provided skip length is negative
  202.      * @since 1.7
  203.      */
  204.     @Override
  205.     public long skip(final long n) throws IOException {
  206.         if (n < 0) {
  207.             throw new IllegalArgumentException("Negative skip length: " + n);
  208.         }
  209.         // skip in chunks of 512 bytes
  210.         final byte[] b = new byte[512];
  211.         long todo = n;
  212.         while (todo > 0) {
  213.             int len = (int) Math.min(b.length, todo);
  214.             len = this.read(b, 0, len);
  215.             if (len == EOF) {
  216.                 break;
  217.             }
  218.             todo -= len;
  219.         }
  220.         return n - todo;
  221.     }
  222. }