/* * The contents of this file are subject to the Netscape Public * License Version 1.1 (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) */ /** * The MIMEParser class represents a MIME parser. * This generic parser takes an email message encoded in * MIME and decodes it into a set of usable structures. * @author Carson Lee * @version 1.0 * Dec 17,1997 */ package netscape.messaging.mime; import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Vector; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.util.Enumeration; import netscape.messaging.mime.MIMEMessage; import netscape.messaging.mime.MIMEBasicPart; import netscape.messaging.mime.MIMEMultiPart; import netscape.messaging.mime.MIMEMessagePart; import netscape.messaging.mime.MIMEHelper; import netscape.messaging.mime.MIMEDataSink; import netscape.messaging.mime.log; public final class MIMEParser { // used internally by parser to track information final class mimeInfo { int m_type; // content type byte[] m_name; // header name byte[] m_value; // header value Vector m_param; // header param mimeInfo( int type1, byte[] name1, byte[] value1 ) { m_type = type1; m_name = name1; m_value = value1; m_param = new Vector(); } mimeInfo() { m_type = 0; m_name = null; m_value = null; m_param = new Vector(); } mimeInfo( int type1, byte[] name1 ) { m_type = type1; m_name = name1; m_param = new Vector(); } } final static int MIME_INFO_CONTENT_TYPE = 1; final static int MIME_INFO_CONTENT_SUBTYPE = 2; final static int MIME_INFO_CONTENT_TRANSFER_ENCODING = 3; final static int MIME_INFO_CONTENT_DISPOSITION = 4; final static int MIME_INFO_CONTENT_ID = 5; final static int MIME_INFO_CONTENT_DESCRIPTION = 6; final static int MIME_INFO_CONTENT_BASE = 7; final static int MIME_INFO_MIME_VERSION = 8; final static private int MIME_INFO = 31; final static private int MIME_HEADER = 32; final static private int MIME_XHEADER = 33; final static private int MIME_MESSAGE_DATA = 34; final static private int MIME_PARAM = 35; final static private int MIME_BOUNDARY = 36; final static private int MIME_CRLF = 37; final static private int MIME_UNINITIALIZED = -88; final static private int MIME_CONTENT_PARSE_ALL = -99; final static private int NSMAIL_ERR_INVALIDPARAM = -1; final static private int NSMAIL_ERR_OUTOFMEMORY = -2; final static private int NSMAIL_OK = 0; final static private int NOT_A_BOUNDARY = 21; final static private int START_BOUNDARY = 22; final static private int END_BOUNDARY = 23; final static int BUFFER_SIZE = 2048; final static int SUBTYPE_RFC822 = 1; final static int SUBTYPE_EXTERNAL_BODY = 2; final static byte[] mb_MultiPart = "Multipart".getBytes(); //final static byte[] mb_MessagePart = "Messagepart".getBytes(); final static byte[] mb_MessagePart = "Message".getBytes(); final static byte[] mb_TextPart = "Text".getBytes(); final static byte[] mb_AudioPart = "Audio".getBytes(); final static byte[] mb_VideoPart = "Video".getBytes(); final static byte[] mb_ImagePart = "Image".getBytes(); final static byte[] mb_ApplPart = "Application".getBytes(); private static final byte[] CRLF = "\r\n".getBytes(); private Vector m_messageData; // text buffer private Vector m_mimeInfo; // message info private Vector m_msgParts; // message info private MIMEMessage m_mimeMessage; // entire decoded mime message private int m_nMessageType; // message type, ie: message, basicpart, etc. private Object m_currentMessage; // current message being processed private MIMEBasicPart m_currentBasicPart; private MIMEMultiPart m_currentMultiPart; private MIMEMessagePart m_currentMessagePart; private int m_nCurrentMessageType; // current message type private boolean m_bStartData; // TRUE if start of data private int m_emptyLineNo; // TRUE if current line is empty private MIMEDataSink m_dataSink; // callback datasink private byte[] m_leftoverBuffer; // leftover buffer private byte[] m_inputBuffer; // inputbuffer private int m_leftoverBytes; // left over bytes from previous parse private int m_out_byte; // state info private int m_out_bits; // state info private boolean m_bParseEntireFile; // false if parsing in chunks private byte[] m_QPLeftoverBuffer; // leftover buffer private int m_QPLeftoverBytes; // left over bytes from previous parse private Vector m_currentParent; // current message parent private MIMEMessage m_currentMimeMessage; // current mime message private boolean m_bDecodeData = true; private boolean m_bLocalStorage = true; private MIMEMessage m_nextMimeMessage; private int m_lastBoundry; private Vector m_mimeInfoQueue; private Vector m_headerQueue; private boolean m_qp = false; private boolean m_readCR = false; private Object m_headerParent; private Object m_nextHeaderParent; private String m_previousHeaderName; private int m_messagePartSubType; private boolean m_fSeenBoundary = false; private boolean m_fEndMessageHeader = false; // exposed routines ------------------------------------------------------------ /** * Default Constructor. Use with static parsing * when the entire message to be parsed is available. * @see #parseEntireMessage * */ public MIMEParser() { this( (MIMEMessage) null); } /** * Parses an entire message from an input stream. * @param input User's input stream; source of message to parse. * @return New parsed MIMEMessage object. * @exception MIMEException If any parameter is NULL. */ public MIMEMessage parseEntireMessage( InputStream input ) throws MIMEException { init( null ); return (MIMEMessage) parseMimeMessage( input, MIME_CONTENT_PARSE_ALL ); } // internal routines ------------------------------------------------------------ /** * @param dataSink User's datasink for callbacks. Can not be null. * @return New MIMEParser object * @exception none */ protected MIMEParser (MIMEDataSink dataSink, boolean decodeData, boolean localStorage) throws MIMEException { this( (MIMEMessage) null ); if ( dataSink == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); m_dataSink = dataSink; m_bParseEntireFile = false; m_bDecodeData = decodeData; m_bLocalStorage = localStorage; } /** * Begin parse / reset parser. * Only used for callbacks * * @author Carson Lee * @version %I%, %G% * * @param none * @return none * @exception none */ protected void beginParse() throws MIMEException { m_leftoverBytes = 0; m_QPLeftoverBytes = 0; m_out_byte = 0; m_out_bits = 0; if ( m_dataSink == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); reInit (null); //if ( m_bLocalStorage ) // m_mimeMessage.setUserObject( m_dataSink.startMessage( m_mimeMessage ) ); //else m_mimeMessage.setUserObject( m_dataSink.startMessage() ); } /** * Parse incoming data. Only used for callbacks * * @author Carson Lee * @version %I%, %G% * * @param input User's inputstream. Data will be read off this inputstream. * @return none * @exception MIMEException */ protected void parse( InputStream input ) throws MIMEException { parseMimeMessage( input, MIME_CONTENT_PARSE_ALL ); } /** * End parse. Tell parse there is no more data. * Used only for callbacks. * * @author Carson Lee * @version %I%, %G% * * @param none * @return none * @exception MIMEException */ protected void endParse() throws MIMEException { decodeDataBuffer(); if ( m_dataSink == null ) return; MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; // take care of leftover bytes if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) { MIMEBasicPart mimeBasicPart = (MIMEBasicPart) m_currentMessage; /* base64 */ if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 && m_out_bits > 0 ) { byte[] decodedBuffer = MIMEHelper.decodeBase64LeftOverBytes( m_out_bits, m_out_byte ); saveBodyData( new ByteBuffer( decodedBuffer ), decodedBuffer.length, mimeBasicPart ); } // will never happen /* else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP && m_QPLeftoverBytes > 0 ) { int[] param = new int[2]; param[0] = m_QPLeftoverBytes; byte[] decodedBuffer = MIMEHelper.decodeQPVector( m_messageData, mimeBasicPart.getStartMessageDataIndex(), mimeBasicPart.getEndMessageDataIndex(), param, m_QPLeftoverBuffer ); if ( decodedBuffer != null ) { m_QPLeftoverBytes = param[0]; param = null; saveBodyData( new ByteBuffer( decodedBuffer ), param[1], mimeBasicPart ); } } */ m_dataSink.endBasicPart( m.getUserObject() ); } else if ( m_nCurrentMessageType == MIMEMessage.MULTIPART ) { m_dataSink.endMultiPart( m.getUserObject() ); } else if ( m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) { m_dataSink.endMessagePart( m.getUserObject() ); m_msgParts.removeElement (m); } // systematically invoke end* callback on all pending parts. int nCurrentParentType = getCurrentParentType(); while (nCurrentParentType != MIME_UNINITIALIZED) { switch (nCurrentParentType) { case MIMEMessage.MULTIPART: //MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; //m_dataSink.endMultiPart (mp.getUserObject()); // ingore. taken care of at END_BOUNDARY! break; case MIMEMessage.MESSAGEPART: MIMEMessagePart msgP = (MIMEMessagePart) m_currentParent.lastElement(); String cst = msgP.getContentSubType(); if (cst.equalsIgnoreCase ("rfc822")) { // if it is rfc822. do endMessage() also MIMEMessage mmm = msgP.getMessage(); m_dataSink.endMessage (mmm.getUserObject()); } m_dataSink.endMessagePart (msgP.getUserObject()); m_msgParts.removeElement (msgP); break; } m_currentParent.removeElementAt (m_currentParent.size() - 1); nCurrentParentType = getCurrentParentType(); //System.out.println ("placeyy> currentParentType = " + nCurrentParentType); } // now check for any leftover ones still! int partCount = m_msgParts.size(); while (partCount > 0) { MIMEMessagePart msgP = (MIMEMessagePart) m_msgParts.elementAt(partCount-1); String cst = msgP.getContentSubType(); if (cst.equalsIgnoreCase ("rfc822")) { // if it is rfc822. do endMessage() also MIMEMessage mmm = msgP.getMessage(); m_dataSink.endMessage (mmm.getUserObject()); } m_dataSink.endMessagePart (msgP.getUserObject()); m_msgParts.removeElement (msgP); partCount--; } m_dataSink.endMessage( m_mimeMessage.getUserObject() ); //checkForEmptyMessages( m_mimeMessage ); } /** * General constructor * * @author Carson Lee * @version %I%, %G% * * @param m User's MimeMessage object. Will populate data into this object. * @return none * @exception none */ protected MIMEParser( MIMEMessage m ) { init( m ); } /** * General constructor * * @author Carson Lee * @version %I%, %G% * * @param m User's MimeMessage object. Will populate data into this object. * @return none * @exception none */ protected void init( MIMEMessage m ) { m_messageData = new Vector(); m_mimeInfo = new Vector(); m_msgParts = new Vector(); m_mimeMessage = m == null ? new MIMEMessage() : m; m_bStartData = false; m_mimeMessage.m_parsedPart = 1; m_nMessageType = MIME_UNINITIALIZED; m_nCurrentMessageType = MIME_UNINITIALIZED; m_currentMessage = null; m_dataSink = null; m_leftoverBuffer = new byte[2048]; m_inputBuffer = new byte[2048]; m_out_byte = 0; m_out_bits = 0; m_currentBasicPart = null; m_currentMultiPart = null; m_currentMessagePart = null; m_bParseEntireFile = true; m_currentParent = new Vector(); m_currentMimeMessage = m_mimeMessage; m_currentParent.addElement( m_mimeMessage ); m_nextMimeMessage = null; m_lastBoundry = START_BOUNDARY; m_mimeInfoQueue = new Vector(); m_headerQueue = new Vector(); m_QPLeftoverBuffer = new byte[2]; m_qp = false; m_readCR = false; m_headerParent = m_mimeMessage; m_nextHeaderParent = null; m_previousHeaderName = null; m_messagePartSubType = SUBTYPE_RFC822; m_fEndMessageHeader = false; } protected void reInit( MIMEMessage m ) { m_messageData = new Vector(); m_mimeInfo = new Vector(); m_msgParts = new Vector(); m_mimeMessage = m == null ? new MIMEMessage() : m; m_bStartData = false; m_mimeMessage.m_parsedPart = 1; m_nMessageType = MIME_UNINITIALIZED; m_nCurrentMessageType = MIME_UNINITIALIZED; m_currentMessage = null; //m_dataSink = null; m_leftoverBuffer = new byte[2048]; m_inputBuffer = new byte[2048]; m_out_byte = 0; m_out_bits = 0; m_currentBasicPart = null; m_currentMultiPart = null; m_currentMessagePart = null; m_bParseEntireFile = true; m_currentParent = new Vector(); m_currentMimeMessage = m_mimeMessage; m_currentParent.addElement( m_mimeMessage ); m_nextMimeMessage = null; m_lastBoundry = START_BOUNDARY; m_mimeInfoQueue = new Vector(); m_headerQueue = new Vector(); m_QPLeftoverBuffer = new byte[2]; m_qp = false; m_readCR = false; m_headerParent = m_mimeMessage; m_nextHeaderParent = null; m_previousHeaderName = null; m_messagePartSubType = SUBTYPE_RFC822; m_fEndMessageHeader = false; } /** * Parse user inputstream and return a populated data structure * * @author Carson Lee * @version %I%, %G% * * @param input User's inputstream * @param nMessageType
    *               MIME_CONTENT_PARSE_ALL      if you want it to parse the entire message
    *               MIMEMessage.BASICPART       for basicPart
    *               MIMEMessage.MULTIPART       for multiPart
    *               MIMEMessage.MESSAGEPART     for messagePart
* * @return
MIMEMessage object if nMessageType = MIME_CONTENT_PARSE_ALL
    *MIMEBasicPart object if nMessageType = MIMEMessage.BASICPART;
    *MIMEMultiPart object if nMessageType = MIMEMessage.MULTIPART;
    *MIMEMessagePart object if nMessageType = MIMEMessage.MESSAGEPART; 
* @exception MIMEException */ protected Object parseMimeMessage( InputStream input, int nMessageType ) throws MIMEException { byte ch = 0; String s; int type, lineLen, messageLen; Object returnObject = null; int i = 0, j = 0, ii; boolean lastLine = false; byte[] swap; boolean bMoreData = true; if ( input == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); type = MIME_HEADER; try { messageLen = input.available(); for ( i = 0, j = m_leftoverBytes; bMoreData; ) { lineLen = input.read( m_inputBuffer ); if ( lineLen < m_inputBuffer.length ) bMoreData = false; if ( lineLen < 0 ) break; i = i + lineLen; for ( ii = 0; ii < lineLen; ii++ ) { ch = m_inputBuffer[ii]; if ( ch == -1 ) break; // eat it if ( !m_readCR && ch == '\r' ) continue; else if ( ch == '\n' ) { // look for continuation if ( MIMEHelper.bStringEquals( m_leftoverBuffer, "content-" ) ) { int jj = j > 0 ? j - 1 : 0; char ch2; for ( ch2 = (char) m_leftoverBuffer[jj]; jj > 0 && Character.isWhitespace( ch2 ); ch2 = (char) m_leftoverBuffer[--jj] ) ; // implement look ahead logic if ( ( m_inputBuffer[ii+1] == ' ' || m_inputBuffer[ii+1] == '\t' ) && !MIMEHelper.bStringEquals( m_leftoverBuffer, "content-transfer-encoding" ) ) { // System.out.println("continue = [" + new String( m_leftoverBuffer, 0, j) + "]" ); m_leftoverBuffer[j++] = ch; continue; } } if ( m_readCR ) m_leftoverBuffer[j++] = ch; type = parseLine( m_leftoverBuffer, j, type, lastLine ); j = 0; continue; } m_leftoverBuffer[j++] = ch; } } } catch ( IOException e ) { // rethrow to a common exception handler throw new MIMEException( e.getMessage() ); } // handle lastline if ( m_dataSink == null && j != 0 ) { type = parseLine( m_leftoverBuffer, j, type, lastLine ); } m_leftoverBytes = j; if ( m_mimeMessage != null && m_dataSink == null ) checkForEmptyMessages( m_mimeMessage ); if ( m_mimeMessage != null && m_dataSink == null ) { decodeDataBuffer(); if ( nMessageType == MIME_CONTENT_PARSE_ALL ) returnObject = m_mimeMessage; else if ( nMessageType == MIMEMessage.BASICPART || nMessageType == MIMEMessage.MULTIPART || nMessageType == MIMEMessage.MESSAGEPART ) returnObject = m_mimeMessage.getBody(); } // send data out right now using callbacks else if ( m_dataSink != null && m_bStartData ) { decodeDataBuffer(); } return returnObject; } /** * Save decoded data * * @author Carson Lee * @version %I%, %G% * * @param byteBuffer data buffer * @param len number of bytes in the data buffer to save * @param mimeBasicPart save data to this mimeBasicPart * @return none * @exception MIMEException */ private void saveBodyData( ByteBuffer byteBuffer, int len, MIMEBasicPart mimeBasicPart ) throws MIMEException { if ( byteBuffer == null || mimeBasicPart == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); if ( m_bLocalStorage ) { if ( m_bParseEntireFile ) { mimeBasicPart.setBodyData( byteBuffer ); mimeBasicPart.setMessageDataLen( len ); } else { ByteBuffer b = mimeBasicPart.getDataBuf(); if ( b == null ) { mimeBasicPart.setBodyData( byteBuffer ); mimeBasicPart.setMessageDataLen( len ); } else { b.append( byteBuffer ); mimeBasicPart.setMessageDataLen( mimeBasicPart.getMessageDataLen() + len ); } } } if (m_dataSink != null) { if (len>0) m_dataSink.bodyData (mimeBasicPart.getUserObject(), byteBuffer.getInputStream(), len); if (mimeBasicPart.m_endData == true) m_dataSink.endBasicPart (mimeBasicPart.getUserObject()); if (mimeBasicPart.m_parentContainer != null) { MIMEMultiPart mp = mimeBasicPart.m_parentContainer; if (mp.m_endPart == true) m_dataSink.endMultiPart (mp.getUserObject()); else; // ignore taken care of at end_boundary } } else mimeBasicPart.setDecodedData( true ); } /** * Decode message Data * * @author Carson Lee * @version %I%, %G% * * @param none * @return none * @exception MIMEException */ private void decodeDataBuffer() throws MIMEException { int i; int type2; int len = 0; int nIndex; boolean finished = false; mimeInfo mi; MIMEMessagePart mimeMessagePart; MIMEBasicPart mimeBasicPart; MIMEMultiPart mimeMultiPart; ByteBuffer newBuffer = null; byte decodedBuffer[] = null; byte line[]; int param[]; boolean bDecoded = false; int nStart; int nEnd; // no decoding if (m_nCurrentMessageType != MIMEMessage.BASICPART || m_currentMessage == null) return; mimeBasicPart = (MIMEBasicPart) m_currentMessage; nStart = mimeBasicPart.getStartMessageDataIndex(); nEnd = mimeBasicPart.getEndMessageDataIndex(); // already being here if (mimeBasicPart.getDecodedData()) return; /* base64 */ if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 && m_bDecodeData ) { param = new int[3]; param[0] = m_out_byte; param[1] = m_out_bits; decodedBuffer = MIMEHelper.decodeBase64Vector( m_messageData, nStart, nEnd, mimeBasicPart.getMessageDataLen(), param ); if ( decodedBuffer != null ) { m_out_byte = param[0]; m_out_bits = param[1]; len = param[2]; param = null; saveBodyData( new ByteBuffer( decodedBuffer, len ), len, mimeBasicPart ); } bDecoded = true; } /* quoted printable */ else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP && m_bDecodeData ) { param = new int[2]; param[0] = m_QPLeftoverBytes; decodedBuffer = MIMEHelper.decodeQPVector( m_messageData, nStart, nEnd, param, m_QPLeftoverBuffer ); if ( decodedBuffer != null ) { m_QPLeftoverBytes = param[0]; len = param[1]; param = null; saveBodyData( new ByteBuffer( decodedBuffer ), len, mimeBasicPart ); } bDecoded = true; } /* base64 without decodeing */ else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 ) { if ( nEnd >= nStart && nStart != MIMEBasicPart.UNINITIALIZED ) { // get message size for (i = nStart, len = 0; i <= nEnd; i++) { line = (byte[]) m_messageData.elementAt( i ); //len = len + line.length; len = len + line.length + 2; // for CRLF } newBuffer = new ByteBuffer( len ); if ( newBuffer != null ) { for ( i = nStart; i <= nEnd; i++ ) { line = (byte[]) m_messageData.elementAt( i ); newBuffer.append( line ); newBuffer.append( CRLF ); } } saveBodyData( newBuffer, len, mimeBasicPart ); bDecoded = true; } } /* plain buffer creation */ else { boolean fQP = false; if (mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP) fQP = true; if ( nEnd >= nStart && nStart != MIMEBasicPart.UNINITIALIZED ) { // get message size for ( i = nStart, len = 0; i <= nEnd; i++ ) { line = (byte[]) m_messageData.elementAt( i ); //len = len + line.length + 1; len = len + line.length; if (!fQP) len = len + 2; } newBuffer = new ByteBuffer( len ); byte b = '\n'; byte r = '\r'; if ( newBuffer != null ) { for ( i = nStart; i <= nEnd; i++ ) { line = (byte[]) m_messageData.elementAt( i ); newBuffer.append( line ); if (!fQP) { newBuffer.append(r); newBuffer.append(b); } } } saveBodyData( newBuffer, len, mimeBasicPart ); bDecoded = true; } } if ( bDecoded ) { if ( m_dataSink == null ) mimeBasicPart.setDecodedData( true ); else { // reset mimeBasicPart.setStartMessageDataIndex( MIMEBasicPart.UNINITIALIZED ); mimeBasicPart.setEndMessageDataIndex( MIMEBasicPart.UNINITIALIZED ); } } } /** * Parse one line of data * * @author Carson Lee * @version %I%, %G% * * @param s input line * @param len length of line * @param type line type for the previous line
    *   MIME_HEADER     header line
    *   MIME_INFO       mime info line
* * @return line type for current line * @exception MIMEException */ private int parseLine( byte s[], int len, int type, boolean lastLine ) throws MIMEException { int i; int type2; int nIndex; boolean finished = false; mimeInfo mi; MIMEMessagePart mimeMessagePart; MIMEBasicPart mimeBasicPart; MIMEMultiPart mimeMultiPart; MIMEMessage mimeMessage; MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; if ( s == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); // debug /* //if ( MIMEHelper.bStringEquals( s, 7, "Subject" ) ) //if ( MIMEHelper.bStringEquals (s, 13, "--mail.sleepy")) String ss = new String(s,0,len); System.out.println (ss); boolean fRightHdr = false; //if (MIMEHelper.bStringEquals (s, "X-Original-In-reply-to")) if (MIMEHelper.bStringEquals (s, "Content-Type: message/rfc822")) { ss = new String(s,0,len); fRightHdr = true; } else if (MIMEHelper.bStringEquals (s, "Content-type: message/external-body")) { ss = new String(s,0,len); fRightHdr = false; } */ /* ignore additional header info */ if ( ( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER )&& ( len > 0 &&( s[0] == '\t' || s[0] == ' ' ) ) ) { if (!(m_nCurrentMessageType == MIMEMessage.BASICPART && m_bStartData == true)) { Header h[]; // continuation headers if ( m_headerParent instanceof MIMEBasicPart ) { mimeBasicPart = (MIMEBasicPart) m_currentMessage; mimeBasicPart.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); } else if ( m_headerParent instanceof MIMEMessagePart ) { // Ignore!! // mimeMessagePart = (MIMEMessagePart) m_currentMessage; // h = mimeMessagePart.getAllHeaders(); // mimeMessagePart.addHeader( m_previousHeaderName, new String( s, 0, len ) ); //m_currentMimeMessage.addHeader( m_previousHeaderName, new String( s, 0, len ) ); m_currentMimeMessage.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); } else { if (m_previousHeaderName == null) { appToLastHdrOnQueue (MIMEHelper.byteSubstring( s, 0, len)); } else { m_currentMimeMessage.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); if (m_dataSink != null) { m_dataSink.addHeader (m_currentMimeMessage.getUserObject(), m_previousHeaderName.getBytes(), MIMEHelper.byteSubstring(s, 0, len)); } } } /* if ( h != null && h[ h.length - 1 ] != null ) { h[ h.length - 1 ].setValue( h[ h.length - 1 ].getValue() + "\n" + new String( s,0,len ) ); } */ return type; } } else if (( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER ) && m_dataSink != null) { // a new header with same name m_previousHeaderName = null; } if ( len == 0 ) { m_emptyLineNo++; /* end of header callback (only on top level MimeMessage) */ if (m_emptyLineNo == 1 && m_fEndMessageHeader == false) { m_fEndMessageHeader = true; if (m_dataSink != null) m_dataSink.endMessageHeader (m_mimeMessage.getUserObject()); } } if ( len == 0 && !m_bStartData ) { int nCurrentParentType = getCurrentParentType(); if ( m_qp && m_emptyLineNo == 1 ) m_readCR = true; //if ( m_emptyLineNo == 2 ) if (m_emptyLineNo >= 2 && !(m_nCurrentMessageType == MIMEMessage.MULTIPART && m_fSeenBoundary==false)) { m_bStartData = true; } else if (m_emptyLineNo == 1 && nCurrentParentType == MIMEMessage.MULTIPART && m_fSeenBoundary == true) { if (m_nCurrentMessageType == MIME_UNINITIALIZED) m_bStartData = true; } /*else if (m_emptyLineNo == 1 && m_nCurrentMessageType == MIMEMessage.MULTIPART) This broke mime11 */ else if (m_emptyLineNo > 1 && m_nCurrentMessageType == MIMEMessage.MULTIPART) { //System.out.println ("m_fSeenBoundary=" + m_fSeenBoundary); if (m_fSeenBoundary == true) m_bStartData = true; else { mimeMultiPart = (MIMEMultiPart) m_currentMessage; s[0] = '\r'; s[1] = '\n'; mimeMultiPart.addPreamble (s, 2); return MIME_CRLF; } } else if ( m_emptyLineNo == 1 ) { // change mimeMessage parent if ( m_nextMimeMessage != null ) { // change parent only after first blank line m_currentMimeMessage = m_nextMimeMessage; m_nextMimeMessage = null; } // insert headers from queue if ( m_nCurrentMessageType != MIME_UNINITIALIZED && m_headerQueue.size() > 0 ) { if ( m_headerQueue.size() > 0 ) { for ( i = 0; i < m_headerQueue.size(); i++ ) { mi = (mimeInfo) m_headerQueue.elementAt(i); addHeader( mi.m_name, mi.m_value, false ); } m_headerQueue.removeAllElements(); } } // change header parent if ( m_nextHeaderParent != null && !( m_nextHeaderParent instanceof MIMEMultiPart) ) m_headerParent = m_nextHeaderParent; } } if ( MIMEHelper.bStringEquals( s, len, "content-type" ) ) { if (m_emptyLineNo > 0 && m_nCurrentMessageType != MIMEMessage.MESSAGEPART) m_emptyLineNo = 0; } /* boundary */ if ( ( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER ) && len == 0 && m_nCurrentMessageType != MIME_UNINITIALIZED && m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) { if ( m_nCurrentMessageType == MIMEMessage.MESSAGEPART || m_nCurrentMessageType == MIMEMessage.MULTIPART ) { if ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) type = MIME_HEADER; else { mi = new mimeInfo(); // content type mi.m_type = MIME_INFO; mi.m_name = MIMEHelper.stringToByte( "content-type" ); mi.m_value = MIMEHelper.stringToByte( "text/plain" ); setData( mi ); parseMimeInfo( mi ); m_mimeInfo.addElement( mi ); // content type //mi.m_type = MIME_INFO; //mi.m_name = MIMEHelper.stringToByte( "content-transfer-encoding" ); //mi.m_value = MIMEHelper.stringToByte( "7bit" ); //setData( mi ); //parseMimeInfo( mi ); //m_mimeInfo.addElement( mi ); m_bStartData = true; m_fSeenBoundary = false; } } else if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) { m_bStartData = true; m_fSeenBoundary = false; } if ( m_emptyLineNo == 1 ) return type; } if ( !m_bStartData && len == 0 ) { // Add to preamble if multi-part if (m_nCurrentMessageType == MIMEMessage.MULTIPART) { m_emptyLineNo = 0; mimeMultiPart = (MIMEMultiPart) m_currentMessage; s[0] = '\r'; s[1] = '\n'; mimeMultiPart.addPreamble (s, 2); } return MIME_CRLF; } /* -------------------------- identify line ----------------------- */ type2 = checkForLineType( s, len, m_bStartData ); if (type2 == START_BOUNDARY) { m_fSeenBoundary = true; m_emptyLineNo = 0; if (m_nCurrentMessageType == MIMEMessage.MESSAGEPART && m_messagePartSubType == SUBTYPE_EXTERNAL_BODY) m_bStartData = false; // give the boundary if (m_nCurrentMessageType == MIMEMessage.MULTIPART && m_dataSink != null) { MIMEMultiPart mp = (MIMEMultiPart) m_currentMessage; m_dataSink.boundary (mp.getUserObject(), (mp.getBoundary()).getBytes()); } else if (m_dataSink != null) { int nCurrentParentType = getCurrentParentType(); if (nCurrentParentType == MIMEMessage.MULTIPART) { MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; m_dataSink.boundary (mp.getUserObject(), (mp.getBoundary()).getBytes()); } } } // start of simple basicpart message if ( type == MIME_CRLF && m_nCurrentMessageType == MIME_UNINITIALIZED && m_lastBoundry == START_BOUNDARY && type2 == MIME_MESSAGE_DATA && ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) && m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) { mi = new mimeInfo(); // content type mi.m_type = MIME_INFO; mi.m_name = MIMEHelper.stringToByte( "content-type" ); mi.m_value = MIMEHelper.stringToByte( "text/plain" ); setData( mi ); parseMimeInfo( mi ); m_mimeInfo.addElement( mi ); m_bStartData = true; m_fSeenBoundary = false; if ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) { // insert headers from queue if ( m_headerQueue.size() > 0 ) // if ( m_nCurrentMessageType != MIME_UNINITIALIZED && m_headerQueue.size() > 0 ) { if ( m_nextHeaderParent != null ) m_headerParent = m_nextHeaderParent; for ( i = 0; i < m_headerQueue.size(); i++ ) { mi = (mimeInfo) m_headerQueue.elementAt(i); addHeader( mi.m_name, mi.m_value, false ); } m_headerQueue.removeAllElements(); } // change header parent if ( m_nextHeaderParent != null ) m_headerParent = m_nextHeaderParent; m_emptyLineNo = 0; // reset for headers } } /* start boundary */ if ( !m_bStartData && type2 == START_BOUNDARY ) { unwindCurrentParent( s, false ); m_out_byte = 0; m_out_bits = 0; m_leftoverBytes = 0; m_QPLeftoverBytes = 0; m_emptyLineNo = 0; m_nCurrentMessageType = MIME_UNINITIALIZED; m_lastBoundry = START_BOUNDARY; m_fSeenBoundary = true; m_qp = false; m_readCR = false; m_messagePartSubType = SUBTYPE_RFC822; return MIME_INFO; } /* end boundary */ else if ( type2 == END_BOUNDARY || type2 == START_BOUNDARY ) { if ( m_dataSink != null ) { switch( m_nCurrentMessageType ) { case MIMEMessage.BASICPART: //m_dataSink.endBasicPart( m.getUserObject() ); break; ((MIMEBasicPart)m).m_endData = true; break; case MIMEMessage.MULTIPART: if (type2 == END_BOUNDARY) m_dataSink.endMultiPart (m.getUserObject()); break; case MIMEMessage.MESSAGEPART: m_dataSink.endMessagePart (m.getUserObject()); m_msgParts.removeElement (m); break; } if (type2 == END_BOUNDARY) { int nCurrentParentType = getCurrentParentType(); if (nCurrentParentType == MIMEMessage.MULTIPART) { MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; if (m_nCurrentMessageType != MIMEMessage.BASICPART) m_dataSink.endMultiPart (mp.getUserObject()); else { mp.m_endPart = true; ((MIMEBasicPart)m).m_parentContainer = mp; } } //else if (m_nCurrentMessageType == MIMEMessage.MULTIPART) //{ // MIMEMultiPart mp = (MIMEMultiPart) m_currentMessage; // m_dataSink.endMultiPart (mp.getUserObject()); // System.out.println ("at END_BOUNDARY() nCurrentParentType= " + nCurrentParentType); // System.out.println ("at END_BOUNDARY() m_nCurrentMessageType= " + m_nCurrentMessageType); //} } } if ( type2 == END_BOUNDARY ) { unwindCurrentParent( s, true ); m_lastBoundry = END_BOUNDARY; m_fSeenBoundary = false; } else m_lastBoundry = START_BOUNDARY; m_emptyLineNo = 0; m_bStartData = false; decodeDataBuffer(); m_nCurrentMessageType = MIME_UNINITIALIZED; m_qp = false; m_readCR = false; m_messagePartSubType = SUBTYPE_RFC822; return type2; } /* message data */ else if ( m_bStartData ) { // clone byte sss[] = new byte[len]; System.arraycopy( s, 0, sss, 0, len ); m_messageData.addElement( sss ); nIndex = m_messageData.size() - 1; if (m_nCurrentMessageType == MIME_UNINITIALIZED) { int nCurrentParentType = getCurrentParentType(); //System.out.println ("m_nCurrentMessageType== UNINIT"); //System.out.println ("nCurrentParentType= " + nCurrentParentType); if (nCurrentParentType == MIMEMessage.MULTIPART) { mi = new mimeInfo(); // content type mi.m_type = MIME_INFO; mi.m_name = MIMEHelper.stringToByte("content-type"); mi.m_value = MIMEHelper.stringToByte("text/plain"); setData( mi ); parseMimeInfo( mi ); m_mimeInfo.addElement( mi ); } } if ( m_nCurrentMessageType == MIMEMessage.BASICPART && m_currentMessage != null ) { mimeBasicPart = (MIMEBasicPart) m_currentMessage; if ( mimeBasicPart.getStartMessageDataIndex() == MIMEBasicPart.UNINITIALIZED ) mimeBasicPart.setStartMessageDataIndex( nIndex ); mimeBasicPart.setEndMessageDataIndex( nIndex ); if ( lastLine ) { decodeDataBuffer(); if ( m_dataSink != null ) { switch( m_nCurrentMessageType ) { case MIMEMessage.BASICPART: m_dataSink.endBasicPart( m.getUserObject() ); break; case MIMEMessage.MULTIPART: m_dataSink.endMultiPart( m.getUserObject() ); break; case MIMEMessage.MESSAGEPART: m_dataSink.endMessagePart( m.getUserObject() ); m_msgParts.removeElement (m); break; } } } } return MIME_MESSAGE_DATA; } // add preamble if (m_emptyLineNo> 0 && m_nCurrentMessageType == MIMEMessage.MULTIPART && ! m_fSeenBoundary) { mimeMultiPart = (MIMEMultiPart) m_currentMessage; s[len] = '\n'; mimeMultiPart.addPreamble(s, len + 1); return type2; } mi = new mimeInfo(); mi.m_type = type2; boolean startQuote = false; for ( i = 0; i < len && !finished; i++ ) { if ( s[i] == '"' ) startQuote = startQuote ? false : true; if ( s[i] == ':' && !startQuote ) { mi.m_name = MIMEHelper.byteSubstring( s, 0, i ); /* remove junk spaces */ for ( ++i; i < len && s[i] != 0 && Character.isWhitespace( (char) s[i] ); i++ ) ; mi.m_value = MIMEHelper.byteSubstring( s, i, len - i ); if ( mi.m_type == MIME_INFO ) { setData( mi ); if ( m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) { parseMimeInfo( mi ); m_mimeInfo.addElement( mi ); } } else if ( ( mi.m_type == MIME_XHEADER || mi.m_type == MIME_HEADER ) && m_headerParent != null ) { addHeader( mi.m_name, mi.m_value, true ); m_emptyLineNo = 0; } finished = true; } } // add preamble if ( !finished && m_nCurrentMessageType == MIMEMessage.MULTIPART ) { m_emptyLineNo = 0; mimeMultiPart = (MIMEMultiPart) m_currentMessage; s[len] = '\n'; mimeMultiPart.addPreamble( s, len + 1 ); } mi = null; if ( lastLine ) { decodeDataBuffer(); if ( m_dataSink != null ) { switch( m_nCurrentMessageType ) { case MIMEMessage.BASICPART: m_dataSink.endBasicPart( m.getUserObject() ); break; case MIMEMessage.MULTIPART: m_dataSink.endMultiPart( m.getUserObject() ); break; case MIMEMessage.MESSAGEPART: m_dataSink.endMessagePart( m.getUserObject() ); m_msgParts.removeElement (m); break; } } } return type2; } private void appToLastHdrOnQueue (byte value[]) { mimeInfo mi; int i = m_headerQueue.size(); if (i > 0) { mi = (mimeInfo) m_headerQueue.elementAt(i-1); m_headerQueue.removeElementAt (i-1); String val = new String (mi.m_value); val = val.concat ("\r\n"); String s2 = new String (value); val = val.concat (s2); //val.concat (new String (value)); mi.m_value = val.getBytes(); m_headerQueue.addElement( mi ); } } //add cont * headers to the bodypart. used by MessagePart only at this point. private void addContHeader (MIMEBodyPart bp, mimeInfo mi) { try { if (MIMEHelper.bStringEquals (mi.m_name, "content-description")) { bp.setContentDescription (new String (mi.m_value)); } else if (MIMEHelper.bStringEquals (mi.m_name, "content-disposition")) { int disp = translateDispType (mi.m_value); bp.setContentDisposition (disp); } else if (MIMEHelper.bStringEquals (mi.m_name, "content-id")) { bp.setContentID (new String (mi.m_value)); } else if (MIMEHelper.bStringEquals (mi.m_name, "content-md5")) { if (bp instanceof MIMEBasicPart) ((MIMEBasicPart)bp).setContentMD5 (new String (mi.m_value)); } else if (MIMEHelper.bStringEquals (mi.m_name, "content-transfer-encoding")) { //int encoding = mime_translateMimeEncodingType (mi.m_value); //bp.setContentEncoding (encoding); } } catch (Exception e) { // ignore } } /** * add header * * @author Carson Lee * @version %I%, %G% * * @param mi.name header name * @param mi.value header value * @return none * @exception MIMEException */ private void addHeader( byte name[], byte value[], boolean addToQueue ) throws MIMEException { MIMEMessagePart mimeMessagePart; MIMEBasicPart mimeBasicPart; MIMEMessage mimeMessage; mimeInfo mi; if ( name == null || value == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); if (m_nCurrentMessageType == MIME_UNINITIALIZED && m_headerParent == m_mimeMessage) { m_mimeMessage.addRHeader( new String( name), new String( value ) ); if (m_dataSink != null) { if (m_previousHeaderName != null && m_previousHeaderName.equals (new String (name))) m_dataSink.addHeader (m_mimeMessage.getUserObject(), name, value ); else { m_dataSink.header (m_mimeMessage.getUserObject(), name, value ); } } m_previousHeaderName = new String( name ); return; } // insert headers from queue, restore header order if ( m_nCurrentMessageType != MIME_UNINITIALIZED && addToQueue && m_headerQueue.size() > 0 ) { for ( int i = 0; i < m_headerQueue.size(); i++ ) { mi = (mimeInfo) m_headerQueue.elementAt(i); addHeader( mi.m_name, mi.m_value, false ); } m_headerQueue.removeAllElements(); } // add to header queue if ( m_nCurrentMessageType == MIME_UNINITIALIZED && addToQueue ) { mi = new mimeInfo(); if ( mi != null ) { mi.m_name = name; mi.m_value = value; m_headerQueue.addElement( mi ); } } else if ( m_headerParent instanceof MIMEMessage ) { mimeMessage = (MIMEMessage) m_headerParent; mimeMessage.addRHeader( new String( name ), new String( value ) ); if (m_dataSink != null) { if (m_previousHeaderName != null && m_previousHeaderName.equals (new String(name))) m_dataSink.addHeader (mimeMessage.getUserObject(), name, value); else m_dataSink.header (mimeMessage.getUserObject(), name, value); } m_previousHeaderName = new String( name ); } else if ( m_emptyLineNo > 0 || (m_headerParent instanceof MIMEBasicPart && m_fSeenBoundary)) { if ( m_headerParent instanceof MIMEBasicPart ) { mimeBasicPart = (MIMEBasicPart) m_headerParent; mimeBasicPart.setHeader( new String( name ), new String( value ) ); m_previousHeaderName = new String( name ); if ( m_dataSink != null ) { m_dataSink.header( mimeBasicPart.getUserObject(), name, value ); } } else if ( m_headerParent instanceof MIMEMessagePart || m_headerParent instanceof MIMEMultiPart ) { mimeMessage = (MIMEMessage) m_currentMimeMessage; mimeMessage.addRHeader( new String( name), new String( value ) ); if ( m_dataSink != null ) { if (m_previousHeaderName != null && m_previousHeaderName.equals (new String (name))) m_dataSink.addHeader (mimeMessage.getUserObject(), name, value); else { m_dataSink.header(mimeMessage.getUserObject(), name, value); } } m_previousHeaderName = new String( name ); } } } /** * fill in data fields from pMimeinfo * * @author Carson Lee * @version %I%, %G% * * @param mi mimeInfo structure * @return none * @exception MIMEException */ private void setData( mimeInfo mi ) throws MIMEException { MIMEMessagePart mimeMessagePart; MIMEBasicPart mimeBasicPart; byte[] param[]; int contentDisposition; MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; if ( mi == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); param = new byte[1][]; if ( m_messagePartSubType == SUBTYPE_EXTERNAL_BODY && m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) { // add as external body headers mimeMessagePart = (MIMEMessagePart) m_currentMessage; if ( m_emptyLineNo >= 1 ) { mimeMessagePart.setExtBodyHeader (new String( mi.m_name), new String( mi.m_value ) ); m_previousHeaderName = new String( mi.m_name ); } else { addContHeader (mimeMessagePart, mi); //addHeader( mi.m_name, mi.m_value, true ); } } //if ( m_messagePartSubType == SUBTYPE_EXTERNAL_BODY && m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) //{ // // add as external body headers // mimeMessagePart = (MIMEMessagePart) m_currentMessage; // mimeMessagePart.setExtBodyHeader( new String( mi.m_name), new String( mi.m_value ) ); // m_previousHeaderName = new String( mi.m_name ); //} /* create message structure */ else if ( MIMEHelper.bStringEquals( mi.m_name, "content-type" ) ) { newMessageStructure( mi.m_value ); // add from queue if ( m_mimeInfoQueue.size() > 0 && m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) { for ( int i = 0; i < m_mimeInfoQueue.size(); i++ ) { setData( (mimeInfo) m_mimeInfoQueue.elementAt(i) ); } m_mimeInfoQueue.removeAllElements(); } } // no structure created yet else if ( m_currentMessage == null ) return; else if ( MIMEHelper.bStringEquals( mi.m_name, "content-description" ) ) { if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) m_mimeInfoQueue.addElement( mi ); else { m.setContentDescription( new String( mi.m_value ) ); if ( m_dataSink != null ) m_dataSink.contentDescription( m.getUserObject(), mi.m_value ); } } else if ( MIMEHelper.bStringEquals( mi.m_name, "content-disposition" ) ) { if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) m_mimeInfoQueue.addElement( mi ); else { contentDisposition = translateDispType( mi.m_value, param ); if ( contentDisposition != NSMAIL_ERR_INVALIDPARAM ) { m.setContentDisposition( contentDisposition ); if ( m_dataSink != null ) m_dataSink.contentDisposition( m.getUserObject(), contentDisposition ); if ( param[0] != null ) { m.setContentDispParams( new String( param[0] ) ); if ( m_dataSink != null ) m_dataSink.contentDispParams( m.getUserObject(), param[0] ); } } } } else if ( MIMEHelper.bStringEquals( mi.m_name, "content-id" ) ) { if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) m_mimeInfoQueue.addElement( mi ); else { m.setContentID( new String( mi.m_value ) ); if ( m_dataSink != null ) m_dataSink.contentID( m.getUserObject(), mi.m_value ); } } else if ( MIMEHelper.bStringEquals( mi.m_name, "content-transfer-encoding" ) ) { if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) m_mimeInfoQueue.addElement( mi ); else { if ( m_currentMessage instanceof MIMEBasicPart ) { mimeBasicPart = (MIMEBasicPart) m_currentMessage; int encoding; encoding = mime_translateMimeEncodingType( mi.m_value ); mimeBasicPart.setContentEncoding( encoding ); if ( encoding == MIMEBodyPart.QP ) m_qp = true; if ( m_dataSink != null ) m_dataSink.contentEncoding( mimeBasicPart.getUserObject(), encoding ); } else if ( m_currentMessage instanceof MIMEMessagePart ) { mimeMessagePart = (MIMEMessagePart) m_currentMessage; int encoding; encoding = mime_translateMimeEncodingType( mi.m_value ); mimeMessagePart.setContentEncoding( encoding ); if ( m_dataSink != null ) m_dataSink.contentEncoding( mimeMessagePart.getUserObject(), encoding ); } else if ( m_currentMessage instanceof MIMEMultiPart ) { MIMEMultiPart mimeMultiPart = (MIMEMultiPart) m_currentMessage; int encoding = mime_translateMimeEncodingType( mi.m_value ); mimeMultiPart.setContentEncoding( encoding ); } } } else if ( MIMEHelper.bStringEquals( mi.m_name, "content-md5" ) ) { if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) m_mimeInfoQueue.addElement( mi ); else { mimeBasicPart = (MIMEBasicPart) m_currentMessage; mimeBasicPart.setContentMD5( new String( mi.m_value ) ); if ( m_dataSink != null ) m_dataSink.contentMD5( mimeBasicPart.getUserObject(), mi.m_value ); } } else { if (m_nCurrentMessageType == MIME_UNINITIALIZED) m_mimeInfoQueue.addElement (mi); else { // add unknown tags as header addHeader( mi.m_name, mi.m_value, true ); /* mimeBasicPart = (MIMEBasicPart) m_currentMessage; mimeBasicPart.setHeader (new String(mi.m_name), new String (mi.m_value)); if ( m_dataSink != null ) m_dataSink.header (mimeBasicPart.getUserObject(), mi.m_name, mi.m_value); */ } } } /** * Parse a line into name, value, parameters and store it in a mimeInfo structure * * @author Carson Lee * @version %I%, %G% * * @param mi mimeInfo structure to be parsed * @return none * @exception MIMEException */ private void parseMimeInfo( mimeInfo mi ) throws MIMEException { byte[] paramName = null; byte[] value = null; int offset = 0; int len = 0; boolean beginQuote = false; byte ch; int i; mimeInfo mi2; int nValueLen; if ( mi == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); nValueLen = mi.m_value.length; /* go through entire line */ for ( i = 0; i < nValueLen; i++ ) { ch = mi.m_value[i]; /* handle quotes */ if ( ch == '"' ) beginQuote = beginQuote ? false : true; if ( beginQuote ) { len++; continue; } /* parse and fill in fields */ switch ( ch ) { case ';': value = MIMEHelper.byteSubstring( mi.m_value, offset, len ); // trim(); offset = i + 1; mi2 = new mimeInfo( MIME_INFO, paramName, value ); if ( mi2 == null ) throw new MIMEException( MIMEHelper.szERROR_OUT_OF_MEMORY ); mi.m_param.addElement( mi2 ); value = null; paramName = null; len = 0; break; case '=': paramName = MIMEHelper.byteSubstring( mi.m_value, offset, len ); // trim(); offset = i + 1; len = 0; break; default: len++; } } /* add it to internal buffer */ if ( len > 0 ) { value = MIMEHelper.byteSubstring( mi.m_value, offset, len ); //trim(); mi2 = new mimeInfo( MIME_INFO, paramName, value ); if ( mi2== null ) throw new MIMEException( MIMEHelper.szERROR_OUT_OF_MEMORY ); mi.m_param.addElement( mi2 ); } } private byte[] getBytecontType (int nMessageType, int basicPartcontType) { switch ( nMessageType ) { case MIMEMessage.BASICPART: switch (basicPartcontType) { case MIMEBasicPart.TEXT: return mb_TextPart; case MIMEBasicPart.AUDIO: return mb_AudioPart; case MIMEBasicPart.VIDEO: return mb_VideoPart; case MIMEBasicPart.IMAGE: return mb_ImagePart; case MIMEBasicPart.APPLICATION: return mb_ApplPart; } case MIMEMessage.MULTIPART: return mb_MultiPart; case MIMEMessage.MESSAGEPART: return mb_MessagePart; } return null; } /** * create new message structure based on content type * the input string will be parsed to determine the content type * * @author Carson Lee * @version %I%, %G% * * @param s input line * @return none * @exception MIMEException */ private void newMessageStructure( byte[] s ) throws MIMEException { MIMEMessagePart mimeMessagePart; MIMEBasicPart mimeBasicPart; MIMEMultiPart mimeMultiPart; int nMessageType; byte[] param[] = new byte[2][]; int contentType[] = new int[1]; nMessageType = nGetMessageType( s, param, contentType ); switch ( nMessageType ) { case MIMEMessage.BASICPART: mimeBasicPart = new MIMEBasicPart(); if ( m_dataSink != null ) { //if ( m_bLocalStorage ) // mimeBasicPart.setUserObject( m_dataSink.startBasicPart( mimeBasicPart ) ); //else mimeBasicPart.setUserObject( m_dataSink.startBasicPart() ); } addMessage( mimeBasicPart, MIMEMessage.BASICPART ); break; case MIMEMessage.MULTIPART: mimeMultiPart = new MIMEMultiPart(); mimeMultiPart.m_parsedPart=1; if ( m_dataSink != null ) { //if ( m_bLocalStorage ) // mimeMultiPart.setUserObject( m_dataSink.startMultiPart( mimeMultiPart ) ); //else mimeMultiPart.setUserObject( m_dataSink.startMultiPart() ); } addMessage( mimeMultiPart, MIMEMessage.MULTIPART ); /* save boundary */ mimeMultiPart = (MIMEMultiPart) m_currentMessage; String boundary = parseForBoundary( new String(s) ); mimeMultiPart.setBoundary( boundary ); // debug // System.out.println( "boundary = [" + boundary + "]" ); // if ( m_dataSink != null ) // m_dataSink.boundary( mimeMultiPart.getUserObject(), boundary.getBytes() ); break; case MIMEMessage.MESSAGEPART: mimeMessagePart = new MIMEMessagePart(); if ( m_dataSink != null ) { //if ( m_bLocalStorage ) // mimeMessagePart.setUserObject( m_dataSink.startMessagePart( mimeMessagePart ) ); //else mimeMessagePart.setUserObject( m_dataSink.startMessagePart() ); m_msgParts.addElement (mimeMessagePart); } if ( MIMEHelper.bStringEquals( param[0], "external-body" ) ) { m_messagePartSubType = SUBTYPE_EXTERNAL_BODY; } // unsupported partial subtype else if ( MIMEHelper.bStringEquals( param[0], "partial" ) ) { throw new MIMEException( MIMEHelper.szERROR_UNSUPPORTED_PARTIAL_SUBTYPE ); } addMessage( mimeMessagePart, MIMEMessage.MESSAGEPART ); // determine subtype if ( MIMEHelper.bStringEquals( param[0], "rfc822" ) ) { m_messagePartSubType = SUBTYPE_RFC822; MIMEMessage m = new MIMEMessage(); m.m_parsedPart = 1; mimeMessagePart.setMessage( m, false ); if ( m_dataSink != null ) { //if (m_bLocalStorage) // m.setUserObject (m_dataSink.startMessage (m)); //else m.setUserObject (m_dataSink.startMessage()); } m_nextHeaderParent = m; // defered assignment till received first blank line m_nextMimeMessage = m; } break; } // initialize structure if ( m_currentMessage != null ) { MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; if ( param[0] != null ) m.setContentSubType( new String( param[0] ) ); if ( param[1] != null && !MIMEHelper.bStringEquals( param[1], "boundary")) { String cps = new String (param[1]); if (! (cps.indexOf ("boundary") > 0)) m.setContentTypeParams( new String( param[1] ) ); } if ( m_dataSink != null ) { //m_dataSink.contentType( m.getUserObject(), contentType[0] ); m_dataSink.contentType (m.getUserObject(), getBytecontType (nMessageType, contentType[0])); if ( param[0] != null ) m_dataSink.contentSubType( m.getUserObject(), param[0] ); if (param[1] != null && param[1].length > 0 && !MIMEHelper.bStringEqualsLtrim(param[1], "boundary")) { m_dataSink.contentTypeParams( m.getUserObject(), param[1] ); } } if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) { mimeBasicPart = (MIMEBasicPart) m_currentMessage; mimeBasicPart.setContentType( contentType[0] ); } } } /** * add a new message ( part ) to a multipart message, mimeMessage of messagePart * * @author Carson Lee * @version %I%, %G% * * @param message input message * @param nMessageType message type * @return none * @exception MIMEException */ private void addMessage( Object message, int nMessageType ) throws MIMEException { MIMEMultiPart multiPart = null; MIMEBasicPart basicPart = null; MIMEMessagePart messagePart = null; int nCurrentParentType = getCurrentParentType(); if ( message == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); // determine current message if ( nCurrentParentType == MIMEMessage.BASICPART ) { // can't add messages to a basicpart return; } else if ( nCurrentParentType == MIME_UNINITIALIZED ) { if ( nMessageType == MIMEMessage.BASICPART ) { //debug //System.out.println( "adding basicpart to message" ); m_mimeMessage.setBody( (MIMEBasicPart) message, false ); m_nMessageType = MIMEMessage.BASICPART; } else if ( nMessageType == MIMEMessage.MULTIPART ) { //debug //System.out.println( "adding multipart to message" ); m_mimeMessage.setBody( (MIMEMultiPart) message, false ); m_nMessageType = MIMEMessage.MULTIPART; m_currentParent.addElement( (MIMEMultiPart) message ); m_fSeenBoundary = false; } else if ( nMessageType == MIMEMessage.MESSAGEPART ) { //debug //System.out.println( "adding messagepart to message" ); m_mimeMessage.setBody( (MIMEMessagePart) message, false ); m_nMessageType = MIMEMessage.MESSAGEPART; m_currentParent.addElement( (MIMEMessagePart) message ); } setCurrentMessage( message, nMessageType ); } // add to vector else if ( nCurrentParentType == MIMEMessage.MULTIPART && m_currentMessage != null ) { multiPart = (MIMEMultiPart) m_currentParent.lastElement(); if ( nMessageType == MIMEMessage.BASICPART ) { //debug //System.out.println( "adding basicpart to multipart" ); multiPart.addBodyPart( (MIMEBasicPart) message, false ); } else if ( nMessageType == MIMEMessage.MULTIPART ) { //debug //System.out.println( "adding multipart to multipart" ); multiPart.addBodyPart( (MIMEMultiPart) message, false ); m_currentParent.addElement( (MIMEMultiPart) message ); } else if ( nMessageType == MIMEMessage.MESSAGEPART ) { //debug //System.out.println( "adding messagepart to multipart" ); multiPart.addBodyPart( (MIMEMessagePart) message, false ); m_currentParent.addElement( (MIMEMessagePart) message ); } setCurrentMessage( message, nMessageType ); } else if ( nCurrentParentType == MIMEMessage.MESSAGEPART && m_currentMessage != null ) { messagePart = (MIMEMessagePart) m_currentParent.lastElement(); if ( nMessageType == MIMEMessage.BASICPART ) { //debug //System.out.println( "adding basicpart to messagepart" ); messagePart.getMessage( false ).setBody( (MIMEBasicPart) message, false ); } else if ( nMessageType == MIMEMessage.MULTIPART ) { //debug //System.out.println( "adding multipart to messagepart" ); messagePart.getMessage( false ).setBody( (MIMEMultiPart) message, false ); m_currentParent.addElement( (MIMEMultiPart) message ); } else if ( nMessageType == MIMEMessage.MESSAGEPART ) { //debug //System.out.println( "adding messagepart to messagepart" ); messagePart.getMessage( false ).setBody( (MIMEMessagePart) message, false ); m_currentParent.addElement( (MIMEMessagePart) message ); } setCurrentMessage( message, nMessageType ); } } // helper functions ---------------------------------------- /** * Set current message * * @author Carson Lee * @version %I%, %G% * * @param message input message * @param nMessageType message type * @return MIME_OK if successful, an error code otherwise * @exception none */ private int setCurrentMessage( Object message, int nMessageType ) { if ( message == null ) return NSMAIL_ERR_INVALIDPARAM; m_currentMessage = message; m_nCurrentMessageType = nMessageType; m_nextHeaderParent = message; if ( nMessageType == MIMEMessage.BASICPART ) { m_currentBasicPart = (MIMEBasicPart) message; m_currentMultiPart = null; m_currentMessagePart = null; int nCurrentParentType = getCurrentParentType(); if (nCurrentParentType == MIMEMessage.MULTIPART && m_fSeenBoundary) { //start following adding headers to this part itself m_headerParent = message; m_nextHeaderParent = message; } } else if ( nMessageType == MIMEMessage.MULTIPART ) { m_currentBasicPart = null; m_currentMultiPart = (MIMEMultiPart) message; m_currentMessagePart = null; } else if ( nMessageType == MIMEMessage.MESSAGEPART ) { m_currentBasicPart = null; m_currentMultiPart = null; m_currentMessagePart = (MIMEMessagePart) message; } return NSMAIL_OK; } /** * Determine if line is a boundary * * @author Carson Lee * @version %I%, %G% * * @param s input string * @param len size of input string * @return : START_BOUNDARY if it's a start boundary, END_BOUNDARY if it's a end boundary, NOT_A_BOUNDARY if it's not a boundary * @exception none */ private int boundaryCheck( byte s[], int len ) { String currentBoundary = getCurrentBoundary(); int boundaryLen = 0; if ( len > 0 && Character.isWhitespace( (char) s[len-1] ) ) len--; if ( len > 0 && Character.isWhitespace( (char) s[len-1] ) ) len--; if ( len >= 2 && s != null && currentBoundary != null ) { boundaryLen = currentBoundary.length(); if ( (len == boundaryLen + 4) && s[0] == '-' && s[1] == '-' && s[ boundaryLen + 2 ] == '-' && s[ boundaryLen + 3 ] == '-' && MIMEHelper.bStringEquals( 2, s, currentBoundary ) ) { return END_BOUNDARY; } else if ((len == boundaryLen + 2) && s[0] == '-' && s[1] == '-' && MIMEHelper.bStringEquals( 2, s, currentBoundary ) ) { return START_BOUNDARY; } } return NOT_A_BOUNDARY; } /** * check what kind of line this is * * @author Carson Lee * @version %I%, %G% * * @param s input string * @param len size of input string * @return line type can be MIME_INFO, MIME_XHEADER, MIME_CRLF, MIME_HEADER * @exception none */ private int checkForLineType( byte s[], int len, boolean startData ) { if ( s == null ) return NSMAIL_ERR_INVALIDPARAM; if ( startData ) { int type = boundaryCheck(s, len); if ( type != NOT_A_BOUNDARY ) return type; return MIME_HEADER; } else if ( MIMEHelper.bStringEquals( s, "content-" ) ) return MIME_INFO; else if ( MIMEHelper.bStringEquals( s, "x-" ) ) return MIME_XHEADER; else if ( len == 0 ) return MIME_CRLF; else { int type = boundaryCheck(s, len); if ( type != NOT_A_BOUNDARY ) return type; else { boolean seenNonSpace = false; for ( int i = 0; i < len; i++ ) { if (!Character.isWhitespace ((char) s[i])) seenNonSpace = true; else if (seenNonSpace && Character.isWhitespace ((char) s[i])) // its not a header! Its MESSAGE_DATA! { break; } if ( s[i] == ':' ) return MIME_HEADER; } } } return MIME_MESSAGE_DATA; } /** * Get message type, subtype, content type parameters and content type * * @author Carson Lee * @version %I%, %G% * * @param s input string * @param param[] (output) returns subtype in param[0], parameters in param[1] * @param contentType[] (output) returns content type in contentType[0] * @return message type, returns subtype in param[0], parameters in param[1] * @exception none */ private int nGetMessageType( byte[] s, byte[] param[], int contentType[] ) throws MIMEException { int i, nStart, nSubtypeStart; byte[] MessageType = null; int nMessageType = MIME_UNINITIALIZED; byte[] subtype = null; if ( s == null || param == null ) return NSMAIL_ERR_INVALIDPARAM; int len = s.length; /* skip spaces and colons */ for ( i=0; i < len && s[i] != 0 && ( Character.isWhitespace( (char)s[i] ) || s[i] == ':' ); i++ ) ; /* skip to subtype */ for ( nStart = i; i < len && s[i] != 0 && s[i] != '/' && s[i] != ';'; i++ ) ; /* content type */ MessageType = MIMEHelper.byteSubstring( s, nStart, i - nStart ); /* content subtype */ if ( i < len && s[i] == '/' ) { for ( nSubtypeStart = ++i; i < len && s[i] != 0 && s[i] != ';' && s[i] != ','; i++ ) ; /* content subtype */ subtype = MIMEHelper.byteSubstring( s, nSubtypeStart, i - nSubtypeStart ); } param[0] = subtype; /* skip spaces and colons */ if ( i < len ) { for ( ; i < len && s[i] != 0 && ( s[i] == ' ' || s[i] == ';' || s[i] == ',' ); i++ ) ; } param[1] = i > (int) s.length ? null : MIMEHelper.byteSubstring( s, i, s.length-i ); // trim /* determine content type */ if ( MIMEHelper.bStringEquals( MessageType, "text" ) ) { nMessageType = MIMEMessage.BASICPART; contentType[0] = MIMEBasicPart.TEXT; } else if ( MIMEHelper.bStringEquals( MessageType, "audio" ) ) { nMessageType = MIMEMessage.BASICPART; contentType[0] = MIMEBasicPart.AUDIO; } else if ( MIMEHelper.bStringEquals( MessageType, "image" ) ) { nMessageType = MIMEMessage.BASICPART; contentType[0] = MIMEBasicPart.IMAGE; } else if ( MIMEHelper.bStringEquals( MessageType, "video" ) ) { nMessageType = MIMEMessage.BASICPART; contentType[0] = MIMEBasicPart.VIDEO; } else if ( MIMEHelper.bStringEquals( MessageType, "application" ) ) { nMessageType = MIMEMessage.BASICPART; contentType[0] = MIMEBasicPart.APPLICATION; } else if ( MIMEHelper.bStringEquals( MessageType, "multipart" ) ) { nMessageType = MIMEMessage.MULTIPART; contentType[0] = MIMEBasicPart.TEXT; } else if ( MIMEHelper.bStringEquals( MessageType, "message" ) ) { nMessageType = MIMEMessage.MESSAGEPART; contentType[0] = MIMEBasicPart.TEXT; } else { //nMessageType = MIMEBasicPart.TEXT; //contentType[0] = MIMEBasicPart.TEXT; throw new MIMEException ("Error: Unknown/Unsupported Content-Type: " + new String (MessageType)); } return nMessageType; } /** * Parse an input string for boundary * default to "-----" if none is found * * @author Carson Lee * @version %I%, %G% * * @param s input string * @return boundary string * @exception none */ private String parseForBoundary( String s ) { String boundary; int nLen; int len; int start; int pos; boolean bQuotes = false; char ch; if ( s == null ) return null; pos = s.indexOf( "boundary" ); len = s.length(); if ( pos == -1 ) boundary = "-----"; else { /* skip spaces and colons */ for ( pos = pos + 9, ch = s.charAt(pos); pos < len && ( ch == ' ' || ch == '=' ); ch = s.charAt( ++pos >= len ? 0 : pos ) ) ; if ( ch == '"' ) { bQuotes = true; pos++; } start = pos; for ( nLen = 0, ch = s.charAt(pos); pos < len && ch != ';'; nLen++, ch = s.charAt( ++pos >= len ? 0 : pos ) ) { if ( bQuotes && ch == '"' ) break; } boundary = s.substring( start, start + nLen ); } return boundary; } /** * translate encoding type to an integer * * @author Carson Lee * @version %I%, %G% * * @param s input string * @return encoding type * @exception none */ private int mime_translateMimeEncodingType( byte[] s ) { if ( s == null ) return NSMAIL_ERR_INVALIDPARAM; if ( MIMEHelper.bStringEquals( s, "base64" ) ) return MIMEBodyPart.BASE64; else if ( MIMEHelper.bStringEquals( s, "qp" ) || MIMEHelper.bStringEquals( s, "quoted-printable" ) ) return MIMEBodyPart.QP; else if ( MIMEHelper.bStringEquals( s, "7bit" ) ) return MIMEBodyPart.E7BIT; else if ( MIMEHelper.bStringEquals( s, "8bit" ) ) return MIMEBodyPart.E8BIT; else if ( MIMEHelper.bStringEquals( s, "binary" ) ) return MIMEBodyPart.BINARY; return MIMEBodyPart.E7BIT; } /** * translate disposition type to an integer * * @author Carson Lee * @version %I%, %G% * * @param s input string * @param param[] return content disposition parameters in param[0] * @return
    *   NSMAIL_ERR_INVALIDPARAM     if parameters are invalid
    *   MIMEBodyPart.INLINE         disposition type is inline
    *   MIMEBodyPart.ATTACHMENT     disposition type is an attachment
* * @exception MIMEException */ private int translateDispType( byte[] s, byte[] param[] ) { if ( s == null || param == null ) return NSMAIL_ERR_INVALIDPARAM; param[0] = null; if ( MIMEHelper.bStringEquals( s, "inline" ) ) { if ( s.length >= 8 ) param[0] = MIMEHelper.byteSubstring( s, 0, 7 ); return MIMEBodyPart.INLINE; } else if ( MIMEHelper.bStringEquals( s, "attachment" ) ) { if ( s.length >= 12 ) param[0] = MIMEHelper.byteSubstring( s, 0, 12 ); return MIMEBodyPart.ATTACHMENT; } else if ( s.length >= 8 ) param[0] = MIMEHelper.byteSubstring( s, 0, 7 ); return MIMEBodyPart.INLINE; } // ----------new one private int translateDispType (byte[] s) { if ( s == null) return NSMAIL_ERR_INVALIDPARAM; if (MIMEHelper.bStringEquals (s, "inline")) return MIMEBodyPart.INLINE; if (MIMEHelper.bStringEquals (s, "attachment")) return MIMEBodyPart.ATTACHMENT; return MIMEBodyPart.INLINE; } private int getCurrentParentType() { if ( m_currentParent.size() > 0 ) { Object o = m_currentParent.lastElement(); if ( o != null ) { if ( o instanceof MIMEBasicPart ) return MIMEMessage.BASICPART; else if ( o instanceof MIMEMultiPart ) return MIMEMessage.MULTIPART; else if ( o instanceof MIMEMessagePart ) return MIMEMessage.MESSAGEPART; } } return MIME_UNINITIALIZED; } /** * get the boundary corresponding to the current multiPart message * * @author Carson Lee * @version %I%, %G% * * @param none * @return current boundary string, null if there isn't a current boundary * @exception none */ private String getCurrentBoundary() { if ( m_currentParent.size() > 0 ) { for ( int i = m_currentParent.size(); i > 0; i-- ) { Object o = m_currentParent.elementAt( i - 1 ); if ( o != null && o instanceof MIMEMultiPart ) return ( (MIMEMultiPart) o ).getBoundary(); } } return null; } /** * get the boundary corresponding to the current multiPart message * * @author Carson Lee * @version %I%, %G% * * @param none * @return current boundary string, null if there isn't a current boundary * @exception none */ private void unwindCurrentParent( byte[] s, boolean delete ) { if ( m_currentParent.size() > 0 ) { String boundary = new String( s, 2, s.length - 2 ); for ( int i = m_currentParent.size(); i > 0; i-- ) { Object o = m_currentParent.elementAt( i - 1 ); if ( o != null && o instanceof MIMEMultiPart && boundary.startsWith( ((MIMEMultiPart) o ).getBoundary() ) ) { if ( delete ) { m_currentParent.removeElementAt( i - 1 ); // reposition to next multipart for ( ; m_currentParent.size() > 0; ) { o = m_currentParent.lastElement(); if ( o instanceof MIMEMultiPart ) return; m_currentParent.removeElementAt( m_currentParent.size() - 1 ); } } return; } m_currentParent.removeElementAt( i - 1 ); } } } /** * check for empty messages in all basicparts * * @author Carson Lee * @version %I%, %G% * * @param * * o : any mime message structure, usually MIMEMessage * * @return none * @exception MIMEException */ void checkForEmptyMessages( Object o ) throws MIMEException { if ( o == null ) throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); if ( o instanceof MIMEMessage ) { checkForEmptyMessages( ((MIMEMessage) o).getBody() ); } else if ( o instanceof MIMEBasicPart ) { if ( ((MIMEBasicPart) o).getMessageDataLen() == 0 ) throw new MIMEException( MIMEHelper.szERROR_EMPTY_MESSAGE ); } else if ( o instanceof MIMEMultiPart ) { MIMEMultiPart m = (MIMEMultiPart) o; int count = m.getBodyPartCount(); for ( int i = 0; i < count; i++ ) { Object part = m.getBodyPart( i, false ); checkForEmptyMessages( part ); } } else if ( o instanceof MIMEMessagePart ) { String cst = (((MIMEMessagePart)o).getContentSubType()).trim(); if (cst == null || cst.equalsIgnoreCase("rfc822")) { MIMEMessage messagePart = ((MIMEMessagePart) o).getMessage(); checkForEmptyMessages( messagePart ); } else if (cst.equalsIgnoreCase("external-body")) { Header[] hdrs = ((MIMEMessagePart)o).getAllHeaders(); //if (hdrs == null) // throw new MIMEException (MIMEHelper.szERROR_BAD_EXTERNAL_MESSAGE_PART); } else if (cst.equalsIgnoreCase("partial")) { throw new MIMEException (MIMEHelper.szERROR_UNSUPPORTED_PARTIAL_SUBTYPE); } else throw new MIMEException( MIMEHelper.szERROR_BAD_MIME_MESSAGE); } } }