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