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.IOException;
21  import java.io.OutputStream;
22  import java.io.UnsupportedEncodingException;
23  
24  public class UUEncoder implements Encoder {
25  
26      // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
27      // the start of each line.
28      static private final int MAX_CHARS_PER_LINE = 45;
29  
30  
31      public UUEncoder()
32      {
33      }
34  
35      /**
36       * encode the input data producing a UUEncoded output stream.
37       *
38       * @param data   The array of byte data.
39       * @param off    The starting offset within the data.
40       * @param length Length of the data to encode.
41       * @param out    The output stream the encoded data is written to.
42       *
43       * @return the number of bytes produced.
44       */
45      public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
46      {
47          int byteCount = 0;
48  
49          while (true) {
50              // keep writing complete lines until we've exhausted the data.
51              if (length > MAX_CHARS_PER_LINE) {
52                  // encode another line and adjust the length and position
53                  byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
54                  length -= MAX_CHARS_PER_LINE;
55                  off += MAX_CHARS_PER_LINE;
56              }
57              else {
58                  // last line.  Encode the partial and quit
59                  byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
60                  break;
61              }
62          }
63          return byteCount;
64      }
65  
66  
67      /**
68       * Encode a single line of data (less than or equal to 45 characters).
69       *
70       * @param data   The array of byte data.
71       * @param off    The starting offset within the data.
72       * @param length Length of the data to encode.
73       * @param out    The output stream the encoded data is written to.
74       *
75       * @return The number of bytes written to the output stream.
76       * @exception IOException
77       */
78      private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
79          // write out the number of characters encoded in this line.
80          out.write((byte)((length & 0x3F) + ' '));
81          byte a;
82          byte b;
83          byte c;
84  
85          // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
86          int bytesWritten = 2;
87  
88          for (int i = 0; i < length;) {
89              // set the padding defauls
90              b = 1;
91              c = 1;
92              // get the next 3 bytes (if we have them)
93              a = data[offset + i++];
94              if (i < length) {
95                  b = data[offset + i++];
96                  if (i < length) {
97                      c = data[offset + i++];
98                  }
99              }
100 
101             byte d1 = (byte)(((a >>> 2) & 0x3F) + ' ');
102             byte d2 = (byte)(((( a << 4) & 0x30) | ((b >>> 4) & 0x0F)) + ' ');
103             byte d3 = (byte)((((b << 2) & 0x3C) | ((c >>> 6) & 0x3)) + ' ');
104             byte d4 = (byte)((c & 0x3F) + ' ');
105 
106             out.write(d1);
107             out.write(d2);
108             out.write(d3);
109             out.write(d4);
110 
111             bytesWritten += 4;
112         }
113 
114         // terminate with a linefeed alone
115         out.write('\n');
116 
117         return bytesWritten;
118     }
119 
120 
121     /**
122      * decode the uuencoded byte data writing it to the given output stream
123      *
124      * @param data   The array of byte data to decode.
125      * @param off    Starting offset within the array.
126      * @param length The length of data to encode.
127      * @param out    The output stream used to return the decoded data.
128      *
129      * @return the number of bytes produced.
130      * @exception IOException
131      */
132     public int decode(byte[] data, int off, int length, OutputStream out) throws IOException
133     {
134         int bytesWritten = 0;
135 
136         while (length > 0) {
137             int lineOffset = off;
138 
139             // scan forward looking for a EOL terminator for the next line of data.
140             while (length > 0 && data[off] != '\n') {
141                 off++;
142                 length--;
143             }
144 
145             // go decode this line of data
146             bytesWritten += decodeLine(data, lineOffset, off - lineOffset, out);
147 
148             // the offset was left pointing at the EOL character, so step over that one before
149             // scanning again.
150             off++;
151             length--;
152         }
153         return bytesWritten;
154     }
155 
156 
157     /**
158      * decode a single line of uuencoded byte data writing it to the given output stream
159      *
160      * @param data   The array of byte data to decode.
161      * @param off    Starting offset within the array.
162      * @param length The length of data to decode (length does NOT include the terminating new line).
163      * @param out    The output stream used to return the decoded data.
164      *
165      * @return the number of bytes produced.
166      * @exception IOException
167      */
168     private int decodeLine(byte[] data, int off, int length, OutputStream out) throws IOException {
169         int count = data[off++];
170 
171         // obtain and validate the count
172         if (count < ' ') {
173             throw new IOException("Invalid UUEncode line length");
174         }
175 
176         count = (count - ' ') & 0x3F;
177 
178         // get the rounded count of characters that should have been used to encode this.  The + 1 is for the
179         // length encoded at the beginning
180         int requiredLength = (((count * 8) + 5) / 6) + 1;
181         if (length < requiredLength) {
182             throw new IOException("UUEncoded data and length do not match");
183         }
184 
185         int bytesWritten = 0;
186         // now decode the bytes.
187         while (bytesWritten < count) {
188             // even one byte of data requires two bytes to encode, so we should have that.
189             byte a = (byte)((data[off++] - ' ') & 0x3F);
190             byte b = (byte)((data[off++] - ' ') & 0x3F);
191             byte c = 0;
192             byte d = 0;
193 
194             // do the first byte
195             byte first = (byte)(((a << 2) & 0xFC) | ((b >>> 4) & 3));
196             out.write(first);
197             bytesWritten++;
198 
199             // still have more bytes to decode? do the second byte of the second.  That requires
200             // a third byte from the data.
201             if (bytesWritten < count) {
202                 c = (byte)((data[off++] - ' ') & 0x3F);
203                 byte second = (byte)(((b << 4) & 0xF0) | ((c >>> 2) & 0x0F));
204                 out.write(second);
205                 bytesWritten++;
206 
207                 // need the third one?
208                 if (bytesWritten < count) {
209                     d = (byte)((data[off++] - ' ') & 0x3F);
210                     byte third = (byte)(((c << 6) & 0xC0) | (d & 0x3F));
211                     out.write(third);
212                     bytesWritten++;
213                 }
214             }
215         }
216         return bytesWritten;
217     }
218 
219 
220     /**
221      * decode the UUEncoded String data writing it to the given output stream.
222      *
223      * @param data   The String data to decode.
224      * @param out    The output stream to write the decoded data to.
225      *
226      * @return the number of bytes produced.
227      * @exception IOException
228      */
229     public int decode(String data, OutputStream out) throws IOException
230     {
231         try {
232             // just get the byte data and decode.
233             byte[] bytes = data.getBytes("US-ASCII");
234             return decode(bytes, 0, bytes.length, out);
235         } catch (UnsupportedEncodingException e) {
236             throw new IOException("Invalid UUEncoding");
237         }
238     }
239 }
240 
241