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