All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.geronimo.mail.util.UUDecoderStream Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.geronimo.mail.util;

import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

/**
 * An implementation of a FilterOutputStream that decodes the
 * stream data in UU encoding format.  This version does the
 * decoding "on the fly" rather than decoding a single block of
 * data.  Since this version is intended for use by the MimeUtilty class,
 * it also handles line breaks in the encoded data.
 */
public class UUDecoderStream extends FilterInputStream {
    // maximum number of chars that can appear in a single line
    protected static final int MAX_CHARS_PER_LINE = 45;

    // our decoder for processing the data
    protected UUEncoder decoder = new UUEncoder();

    // a buffer for one decoding unit's worth of data (45 bytes).
    protected byte[] decodedChars;
    // count of characters in the buffer
    protected int decodedCount = 0;
    // index of the next decoded character
    protected int decodedIndex = 0;

    // indicates whether we've already processed the "begin" prefix.
    protected boolean beginRead = false;


    public UUDecoderStream(InputStream in) {
        super(in);
    }


    /**
     * Test for the existance of decoded characters in our buffer
     * of decoded data.
     *
     * @return True if we currently have buffered characters.
     */
    private boolean dataAvailable() {
        return decodedCount != 0;
    }

    /**
     * Get the next buffered decoded character.
     *
     * @return The next decoded character in the buffer.
     */
    private byte getBufferedChar() {
        decodedCount--;
        return decodedChars[decodedIndex++];
    }

    /**
     * Decode a requested number of bytes of data into a buffer.
     *
     * @return true if we were able to obtain more data, false otherwise.
     */
    private boolean decodeStreamData() throws IOException {
        decodedIndex = 0;

        // fill up a data buffer with input data
        return fillEncodedBuffer() != -1;
    }


    /**
     * Retrieve a single byte from the decoded characters buffer.
     *
     * @return The decoded character or -1 if there was an EOF condition.
     */
    private int getByte() throws IOException {
        if (!dataAvailable()) {
            if (!decodeStreamData()) {
                return -1;
            }
        }
        decodedCount--;
        return decodedChars[decodedIndex++];
    }

    private int getBytes(byte[] data, int offset, int length) throws IOException {

        int readCharacters = 0;
        while (length > 0) {
            // need data?  Try to get some
            if (!dataAvailable()) {
                // if we can't get this, return a count of how much we did get (which may be -1).
                if (!decodeStreamData()) {
                    return readCharacters > 0 ? readCharacters : -1;
                }
            }

            // now copy some of the data from the decoded buffer to the target buffer
            int copyCount = Math.min(decodedCount, length);
            System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
            decodedIndex += copyCount;
            decodedCount -= copyCount;
            offset += copyCount;
            length -= copyCount;
            readCharacters += copyCount;
        }
        return readCharacters;
    }

    /**
     * Verify that the first line of the buffer is a valid begin
     * marker.
     *
     * @exception IOException
     */
    private void checkBegin() throws IOException {
        // we only do this the first time we're requested to read from the stream.
        if (beginRead) {
            return;
        }

        // we might have to skip over lines to reach the marker.  If we hit the EOF without finding
        // the begin, that's an error.
        while (true) {
            String line = readLine();
            if (line == null) {
                throw new IOException("Missing UUEncode begin command");
            }

            // is this our begin?
            if (line.regionMatches(true, 0, "begin ", 0, 6)) {
                // This is the droid we're looking for.....
                beginRead = true;
                return;
            }
        }
    }


    /**
     * Read a line of data.  Returns null if there is an EOF.
     *
     * @return The next line read from the stream.  Returns null if we
     *         hit the end of the stream.
     * @exception IOException
     */
    protected String readLine() throws IOException {
        decodedIndex = 0;
        // get an accumulator for the data
        StringBuffer buffer = new StringBuffer();

        // now process a character at a time.
        int ch = in.read();
        while (ch != -1) {
            // a naked new line completes the line.
            if (ch == '\n') {
                break;
            }
            // a carriage return by itself is ignored...we're going to assume that this is followed
            // by a new line because we really don't have the capability of pushing this back .
            else if (ch == '\r') {
                ;
            }
            else {
                // add this to our buffer
                buffer.append((char)ch);
            }
            ch = in.read();
        }

        // if we didn't get any data at all, return nothing
        if (ch == -1 && buffer.length() == 0) {
            return null;
        }
        // convert this into a string.
        return buffer.toString();
    }


    /**
     * Fill our buffer of input characters for decoding from the
     * stream.  This will attempt read a full buffer, but will
     * terminate on an EOF or read error.  This will filter out
     * non-Base64 encoding chars and will only return a valid
     * multiple of 4 number of bytes.
     *
     * @return The count of characters read.
     */
    private int fillEncodedBuffer() throws IOException
    {
        checkBegin();
        // reset the buffer position
        decodedIndex = 0;

        while (true) {

            // we read these as character lines.  We need to be looking for the "end" marker for the
            // end of the data.
            String line = readLine();

            // this should NOT be happening....
            if (line == null) {
                throw new IOException("Missing end in UUEncoded data");
            }

            // Is this the end marker?  EOF baby, EOF!
            if (line.equalsIgnoreCase("end")) {
                // this indicates we got nuttin' more to do.
                return -1;
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE);

            byte [] lineBytes;
            try {
                lineBytes = line.getBytes("US-ASCII");
            } catch (UnsupportedEncodingException e) {
                throw new IOException("Invalid UUEncoding");
            }

            // decode this line
            decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out);

            // not just a zero-length line?
            if (decodedCount != 0) {
                // get the resulting byte array
                decodedChars = out.toByteArray();
                return decodedCount;
            }
        }
    }


    // in order to function as a filter, these streams need to override the different
    // read() signature.

    public int read() throws IOException
    {
        return getByte();
    }


    public int read(byte [] buffer, int offset, int length) throws IOException {
        return getBytes(buffer, offset, length);
    }


    public boolean markSupported() {
        return false;
    }


    public int available() throws IOException {
        return ((in.available() / 4) * 3) + decodedCount;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy