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.ByteArrayOutputStream;
023    import java.io.FilterInputStream;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.UnsupportedEncodingException;
027    
028    /**
029     * An implementation of a FilterOutputStream that decodes the
030     * stream data in UU encoding format.  This version does the
031     * decoding "on the fly" rather than decoding a single block of
032     * data.  Since this version is intended for use by the MimeUtilty class,
033     * it also handles line breaks in the encoded data.
034     *
035     * @version $Rev: 467553 $ $Date: 2006-10-25 06:01:51 +0200 (Mi, 25. Okt 2006) $
036     */
037    public class UUDecoderStream extends FilterInputStream {
038        // maximum number of chars that can appear in a single line
039        protected static final int MAX_CHARS_PER_LINE = 45;
040    
041        // our decoder for processing the data
042        protected UUEncoder decoder = new UUEncoder();
043    
044        // a buffer for one decoding unit's worth of data (45 bytes).
045        protected byte[] decodedChars;
046        // count of characters in the buffer
047        protected int decodedCount = 0;
048        // index of the next decoded character
049        protected int decodedIndex = 0;
050    
051        // indicates whether we've already processed the "begin" prefix.
052        protected boolean beginRead = false;
053    
054    
055        public UUDecoderStream(InputStream in) {
056            super(in);
057        }
058    
059    
060        /**
061         * Test for the existance of decoded characters in our buffer
062         * of decoded data.
063         *
064         * @return True if we currently have buffered characters.
065         */
066        private boolean dataAvailable() {
067            return decodedCount != 0;
068        }
069    
070        /**
071         * Get the next buffered decoded character.
072         *
073         * @return The next decoded character in the buffer.
074         */
075        private byte getBufferedChar() {
076            decodedCount--;
077            return decodedChars[decodedIndex++];
078        }
079    
080        /**
081         * Decode a requested number of bytes of data into a buffer.
082         *
083         * @return true if we were able to obtain more data, false otherwise.
084         */
085        private boolean decodeStreamData() throws IOException {
086            decodedIndex = 0;
087    
088            // fill up a data buffer with input data
089            return fillEncodedBuffer() != -1;
090        }
091    
092    
093        /**
094         * Retrieve a single byte from the decoded characters buffer.
095         *
096         * @return The decoded character or -1 if there was an EOF condition.
097         */
098        private int getByte() throws IOException {
099            if (!dataAvailable()) {
100                if (!decodeStreamData()) {
101                    return -1;
102                }
103            }
104            decodedCount--;
105            return decodedChars[decodedIndex++];
106        }
107    
108        private int getBytes(byte[] data, int offset, int length) throws IOException {
109    
110            int readCharacters = 0;
111            while (length > 0) {
112                // need data?  Try to get some
113                if (!dataAvailable()) {
114                    // if we can't get this, return a count of how much we did get (which may be -1).
115                    if (!decodeStreamData()) {
116                        return readCharacters > 0 ? readCharacters : -1;
117                    }
118                }
119    
120                // now copy some of the data from the decoded buffer to the target buffer
121                int copyCount = Math.min(decodedCount, length);
122                System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
123                decodedIndex += copyCount;
124                decodedCount -= copyCount;
125                offset += copyCount;
126                length -= copyCount;
127                readCharacters += copyCount;
128            }
129            return readCharacters;
130        }
131    
132        /**
133         * Verify that the first line of the buffer is a valid begin
134         * marker.
135         *
136         * @exception IOException
137         */
138        private void checkBegin() throws IOException {
139            // we only do this the first time we're requested to read from the stream.
140            if (beginRead) {
141                return;
142            }
143    
144            // we might have to skip over lines to reach the marker.  If we hit the EOF without finding
145            // the begin, that's an error.
146            while (true) {
147                String line = readLine();
148                if (line == null) {
149                    throw new IOException("Missing UUEncode begin command");
150                }
151    
152                // is this our begin?
153                if (line.regionMatches(true, 0, "begin ", 0, 6)) {
154                    // This is the droid we're looking for.....
155                    beginRead = true;
156                    return;
157                }
158            }
159        }
160    
161    
162        /**
163         * Read a line of data.  Returns null if there is an EOF.
164         *
165         * @return The next line read from the stream.  Returns null if we
166         *         hit the end of the stream.
167         * @exception IOException
168         */
169        protected String readLine() throws IOException {
170            decodedIndex = 0;
171            // get an accumulator for the data
172            StringBuffer buffer = new StringBuffer();
173    
174            // now process a character at a time.
175            int ch = in.read();
176            while (ch != -1) {
177                // a naked new line completes the line.
178                if (ch == '\n') {
179                    break;
180                }
181                // a carriage return by itself is ignored...we're going to assume that this is followed
182                // by a new line because we really don't have the capability of pushing this back .
183                else if (ch == '\r') {
184                    ;
185                }
186                else {
187                    // add this to our buffer
188                    buffer.append((char)ch);
189                }
190                ch = in.read();
191            }
192    
193            // if we didn't get any data at all, return nothing
194            if (ch == -1 && buffer.length() == 0) {
195                return null;
196            }
197            // convert this into a string.
198            return buffer.toString();
199        }
200    
201    
202        /**
203         * Fill our buffer of input characters for decoding from the
204         * stream.  This will attempt read a full buffer, but will
205         * terminate on an EOF or read error.  This will filter out
206         * non-Base64 encoding chars and will only return a valid
207         * multiple of 4 number of bytes.
208         *
209         * @return The count of characters read.
210         */
211        private int fillEncodedBuffer() throws IOException
212        {
213            checkBegin();
214            // reset the buffer position
215            decodedIndex = 0;
216    
217            while (true) {
218    
219                // we read these as character lines.  We need to be looking for the "end" marker for the
220                // end of the data.
221                String line = readLine();
222    
223                // this should NOT be happening....
224                if (line == null) {
225                    throw new IOException("Missing end in UUEncoded data");
226                }
227    
228                // Is this the end marker?  EOF baby, EOF!
229                if (line.equalsIgnoreCase("end")) {
230                    // this indicates we got nuttin' more to do.
231                    return -1;
232                }
233    
234                ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE);
235    
236                byte [] lineBytes;
237                try {
238                    lineBytes = line.getBytes("US-ASCII");
239                } catch (UnsupportedEncodingException e) {
240                    throw new IOException("Invalid UUEncoding");
241                }
242    
243                // decode this line
244                decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out);
245    
246                // not just a zero-length line?
247                if (decodedCount != 0) {
248                    // get the resulting byte array
249                    decodedChars = out.toByteArray();
250                    return decodedCount;
251                }
252            }
253        }
254    
255    
256        // in order to function as a filter, these streams need to override the different
257        // read() signature.
258    
259        public int read() throws IOException
260        {
261            return getByte();
262        }
263    
264    
265        public int read(byte [] buffer, int offset, int length) throws IOException {
266            return getBytes(buffer, offset, length);
267        }
268    
269    
270        public boolean markSupported() {
271            return false;
272        }
273    
274    
275        public int available() throws IOException {
276            return ((in.available() / 4) * 3) + decodedCount;
277        }
278    }
279