View Javadoc

1   /**
2    *
3    * Copyright 2003-2006 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.ByteArrayOutputStream;
21  import java.io.FilterInputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.UnsupportedEncodingException;
25  
26  /**
27   * An implementation of a FilterOutputStream that decodes the
28   * stream data in UU 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 UUDecoderStream extends FilterInputStream {
34      // maximum number of chars that can appear in a single line
35      protected static final int MAX_CHARS_PER_LINE = 45;
36  
37      // our decoder for processing the data
38      protected UUEncoder decoder = new UUEncoder();
39  
40      // a buffer for one decoding unit's worth of data (45 bytes).
41      protected byte[] decodedChars;
42      // count of characters in the buffer
43      protected int decodedCount = 0;
44      // index of the next decoded character
45      protected int decodedIndex = 0;
46  
47      // indicates whether we've already processed the "begin" prefix.
48      protected boolean beginRead = false;
49  
50  
51      public UUDecoderStream(InputStream in) {
52          super(in);
53      }
54  
55  
56      /**
57       * Test for the existance of decoded characters in our buffer
58       * of decoded data.
59       *
60       * @return True if we currently have buffered characters.
61       */
62      private boolean dataAvailable() {
63          return decodedCount != 0;
64      }
65  
66      /**
67       * Get the next buffered decoded character.
68       *
69       * @return The next decoded character in the buffer.
70       */
71      private byte getBufferedChar() {
72          decodedCount--;
73          return decodedChars[decodedIndex++];
74      }
75  
76      /**
77       * Decode a requested number of bytes of data into a buffer.
78       *
79       * @return true if we were able to obtain more data, false otherwise.
80       */
81      private boolean decodeStreamData() throws IOException {
82          decodedIndex = 0;
83  
84          // fill up a data buffer with input data
85          return fillEncodedBuffer() != -1;
86      }
87  
88  
89      /**
90       * Retrieve a single byte from the decoded characters buffer.
91       *
92       * @return The decoded character or -1 if there was an EOF condition.
93       */
94      private int getByte() throws IOException {
95          if (!dataAvailable()) {
96              if (!decodeStreamData()) {
97                  return -1;
98              }
99          }
100         decodedCount--;
101         return decodedChars[decodedIndex++];
102     }
103 
104     private int getBytes(byte[] data, int offset, int length) throws IOException {
105 
106         int readCharacters = 0;
107         while (length > 0) {
108             // need data?  Try to get some
109             if (!dataAvailable()) {
110                 // if we can't get this, return a count of how much we did get (which may be -1).
111                 if (!decodeStreamData()) {
112                     return readCharacters > 0 ? readCharacters : -1;
113                 }
114             }
115 
116             // now copy some of the data from the decoded buffer to the target buffer
117             int copyCount = Math.min(decodedCount, length);
118             System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
119             decodedIndex += copyCount;
120             decodedCount -= copyCount;
121             offset += copyCount;
122             length -= copyCount;
123             readCharacters += copyCount;
124         }
125         return readCharacters;
126     }
127 
128     /**
129      * Verify that the first line of the buffer is a valid begin
130      * marker.
131      *
132      * @exception IOException
133      */
134     private void checkBegin() throws IOException {
135         // we only do this the first time we're requested to read from the stream.
136         if (beginRead) {
137             return;
138         }
139 
140         // we might have to skip over lines to reach the marker.  If we hit the EOF without finding
141         // the begin, that's an error.
142         while (true) {
143             String line = readLine();
144             if (line == null) {
145                 throw new IOException("Missing UUEncode begin command");
146             }
147 
148             // is this our begin?
149             if (line.regionMatches(true, 0, "begin ", 0, 6)) {
150                 // This is the droid we're looking for.....
151                 beginRead = true;
152                 return;
153             }
154         }
155     }
156 
157 
158     /**
159      * Read a line of data.  Returns null if there is an EOF.
160      *
161      * @return The next line read from the stream.  Returns null if we
162      *         hit the end of the stream.
163      * @exception IOException
164      */
165     protected String readLine() throws IOException {
166         decodedIndex = 0;
167         // get an accumulator for the data
168         StringBuffer buffer = new StringBuffer();
169 
170         // now process a character at a time.
171         int ch = in.read();
172         while (ch != -1) {
173             // a naked new line completes the line.
174             if (ch == '\n') {
175                 break;
176             }
177             // a carriage return by itself is ignored...we're going to assume that this is followed
178             // by a new line because we really don't have the capability of pushing this back .
179             else if (ch == '\r') {
180                 ;
181             }
182             else {
183                 // add this to our buffer
184                 buffer.append((char)ch);
185             }
186             ch = in.read();
187         }
188 
189         // if we didn't get any data at all, return nothing
190         if (ch == -1 && buffer.length() == 0) {
191             return null;
192         }
193         // convert this into a string.
194         return buffer.toString();
195     }
196 
197 
198     /**
199      * Fill our buffer of input characters for decoding from the
200      * stream.  This will attempt read a full buffer, but will
201      * terminate on an EOF or read error.  This will filter out
202      * non-Base64 encoding chars and will only return a valid
203      * multiple of 4 number of bytes.
204      *
205      * @return The count of characters read.
206      */
207     private int fillEncodedBuffer() throws IOException
208     {
209         checkBegin();
210         // reset the buffer position
211         decodedIndex = 0;
212 
213         while (true) {
214 
215             // we read these as character lines.  We need to be looking for the "end" marker for the
216             // end of the data.
217             String line = readLine();
218 
219             // this should NOT be happening....
220             if (line == null) {
221                 throw new IOException("Missing end in UUEncoded data");
222             }
223 
224             // Is this the end marker?  EOF baby, EOF!
225             if (line.equalsIgnoreCase("end")) {
226                 // this indicates we got nuttin' more to do.
227                 return -1;
228             }
229 
230             ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE);
231 
232             byte [] lineBytes;
233             try {
234                 lineBytes = line.getBytes("US-ASCII");
235             } catch (UnsupportedEncodingException e) {
236                 throw new IOException("Invalid UUEncoding");
237             }
238 
239             // decode this line
240             decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out);
241 
242             // not just a zero-length line?
243             if (decodedCount != 0) {
244                 // get the resulting byte array
245                 decodedChars = out.toByteArray();
246                 return decodedCount;
247             }
248         }
249     }
250 
251 
252     // in order to function as a filter, these streams need to override the different
253     // read() signature.
254 
255     public int read() throws IOException
256     {
257         return getByte();
258     }
259 
260 
261     public int read(byte [] buffer, int offset, int length) throws IOException {
262         return getBytes(buffer, offset, length);
263     }
264 
265 
266     public boolean markSupported() {
267         return false;
268     }
269 
270 
271     public int available() throws IOException {
272         return ((in.available() / 4) * 3) + decodedCount;
273     }
274 }
275