001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.geronimo.mail.util; 021 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.io.FilterInputStream; 025 026 /** 027 * An implementation of a FilterInputStream that decodes the 028 * stream data in BASE64 encoding format. This version does the 029 * decoding "on the fly" rather than decoding a single block of 030 * data. Since this version is intended for use by the MimeUtilty class, 031 * it also handles line breaks in the encoded data. 032 * 033 * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $ 034 */ 035 public class Base64DecoderStream extends FilterInputStream { 036 037 static protected final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors"; 038 039 // number of decodeable units we'll try to process at one time. We'll attempt to read that much 040 // data from the input stream and decode in blocks. 041 static protected final int BUFFERED_UNITS = 2000; 042 043 // our decoder for processing the data 044 protected Base64Encoder decoder = new Base64Encoder(); 045 046 // can be overridden by a system property. 047 protected boolean ignoreErrors = false; 048 049 // buffer for reading in chars for decoding (which can support larger bulk reads) 050 protected byte[] encodedChars = new byte[BUFFERED_UNITS * 4]; 051 // a buffer for one decoding unit's worth of data (3 bytes). This is the minimum amount we 052 // can read at one time. 053 protected byte[] decodedChars = new byte[BUFFERED_UNITS * 3]; 054 // count of characters in the buffer 055 protected int decodedCount = 0; 056 // index of the next decoded character 057 protected int decodedIndex = 0; 058 059 060 public Base64DecoderStream(InputStream in) { 061 super(in); 062 // make sure we get the ignore errors flag 063 ignoreErrors = SessionUtil.getBooleanProperty(MAIL_BASE64_IGNOREERRORS, false); 064 } 065 066 /** 067 * Test for the existance of decoded characters in our buffer 068 * of decoded data. 069 * 070 * @return True if we currently have buffered characters. 071 */ 072 private boolean dataAvailable() { 073 return decodedCount != 0; 074 } 075 076 /** 077 * Get the next buffered decoded character. 078 * 079 * @return The next decoded character in the buffer. 080 */ 081 private byte getBufferedChar() { 082 decodedCount--; 083 return decodedChars[decodedIndex++]; 084 } 085 086 /** 087 * Decode a requested number of bytes of data into a buffer. 088 * 089 * @return true if we were able to obtain more data, false otherwise. 090 */ 091 private boolean decodeStreamData() throws IOException { 092 decodedIndex = 0; 093 094 // fill up a data buffer with input data 095 int readCharacters = fillEncodedBuffer(); 096 097 if (readCharacters > 0) { 098 decodedCount = decoder.decode(encodedChars, 0, readCharacters, decodedChars); 099 return true; 100 } 101 return false; 102 } 103 104 105 /** 106 * Retrieve a single byte from the decoded characters buffer. 107 * 108 * @return The decoded character or -1 if there was an EOF condition. 109 */ 110 private int getByte() throws IOException { 111 if (!dataAvailable()) { 112 if (!decodeStreamData()) { 113 return -1; 114 } 115 } 116 decodedCount--; 117 return decodedChars[decodedIndex++]; 118 } 119 120 private int getBytes(byte[] data, int offset, int length) throws IOException { 121 122 int readCharacters = 0; 123 while (length > 0) { 124 // need data? Try to get some 125 if (!dataAvailable()) { 126 // if we can't get this, return a count of how much we did get (which may be -1). 127 if (!decodeStreamData()) { 128 return readCharacters > 0 ? readCharacters : -1; 129 } 130 } 131 132 // now copy some of the data from the decoded buffer to the target buffer 133 int copyCount = Math.min(decodedCount, length); 134 System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount); 135 decodedIndex += copyCount; 136 decodedCount -= copyCount; 137 offset += copyCount; 138 length -= copyCount; 139 readCharacters += copyCount; 140 } 141 return readCharacters; 142 } 143 144 145 /** 146 * Fill our buffer of input characters for decoding from the 147 * stream. This will attempt read a full buffer, but will 148 * terminate on an EOF or read error. This will filter out 149 * non-Base64 encoding chars and will only return a valid 150 * multiple of 4 number of bytes. 151 * 152 * @return The count of characters read. 153 */ 154 private int fillEncodedBuffer() throws IOException 155 { 156 int readCharacters = 0; 157 158 while (true) { 159 // get the next character from the stream 160 int ch = in.read(); 161 // did we hit an EOF condition? 162 if (ch == -1) { 163 // now check to see if this is normal, or potentially an error 164 // if we didn't get characters as a multiple of 4, we may need to complain about this. 165 if ((readCharacters % 4) != 0) { 166 // the error checking can be turned off...normally it isn't 167 if (!ignoreErrors) { 168 throw new IOException("Base64 encoding error, data truncated"); 169 } 170 // we're ignoring errors, so round down to a multiple and return that. 171 return (readCharacters / 4) * 4; 172 } 173 // return the count. 174 return readCharacters; 175 } 176 // if this character is valid in a Base64 stream, copy it to the buffer. 177 else if (decoder.isValidBase64(ch)) { 178 encodedChars[readCharacters++] = (byte)ch; 179 // if we've filled up the buffer, time to quit. 180 if (readCharacters >= encodedChars.length) { 181 return readCharacters; 182 } 183 } 184 185 // we're filtering out whitespace and CRLF characters, so just ignore these 186 } 187 } 188 189 190 // in order to function as a filter, these streams need to override the different 191 // read() signature. 192 193 public int read() throws IOException 194 { 195 return getByte(); 196 } 197 198 199 public int read(byte [] buffer, int offset, int length) throws IOException { 200 return getBytes(buffer, offset, length); 201 } 202 203 204 public boolean markSupported() { 205 return false; 206 } 207 208 209 public int available() throws IOException { 210 return ((in.available() / 4) * 3) + decodedCount; 211 } 212 }