/*
 *	The contents of this file are subject to the Netscape Public License 
 *	Version 1.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.mozilla.org/NPL/. 
 *	
 *	Software distributed under the License is distributed on an "AS IS" basis, 
 *	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
 *	for the specific language governing rights and limitations under the License. 
 *	
 *	The Original Code is the Netscape Messaging Access SDK Version 3.5 code, 
 *	released on or about June 15, 1998. 
 *	
 *	The Initial Developer of the Original Code is Netscape Communications 
 *	Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape 
 *	Communications Corporation. All Rights Reserved. 
 *	
 *	Contributor(s): ______________________________________. 
*/
  
/*
* Copyright (c) 1997 and 1998 Netscape Communications Corporation
* (http://home.netscape.com/misc/trademarks.html)
*/

package netscape.messaging.mime;
import java.io.*;
import java.lang.*;
import java.net.*;
import java.util.*;


/**
 * The MIMEHelper class defines a set of utility functions.
 * @author Prasad Yendluri,  Carson Lee
 */
public class MIMEHelper
{
    // error messages
    final static public String szERROR_BAD_PARAMETER = "Error: Bad parameter";
    final static public String szERROR_OUT_OF_MEMORY = "Error: Out of memory";
    final public static String szERROR_EMPTY_MESSAGE = "Error: Empty message";
    final public static String szERROR_BAD_MIME_MESSAGE = "Error: Bad mime message";
    final public static String szERROR_BAD_EXTERNAL_MESSAGE_PART = "Error:  No External headers in Message/external-body";
    final public static String szERROR_UNSUPPORTED_PARTIAL_SUBTYPE = "Error: Unsupported Partial SubType";
    final public static String szINVALID_CODE = "is an invalid code";
     protected static final int BUFSZ  = 4096;

	private static final byte base64map [] =
        {//      0   1   2   3   4   5   6   7
                'A','B','C','D','E','F','G','H', // 0
                'I','J','K','L','M','N','O','P', // 1
                'Q','R','S','T','U','V','W','X', // 2
                'Y','Z','a','b','c','d','e','f', // 3
                'g','h','i','j','k','l','m','n', // 4
                'o','p','q','r','s','t','u','v', // 5
                'w','x','y','z','0','1','2','3', // 6
                '4','5','6','7','8','9','+','/'  // 7
        };

	private static final byte hexmap [] =
	{
		 '0', '1', '2', '3', '4', '5', '6', '7',
    		 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
	};

	private static final byte CR      = '\r';
	private static final byte LF      = '\n';
	private static final byte EQ      = '=';
	private static final byte HT      = '\t';
	private static final byte[] CRLF  = "\r\n".getBytes();
	private static final byte[] EQCRLF = "=\r\n".getBytes();
	private static final byte[] EQQ    = "=?".getBytes();
	private static final byte[] QEQ    = "?=".getBytes();
	private static final byte[] QB64Q  = "?B?".getBytes();
	private static final byte[] QQPQ   = "?Q?".getBytes();


    // Generate base64 decode mapping
    private static byte[] Base64DecMap;
    static
    {
    	Base64DecMap = new byte[128];

    	for ( int idx = 0; idx < base64map.length; idx++ )
    	    Base64DecMap[ base64map[idx] ] = (byte) idx;
    }

//============= BEGIN NEW ONE

	/**
         * Base64 Encodes data from InputStream and writes to OutputStream.
         * @param input InputStream that supplies the data to be encoded.
         * @param output OutputStream that accepts the encoded data.
         * @return Number of bytes written.
         * @exception MIMEException If an encoding error occurs.
	 * @exception IOException If an I/O error occurs.
         */
	public static long encodeBase64 (InputStream input,
					  OutputStream output) throws MIMEException, IOException
	{
		byte buf[] = new byte [3];
		byte a, b, c;
		int read =0, offset=0, nullCount = 0, linelen = 0, written = 0, readlen = 0, leftover=0;
		byte l_bufenc[] = new byte [80];
		byte l_readbuf[] = new byte [BUFSZ];
		int triggerCount = 0;


		while (true)
		{
		    readlen = input.read (l_readbuf, 0, BUFSZ);

		    // trim off the last potential nulls
		    if (readlen < BUFSZ && readlen > 0)
		    {
			    while (l_readbuf [readlen-1] == 0x00)
			    {
				readlen--;
				if (readlen == 0)
				    break;
			        //System.out.println ("decrementing readlen =" + readlen);
			    }
		    }

		    if (readlen <= 0)
		    {
			if (leftover == 0)
			{
		            break;
			}
			else if (leftover == 2)
			{
				a = buf[0];
            			b = buf[1];
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
            			l_bufenc[linelen++] = EQ;
				written += 4;
		                break;
			}
			else if (leftover == 1)
			{
				a = buf[0];
            			b = 0;
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = EQ;
            			l_bufenc[linelen++] = EQ;
				written += 4;
		                break;
			}
		    }

		    offset = 0;

		    while (readlen > 0)
		    {
			if (leftover > 0)
			{
				if (leftover == 2)
				{
					buf [2] = l_readbuf [offset++];
					readlen -= 1;
					read = 3;
					leftover = 0;
				}
				else if (leftover == 1)
				{
					buf [1] = l_readbuf [offset++];
					readlen -= 1;
					if (readlen > 0)
					{
					    buf [2] = l_readbuf [offset++];
					    readlen -= 1;
					    read = 3;
					    leftover = 0;
					}
					else
					{
				             leftover = 2;
					     continue;
					}
				}

			}
			else if (readlen >= 3)
			{
				buf [0] = l_readbuf [offset++];
				buf [1] = l_readbuf [offset++];
				buf [2] = l_readbuf [offset++];
				readlen -= 3;
				read = 3;
				leftover = 0;
			}
			else if  (readlen == 2)
			{
				buf [0] = l_readbuf [offset++];
				buf [1] = l_readbuf [offset++];
				readlen = 0;
				leftover = 2;
				continue;
			}
			else if  (readlen == 1)
			{
				buf [0] = l_readbuf [offset++];
				readlen = 0;
				leftover = 1;
				continue;
			}

			if ((read == 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) ||
			    (read == 2 && buf[0] == 0x00 && buf[1] == 0x00) ||
		    	    (read == 1 && buf[0] == 0x00))
			{
				nullCount += read;
			}
			else
				nullCount = 0;

			//if (read == 3 && !(nullCount > 100))
			if (read == 3)
			{
				a = buf[0];
            			b = buf[1];
            			c = buf[2];
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
            			l_bufenc[linelen++] = (base64map[c & 0x3F]);
				//linelen += 4;
				written += 4;
			}
			//else if (read == 2 && !(nullCount > 100))
			else if (read == 2)
			{
				a = buf[0];
            			b = buf[1];
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);

            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
            			l_bufenc[linelen++] = EQ;
				//linelen += 4;
				written += 4;
			}
			//else if (read == 1 && !(nullCount > 100))
			else if (read == 1)
			{
				a = buf[0];
            			b = 0;
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = EQ;
            			l_bufenc[linelen++] = EQ;
				//linelen += 4;
				written += 4;
			}

			if (linelen > 71)
			{
				//output.write (LF);
				//written += 1;
				l_bufenc[linelen++] =  CR;
				l_bufenc[linelen++] =  LF;
				//linelen += 2;
				written += 2;
				output.write (l_bufenc, 0, linelen);
				linelen = 0;
			}

		    } // end while
		} // end outer while

		// write out the leftover stuff
		if (linelen > 0)
		{
			l_bufenc[linelen++] =  CR;
			l_bufenc[linelen++] =  LF;
			written += 2;
			//l_bufenc[linelen++] =  CR;
			//l_bufenc[linelen++] =  LF;
			//written += 4;
			//linelen += 4;
			output.write (l_bufenc, 0, linelen);

			triggerCount += linelen;
			if (triggerCount > 1000000)
			{
				System.gc();
				//output.flush();
				triggerCount = 0;
			}

			linelen = 0;
		}

		return (written);
	}
//============= END NEW ONE

//============= BEGIN OLD ONE
	/**
         * Base64 Encodes data from InputStream and writes to OutputStream.
         * @param input InputStream that supplies the data to be encoded.
         * @param output OutputStream that accepts the encoded data.
         * @return Number of bytes written.
         * @exception MIMEException If an encoding error occurs.
	 * @exception IOException If an I/O error occurs.
         */
	protected static long encodeBase64O (InputStream input,
					  OutputStream output) throws MIMEException, IOException
	{
		byte buf[] = new byte [3];
		byte a, b, c;
		int nullCount = 0, linelen = 0, written = 0, dummy[] = new int[2];
		byte l_bufenc[] = new byte [80];
		int triggerCount = 0;

		while (true)
		{
			int read = input.read (buf, 0, 3);

			if (read <= 0)
			{
			        break;
			}

			if ((read == 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) ||
			    (read == 2 && buf[0] == 0x00 && buf[1] == 0x00) ||
		    	    (read == 1 && buf[0] == 0x00))
			{
				nullCount += read;
			}
			else
				nullCount = 0;

			//if (read == 3 && !(nullCount > 100))
			if (read == 3)
			{
				a = buf[0];
            			b = buf[1];
            			c = buf[2];
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
            			l_bufenc[linelen++] = (base64map[c & 0x3F]);
				//linelen += 4;
				written += 4;
			}
			//else if (read == 2 && !(nullCount > 100))
			else if (read == 2)
			{
				a = buf[0];
            			b = buf[1];
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
				if (b != 0x00)
				{
            			   l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			   l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
				}
				else
            			     l_bufenc[linelen++] = EQ;
            			l_bufenc[linelen++] = EQ;
				//linelen += 4;
				written += 4;
			}
			//else if (read == 1 && !(nullCount > 100))
			else if (read == 1)
			{
				a = buf[0];
            			b = 0;
            			c = 0;
            			l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]);
            			l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
            			l_bufenc[linelen++] = EQ;
            			l_bufenc[linelen++] = EQ;
				//linelen += 4;
				written += 4;
			}

			if (linelen > 71)
			{
				//output.write (LF);
				//written += 1;
				l_bufenc[linelen++] =  CR;
				l_bufenc[linelen++] =  LF;
				//linelen += 2;
				written += 2;
				output.write (l_bufenc, 0, linelen);
				linelen = 0;
			}

		} // end while

		// write out the leftover stuff
		if (linelen > 0)
		{
			l_bufenc[linelen++] =  CR;
			l_bufenc[linelen++] =  LF;
			l_bufenc[linelen++] =  CR;
			l_bufenc[linelen++] =  LF;
			//linelen += 4;
			written += 4;
			output.write (l_bufenc, 0, linelen);

			triggerCount += linelen;
			if (triggerCount > 1000000)
			{
				System.gc();
				//output.flush();
				triggerCount = 0;
			}

			linelen = 0;
		}

		return (written);
	}
//============= END OLD ONE


	/**
         * QuotedPrintable Encodes data from InputStream and writes to OutputStream.
         * @param input InputStream that supplies the data to be encoded.
         * @param output OutputStream that accepts the encoded data.
         * @return Number of bytes written.
         * @exception MIMEException If an encoding error occurs.
	 * @exception IOException If an I/O error occurs.
         */
	public static long encodeQP (InputStream input,
				     OutputStream output) throws MIMEException, IOException
        {
		byte current = (byte)0, previous = (byte)0;
		int read, linelen = 0, written = 0, lastspace = 0, nullCount = 0;
		byte l_bufenc[] = new byte [80];

		while (true)
		{
			read = input.read();

			if (read == -1)
			{
				if (linelen > 0)
				{
				    output.write (l_bufenc, 0, linelen);
				    output.write (CRLF);
				    written += 2;
				}

				return (written);
			}

			current = (byte) read;

			if (current == 0x00)
			{
				nullCount++;
			        previous = current;
				lastspace = 0;
				continue;
			}
			else if (nullCount > 0)
			{
				// write out all the nulls first and fall through to process current char.
				for (int idx = 1; idx <= nullCount ; idx++)
				{
					byte tmp = 0x00;
					l_bufenc [linelen++] = EQ;
					l_bufenc [linelen++] = (byte) hexmap [(tmp >>> 4) & 0xF];
					l_bufenc [linelen++] = (byte) hexmap [(tmp & 0xF)];
				//	l_bufenc [linelen++] = (byte)0x00;
				//	l_bufenc [linelen++] = (byte)0x00;
					written += 3;

					if (linelen > 74)
					{
						output.write (l_bufenc, 0, linelen);
						output.write (EQCRLF);
						written += 3;
						linelen = 0;
					}
				}

				previous = (byte) 0;
				nullCount = 0;
			}

			if ((current > ' ') && (current < 0x7F) && (current != '='))
			{
				// Printable chars
				//output.write ((byte) current);
				l_bufenc [linelen++] = (byte) current;
				//linelen += 1;
				written += 1;
				lastspace = 0;
			        previous = current;
			}
			else if ((current == ' ') || (current == HT))
			{
				//output.write ((byte) current);
				l_bufenc [linelen++] = (byte) current;
				//linelen += 1;
				written += 1;
				lastspace = 1;
			        previous = current;
			}
			else if ((current == LF) && (previous == CR))
			{
				// handled this already. Ignore.
			        previous = (byte) 0;
			}
			else if ((current == CR ) || (current == LF))
			{
				// Need to emit a soft line break if last char was SPACE/HT or
				// if we have a period on a line by itself.
				if ((lastspace == 1) || ((previous == '.') && (linelen == 1)))
				{
					l_bufenc [linelen++] = EQ;
					l_bufenc [linelen++] = CR;
					l_bufenc [linelen++] = LF;
					written += 3;
				}

				l_bufenc [linelen++] = CR;
				l_bufenc [linelen++] = LF;
				lastspace = 0;
				written += 2;
				output.write (l_bufenc, 0, linelen);
				previous = (byte) 0;
				linelen = 0;
				//output.write (CRLF);
			        //previous = current;
			}
			else if ( (current < ' ') || (current == '=') || (current >= 0x7F) )
			{
				// Special Chars
				//output.write ((byte) '=');
				//output.write ((byte) hexmap [(current >>> 4)]);
				//output.write ((byte) hexmap [(current & 0xF)]);
				l_bufenc [linelen++] = EQ;

				l_bufenc [linelen++] = (byte) hexmap [(current >>> 4) & 0xF];
				l_bufenc [linelen++] = (byte) hexmap [(current & 0xF)];
				lastspace = 0;
				//linelen += 3;
				written += 3;
				previous = current;
			}
			else
			{
				//output.write ((byte) current);
				l_bufenc [linelen++] = (byte) current;
				lastspace = 0;
				//linelen += 1;
				written += 1;
				previous = current;
			}

			if (linelen > 74)
			{

				output.write (l_bufenc, 0, linelen);
				output.write (EQCRLF);
				written += 3;
				linelen = 0;
				previous = (byte) 0;
			}

		} // while

        } //encodeQP


	/**
         * Q Encodes data from InputStream and writes to OutputStream.
         * @param input InputStream that supplies the data to be encoded.
         * @param output OutputStream that accepts the encoded data.
         * @return Number of bytes written.
         * @exception MIMEException If an encoding error occurs.
	 * @exception IOException If an I/O error occurs.
         */
	private static long encodeQ (InputStream input,
				     OutputStream output) throws MIMEException, IOException
        {
		byte current = (byte)0;
		boolean encode = false;
		int read, written = 0;

		while (true)
		{
			read = input.read();

			if (read == -1)
			{
				return (written);
			}

			current = (byte) read;

			if ((current >= 0x30 && current <= 0x39) ||      // 0-9
			    (current >= 0x41 && current <= 0x5A) ||      // A-Z
			    (current >= 0x61 && current <= 0x7A) ||      // a-z
			    (current == 0x21 || current == 0x2A) ||      // !*
			    (current == 0x2B || current == 0x2D) ||      // +-
			    (current == 0x2F || current == 0x5F))        // /_
			{
				encode = false;
			}
			else
				encode = true;

			if (encode)
			{
				// Special Chars
				output.write ((byte) '=');
				output.write ((byte) hexmap [(current >>> 4)]);
				output.write ((byte) hexmap [(current & 0xF)]);
				written += 3;
			}
			else
			{
				output.write ((byte) current);
				written += 1;
			}

		} // while
        }


    /**
     * String version. Base64 Decodes data from the input string.
     * @param  str The base64-encoded string.
     * @return The decoded string
     */
    protected final static String decodeBase64( String str )
    {
    	if (str == null)
    	    return  null;

//    	byte data[] = new byte[str.length()];
//    	str.getBytes(0, str.length(), data, 0);

	    return new String( decodeBase64( str.getBytes() ) );
    }


    /**
     * byte[] version. Base64 Decodes input bytes.
     * @param  data base64-encoded bytes.
     * @return the decoded bytes
     */
    protected final static byte[] decodeBase64( byte[] data )
    {
        return decodeBase64( data, data.length );
    }


    // byte[] version, ByteBuffer version
    // carsonl
    /**
     * byte[] version. Base64 Decodes input bytes of given length.
     * @param  data base64-encoded bytes.
     * @param  len length of base64-encoded bytes.
     * @return the decoded bytes
     */
    protected final static byte[] decodeBase64( byte[] data, int len )
    {
    	if (data == null)
    	    return null;

    	while (data[len-1] == '=')
    	    len--;

    	byte dest[] = new byte[len - data.length/4];


	    // ascii printable to 0-63 conversion
    	for (int idx = 0; idx <data.length; idx++)
	        data[idx] = Base64DecMap[data[idx]];

    	// 4-byte to 3-byte conversion
	    int sidx, didx;

    	for (sidx = 0, didx=0; didx < dest.length-2; sidx += 4, didx += 3)
	    {
    	    dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) |
    			    ((data[sidx+1] >>> 4) & 003) );

    	    dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) |
    			    ((data[sidx+2] >>> 2) & 017) );

    	    dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) |
    			    (data[sidx+3] & 077) );
    	}

    	if (didx < dest.length)
    	    dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) | ((data[sidx+1] >>> 4) & 003) );

    	if (++didx < dest.length)
    	    dest[didx]   = (byte) ( ((data[sidx+1] << 4) & 255) | ((data[sidx+2] >>> 2) & 017) );

    	return dest;
    }



	/**
         * QuotedPrintable Decodes data from InputStream and writes to OutputStream
         * @param input InputStream that supplies the data to be decoded.
         * @param output OutputStream that accepts the decoded data.
         * @exception MIMEException if a decoding error occurs.
         * @exception IOException if io error occurs.
         */
	public static void decodeQP (InputStream input, OutputStream output) throws MIMEException
        {
            byte inputBuffer[];

            try
            {
                inputBuffer = new byte[ input.available() + 1 ];
                input.read( inputBuffer );
                output.write( decodeQP( inputBuffer ) );
            }
            catch ( IOException e )
            {
	              throw new MIMEException ( e.getMessage() );
            }
            catch ( Exception e )
            {
	              throw new MIMEException ( e.getMessage() );
            }

	       return;
        }


    /**
     * String version. QP decodes data from the input string.
     * @param  str the QP-encoded string.
     * @return the decoded string
     */
    protected final static String decodeQP( String str ) throws MIMEException
    {
        String decodedString = null;

    	if (str == null)
    	    return  null;

//    	byte data[] = new byte[str.length()];
//    	str.getBytes(0, str.length(), data, 0);

        try
        {
            decodedString = new String( decodeQP( str.getBytes() ) );
        }
        catch( Exception e )
        {
            throw new MIMEException( e.getMessage() );
        }

	    return decodedString;
    }



    /**
     * byte[] version. QP decodes input bytes.
     * @param  bytesIn QP-encoded bytes.
     * @return the decoded bytes
     */
    protected final static byte[] decodeQP( byte[] bytesIn ) throws MIMEException
    {
        return decodeQP( bytesIn, bytesIn.length );
    }


    /**
     * byte[] version. QP decodes input bytes of given length.
     * @param  bytesIn QP-encoded bytes.
     * @param  len length of QP-encoded bytes.
     * @return the decoded bytes
     * @exception ParseException If a '=' is not followed by a valid
     *                           2-digit hex number or '\r\n'.
     */
    protected final static byte[] decodeQP( byte[] bytesIn, int len ) throws MIMEException
    {
    	if ( bytesIn == null )
    	    throw new MIMEException( szERROR_BAD_PARAMETER );

    	byte res[] = new byte[ (int) ( len * 1.1) ];
    	byte src[] = bytesIn;
        byte nl[]  = System.getProperty("line.separator", "\n").getBytes();

    	int last   = 0,
    	    j   = 0;

    	for (int i = 0; i < len; )
    	{
    	    byte ch = src[i++];

    	    if (ch == '=')
    	    {
        		if (src[i] == '\n'  ||  src[i] == '\r')
        		{					// Rule #5
        		    i++;

        		    if (src[i-1] == '\r'  && src[i] == '\n')
            			i++;
        		}

        		else					// Rule #1
        		{
        		    byte repl;
        		    int hi = Character.digit( (char) src[i], 16),
        			lo = Character.digit( (char) src[i+1], 16);

        		    if ((hi | lo) < 0)
        		    	throw new MIMEException( new String(src, i-1, 3) + szINVALID_CODE );
        		    else
        		    {
            			repl = (byte) (hi << 4 | lo);
            			i += 2;
        		    }

        		    res[j++] = repl;
        		}

    	    	last = j;
    	    }
    	    else if (ch == '\n'  ||  ch == '\r')	// Rule #4
    	    {
        		if (src[i-1] == '\r'  &&  src[i] == '\n')
        		    i++;

        		for (int idx = 0; idx < nl.length; idx++)
        		    res[last++] = nl[idx];

        		j = last;
    	    }

    	    else					// Rule #1, #2
    	    {
    		    res[j++] = ch;

    		    if (ch != ' '  &&  ch != '\t')		// Rule #3
    		        last = j;
    	    }

    	    if ( j > res.length - 5 )
    	    {
            	byte tmp[] = new byte[ res.length + 500 ];
            	System.arraycopy( res, 0, tmp, 0, res.length );
            	res = tmp;
    	    }
    	}

    	return res;
    }

	/**
         * Encodes a string that typically goes in a header but uses characters other than ASCII
         * per RFC2047. Can be used on unstructured rfc822 headers or comments of structured ones.
	 * The returned string can be passed to setHeader() method in MIMEMessage class.
         * @param inputString The string to encode.
         * @param charsetName Name of the charset the input string is in. If null uses iso-8859-1.
         * @param encoding_type Must "B" or "Q".
         * @return The encoded header string.
         * @exception MIMEException if an encoding error occurs.
         */
	public static String encodeHeader (String inputString,
	                                   String charsetName,
                                       String encoding_type) throws MIMEException, UnsupportedEncodingException
        {
		int l_encoding = -1; // 0 = b64, 1= Q
		int l_inlength = 0;
		byte [] l_inbuf;

		if (encoding_type.equalsIgnoreCase ("B"))
		     l_encoding = 0;
		else if (encoding_type.equalsIgnoreCase ("Q"))
		     l_encoding = 1;
		else
		     throw new MIMEException ("Invalid encoding_type: " + encoding_type);

		if (charsetName == null)
			l_inbuf = inputString.getBytes("iso-8859-1");
		else
			l_inbuf = inputString.getBytes(charsetName);

		l_inlength = l_inbuf.length;


		try
		{
		         ByteArrayInputStream ins  = new ByteArrayInputStream (l_inbuf);
		         ByteArrayOutputStream os  = new ByteArrayOutputStream (l_inlength * 2);

		         long enclen;
			 if (l_encoding == 0)  // base64
 			      enclen = encodeBase64 (ins, os);
			 else
 			      enclen = encodeQ (ins, os);

		         if (enclen <= 0)
		              throw new MIMEException (" Header Encoding error: " +
							 encoding_type);

		         //ByteString l_bs64 = new ByteString (os.toByteArray(), 0, (int)enclen-1);
			 //  hi is length of array not last index!  --------------------^^^^^^^^^^
		         ByteString l_bsenc = new ByteString (os.toByteArray(), 0, (int)enclen);
		         ByteBuffer l_bufenc = new ByteBuffer (l_bsenc);

		         ByteBuffer l_retbuf = new ByteBuffer ((int)enclen+20);
		         l_retbuf.append (EQQ);
		         l_retbuf.append (charsetName.getBytes());
			 if (l_encoding == 0)  // base64
		         	l_retbuf.append (QB64Q);
			 else
		         	l_retbuf.append (QQPQ);
		         l_retbuf.append (l_bsenc);
		         l_retbuf.append (QEQ);

		         return (new String (l_retbuf.buffer));
		}
		catch (Exception e)
		{
			 throw new MIMEException (e.getMessage());
		}
        }

	/**
         * Decodes a string encoded in RFC2047 format. If the string is not encoded
	 * returns the original string. Can be used on the header value returned by
	 * getHeader() and getAllHeaders() methods in MIMEMessage class.
         * @param inputString The string to decode.
         * @return The decoded header string.
         * @exception MIMEException If an decoding error occurs.
         */
	public static String decodeHeader (String inputString) throws MIMEException
    {
    	int len;
    	int i, j;
    	int nStart = 0;
    	int nEnd = 0;
    	String szEncodedText;
    	StringBuffer OutputBuffer = new StringBuffer();
    	String plainPart=null;
    	byte decodedText[] = null;
    	char achCharset[] = new char[16];
    	boolean bEncodeBase64 = false;
    	boolean bEncodeQP = false;
    	boolean bEncodeString = false;

    	if ( inputString == null )
    	    throw new MIMEException( szERROR_BAD_PARAMETER );

        len = inputString.length();

    	/* find start of encoding text */
    	for ( i = 0; i < len && inputString.charAt(i) != 0 ; i++ )
    	{
    		if ( inputString.charAt(i) == '=' && inputString.charAt(i+1) == '?' )
		{
			bEncodeString = true;
    			break;
		}
    	}

	if (!bEncodeString)
		return inputString;

	if (i > 0)
	{
		plainPart = inputString.substring (0, i);
	}

    	/* get charset */
    	for ( i += 2, j = 0; i < len && inputString.charAt(i) != '?'; i++, j++ )
    	{
    		achCharset[j] = inputString.charAt(i);
    	}

    	achCharset[j] = 0;
    	i++;	/* skip ? */

    	/* get encoding type */
    	if ( inputString.charAt(i) == 'Q' || inputString.charAt(i) == 'q' )
    		bEncodeQP = true;

    	else if ( inputString.charAt(i) == 'B' || inputString.charAt(i) == 'b' )
    		bEncodeBase64 = true;

	if (inputString.charAt(i+1) == '?')
    		i++;	/* skip ? */
    	nStart = ++i;

    	/* look for end of encoded text */
    	for ( j = 0;  i < len && inputString.charAt(i) != 0 ; i++, j++ )
    	{
    		if ( inputString.charAt(i) == '?' && i+1 < len && inputString.charAt(i+1) == '=' )
    		{
    			nEnd = i;
    			break;
    		}
    	}


    	if ( nEnd > 0 )
    	{
    		/* extract encoded text */
    		szEncodedText = inputString.substring( nStart, nStart + j );

			if ( bEncodeQP )
				decodedText = decodeQP( szEncodedText.getBytes() );

			else if ( bEncodeBase64 )
				decodedText = decodeBase64( szEncodedText.getBytes() );
    	}
	else 
	{
    	    throw new MIMEException ("Improper encoded String");
	}


	if (plainPart != null)
	{
		OutputBuffer.append (plainPart);	
	}

    	if ( decodedText != null )
	{
	    OutputBuffer.append (new String(decodedText));
	}

	if (nEnd+2 < len-1)
	{
	    String remainder = inputString.substring (nEnd+2, len);
	    String retString = decodeHeader (remainder);
	    if (retString != null)
	    	OutputBuffer.append (retString);	
	}

	return (OutputBuffer.toString());

    }



	/**
	 * Generates and returns a boundary string that can be used in multi-parts.
	 * @return The boundary string.
	 */
        public static String generateBoundary ()
        {
                Random l_rand = new Random (System.currentTimeMillis());
                long l_numboundary = l_rand.nextLong();
                String l_boundary = new String ("-----" + Long.toHexString(l_numboundary));
                return (l_boundary);
        }



	/**
	* Converts the input unicode ASCII printable chars to ASCII bytes.
        * @param inputString The string to convert.
	* @exception MIMEException If the input contains non-printable ASCII or non-ASCII chars.
	*/
	public static byte[] unicodeToASCII (String inputString) throws
							UnsupportedEncodingException, MIMEException
	{
		//byte [] l_bytebuf = inputString.getBytes("iso-8859-1");
		byte [] l_bytebuf = inputString.getBytes();

		for (int i=0, len = l_bytebuf.length; i < len; i++)
		{
			if ( (l_bytebuf[i] > 126  || l_bytebuf[i] < 32) &&
			     l_bytebuf[i] != CR  && l_bytebuf[i] != LF )
			   throw new MIMEException ("Invalid non-printable ASCII or non-ASCII character." + l_bytebuf[i]);
		}

		return (l_bytebuf);

	} // unicodeToASCII

        /**
	 * Based on file extension, attempts to determine the file MIME content-type
	 * sub-type, params and extension. By default returns application/octet-stream.
         * @param filename Filename in the form name.ext. If no extension returns application/octet-stream.
         * @return Object of class fileMIMEType that has the MIME content-type* info set.
	 */
	public static fileMIMEType getFileMIMEType (String filename)
	{
		String inStr, extn;
		int strLen, sepIndex;
		fileMIMEType fmt = new fileMIMEType();

		inStr = filename.trim();
		strLen = inStr.length();
		sepIndex = inStr.lastIndexOf ('.');
		extn =  inStr.substring (sepIndex+1, strLen);

		fmt.file_extn = extn;

		File ff = new File (filename);
		fmt.file_shortname = ff.getName();

		if (extn.equalsIgnoreCase ("txt") || extn.equalsIgnoreCase ("java") ||
		    extn.equalsIgnoreCase ("c")   || extn.equalsIgnoreCase ("C")    ||
                    extn.equalsIgnoreCase ("cc")  || extn.equalsIgnoreCase ("CC")   ||
		    extn.equalsIgnoreCase ("h")   || extn.equalsIgnoreCase ("hxx")  ||
                    extn.equalsIgnoreCase ("bat") || extn.equalsIgnoreCase ("rc")   ||
		    extn.equalsIgnoreCase ("ini") ||  extn.equalsIgnoreCase ("cmd") ||
		    extn.equalsIgnoreCase ("awk") || extn.equalsIgnoreCase ("html") ||
		    extn.equalsIgnoreCase ("sh")  || extn.equalsIgnoreCase ("ksh")  ||
		    extn.equalsIgnoreCase ("pl")  ||  extn.equalsIgnoreCase ("DIC") ||
		    extn.equalsIgnoreCase ("EXC") ||  extn.equalsIgnoreCase ("LOG") ||
		    extn.equalsIgnoreCase ("SCP") ||  extn.equalsIgnoreCase ("WT") ||
		    extn.equalsIgnoreCase ("mk")  || extn.equalsIgnoreCase ("htm"))  // what else?
                {
			fmt.content_type = MIMEBasicPart.TEXT;
			if (extn.equalsIgnoreCase ("html") || extn.equalsIgnoreCase ("htm"))
				fmt.content_subtype = "html";
			else
				fmt.content_subtype = "plain";
			fmt.content_params = "us-ascii";
			fmt.mime_encoding = MIMEBodyPart.E7BIT;
		}
		else if (extn.equals ("pdf")) // PDF is different
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "pdf";
		}
		else if (extn.equalsIgnoreCase ("AIF") || extn.equalsIgnoreCase ("AIFC") ||
		         extn.equalsIgnoreCase ("AIFF"))
		{
			fmt.content_type = MIMEBasicPart.AUDIO;
			fmt.content_subtype = "aiff";
		}
		else if (extn.equalsIgnoreCase ("AU") || extn.equalsIgnoreCase ("SND"))
		{
			fmt.content_type = MIMEBasicPart.AUDIO;
			fmt.content_subtype = "basic";
		}
		else if (extn.equalsIgnoreCase ("WAV"))
		{
			fmt.content_type = MIMEBasicPart.AUDIO;
			fmt.content_subtype = "wav";
		}
		else if (extn.equalsIgnoreCase ("gif") )
		{
			fmt.content_type = MIMEBasicPart.IMAGE;
			fmt.content_subtype = "gif";
		}
		else if (extn.equalsIgnoreCase ("jpg") )
		{
			fmt.content_type = MIMEBasicPart.IMAGE;
			fmt.content_subtype = "jpeg";
		}
		else if (extn.equalsIgnoreCase ("jpeg") )
		{
			fmt.content_type = MIMEBasicPart.IMAGE;
			fmt.content_subtype = "jpeg";
		}
		else if (extn.equalsIgnoreCase ("tif") )
		{
			fmt.content_type = MIMEBasicPart.IMAGE;
			fmt.content_subtype = "tiff";
		}
		else if (extn.equalsIgnoreCase ("XBM") )
		{
			fmt.content_type = MIMEBasicPart.IMAGE;
			fmt.content_subtype = "x-xbitmap";
		}
		else if (extn.equalsIgnoreCase ("avi") )
		{
			fmt.content_type = MIMEBasicPart.VIDEO;
			fmt.content_subtype = "avi";
		}
		else if (extn.equalsIgnoreCase ("mpeg") )
		{
			fmt.content_type = MIMEBasicPart.VIDEO;
			fmt.content_subtype = "mpeg";
		}
		else if (extn.equalsIgnoreCase ("ps") || extn.equalsIgnoreCase ("EPS"))
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "postscript";
		}
		else if (extn.equalsIgnoreCase ("tar") )
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "x-tar";
		}
		else if (extn.equalsIgnoreCase ("zip") )
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "zip";
		}
		else if (extn.equalsIgnoreCase ("js") )
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "x-javascript";
		}
		else if (extn.equalsIgnoreCase ("doc") )
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "msword";
		}
		else if (extn.equalsIgnoreCase ("nsc") )
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "x-conference";
		}
		else if (extn.equalsIgnoreCase ("ARC") || extn.equalsIgnoreCase ("ARJ") ||
			 extn.equalsIgnoreCase ("B64") || extn.equalsIgnoreCase ("BHX") ||
			 extn.equalsIgnoreCase ("GZ") || extn.equalsIgnoreCase ("HQX"))
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "x-gzip";
		}
		else
		{
			fmt.content_type = MIMEBasicPart.APPLICATION;
			fmt.content_subtype = "octet-stream";
		}

		return (fmt);
	}


/*
	protected static byte[] decodeQPVector( Vector v, int nStart, int nEnd, int nMessageLen, int[] param ) throws MIMEException
	{
		int	i, j, nLen = 0, nTotalLen = 0, last = 0, jj = 0;
        byte[] line;
        byte[] output;
        byte nl[]  = System.getProperty("line.separator", "\n").getBytes();

		if ( v == null || param == null )
    	    throw new MIMEException( szERROR_BAD_PARAMETER );

        // get message len
		for ( j = nStart; j <= nEnd; j++ )
		{
			line = (byte[]) v.elementAt( j );
			nTotalLen = nTotalLen + line.length;
        }

        output = new byte[ (int)( nTotalLen * 1.1 ) ];

		if ( output == null )
    	    throw new MIMEException( "Not enough memory" );

        // read off data from vector line by line
		for ( j = nStart; j <= nEnd; j++ )
		{
			line = (byte[]) v.elementAt( j );
			nLen = line.length;

        	for ( i = 0; i < nLen; )
        	{
        	    byte ch = line[i++];

        	    if (ch == '=')
        	    {
            		if (line[i] == '\n'  ||  line[i] == '\r')
            		{					// Rule #5
            		    i++;

            		    if (line[i-1] == '\r'  && line[i] == '\n')
                			i++;
            		}

            		else					// Rule #1
            		{
            		    byte repl;
            		    int hi = Character.digit( (char) line[i], 16),
            			lo = Character.digit( (char) line[i+1], 16);

            		    if ((hi | lo) < 0)
            		    	throw new MIMEException(new String(line, i-1, 3) + "is an invalid code");
            		    else
            		    {
                			repl = (byte) (hi << 4 | lo);
                			i += 2;
            		    }

            		    output[j++] = repl;
            		}

        	    	last = jj;
        	    }
        	    else if (ch == '\n'  ||  ch == '\r')	// Rule #4
        	    {
            		if (line[i-1] == '\r'  &&  line[i] == '\n')
            		    i++;

            		for (int idx = 0; idx < nl.length; idx++)
            		    output[last++] = nl[idx];

            		jj = last;
        	    }

        	    else					// Rule #1, #2
        	    {
        		    output[jj++] = ch;

        		    if (ch != ' '  &&  ch != '\t')		// Rule #3
        		        last = jj;
        	    }

        	    if ( jj > output.length - 5 )
        	    {
                	byte tmp[] = new byte[ output.length + 500 ];
                	System.arraycopy( output, 0, tmp, 0, output.length );
                	output = tmp;
        	    }

        	}  // for each line

        }   // for vector

        param[2] = jj;  // len

	    return output;
	}
*/

/*
* carsonl, jan 8,98
* hex to decimal
*
* parameter :
*
*	int nFirstByte : first hex byte
*	int nSecondByte : second hex byte
*
* returns : decimal
*/
static byte nConvertHexToDec( int nFirstByte, int nSecondByte )
{
	byte nValue = 0;

	switch ( nFirstByte )
	{
		case ' ':
		case '0':	nValue += 0; break;
		case '1':	nValue += 16; break;
		case '2':	nValue += 32; break;
		case '3':	nValue += 48; break;
		case '4':	nValue += 64; break;
		case '5':	nValue += 80; break;
		case '6':	nValue += 96; break;
		case '7':	nValue += 112; break;
		case '8':	nValue += 128; break;
		case '9':	nValue += 144; break;
		case 'A':
		case 'a':	nValue += 160; break;
		case 'B':
		case 'b':	nValue += 176; break;
		case 'C':
		case 'c':	nValue += 192; break;
		case 'D':
		case 'd':	nValue += 208; break;
		case 'E':
		case 'e':	nValue += 224; break;
		case 'F':
		case 'f':	nValue += 240; break;
	}

	switch ( nSecondByte )
	{
		case ' ':
		case '0':	nValue += 0; break;
		case '1':	nValue += 1; break;
		case '2':	nValue += 2; break;
		case '3':	nValue += 3; break;
		case '4':	nValue += 4; break;
		case '5':	nValue += 5; break;
		case '6':	nValue += 6; break;
		case '7':	nValue += 7; break;
		case '8':	nValue += 8; break;
		case '9':	nValue += 9; break;
		case 'A':
		case 'a':	nValue += 10; break;
		case 'B':
		case 'b':	nValue += 11; break;
		case 'C':
		case 'c':	nValue += 12; break;
		case 'D':
		case 'd':	nValue += 13; break;
		case 'E':
		case 'e':	nValue += 14; break;
		case 'F':
		case 'f':	nValue += 15; break;
	}

	return nValue;
}


protected static byte[] decodeQPVectorNew (Vector v, int nStart, int nEnd,
			                int[] param, byte[] leftOverBytes) throws MIMEException
{
	int i = 0, j = 0, ii = 0, nLen = 0, written=0;
	byte[] line, buffer, retbuf;
	byte[] token = new byte [3];
	int nNoOfLeftOverBytes;

	if ( v == null || param == null )
	    throw new MIMEException( szERROR_BAD_PARAMETER );

    	if ( nStart == -1 || nEnd == -1 )
       	    return null;

	nNoOfLeftOverBytes = param[0];

        // get message len
	nLen = nNoOfLeftOverBytes;
	for (j = nStart; j <= nEnd; j++)
	{
		line = (byte[]) v.elementAt (j);
		nLen = nLen + line.length;
        }

        j = 0;
        buffer = new byte[(int) (nLen * 1.1)];

	if (buffer == null)
	    throw new MIMEException( szERROR_OUT_OF_MEMORY );

        nLen = 0;
	/* get each line */
	for ( ii = nStart; ii <= nEnd; ii++ )
	{
		line = (byte[]) v.elementAt(ii);

		if (ii == nStart && nNoOfLeftOverBytes > 0)
		{
		    System.arraycopy (leftOverBytes, 0, buffer, 0, nNoOfLeftOverBytes);
		    nLen += nNoOfLeftOverBytes;
		}

        	if (line == null || line.length == 0)
            		continue;
	        System.arraycopy (line, 0, buffer, nLen, line.length);
		nLen += line.length;
	}

	written = 0; i=0;
	int read_offset = 0;

	/* first time do it outside the loop */
        while (i < 3 && read_offset < nLen)
        {
		token [i++] =  buffer [read_offset++];
        }

	while (read_offset < nLen || i != 0)
	{
		 while (i < 3 && read_offset < nLen)
		 {
			token [i++] =  buffer [read_offset++];
		 }

		 if (i < 3) /* did not get enough for a token */
		 {
		     if (i == 2 && token [0] == CR && token [1] == LF)
		     {
			 buffer[written++] = token[0];
			 buffer[written++] = token[1];
		         nNoOfLeftOverBytes = 0;
		     }
		     else
		     {
		          for (j=0; j < i; j++)
			      leftOverBytes [j] = token [j];
		          nNoOfLeftOverBytes = i;
		     }

    		     param[0] = nNoOfLeftOverBytes;

		     if (written > 0)
		     {
			retbuf = new byte [written];
	        	System.arraycopy (buffer, 0, retbuf, 0, written);
		        return (retbuf);
		     }
		 }

		i = 0;

	  	if (token [0] == '=')
		{
		  byte c = (byte)0;
		  if ((token[1] >= '0' && token[1] <= '9') ||
		      (token[1] >= 'A' && token[1] <= 'F') ||
		      (token[1] >= 'a' && token[1] <= 'f'))
			c = token[1];
		  else if (token[1] == CR || token[1] == LF)
		  {
		        /* =\n means ignore the newline. */
		        if (token[1] == CR && token[2] == LF)
				;		/* swallow all three chars */
			else
			{
			     read_offset--;	/* put the third char back */
			}
			continue;
		  }
		  else
		  {
		       /* = followed by something other than hex or newline -
			 pass it through unaltered, I guess.
			*/
			if (read_offset > written) { buffer[written++] = token[0]; }
			if (read_offset > written) { buffer[written++] = token[1]; }
			if (read_offset > written) { buffer[written++] = token[2]; }
			continue;
		  }

		  /* Second hex digit */
		  if ((token[2] >= '0' && token[2] <= '9') ||
		      (token[2] >= 'A' && token[2] <= 'F') ||
		      (token[2] >= 'a' && token[2] <= 'f'))
		  	  c = (byte) ((c << 4) | token[2]);
		  else
		  {
			/* We got =xy where "x" was hex and "y" was not, so
			   treat that as a literal "=", x, and y. */
			if (read_offset > written) { buffer[written++] = token[0];}
			if (read_offset > written) { buffer[written++] = token[1];}
			if (read_offset > written) { buffer[written++] = token[2];}
			continue;
		  }

		  buffer[written++] = c;
		}
	  	else
		{
		  buffer[written++] = token[0];

		  token[0] = token[1];
		  token[1] = token[2];
		  i = 2;
		}
	}

        param[0] = 0; // nothing leftover

	if (written > 0)
	{
		retbuf = new byte [written];
		System.arraycopy (buffer, 0, retbuf, 0, written);
		return (retbuf);
	}

	return null;
}

