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}