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 * http://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 * Abstract superclass for Base-N input streams. 031 * 032 * @since 1.5 033 */ 034public class BaseNCodecInputStream extends FilterInputStream { 035 036 private final BaseNCodec baseNCodec; 037 038 private final boolean doEncode; 039 040 private final byte[] singleByte = new byte[1]; 041 042 private final byte[] buf; 043 044 private final Context context = new Context(); 045 046 /** 047 * Create an instance. 048 * 049 * @param inputStream the input stream 050 * @param baseNCodec the codec 051 * @param doEncode set to true to perform encoding, else decoding 052 */ 053 protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) { 054 super(inputStream); 055 this.doEncode = doEncode; 056 this.baseNCodec = baseNCodec; 057 this.buf = new byte[doEncode ? 4096 : 8192]; 058 } 059 060 /** 061 * {@inheritDoc} 062 * 063 * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, 064 * {@code 1} otherwise 065 * @since 1.7 066 */ 067 @Override 068 public int available() throws IOException { 069 // Note: the logic is similar to the InflaterInputStream: 070 // as long as we have not reached EOF, indicate that there is more 071 // data available. As we do not know for sure how much data is left, 072 // just return 1 as a safe guess. 073 074 return context.eof ? 0 : 1; 075 } 076 077 /** 078 * Returns true if decoding behavior is strict. Decoding will raise an 079 * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. 080 * 081 * <p> 082 * The default is false for lenient encoding. Decoding will compose trailing bits 083 * into 8-bit bytes and discard the remainder. 084 * </p> 085 * 086 * @return true if using strict decoding 087 * @since 1.15 088 */ 089 public boolean isStrictDecoding() { 090 return baseNCodec.isStrictDecoding(); 091 } 092 093 /** 094 * Marks the current position in this input stream. 095 * <p> 096 * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing. 097 * </p> 098 * 099 * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. 100 * @see #markSupported() 101 * @since 1.7 102 */ 103 @Override 104 public synchronized void mark(final int readLimit) { 105 // noop 106 } 107 108 /** 109 * {@inheritDoc} 110 * 111 * @return Always returns {@code false} 112 */ 113 @Override 114 public boolean markSupported() { 115 return false; // not an easy job to support marks 116 } 117 118 /** 119 * Reads one {@code byte} from this input stream. 120 * 121 * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. 122 * @throws IOException 123 * if an I/O error occurs. 124 */ 125 @Override 126 public int read() throws IOException { 127 int r = read(singleByte, 0, 1); 128 while (r == 0) { 129 r = read(singleByte, 0, 1); 130 } 131 if (r > 0) { 132 final byte b = singleByte[0]; 133 return b < 0 ? 256 + b : b; 134 } 135 return EOF; 136 } 137 138 /** 139 * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} 140 * from this InputStream. 141 * 142 * @param array 143 * destination byte array 144 * @param offset 145 * where to start writing the bytes 146 * @param len 147 * maximum number of bytes to read 148 * 149 * @return number of bytes read 150 * @throws IOException 151 * if an I/O error occurs. 152 * @throws NullPointerException 153 * if the byte array parameter is null 154 * @throws IndexOutOfBoundsException 155 * if offset, len or buffer size are invalid 156 */ 157 @Override 158 public int read(final byte[] array, final int offset, final int len) throws IOException { 159 Objects.requireNonNull(array, "array"); 160 if (offset < 0 || len < 0) { 161 throw new IndexOutOfBoundsException(); 162 } 163 if (offset > array.length || offset + len > array.length) { 164 throw new IndexOutOfBoundsException(); 165 } 166 if (len == 0) { 167 return 0; 168 } 169 int readLen = 0; 170 /* 171 Rationale for while-loop on (readLen == 0): 172 ----- 173 Base32.readResults() usually returns > 0 or EOF (-1). In the 174 rare case where it returns 0, we just keep trying. 175 176 This is essentially an undocumented contract for InputStream 177 implementors that want their code to work properly with 178 java.io.InputStreamReader, since the latter hates it when 179 InputStream.read(byte[]) returns a zero. Unfortunately our 180 readResults() call must return 0 if a large amount of the data 181 being decoded was non-base32, so this while-loop enables proper 182 interop with InputStreamReader for that scenario. 183 ----- 184 This is a fix for CODEC-101 185 */ 186 // Attempt to read the request length 187 while (readLen < len) { 188 if (!baseNCodec.hasData(context)) { 189 // Obtain more data. 190 // buf is reused across calls to read to avoid repeated allocations 191 final int c = in.read(buf); 192 if (doEncode) { 193 baseNCodec.encode(buf, 0, c, context); 194 } else { 195 baseNCodec.decode(buf, 0, c, context); 196 } 197 } 198 final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context); 199 if (read < 0) { 200 // Return the amount read or EOF 201 return readLen != 0 ? readLen : -1; 202 } 203 readLen += read; 204 } 205 return readLen; 206 } 207 208 /** 209 * Repositions this stream to the position at the time the mark method was last called on this input stream. 210 * <p> 211 * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. 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 233 // skip in chunks of 512 bytes 234 final byte[] b = new byte[512]; 235 long todo = n; 236 237 while (todo > 0) { 238 int len = (int) Math.min(b.length, todo); 239 len = this.read(b, 0, len); 240 if (len == EOF) { 241 break; 242 } 243 todo -= len; 244 } 245 246 return n - todo; 247 } 248}