1419 lines
45 KiB
Java
1419 lines
45 KiB
Java
/*
|
|
* 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.smtp;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.Vector;
|
|
import netscape.messaging.*;
|
|
|
|
/**
|
|
*The SMTPClient class represents the SMTP client.
|
|
*The client uses this class for communicating with a
|
|
*server using the SMTP protocol.
|
|
*The SMTP client conforms to the specifications of the client
|
|
*described in RFC 821.
|
|
*<p>For a list of the SMTP RFCs referenced in the
|
|
*Messaging Access SDK and their URLs,
|
|
*see "Where to Find More Information" in "About This Book."
|
|
*@author derekt@netscape.com
|
|
*@version 1.0
|
|
*/
|
|
|
|
public class SMTPClient
|
|
{
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Internal class used for socket I/O.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private IO m_io;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Data member used for C-runtime equivalent routines.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private Common m_common;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Data member used to store the timeout value.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private int m_timeout;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Last character sent to the server used for byte stuffing messages.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private byte m_lastSentChar;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// State variable indicating if the DATA command was last executed.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private boolean m_fSendingData;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Boolean variable to determine if we can send another command.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
private boolean m_mustProcess;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Callback data members.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private Vector m_pendingList;
|
|
private ISMTPSink m_notifySink;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Data members specific for PIPELINING mode.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private Vector m_pipelinedCommandList;
|
|
private boolean m_fPipeliningSupported;
|
|
private boolean m_fPipeliningEnabled;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Reused response objects.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private StringBuffer m_responseMessage;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Byte arrays that are reused for commands and responses.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private byte[] m_response;
|
|
private byte[] m_messageData;
|
|
private byte[] m_itoaBuffer;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Constants.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private final static int m_defaultPort = 25;
|
|
private final static int m_minChunkSize = 1024;
|
|
private final static int m_maxReplyLine = 512;
|
|
|
|
private final static byte[] m_bdat = { 'B', 'D', 'A', 'T', ' ' };
|
|
private final static byte[] m_data = { 'D', 'A', 'T', 'A' };
|
|
private final static byte[] m_ehlo = { 'E', 'H', 'L', 'O', ' ' };
|
|
private final static byte[] m_expn = { 'E', 'X', 'P', 'N', ' ' };
|
|
private final static byte[] m_help = { 'H', 'E', 'L', 'P', ' ' };
|
|
private final static byte[] m_mail = { 'M', 'A', 'I', 'L', ' ', 'F', 'R', 'O', 'M', ':', '<' };
|
|
private final static byte[] m_noop = { 'N', 'O', 'O', 'P' };
|
|
private final static byte[] m_quit = { 'Q', 'U', 'I', 'T' };
|
|
private final static byte[] m_rcpt = { 'R', 'C', 'P', 'T', ' ', 'T', 'O', ':', '<' };
|
|
private final static byte[] m_rset = { 'R', 'S', 'E', 'T' };
|
|
private final static byte[] m_vrfy = { 'V', 'R', 'F', 'Y', ' ' };
|
|
//private final static byte[] m_rBracket = { '>', ' ' };
|
|
private final static byte[] m_rBracket = { '>' };
|
|
private final static byte[] m_newline = { '\r', '\n' };
|
|
private final static byte[] m_eomessage = { '\r', '\n', '.' };
|
|
private final static byte[] m_bdatLast = { ' ', 'L', 'A', 'S', 'T' };
|
|
private final static byte[] m_empty = {};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Command types for SMTP.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private final static int BDAT = 0;
|
|
private final static int CONN = 1;
|
|
private final static int DATA = 2;
|
|
private final static int EHLO = 3;
|
|
private final static int EXPN = 4;
|
|
private final static int HELP = 5;
|
|
private final static int MAIL = 6;
|
|
private final static int NOOP = 7;
|
|
private final static int QUIT = 8;
|
|
private final static int RCPT = 9;
|
|
private final static int RSET = 10;
|
|
private final static int SEND = 11;
|
|
private final static int SENDCOMMAND = 12;
|
|
private final static int VRFY = 13;
|
|
|
|
private final static Integer BDAT_OBJ = new Integer(BDAT);
|
|
private final static Integer CONN_OBJ = new Integer(CONN);
|
|
private final static Integer DATA_OBJ = new Integer(DATA);
|
|
private final static Integer EHLO_OBJ = new Integer(EHLO);
|
|
private final static Integer EXPN_OBJ = new Integer(EXPN);
|
|
private final static Integer HELP_OBJ = new Integer(HELP);
|
|
private final static Integer MAIL_OBJ = new Integer(MAIL);
|
|
private final static Integer NOOP_OBJ = new Integer(NOOP);
|
|
private final static Integer QUIT_OBJ = new Integer(QUIT);
|
|
private final static Integer RCPT_OBJ = new Integer(RCPT);
|
|
private final static Integer RSET_OBJ = new Integer(RSET);
|
|
private final static Integer SEND_OBJ = new Integer(SEND);
|
|
private final static Integer SENDCOMMAND_OBJ = new Integer(SENDCOMMAND);
|
|
private final static Integer VRFY_OBJ = new Integer(VRFY);
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Error messages for SMTP.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private final static String errProcessResponses = new String("Must call processResponses()");
|
|
private final static String errSendingData = new String("Must send data immediately after the Data() command");
|
|
private final static String errNoPipelining = new String("Pipelining is not supported");
|
|
private final static String errParse = new String("Error while parsing response");
|
|
|
|
/**
|
|
*Constructor for SMTPClient that takes an SMTPSink as a parameter.
|
|
*@param in_sink Response sink that implements the ISMTPSink interface.
|
|
*@see ISMTPSink
|
|
*/
|
|
public SMTPClient( ISMTPSink in_sink )
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Variables that get initialized once in the constructor.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
m_io = new IO();
|
|
m_common = new Common();
|
|
m_timeout = 0;
|
|
m_pendingList = new Vector();
|
|
m_notifySink = in_sink;
|
|
|
|
m_response = new byte[m_maxReplyLine];
|
|
m_messageData = new byte[m_minChunkSize];
|
|
m_itoaBuffer = new byte[m_maxReplyLine];
|
|
m_responseMessage = new StringBuffer( m_maxReplyLine );
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Variables that get initialized in the constructor and on every new
|
|
// connection.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
m_fSendingData = false;
|
|
m_fPipeliningSupported = false;
|
|
m_fPipeliningEnabled = false;
|
|
m_lastSentChar = '\n';
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Protocol commands
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
*Sends binary data chunks to the server.
|
|
*<p>Not to be used with the data() command.
|
|
*With SMTPClient.send(), data should be sent with SMTPClient.data
|
|
*and not with SMTPClient.bdat.
|
|
*For more information, see "Sending the Message."
|
|
*<P>Note: bdat is not supported by Messaging Server 4.0. Use SMTPClient.data instead.
|
|
*@param in_data Array for the raw data to send to the server.
|
|
*@param in_offset Offset for data.
|
|
*@param in_length Number of bytes to send.
|
|
*@param in_fLast Indicates whether this is the last chunk of data to send.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#bdat
|
|
*@see ISMTPSink#error
|
|
*@see #data
|
|
*/
|
|
public synchronized void bdat( byte[] in_data,
|
|
int in_offset,
|
|
int in_length,
|
|
boolean in_fLast ) throws IOException
|
|
{
|
|
int l_numBytes;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_length < 0 || in_offset < 0 )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_bdat );
|
|
|
|
l_numBytes = m_common.itoa( in_length, m_itoaBuffer );
|
|
m_io.write( m_itoaBuffer, 0, l_numBytes );
|
|
|
|
if ( in_fLast )
|
|
{
|
|
m_io.write( m_bdatLast );
|
|
}
|
|
|
|
m_io.write( m_newline, 0, m_newline.length );
|
|
m_io.write( in_data, in_offset, in_length );
|
|
m_io.write( m_newline, 0, m_newline.length );
|
|
|
|
m_pendingList.addElement( BDAT_OBJ );
|
|
|
|
if ( m_fPipeliningEnabled == false )
|
|
{
|
|
m_io.flush();
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
*Connects to the server using the default port.
|
|
*<p>To specify the connection port, use the
|
|
*form of connect that takes a port number.
|
|
*@param in_server Name of the host server to connect to.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#connect
|
|
*@see ISMTPSink#error
|
|
*@see #quit
|
|
*@see #connect
|
|
*/
|
|
public synchronized void connect( String in_server ) throws IOException
|
|
{
|
|
connect( in_server, m_defaultPort );
|
|
}
|
|
|
|
/**
|
|
*Connects to the server using the specified port.
|
|
*<p>To use the default connection port, use the
|
|
*form of connect that takes only the server name.
|
|
*@param in_server Name of the host server to connect to.
|
|
*@param in_port Number of the port to connect to.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#connect
|
|
*@see ISMTPSink##error
|
|
*@see #quit
|
|
*@see #connect
|
|
*/
|
|
public synchronized void connect( String in_server, int in_port ) throws IOException
|
|
{
|
|
if ( in_server == null || in_port < 0 )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
m_fSendingData = false;
|
|
m_fPipeliningSupported = false;
|
|
m_fPipeliningEnabled = false;
|
|
m_lastSentChar = '\n';
|
|
|
|
m_pendingList.removeAllElements();
|
|
|
|
m_io.connect( in_server, in_port, m_minChunkSize, m_messageData.length );
|
|
|
|
m_io.setTimeout( m_timeout );
|
|
|
|
m_pendingList.addElement( CONN_OBJ );
|
|
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Prepares to send data to the server.
|
|
*With SMTPClient.send(),
|
|
*data should be sent with SMTPClient.data and not with SMTPClient.bdat.
|
|
*For more information, see "Sending the Message."
|
|
*Note: Not to be used with the bdat() command.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#data
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void data() throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.send( m_data, false );
|
|
|
|
m_pendingList.addElement( DATA_OBJ );
|
|
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Closes the socket connection with the server.
|
|
*Can be used to perform a high level "Cancel" while
|
|
*sending a message.
|
|
*NOTE: Do not call processResponses() after this method.
|
|
*@exception IOException thrown on IO error.
|
|
*/
|
|
public synchronized void disconnect() throws IOException
|
|
{
|
|
m_io.disconnect();
|
|
}
|
|
|
|
/**
|
|
*Determines the ESMTP server extensions.
|
|
*The callback on the response
|
|
*sink identifies the various SMTP extensions supported by the server.
|
|
*@param in_domain The domain name.
|
|
*@exception IOException thrown on IO error.
|
|
*@see ISMTPSink#ehlo
|
|
*@see ISMTPSink#ehloComplete
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void ehlo( String in_domain ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_domain == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_ehlo );
|
|
m_io.send( in_domain , false );
|
|
|
|
m_pendingList.addElement( EHLO_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Expands a given mailing list.
|
|
*Gets the email addresses of the users on the mailing list.
|
|
*@param in_mailingList The mailing list to expand.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#expand
|
|
*@see ISMTPSink#expandComplete
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void expand( String in_mailingList ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_mailingList == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_expn );
|
|
m_io.send( in_mailingList, false );
|
|
|
|
m_pendingList.addElement( EXPN_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Obtains help on a given topic.
|
|
*@param in_helpTopic One-word help topic to get information on. If null,
|
|
*user may get Help on Help or a default. See implementation on the server.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#help
|
|
*@see ISMTPSink#helpComplete
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void help( String in_helpTopic ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_help );
|
|
|
|
if ( in_helpTopic != null )
|
|
{
|
|
m_io.send( in_helpTopic, false );
|
|
}
|
|
else
|
|
{
|
|
m_io.send( m_empty, false );
|
|
}
|
|
|
|
m_pendingList.addElement( HELP_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Sets the sender of the message with optional ESMTP parameters.
|
|
*@param in_reverseAddress Address of the sender of the message.
|
|
*@param in_esmtpParams Any ESMTP (Extended SMTP) parameter.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#mailFrom
|
|
*@see ISMTPSink#error
|
|
*@see #rcptTo
|
|
*/
|
|
public synchronized void mailFrom( String in_reverseAddress, String in_esmtpParams ) throws IOException
|
|
{
|
|
int count;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_reverseAddress == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_mail );
|
|
m_io.write( in_reverseAddress );
|
|
m_io.write( m_rBracket );
|
|
|
|
if ( in_esmtpParams != null )
|
|
{
|
|
m_io.write( in_esmtpParams );
|
|
}
|
|
|
|
m_io.send( m_empty, m_fPipeliningEnabled );
|
|
m_pendingList.addElement( MAIL_OBJ );
|
|
|
|
if ( m_fPipeliningEnabled == false )
|
|
{
|
|
m_mustProcess = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Gets positive server response; does not affect SMTP session.
|
|
*Issues Noop command.
|
|
*<P>The server responds to commands with a "still here" response.
|
|
*Sending the noop method does nothing except force this server response.
|
|
*Can be used to maintain server connection, perhaps being issued
|
|
*at timed intervals to make sure that the server is still active.
|
|
*Not needed by applications that do something and do not maintain a connection with the server.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#noop
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void noop() throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.send( m_noop, false );
|
|
|
|
m_pendingList.addElement( NOOP_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Closes the connection with the server.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#quit
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void quit() throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.send( m_quit, false );
|
|
|
|
m_pendingList.addElement( QUIT_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Sets the recipient of the message with optional ESMTP parameters.
|
|
*@param in_forwardAddress Address of the message recipient.
|
|
*@param in_esmtpParams Any ESMTP parameters to set.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#rcptTo
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void rcptTo( String in_forwardAddress, String in_esmtpParams ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_forwardAddress == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_rcpt );
|
|
m_io.write( in_forwardAddress );
|
|
m_io.write( m_rBracket );
|
|
|
|
if ( in_esmtpParams != null )
|
|
{
|
|
m_io.write( in_esmtpParams );
|
|
}
|
|
|
|
m_io.send( m_empty, m_fPipeliningEnabled );
|
|
m_pendingList.addElement( RCPT_OBJ );
|
|
|
|
if ( m_fPipeliningEnabled == false )
|
|
{
|
|
m_mustProcess = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Resets the state of the server; flushes any sender and recipient
|
|
*information.
|
|
*<p>Cancels the current mail transfer and all current processes,
|
|
*discards data, and clears all states. Returns to the state
|
|
*that followed the last method that sent the EHLO command.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#reset
|
|
*@see ISMTPSink#error
|
|
*@see #ehlo
|
|
*/
|
|
public synchronized void reset() throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.send( m_rset, m_fPipeliningEnabled );
|
|
m_pendingList.addElement( RSET_OBJ );
|
|
}
|
|
|
|
/**
|
|
*Sends message data to the server.
|
|
*NOTE: To be used with the data() command and not with the bdat() command.
|
|
*@param in_inputStream Input stream containing the data to send.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#send
|
|
*@see ISMTPSink#error
|
|
*@see #data
|
|
*/
|
|
public synchronized void send( InputStream in_inputStream ) throws IOException
|
|
{
|
|
int l_offset;
|
|
int l_byteCount;
|
|
byte l_dot = (byte)'.';
|
|
byte l_lineFeed = (byte)'\n';
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_inputStream == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == false )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Send the raw message data to the server and return the response.
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
do
|
|
{
|
|
///////////////////////////////////////////////////////////////
|
|
// Read in the data from the stream.
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
l_byteCount = in_inputStream.read( m_messageData );
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
// Create the string in order to search for the "\n." sequence.
|
|
// and send the data.
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
l_offset = 0;
|
|
|
|
for ( int count = 0; count < l_byteCount; count++ )
|
|
{
|
|
if ( (m_lastSentChar == l_lineFeed) &&
|
|
(l_dot == m_messageData[count]) )
|
|
{
|
|
m_io.write( m_messageData, l_offset, count-l_offset );
|
|
m_io.write( l_dot );
|
|
l_offset = count;
|
|
}
|
|
m_lastSentChar = m_messageData[count];
|
|
}
|
|
|
|
if ( l_offset < l_byteCount )
|
|
{
|
|
m_io.write( m_messageData, l_offset, l_byteCount-l_offset );
|
|
}
|
|
}
|
|
while ( l_byteCount != -1 );
|
|
|
|
m_io.send( m_eomessage, m_fPipeliningEnabled );
|
|
|
|
m_lastSentChar = l_lineFeed;
|
|
m_pendingList.addElement( SEND_OBJ );
|
|
|
|
m_fSendingData = false;
|
|
|
|
if ( m_fPipeliningEnabled == false )
|
|
{
|
|
m_mustProcess = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Sends an unsupported command to the server.
|
|
*Sends commands that are not supported by the Messaging Access SDK
|
|
*implementation of SMTP.
|
|
*<P>NOTE: This method is primarily intended to support extensions
|
|
*to the protocol to meet client application needs.
|
|
*@param in_command Raw command to send to the server.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#sendCommand
|
|
*@see ISMTPSink#sendCommandComplete
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void sendCommand( String in_command ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_command == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.send( in_command, false );
|
|
|
|
m_pendingList.addElement( SENDCOMMAND_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Verifies a username.
|
|
*@param in_user User name to verify.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see ISMTPSink#verify
|
|
*@see ISMTPSink#error
|
|
*/
|
|
public synchronized void verify( String in_user ) throws IOException
|
|
{
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Error checking before proceeding with command.
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
if ( in_user == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if ( m_mustProcess )
|
|
{
|
|
throw new SMTPException(errProcessResponses);
|
|
}
|
|
|
|
if ( m_fSendingData == true )
|
|
{
|
|
throw new SMTPException(errSendingData);
|
|
}
|
|
|
|
m_io.write( m_vrfy );
|
|
m_io.send( in_user, false );
|
|
|
|
m_pendingList.addElement( VRFY_OBJ );
|
|
m_mustProcess = true;
|
|
}
|
|
|
|
/**
|
|
*Reads in responses from the server and invokes the appropriate sink methods.
|
|
*<p>Processes the server responses for API commands.
|
|
*It invokes the callback methods provided by the user for all
|
|
*responses that are available at the time of execution.
|
|
*NOTE: If a timeout occurs the user can continue by calling processResponses() again.
|
|
*@exception SMTPServerException If a server error occurs.
|
|
*@exception InterruptedIOException If a time-out occurs.
|
|
*@exception IOException If an I/O error occurs.
|
|
*/
|
|
public void processResponses() throws IOException
|
|
{
|
|
synchronized(m_responseMessage)
|
|
{
|
|
try
|
|
{
|
|
Integer l_commandType;
|
|
|
|
m_io.flush();
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
// Prcoess as many of the responses as possible.
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
while ( !m_pendingList.isEmpty() )
|
|
{
|
|
l_commandType = (Integer)m_pendingList.firstElement();
|
|
|
|
switch( l_commandType.intValue() )
|
|
{
|
|
case BDAT:
|
|
parseBdat();
|
|
break;
|
|
case CONN:
|
|
parseConnect();
|
|
break;
|
|
case DATA:
|
|
parseData();
|
|
break;
|
|
case EHLO:
|
|
parseEhlo();
|
|
break;
|
|
case EXPN:
|
|
parseExpand();
|
|
break;
|
|
case HELP:
|
|
parseHelp();
|
|
break;
|
|
case MAIL:
|
|
parseMail();
|
|
break;
|
|
case NOOP:
|
|
parseNoop();
|
|
break;
|
|
case QUIT:
|
|
parseQuit();
|
|
m_io.disconnect();
|
|
break;
|
|
case RCPT:
|
|
parseRcpt();
|
|
break;
|
|
case RSET:
|
|
parseReset();
|
|
break;
|
|
case SEND:
|
|
parseSend();
|
|
break;
|
|
case SENDCOMMAND:
|
|
parseSendCommand();
|
|
break;
|
|
case VRFY:
|
|
parseVerify();
|
|
break;
|
|
}
|
|
|
|
m_pendingList.removeElementAt( 0 );
|
|
}
|
|
|
|
m_mustProcess = false;
|
|
}
|
|
catch( SMTPServerException e )
|
|
{
|
|
m_mustProcess = false;
|
|
m_pendingList.removeElementAt( 0 );
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Preferences
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
*Sets the size of the data chunks that are read from the input stream and
|
|
*sent to the server. The minimum chunk size is 1024.
|
|
*NOTE: Do not call processResponses() after this method.
|
|
*@param in_chunkSize Size of chunk used for sending messages using the send() method. Minimum chunk size: 1024. Default: 1 K.
|
|
*@exception IOException thrown on IO error.
|
|
*@see #send
|
|
*/
|
|
public synchronized void setChunkSize( int in_chunkSize )
|
|
{
|
|
synchronized(m_responseMessage)
|
|
{
|
|
if ( in_chunkSize < m_minChunkSize )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
m_messageData = new byte[in_chunkSize];
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Registers a new response sink, overriding the one passed into the
|
|
*constructor or any one set afterwards.
|
|
*NOTE: Do not call processResponses() after setResponseSink().
|
|
*@param in_responseSink The new ISMTPSink to use.
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see #processResponses
|
|
*/
|
|
public synchronized void setResponseSink( ISMTPSink in_responseSink )
|
|
{
|
|
synchronized(m_responseMessage)
|
|
{
|
|
if ( in_responseSink == null )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
m_notifySink = in_responseSink;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Sets the amount of time allowed to wait
|
|
*before returning control to the user.
|
|
*NOTE: Do not call processResponses() after this method.
|
|
*@param in_timeout Time-out period to set. Values, in milliseconds:
|
|
*<ul>
|
|
*<li>0 = infinite time-out (default)
|
|
*<li>-1 = no waiting
|
|
*<li>> 0 = length of time-out period
|
|
*</ul>
|
|
*@exception IOException If an I/O error occurs.
|
|
*@see #processResponses
|
|
*/
|
|
public synchronized void setTimeout( int in_timeout ) throws IOException
|
|
{
|
|
synchronized(m_responseMessage)
|
|
{
|
|
if ( in_timeout < -1 )
|
|
{
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
m_timeout = in_timeout;
|
|
|
|
if ( m_io != null )
|
|
{
|
|
m_io.setTimeout( m_timeout );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*Enables PIPELINING (batching) of commands, if supported by the server.
|
|
*If PIPELINING is not supported by the server, an SMTPException
|
|
*is thrown. The user can determine if PIPELINING is supported through the
|
|
*notification of the ehlo() method.
|
|
*<P>NOTE: Do not call processResponses() after this method.
|
|
*@param in_enablePipelining Boolean value to enable/disable PIPELINING.
|
|
*@exception SMTPException If PIPELINING is not supported by the server.
|
|
*@see #ehlo
|
|
*/
|
|
public synchronized void setPipelining( boolean in_enablePipelining ) throws SMTPException
|
|
{
|
|
synchronized(m_responseMessage)
|
|
{
|
|
if ( in_enablePipelining )
|
|
{
|
|
if ( m_fPipeliningSupported )
|
|
{
|
|
m_fPipeliningEnabled = true;
|
|
}
|
|
else
|
|
{
|
|
throw new SMTPException(errNoPipelining);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fPipeliningEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Internal functions
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
*Separates the response code from the message part of the response.
|
|
*/
|
|
private final int setStatusInfo(
|
|
byte[] in_response,
|
|
int in_length,
|
|
StringBuffer out_responseMessage ) throws IOException
|
|
{
|
|
int l_responseCode;
|
|
|
|
if ( in_length < 4 ||
|
|
Character.isDigit((char)in_response[0]) == false ||
|
|
Character.isDigit((char)in_response[1]) == false ||
|
|
Character.isDigit((char)in_response[2]) == false )
|
|
{
|
|
throw new SMTPException(errParse);
|
|
}
|
|
|
|
l_responseCode = ( (Character.digit( (char)in_response[0], 10 ) * 100) +
|
|
(Character.digit( (char)in_response[1], 10 ) * 10) +
|
|
(Character.digit( (char)in_response[2], 10 ) * 1) );
|
|
|
|
out_responseMessage.setLength( in_length - 4 );
|
|
|
|
for ( int count = 0; count < (in_length-4); count++ )
|
|
{
|
|
out_responseMessage.setCharAt( count, (char)in_response[count+4] );
|
|
}
|
|
|
|
return l_responseCode;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Methods for parsing the responses and invoking sink notifications.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
private final void parseBdat() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.bdat( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseConnect() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.connect( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseData() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.data( l_rCode, m_responseMessage );
|
|
m_fSendingData = true;
|
|
}
|
|
}
|
|
|
|
private final void parseEhlo() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount;
|
|
|
|
do
|
|
{
|
|
l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response,
|
|
l_byteCount,
|
|
m_responseMessage );
|
|
|
|
if ( m_fPipeliningSupported == false &&
|
|
m_responseMessage.length() >= 10 )
|
|
{
|
|
if ( m_responseMessage.charAt( 0 ) == 'P' &&
|
|
m_responseMessage.charAt( 1 ) == 'I' &&
|
|
m_responseMessage.charAt( 2 ) == 'P' &&
|
|
m_responseMessage.charAt( 3 ) == 'E' &&
|
|
m_responseMessage.charAt( 4 ) == 'L' &&
|
|
m_responseMessage.charAt( 5 ) == 'I' &&
|
|
m_responseMessage.charAt( 6 ) == 'N' &&
|
|
m_responseMessage.charAt( 7 ) == 'I' &&
|
|
m_responseMessage.charAt( 8 ) == 'N' &&
|
|
m_responseMessage.charAt( 9 ) == 'G' )
|
|
{
|
|
m_fPipeliningSupported = true;
|
|
}
|
|
}
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
try
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
catch ( SMTPServerException e )
|
|
{
|
|
while( '-' == (char)m_response[3] )
|
|
{
|
|
m_io.readLine( m_response );
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.ehlo( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
while( '-' == (char)m_response[3] );
|
|
|
|
m_notifySink.ehloComplete();
|
|
}
|
|
|
|
private final void parseExpand() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount;
|
|
|
|
do
|
|
{
|
|
l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response,
|
|
l_byteCount,
|
|
m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
try
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
catch ( SMTPServerException e )
|
|
{
|
|
while( '-' == (char)m_response[3] )
|
|
{
|
|
m_io.readLine( m_response );
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.expand( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
while( '-' == (char)m_response[3] );
|
|
|
|
m_notifySink.expandComplete();
|
|
}
|
|
|
|
private final void parseHelp() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount;
|
|
|
|
do
|
|
{
|
|
l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response,
|
|
l_byteCount,
|
|
m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
try
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
catch ( SMTPServerException e )
|
|
{
|
|
while( '-' == (char)m_response[3] )
|
|
{
|
|
m_io.readLine( m_response );
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.help( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
while( '-' == (char)m_response[3] );
|
|
|
|
m_notifySink.helpComplete();
|
|
}
|
|
|
|
private final void parseMail() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.mailFrom( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseNoop() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.noop( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseQuit() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.quit( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseRcpt() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.rcptTo( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseReset() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.reset( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseSend() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.send( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
|
|
private final void parseSendCommand() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount;
|
|
|
|
do
|
|
{
|
|
l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response,
|
|
l_byteCount,
|
|
m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
try
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
catch ( SMTPServerException e )
|
|
{
|
|
while( '-' == (char)m_response[3] )
|
|
{
|
|
m_io.readLine( m_response );
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.sendCommand( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
while( '-' == (char)m_response[3] );
|
|
|
|
m_notifySink.sendCommandComplete();
|
|
}
|
|
|
|
private final void parseVerify() throws IOException
|
|
{
|
|
int l_rCode;
|
|
int l_byteCount = m_io.readLine( m_response );
|
|
|
|
l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage );
|
|
|
|
if ( l_rCode >= 400 )
|
|
{
|
|
m_notifySink.error( l_rCode, m_responseMessage );
|
|
}
|
|
else
|
|
{
|
|
m_notifySink.verify( l_rCode, m_responseMessage );
|
|
}
|
|
}
|
|
}
|