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