1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.geronimo.mail.util; 21 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.FilterInputStream; 25 26 /** 27 * An implementation of a FilterInputStream that decodes the 28 * stream data in BASE64 encoding format. This version does the 29 * decoding "on the fly" rather than decoding a single block of 30 * data. Since this version is intended for use by the MimeUtilty class, 31 * it also handles line breaks in the encoded data. 32 */ 33 public class Base64DecoderStream extends FilterInputStream { 34 35 static protected final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors"; 36 37 // number of decodeable units we'll try to process at one time. We'll attempt to read that much 38 // data from the input stream and decode in blocks. 39 static protected final int BUFFERED_UNITS = 2000; 40 41 // our decoder for processing the data 42 protected Base64Encoder decoder = new Base64Encoder(); 43 44 // can be overridden by a system property. 45 protected boolean ignoreErrors = false; 46 47 // buffer for reading in chars for decoding (which can support larger bulk reads) 48 protected byte[] encodedChars = new byte[BUFFERED_UNITS * 4]; 49 // a buffer for one decoding unit's worth of data (3 bytes). This is the minimum amount we 50 // can read at one time. 51 protected byte[] decodedChars = new byte[BUFFERED_UNITS * 3]; 52 // count of characters in the buffer 53 protected int decodedCount = 0; 54 // index of the next decoded character 55 protected int decodedIndex = 0; 56 57 58 public Base64DecoderStream(InputStream in) { 59 super(in); 60 // make sure we get the ignore errors flag 61 ignoreErrors = SessionUtil.getBooleanProperty(MAIL_BASE64_IGNOREERRORS, false); 62 } 63 64 /** 65 * Test for the existance of decoded characters in our buffer 66 * of decoded data. 67 * 68 * @return True if we currently have buffered characters. 69 */ 70 private boolean dataAvailable() { 71 return decodedCount != 0; 72 } 73 74 /** 75 * Get the next buffered decoded character. 76 * 77 * @return The next decoded character in the buffer. 78 */ 79 private byte getBufferedChar() { 80 decodedCount--; 81 return decodedChars[decodedIndex++]; 82 } 83 84 /** 85 * Decode a requested number of bytes of data into a buffer. 86 * 87 * @return true if we were able to obtain more data, false otherwise. 88 */ 89 private boolean decodeStreamData() throws IOException { 90 decodedIndex = 0; 91 92 // fill up a data buffer with input data 93 int readCharacters = fillEncodedBuffer(); 94 95 if (readCharacters > 0) { 96 decodedCount = decoder.decode(encodedChars, 0, readCharacters, decodedChars); 97 return true; 98 } 99 return false; 100 } 101 102 103 /** 104 * Retrieve a single byte from the decoded characters buffer. 105 * 106 * @return The decoded character or -1 if there was an EOF condition. 107 */ 108 private int getByte() throws IOException { 109 if (!dataAvailable()) { 110 if (!decodeStreamData()) { 111 return -1; 112 } 113 } 114 decodedCount--; 115 return decodedChars[decodedIndex++]; 116 } 117 118 private int getBytes(byte[] data, int offset, int length) throws IOException { 119 120 int readCharacters = 0; 121 while (length > 0) { 122 // need data? Try to get some 123 if (!dataAvailable()) { 124 // if we can't get this, return a count of how much we did get (which may be -1). 125 if (!decodeStreamData()) { 126 return readCharacters > 0 ? readCharacters : -1; 127 } 128 } 129 130 // now copy some of the data from the decoded buffer to the target buffer 131 int copyCount = Math.min(decodedCount, length); 132 System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount); 133 decodedIndex += copyCount; 134 decodedCount -= copyCount; 135 offset += copyCount; 136 length -= copyCount; 137 readCharacters += copyCount; 138 } 139 return readCharacters; 140 } 141 142 143 /** 144 * Fill our buffer of input characters for decoding from the 145 * stream. This will attempt read a full buffer, but will 146 * terminate on an EOF or read error. This will filter out 147 * non-Base64 encoding chars and will only return a valid 148 * multiple of 4 number of bytes. 149 * 150 * @return The count of characters read. 151 */ 152 private int fillEncodedBuffer() throws IOException 153 { 154 int readCharacters = 0; 155 156 while (true) { 157 // get the next character from the stream 158 int ch = in.read(); 159 // did we hit an EOF condition? 160 if (ch == -1) { 161 // now check to see if this is normal, or potentially an error 162 // if we didn't get characters as a multiple of 4, we may need to complain about this. 163 if ((readCharacters % 4) != 0) { 164 // the error checking can be turned off...normally it isn't 165 if (!ignoreErrors) { 166 throw new IOException("Base64 encoding error, data truncated"); 167 } 168 // we're ignoring errors, so round down to a multiple and return that. 169 return (readCharacters / 4) * 4; 170 } 171 // return the count. 172 return readCharacters; 173 } 174 // if this character is valid in a Base64 stream, copy it to the buffer. 175 else if (decoder.isValidBase64(ch)) { 176 encodedChars[readCharacters++] = (byte)ch; 177 // if we've filled up the buffer, time to quit. 178 if (readCharacters >= encodedChars.length) { 179 return readCharacters; 180 } 181 } 182 183 // we're filtering out whitespace and CRLF characters, so just ignore these 184 } 185 } 186 187 188 // in order to function as a filter, these streams need to override the different 189 // read() signature. 190 191 public int read() throws IOException 192 { 193 return getByte(); 194 } 195 196 197 public int read(byte [] buffer, int offset, int length) throws IOException { 198 return getBytes(buffer, offset, length); 199 } 200 201 202 public boolean markSupported() { 203 return false; 204 } 205 206 207 public int available() throws IOException { 208 return ((in.available() / 4) * 3) + decodedCount; 209 } 210 }