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    public class UUEncoder implements Encoder {
027    
028        // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
029        // the start of each line.
030        static private final int MAX_CHARS_PER_LINE = 45;
031    
032    
033        public UUEncoder()
034        {
035        }
036    
037        /**
038         * encode the input data producing a UUEncoded output stream.
039         *
040         * @param data   The array of byte data.
041         * @param off    The starting offset within the data.
042         * @param length Length of the data to encode.
043         * @param out    The output stream the encoded data is written to.
044         *
045         * @return the number of bytes produced.
046         */
047        public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
048        {
049            int byteCount = 0;
050    
051            while (true) {
052                // keep writing complete lines until we've exhausted the data.
053                if (length > MAX_CHARS_PER_LINE) {
054                    // encode another line and adjust the length and position
055                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
056                    length -= MAX_CHARS_PER_LINE;
057                    off += MAX_CHARS_PER_LINE;
058                }
059                else {
060                    // last line.  Encode the partial and quit
061                    byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
062                    break;
063                }
064            }
065            return byteCount;
066        }
067    
068    
069        /**
070         * Encode a single line of data (less than or equal to 45 characters).
071         *
072         * @param data   The array of byte data.
073         * @param off    The starting offset within the data.
074         * @param length Length of the data to encode.
075         * @param out    The output stream the encoded data is written to.
076         *
077         * @return The number of bytes written to the output stream.
078         * @exception IOException
079         */
080        private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
081            // write out the number of characters encoded in this line.
082            out.write((byte)((length & 0x3F) + ' '));
083            byte a;
084            byte b;
085            byte c;
086    
087            // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
088            int bytesWritten = 2;
089    
090            for (int i = 0; i < length;) {
091                // set the padding defauls
092                b = 1;
093                c = 1;
094                // get the next 3 bytes (if we have them)
095                a = data[offset + i++];
096                if (i < length) {
097                    b = data[offset + i++];
098                    if (i < length) {
099                        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