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