001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.mail.util;
021    
022    import java.io.IOException;
023    import java.io.OutputStream;
024    import java.io.UnsupportedEncodingException;
025    
026    /**
027     * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $
028     */
029    public class UUEncoder implements Encoder {
030    
031        // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
032        // the start of each line.
033        static private final int MAX_CHARS_PER_LINE = 45;
034    
035    
036        public UUEncoder()
037        {
038        }
039    
040        /**
041         * encode the input data producing a UUEncoded output stream.
042         *
043         * @param data   The array of byte data.
044         * @param off    The starting offset within the data.
045         * @param length Length of the data to encode.
046         * @param out    The output stream the encoded data is written to.
047         *
048         * @return the number of bytes produced.
049         */
050        public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
051        {
052            int byteCount = 0;
053    
054            while (true) {
055                // keep writing complete lines until we've exhausted the data.
056                if (length > MAX_CHARS_PER_LINE) {
057                    // encode another line and adjust the length and position
058                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
059                    length -= MAX_CHARS_PER_LINE;
060                    off += MAX_CHARS_PER_LINE;
061                }
062                else {
063                    // last line.  Encode the partial and quit
064                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
065                    break;
066                }
067            }
068            return byteCount;
069        }
070    
071    
072        /**
073         * Encode a single line of data (less than or equal to 45 characters).
074         *
075         * @param data   The array of byte data.
076         * @param off    The starting offset within the data.
077         * @param length Length of the data to encode.
078         * @param out    The output stream the encoded data is written to.
079         *
080         * @return The number of bytes written to the output stream.
081         * @exception IOException
082         */
083        private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
084            // write out the number of characters encoded in this line.
085            out.write((byte)((length & 0x3F) + ' '));
086            byte a;
087            byte b;
088            byte c;
089    
090            // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
091            int bytesWritten = 2;
092    
093            for (int i = 0; i < length;) {
094                // set the padding defauls
095                b = 1;
096                c = 1;
097                // get the next 3 bytes (if we have them)
098                a = data[offset + i++];
099                if (i < length) {
100                    b = data[offset + i++];
101                    if (i < length) {
102                        c = data[offset + i++];
103                    }
104                }
105    
106                byte d1 = (byte)(((a >>> 2) & 0x3F) + ' ');
107                byte d2 = (byte)(((( a << 4) & 0x30) | ((b >>> 4) & 0x0F)) + ' ');
108                byte d3 = (byte)((((b << 2) & 0x3C) | ((c >>> 6) & 0x3)) + ' ');
109                byte d4 = (byte)((c & 0x3F) + ' ');
110    
111                out.write(d1);
112                out.write(d2);
113                out.write(d3);
114                out.write(d4);
115    
116                bytesWritten += 4;
117            }
118    
119            // terminate with a linefeed alone
120            out.write('\n');
121    
122            return bytesWritten;
123        }
124    
125    
126        /**
127         * decode the uuencoded byte data writing it to the given output stream
128         *
129         * @param data   The array of byte data to decode.
130         * @param off    Starting offset within the array.
131         * @param length The length of data to encode.
132         * @param out    The output stream used to return the decoded data.
133         *
134         * @return the number of bytes produced.
135         * @exception IOException
136         */
137        public int decode(byte[] data, int off, int length, OutputStream out) throws IOException
138        {
139            int bytesWritten = 0;
140    
141            while (length > 0) {
142                int lineOffset = off;
143    
144                // scan forward looking for a EOL terminator for the next line of data.
145                while (length > 0 && data[off] != '\n') {
146                    off++;
147                    length--;
148                }
149    
150                // go decode this line of data
151                bytesWritten += decodeLine(data, lineOffset, off - lineOffset, out);
152    
153                // the offset was left pointing at the EOL character, so step over that one before
154                // scanning again.
155                off++;
156                length--;
157            }
158            return bytesWritten;
159        }
160    
161    
162        /**
163         * decode a single line of uuencoded byte data writing it to the given output stream
164         *
165         * @param data   The array of byte data to decode.
166         * @param off    Starting offset within the array.
167         * @param length The length of data to decode (length does NOT include the terminating new line).
168         * @param out    The output stream used to return the decoded data.
169         *
170         * @return the number of bytes produced.
171         * @exception IOException
172         */
173        private int decodeLine(byte[] data, int off, int length, OutputStream out) throws IOException {
174            int count = data[off++];
175    
176            // obtain and validate the count
177            if (count < ' ') {
178                throw new IOException("Invalid UUEncode line length");
179            }
180    
181            count = (count - ' ') & 0x3F;
182    
183            // get the rounded count of characters that should have been used to encode this.  The + 1 is for the
184            // length encoded at the beginning
185            int requiredLength = (((count * 8) + 5) / 6) + 1;
186            if (length < requiredLength) {
187                throw new IOException("UUEncoded data and length do not match");
188            }
189    
190            int bytesWritten = 0;
191            // now decode the bytes.
192            while (bytesWritten < count) {
193                // even one byte of data requires two bytes to encode, so we should have that.
194                byte a = (byte)((data[off++] - ' ') & 0x3F);
195                byte b = (byte)((data[off++] - ' ') & 0x3F);
196                byte c = 0;
197                byte d = 0;
198    
199                // do the first byte
200                byte first = (byte)(((a << 2) & 0xFC) | ((b >>> 4) & 3));
201                out.write(first);
202                bytesWritten++;
203    
204                // still have more bytes to decode? do the second byte of the second.  That requires
205                // a third byte from the data.
206                if (bytesWritten < count) {
207                    c = (byte)((data[off++] - ' ') & 0x3F);
208                    byte second = (byte)(((b << 4) & 0xF0) | ((c >>> 2) & 0x0F));
209                    out.write(second);
210                    bytesWritten++;
211    
212                    // need the third one?
213                    if (bytesWritten < count) {
214                        d = (byte)((data[off++] - ' ') & 0x3F);
215                        byte third = (byte)(((c << 6) & 0xC0) | (d & 0x3F));
216                        out.write(third);
217                        bytesWritten++;
218                    }
219                }
220            }
221            return bytesWritten;
222        }
223    
224    
225        /**
226         * decode the UUEncoded String data writing it to the given output stream.
227         *
228         * @param data   The String data to decode.
229         * @param out    The output stream to write the decoded data to.
230         *
231         * @return the number of bytes produced.
232         * @exception IOException
233         */
234        public int decode(String data, OutputStream out) throws IOException
235        {
236            try {
237                // just get the byte data and decode.
238                byte[] bytes = data.getBytes("US-ASCII");
239                return decode(bytes, 0, bytes.length, out);
240            } catch (UnsupportedEncodingException e) {
241                throw new IOException("Invalid UUEncoding");
242            }
243        }
244    }
245    
246