View Javadoc

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         // we need to ensure this doesn't get sign extended 
116         return decodedChars[decodedIndex++] & 0xff;
117     }
118 
119     private int getBytes(byte[] data, int offset, int length) throws IOException {
120 
121         int readCharacters = 0;
122         while (length > 0) {
123             // need data?  Try to get some
124             if (!dataAvailable()) {
125                 // if we can't get this, return a count of how much we did get (which may be -1).
126                 if (!decodeStreamData()) {
127                     return readCharacters > 0 ? readCharacters : -1;
128                 }
129             }
130 
131             // now copy some of the data from the decoded buffer to the target buffer
132             int copyCount = Math.min(decodedCount, length);
133             System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
134             decodedIndex += copyCount;
135             decodedCount -= copyCount;
136             offset += copyCount;
137             length -= copyCount;
138             readCharacters += copyCount;
139         }
140         return readCharacters;
141     }
142 
143 
144     /**
145      * Fill our buffer of input characters for decoding from the
146      * stream.  This will attempt read a full buffer, but will
147      * terminate on an EOF or read error.  This will filter out
148      * non-Base64 encoding chars and will only return a valid
149      * multiple of 4 number of bytes.
150      *
151      * @return The count of characters read.
152      */
153     private int fillEncodedBuffer() throws IOException
154     {
155         int readCharacters = 0;
156 
157         while (true) {
158             // get the next character from the stream
159             int ch = in.read();
160             // did we hit an EOF condition?
161             if (ch == -1) {
162                 // now check to see if this is normal, or potentially an error
163                 // if we didn't get characters as a multiple of 4, we may need to complain about this.
164                 if ((readCharacters % 4) != 0) {
165                     // the error checking can be turned off...normally it isn't
166                     if (!ignoreErrors) {
167                         throw new IOException("Base64 encoding error, data truncated");
168                     }
169                     // we're ignoring errors, so round down to a multiple and return that.
170                     return (readCharacters / 4) * 4;
171                 }
172                 // return the count.
173                 return readCharacters;
174             }
175             // if this character is valid in a Base64 stream, copy it to the buffer.
176             else if (decoder.isValidBase64(ch)) {
177                 encodedChars[readCharacters++] = (byte)ch;
178                 // if we've filled up the buffer, time to quit.
179                 if (readCharacters >= encodedChars.length) {
180                     return readCharacters;
181                 }
182             }
183 
184             // we're filtering out whitespace and CRLF characters, so just ignore these
185         }
186     }
187 
188 
189     // in order to function as a filter, these streams need to override the different
190     // read() signature.
191 
192     public int read() throws IOException
193     {
194         return getByte();
195     }
196 
197 
198     public int read(byte [] buffer, int offset, int length) throws IOException {
199         return getBytes(buffer, offset, length);
200     }
201 
202 
203     public boolean markSupported() {
204         return false;
205     }
206 
207 
208     public int available() throws IOException {
209         return ((in.available() / 4) * 3) + decodedCount;
210     }
211 }