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 * http://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 18 package org.apache.commons.codec.binary; 19 20 import static org.apache.commons.codec.binary.BaseNCodec.EOF; 21 22 import java.io.FilterInputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.util.Objects; 26 27 import org.apache.commons.codec.binary.BaseNCodec.Context; 28 29 /** 30 * Abstract superclass for Base-N input streams. 31 * 32 * @since 1.5 33 */ 34 public class BaseNCodecInputStream extends FilterInputStream { 35 36 private final BaseNCodec baseNCodec; 37 38 private final boolean doEncode; 39 40 private final byte[] singleByte = new byte[1]; 41 42 private final byte[] buf; 43 44 private final Context context = new Context(); 45 46 /** 47 * Constructs a new instance. 48 * 49 * @param inputStream the input stream 50 * @param baseNCodec the codec 51 * @param doEncode set to true to perform encoding, else decoding 52 */ 53 protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) { 54 super(inputStream); 55 this.doEncode = doEncode; 56 this.baseNCodec = baseNCodec; 57 this.buf = new byte[doEncode ? 4096 : 8192]; 58 } 59 60 /** 61 * {@inheritDoc} 62 * 63 * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, 64 * {@code 1} otherwise 65 * @since 1.7 66 */ 67 @Override 68 public int available() throws IOException { 69 // Note: the logic is similar to the InflaterInputStream: 70 // as long as we have not reached EOF, indicate that there is more 71 // data available. As we do not know for sure how much data is left, 72 // just return 1 as a safe guess. 73 return context.eof ? 0 : 1; 74 } 75 76 /** 77 * Returns true if decoding behavior is strict. Decoding will raise an 78 * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. 79 * 80 * <p> 81 * The default is false for lenient encoding. Decoding will compose trailing bits 82 * into 8-bit bytes and discard the remainder. 83 * </p> 84 * 85 * @return true if using strict decoding 86 * @since 1.15 87 */ 88 public boolean isStrictDecoding() { 89 return baseNCodec.isStrictDecoding(); 90 } 91 92 /** 93 * Marks the current position in this input stream. 94 * <p> 95 * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing. 96 * </p> 97 * 98 * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. 99 * @see #markSupported() 100 * @since 1.7 101 */ 102 @Override 103 public synchronized void mark(final int readLimit) { 104 // noop 105 } 106 107 /** 108 * {@inheritDoc} 109 * 110 * @return Always returns {@code false} 111 */ 112 @Override 113 public boolean markSupported() { 114 return false; // not an easy job to support marks 115 } 116 117 /** 118 * Reads one {@code byte} from this input stream. 119 * 120 * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. 121 * @throws IOException 122 * if an I/O error occurs. 123 */ 124 @Override 125 public int read() throws IOException { 126 int r = read(singleByte, 0, 1); 127 while (r == 0) { 128 r = read(singleByte, 0, 1); 129 } 130 if (r > 0) { 131 final byte b = singleByte[0]; 132 return b < 0 ? 256 + b : b; 133 } 134 return EOF; 135 } 136 137 /** 138 * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} 139 * from this InputStream. 140 * 141 * @param array 142 * destination byte array 143 * @param offset 144 * where to start writing the bytes 145 * @param len 146 * maximum number of bytes to read 147 * 148 * @return number of bytes read 149 * @throws IOException 150 * if an I/O error occurs. 151 * @throws NullPointerException 152 * if the byte array parameter is null 153 * @throws IndexOutOfBoundsException 154 * if offset, len or buffer size are invalid 155 */ 156 @Override 157 public int read(final byte[] array, final int offset, final int len) throws IOException { 158 Objects.requireNonNull(array, "array"); 159 if (offset < 0 || len < 0) { 160 throw new IndexOutOfBoundsException(); 161 } 162 if (offset > array.length || offset + len > array.length) { 163 throw new IndexOutOfBoundsException(); 164 } 165 if (len == 0) { 166 return 0; 167 } 168 int readLen = 0; 169 /* 170 Rationale for while-loop on (readLen == 0): 171 ----- 172 Base32.readResults() usually returns > 0 or EOF (-1). In the 173 rare case where it returns 0, we just keep trying. 174 175 This is essentially an undocumented contract for InputStream 176 implementors that want their code to work properly with 177 java.io.InputStreamReader, since the latter hates it when 178 InputStream.read(byte[]) returns a zero. Unfortunately our 179 readResults() call must return 0 if a large amount of the data 180 being decoded was non-base32, so this while-loop enables proper 181 interop with InputStreamReader for that scenario. 182 ----- 183 This is a fix for CODEC-101 184 */ 185 // Attempt to read the request length 186 while (readLen < len) { 187 if (!baseNCodec.hasData(context)) { 188 // Obtain more data. 189 // buf is reused across calls to read to avoid repeated allocations 190 final int c = in.read(buf); 191 if (doEncode) { 192 baseNCodec.encode(buf, 0, c, context); 193 } else { 194 baseNCodec.decode(buf, 0, c, context); 195 } 196 } 197 final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context); 198 if (read < 0) { 199 // Return the amount read or EOF 200 return readLen != 0 ? readLen : -1; 201 } 202 readLen += read; 203 } 204 return readLen; 205 } 206 207 /** 208 * Repositions this stream to the position at the time the mark method was last called on this input stream. 209 * <p> 210 * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. 211 * </p> 212 * 213 * @throws IOException if this method is invoked 214 * @since 1.7 215 */ 216 @Override 217 public synchronized void reset() throws IOException { 218 throw new IOException("mark/reset not supported"); 219 } 220 221 /** 222 * {@inheritDoc} 223 * 224 * @throws IllegalArgumentException if the provided skip length is negative 225 * @since 1.7 226 */ 227 @Override 228 public long skip(final long n) throws IOException { 229 if (n < 0) { 230 throw new IllegalArgumentException("Negative skip length: " + n); 231 } 232 // skip in chunks of 512 bytes 233 final byte[] b = new byte[512]; 234 long todo = n; 235 while (todo > 0) { 236 int len = (int) Math.min(b.length, todo); 237 len = this.read(b, 0, len); 238 if (len == EOF) { 239 break; 240 } 241 todo -= len; 242 } 243 return n - todo; 244 } 245 }