/*
* carson, Jan 8,98
* QuotedPrintable Decodes data from vector, write decoded data to szOutput
*
* params :
*
*	int nStart : starting element in vector
*	int nEndt  : ending element in vector
*	Vector *v  : vector
*	char *szOutput : output buffer
*	int MaxBufferSize : max buffer size
*
* return : number of decoded bytes
*/
protected static byte[] decodeQPVector( Vector v, int nStart, int nEnd, int[] param, byte[] leftOverBytes ) throws MIMEException
{
	byte ch, ch2;
	int i = 0;
	int j = 0;
	int k = 0;
	int	ii, nLen=0;
	byte[] line;
	byte[] output;
	byte[] buffer;
	boolean bAppendLeftOverBytes = false;
	int nNoOfLeftOverBytes;
    int hi = 0, lo = 0, last = 0;
    char nl[] = System.getProperty("line.separator", "\n").toCharArray();

	if ( v == null || param == null )
	    throw new MIMEException( szERROR_BAD_PARAMETER );

    if ( nStart == -1 || nEnd == -1 )
        return null;

	nNoOfLeftOverBytes = param[0];

    // get message len
	for ( j = nStart; j <= nEnd; j++ )
	{
		line = (byte[]) v.elementAt( j );
		nLen = nLen + line.length;
    }

    output = new byte[ (int) ( nLen * 1.1 ) ];
    buffer = new byte[ 128 ];
    j = 0;

	if ( output == null || buffer == null )
	    throw new MIMEException( szERROR_OUT_OF_MEMORY );

	/* get each line */
	for ( ii = nStart; ii <= nEnd; ii++ )
	{
		line = (byte[]) v.elementAt(ii);

		if ( !bAppendLeftOverBytes )
		{
			if ( nNoOfLeftOverBytes > 0 )
			{
			    System.arraycopy( leftOverBytes, 0, buffer, 0, nNoOfLeftOverBytes );
			    System.arraycopy( line, 0, buffer, nNoOfLeftOverBytes, line.length );
			    line = buffer;
			}

			bAppendLeftOverBytes = false;
		}

        if ( line.length == 0 )
            continue;

		/* enumerate through each char */
//		for ( i = 0, ch = line[i]; ch != 0 && i < line.length; ch = line[++i] )

/* ---------------- old ---------------------------
		for ( i = 0; i < line.length; i++ )
		{
		    ch = line[i];

			if ( ( ch >= 33 && ch <= 60 ) || ( ch >= 62 && ch <= 126 ) || ( ch == 9 || ch == 32 ) )
			{
				output[j++] = ch;
			}

			else if ( ch == '=' )
			{
				if ( i + 2 < line.length )
				{
					output[j++] = nConvertHexToDec( line[++i], line[++i] );
				}

				// get more data
				else if ( ii < nEnd )
				{

				    int len2 = line.length - i - 1;
				    System.arraycopy( line, i, buffer, 0, len2 );
        		    line = (byte[]) v.elementAt(++ii);
        		    System.arraycopy( line, 0, buffer, len2, line.length );
        		    line = buffer;
        		    i = -1;
				}

				// save it for next time
				else
				{
					leftOverBytes[0] = ch; nNoOfLeftOverBytes = 1;

					if ( i+1 < line.length )
						leftOverBytes[1] = ch; nNoOfLeftOverBytes = 2;
				}
			}
		}
----------- old ------------------------------------ */


/* -------------------------- new ---------------------*/

    	for ( i = 0; i < line.length; )
    	{
    	    ch = line[i++];

    	    if (ch == '=')
    	    {
        		if ( i < line.length && ( line[i] == '\n' || line[i] == '\r' ) )
        		{					// Rule #5
        		    i++;

        		    if ( i < line.length && line[i-1] == '\r' && line[i] == '\n')
            			i++;
        		}

        		else if ( i + 1 < line.length )					// Rule #1
        		{
        		    hi = Character.digit( (char) line[i], 16);
        			lo = Character.digit( (char) line[i+1], 16);

if ( hi < 0 )
    hi = 0;
if ( lo < 0 )
    lo = 0;

        		    {
        			    ch2 = (byte) (hi << 4 | lo);
            			i += 2;
        		    }

        		    output[j++] = ch2;
        		}

        		// get more data
        		else if ( ii < nEnd )
        		{
				    int len2 = line.length - i;
				    System.arraycopy( line, i, buffer, 0, len2 );
        		    line = (byte[]) v.elementAt(++ii);
        		    System.arraycopy( line, 0, buffer, len2, line.length );
        		    line = buffer;
        		}

        		last = j;
    	    }

    	    else if (ch == '\n'  ||  ch == '\r')	// Rule #4
    	    {
        		if ( ch == '\n' && i >= line.length - 1 && i >= 3 && line[i-3] != '=' )
        		{
               		output[last++] = 10;
                }

        		else if ( ch == '\r' )
        		{
        		    if ( i < line.length && line[i-1] == '\r'  &&  line[i] == '\n')
        		       i++;

               		output[last++] = 13;
                }

/*
        		for ( k = 0; k < nl.length; k++ )
        		{
        		    output[last++] = (byte) nl[k];

        		}
*/
        		j = last;
    	    }

    	    else					// Rule #1, #2
    	    {
        		output[j++] = ch;

        		if (ch != ' '  &&  ch != '\t')		// Rule #3
        		    last = j;
    	    }

//    	    if (j > output.length-5)
//        		output = Util.outputizeArray(output, output.length+500);
    	}
/*-------------------------------------------------------------- */

	}

    param[0] = nNoOfLeftOverBytes;
    param[1] = j;
    buffer = null;

	return output;
}



	/**
     * Base64 Decodes data from InputStream and writes to OutputStream.
     * @param input InputStream that supplies the data to be decoded.
     * @param output OutputStream that accepts the decoded data.
     * @exception MIMEException If a decoding error occurs.
	 * return Number of decoded bytes
     */
	protected static byte[] decodeBase64Vector( Vector v, int nStart, int nEnd, int nMessageLen, int param[] ) throws MIMEException
	{
		int				add_bits;
		int				mask;
		int				out_byte = 0;
		int				out_bits = 0;
		int				byte_pos = 0;
		int				i, j, nLen = 0, nTotalLen = 0;
		byte[]			szLine;
        byte[]          output;

		if ( v == null || param == null )
    	    throw new MIMEException( szERROR_BAD_PARAMETER );

        // restore previous state
        out_byte = param[0];
        out_bits = param[1];

        // invalid range
        if ( nStart == -1 || nEnd == -1 )
            return null;

        // get message len
		for ( j = nStart; j <= nEnd; j++ )
		{
			szLine = (byte[]) v.elementAt( j );
			nTotalLen = nTotalLen + szLine.length;
        }

        output = new byte[ nTotalLen ];

		if ( output == null )
    	    throw new MIMEException( szERROR_OUT_OF_MEMORY );

        // read off data from vector line by line
		for ( j = nStart; j <= nEnd; j++ )
		{
			szLine = (byte[]) v.elementAt( j );
			nLen = szLine.length;

			// process line
			for ( i = 0; i < nLen && szLine[i] != 0; i++ )
			{
				add_bits = Base64DecMap[(char) szLine[i]];

				if (add_bits >= 64)
					continue;

				out_byte = (out_byte << 6) + add_bits;
				out_bits += 6;

				// If the queue has gotten big enough, put into the buffer
				if (out_bits >= 24)
				{
				    if ( byte_pos < nTotalLen - 3 )
				    {
    					output[byte_pos++] = (byte) ( (out_byte & 0xFF0000) >> 16 );
    					output[byte_pos++] = (byte) ( (out_byte & 0x00FF00) >> 8 );
    					output[byte_pos++] = (byte) ( (out_byte & 0x0000FF) );
    					out_bits = 0;
    					out_byte = 0;
    			    }
    			    else
    			    {
    			        System.out.println( "byte_pos = " + byte_pos );
    			        break;
    			    }
				}
			}
		}

        // save current state
        param[0] = out_byte;
        param[1] = out_bits;
        param[2] = byte_pos;

        return output;
	}



	protected static byte[] decodeBase64LeftOverBytes( int out_bits, int out_byte )
	{
		int		mask;
        byte[]  output = new byte[4];
        int     byte_pos = 0;

		/* Handle any bits still in the queue */
		while (out_bits >= 8)
		{
			if (out_bits == 8)
			{
				output[byte_pos++] = (byte) out_byte;
				out_byte = 0;
			}

			else
			{
				mask = out_bits == 8 ? 0xFF : (0xFF << (out_bits - 8));
				output[byte_pos++] = (byte) ( (out_byte & mask) >> (out_bits - 8) );
				out_byte &= ~mask;
			}

			out_bits -= 8;
		}

        return byteSubstring( output, 0, byte_pos );
    }



    /**
     *
     * Base64 Decodes data from InputStream and writes to OutputStream
     * @param input InputStream that supplies the data to be decoded.
     * @param output OutputStream that accepts the decoded data.
     * @exception MIMEException if a decoding error occurs.
     * return number of decoded bytes
     */
	public static void decodeBase64( InputStream input, OutputStream output ) throws MIMEException
	{
		int				add_bits;
		int				mask;
		int				out_byte = 0;
		int				out_bits = 0;
		int				byte_pos = 0;
		int				i, nLen;

		if ( input == null || output == null )
    	    throw new MIMEException( szERROR_BAD_PARAMETER );

        try
        {
    		/* Queue up relevant bits */
    		for ( i = 0, nLen = input.available(); i < nLen; i++ )
    		{
      			add_bits = Base64DecMap[(char) input.read() ];

    			if (add_bits >= 64)
    				continue;

    			out_byte = (out_byte << 6) + add_bits;
    			out_bits += 6;

    			/* If the queue has gotten big enough, put into the buffer */
    			if (out_bits == 24)
    			{
    				output.write( (byte) ( (out_byte & 0xFF0000) >> 16 ) );
    				output.write( (byte) ( (out_byte & 0x00FF00) >> 8 ) );
    				output.write( (byte) ( (out_byte & 0x0000FF) ) );

    				byte_pos =+ 3;
    				out_bits = 0;
    				out_byte = 0;
    			}
    		}

    		/* Handle any bits still in the queue */
    		while ( out_bits >= 8 )
    		{
    			if (out_bits == 8)
    			{
    				output.write( (byte) out_byte );
    				byte_pos++;
    				out_byte = 0;
    			}

    			else
    			{
    				mask = out_bits == 8 ? 0xFF : (0xFF << (out_bits - 8));
    				output.write( (byte) ( (out_byte & mask) >> (out_bits - 8) ) );
    				byte_pos++;
    				out_byte &= ~mask;
    			}

    			out_bits -= 8;
    		}
    	}
        catch ( IOException e )
        {
            throw new MIMEException ( e.getMessage() );
        }

        return;
	}



    /* ------------------- utility functions ------------------
    * determine if both strings are the same based on the length of the second string
    * case insensitive
    * clee, oct 6,97
    * return TRUE if equale
    */
    protected static boolean bStringEquals( String s1, String s2 )
    {
        char ch;

    	if ( s1 != null && s2 != null )
    	{
    		int len = s2.length();

    		if ( len > s1.length() )
    		    return false;

    		for ( int i = 0; i < len; i++ )
    		{
    		    ch = s2.charAt(i);

    		    // lowercase
    		    if ( ch >= 97 )
    		    {
    		        if ( s1.charAt(i) != ch && s1.charAt(i) != ( ch - 32 ) )
       		           return false;
                }
    		    else if ( s1.charAt(i) != ch && s1.charAt(i) != ( ch + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }


    /* ------------------- utility functions ------------------
    * determine if both strings are the same based on the length of the second string
    * case insensitive
    * clee, oct 6,97
    * return TRUE if equale
    */
    protected static boolean bStringEquals( byte s1[], String s2 )
    {
        char ch;

    	if ( s1 != null && s2 != null )
    	{
    		int len = s2.length();

    		if ( len > s1.length )
    		    return false;

    		for ( int i = 0; i < len; i++ )
    		{
    		    ch = s2.charAt(i);

    		    // lowercase
    		    if ( ch >= 97 )
    		    {
    		        if ( s1[i] != ch && s1[i] != ( ch - 32 ) )
           		        return false;
                }
    		    else if ( s1[i] != ch && s1[i] != ( ch + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }

    protected static boolean bStringEqualsLtrim( byte s1[], String s2 )
    {
        char ch;

    	if ( s1 != null && s2 != null )
    	{
		int j=0;

    		int len = s2.length();

    		if ( len > s1.length )
    		    return false;

    		for (j = 0; j < len; j++)
		{
			if (s1[j] == '\n' || s1[j] == '\r' || s1[j] == '\t' || s1[j] == ' ');
			else
			{
			     break;
			}
		}

		if (j > 0)
		{
			return (bStringEquals(j+1, s1, s2));
		}

    		for ( int i = 0; i < len; i++ )
    		{
    		    ch = s2.charAt(i);

    		    // lowercase
    		    if ( ch >= 97 )
    		    {
    		        if ( s1[i] != ch && s1[i] != ( ch - 32 ) )
           		        return false;
                }
    		    else if ( s1[i] != ch && s1[i] != ( ch + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }


    /* ------------------- utility functions ------------------
    * determine if both strings are the same based on the length of the second string
    * case insensitive
    * clee, oct 6,97
    * return TRUE if equale
    */
    protected static boolean bStringEquals( byte s1[], int s1_len, String s2 )
    {
        char ch;

    	if ( s1 != null && s2 != null )
    	{
    		int len = s2.length();

    		if ( len > s1_len )
    		    return false;

    		for ( int i = 0; i < len; i++ )
    		{
    		    ch = s2.charAt(i);

    		    // lowercase
    		    if ( ch >= 97 )
    		    {
    		        if ( s1[i] != ch && s1[i] != ( ch - 32 ) )
           		        return false;
                }
    		    else if ( s1[i] != ch && s1[i] != ( ch + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }


    /* ------------------- utility functions ------------------
    * determine if both strings are the same based on the length of the second string
    * case insensitive
    * clee, oct 6,97
    * return TRUE if equale
    */
    protected static boolean bStringEquals( int offset, byte s1[], String s2 )
    {
        char ch;

    	if ( s1 != null && s2 != null )
    	{
    		int len = s2.length();

    		if ( len > s1.length )
    		    return false;

    		for ( int i = 0; i < len; i++ )
    		{
    		    ch = s2.charAt(i);

    		    // lowercase
    		    if ( ch >= 97 )
    		    {
    		        if ( s1[i+offset] != ch && s1[i+offset] != ( ch - 32 ) )
           		        return false;
                }
    		    else if ( s1[i+offset] != ch && s1[i+offset] != ( ch + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }


    /* ------------------- utility functions ------------------
    * determine if both strings are the same based on the length of the second string
    * case insensitive
    * clee, oct 6,97
    * return TRUE if equale
    */
    protected static boolean bStringEquals( byte s1[], byte s2[] )
    {
        char ch;

    		if ( s2.length > s1.length )
    		    return false;

    	if ( s1 != null && s2 != null )
    	{
    		for ( int i = 0; i < s2.length; i++ )
    		{
    		    // lowercase
    		    if ( s2[i] >= 97 )
    		    {
    		        if ( s1[i] != s2[i] && s1[i] != ( s2[i] - 32 ) )
           		        return false;
                }
    		    else if ( s1[i] != s2[i] && s1[i] != ( s2[i] + 32 ) )
    		        return false;
    		}

    		return true;
    	}

    	return false;
    }

    protected static byte[] stringToByte( String s ) { return s.getBytes(); }


    protected static byte[] stringToByte( char[] s, int len )
    {
        byte b[] = new byte[ len ];

        for ( int i = 0; i < len; i++ )
            b[i] = (byte) s[i];

        return b;
    }

    protected static byte[] byteSubstring( byte[] s, int len )
    {
        byte b[] = new byte[ len ];

        for ( int i = 0; i < len; i++ )
            b[i] = s[i];

        return b;
    }

    protected static byte[] byteSubstring( byte[] s, int offset, int len )
    {
        byte b[] = new byte[ len ];

        for ( int i = 0, j = offset; i < len && j < s.length; i++, j++ )
            b[i] = s[j];

        return b;
    }


//----------------------------------------------------------------------------------------

} // End class
