001    /**
002     *
003     * Copyright 2003-2006 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.mail.util;
019    
020    import java.io.IOException;
021    import java.io.OutputStream;
022    import java.io.UnsupportedEncodingException;
023    
024    public class UUEncoder implements Encoder {
025    
026        // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
027        // the start of each line.
028        static private final int MAX_CHARS_PER_LINE = 45;
029    
030    
031        public UUEncoder()
032        {
033        }
034    
035        /**
036         * encode the input data producing a UUEncoded output stream.
037         *
038         * @param data   The array of byte data.
039         * @param off    The starting offset within the data.
040         * @param length Length of the data to encode.
041         * @param out    The output stream the encoded data is written to.
042         *
043         * @return the number of bytes produced.
044         */
045        public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
046        {
047            int byteCount = 0;
048    
049            while (true) {
050                // keep writing complete lines until we've exhausted the data.
051                if (length > MAX_CHARS_PER_LINE) {
052                    // encode another line and adjust the length and position
053                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
054                    length -= MAX_CHARS_PER_LINE;
055                    off += MAX_CHARS_PER_LINE;
056                }
057                else {
058                    // last line.  Encode the partial and quit
059                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
060                    break;
061                }
062            }
063            return byteCount;
064        }
065    
066    
067        /**
068         * Encode a single line of data (less than or equal to 45 characters).
069         *
070         * @param data   The array of byte data.
071         * @param off    The starting offset within the data.
072         * @param length Length of the data to encode.
073         * @param out    The output stream the encoded data is written to.
074         *
075         * @return The number of bytes written to the output stream.
076         * @exception IOException
077         */
078        private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
079            // write out the number of characters encoded in this line.
080            out.write((byte)((length & 0x3F) + ' '));
081            byte a;
082            byte b;
083            byte c;
084    
085            // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
086            int bytesWritten = 2;
087    
088            for (int i = 0; i < length;) {
089                // set the padding defauls
090                b = 1;
091                c = 1;
092                // get the next 3 bytes (if we have them)
093                a = data[offset + i++];
094                if (i < length) {
095                    b = data[offset + i++];
096                    if (i < length) {
097                        c = data[offset + i++];
098                    }
099                }
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