Hi!

Here are the smtp classes. They have now a namespace that is SmtpClient and 
they should probably be in another namespace. I have designed the classes in 
souch a way so that they will be easy to incorperate in the SmtpMail class. 

It would be nice if someone could insert them into the cvs or tell me of where 
to insert them. I think that a new assembly would be good since the classes 
expose more functionallity than the SmtpMail do. SmtpStream ex: provides 
direct access to the smtp protocol.  but anyhing is ok with me :)

When everything is added it can be used inside SmtpMail.Send like this
----------------------
SmtpClient smtp = new SmtpClient( this.SmtpServer );

smtp.Send( message );

smtp.Close();
----------------------

I think its better if i send a patch later for SmtpMail to manage all the 
exceptions in a copatible way with .NET sdk..

Here is the code size in loc:
    268 SmtpClient.cs
      9 SmtpException.cs
     63 SmtpResponse.cs
    235 SmtpStream.cs
    575 total

Here is the status page: http://p.rsn.bth.se/smtp
 
Best regards
   Per Arneng
CC=mcs
BIN_DIR=../bin
DLL_FILE=$(BIN_DIR)/SmtpClient.dll
SRC_FILES=$(wildcard Smtp*.cs)

all: $(DLL_FILE)


$(DLL_FILE): $(SRC_FILES)
	$(CC) -o $(DLL_FILE) --target library -warn:4 -r System.Web.dll $(SRC_FILES)

clean:
	rm -f *~ $(DLL_FILE)
using System.IO;

namespace SmtpClient {

    public class SmtpException : IOException {
	public SmtpException( string message ) : base( message ) {}
    }

}
// SmtpClient.cs
// author: Per Arneng <[EMAIL PROTECTED]>
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Collections;
using System.Net.Sockets;
using System.Web.Mail;

namespace SmtpClient {

    /// represents a conntection to a smtp server
    public class SmtpClient {
	
	private string server;
	private TcpClient tcpConnection;
	private SmtpStream smtp;
	private Encoding encoding;
	
	//Initialise the variables and connect
	public SmtpClient( string server ) {
	    
	    this.server = server;
	    encoding = new ASCIIEncoding( );

	    Connect();
	}
	
	// make the actual connection
	// and HELO handshaking
	private void Connect() {
	    tcpConnection = new TcpClient( server , 25 );
	    
	    Stream stream = tcpConnection.GetStream();
	    smtp = new SmtpStream( stream );
	    
	    // read the server greeting
	    smtp.ReadResponse();
	    smtp.CheckForStatusCode( 220 );
	   
	    // write the HELO command to the server
	    smtp.WriteHelo( Dns.GetHostName() );
	    	    
	}
	
	public void Send( MailMessage msg ) {

	    if( ( ! HasData( msg.From )  ) || ( ! HasData( msg.To ) ) )
		throw new ArgumentException( "From & To properties must be set." );
	    
	    // start with a reset incase old data
	    // is present at the server in this session
	    smtp.WriteRset();
	    
	    // write the mail from command
	    smtp.WriteMailFrom( msg.From );
	    	    
	    // write the rcpt to command
	    smtp.WriteRcptTo( msg.To );
	    
	    // write the data command and then
	    // send the email
	    smtp.WriteData();
	   

	    if( msg.Attachments.Count == 0 ) {
		
		SendSinglepartMail( msg );
	    
	    } else {
		
		SendMultipartMail( msg );
	    
	    }

	    // write the data end tag "."
	    smtp.WriteDataEndTag();

	}
	
	// sends a single part mail to the server
	private void SendSinglepartMail( MailMessage msg ) {
	    	    	    
	    // create the headers
	    IDictionary headers = CreateHeaders( msg );
	
	    smtp.WriteHeaders( headers );
	    
	    // send the mail body
	    smtp.WriteLine( msg.Body );

	}
	
	// sends a multipart mail to the server
	private void SendMultipartMail( MailMessage msg ) {
	    	    	    
	    // create the headers
	    IDictionary headers = CreateHeaders( msg );

	    // set the part boundary
	    string boundary = "NextPart_000_1113_1962_1fe8";
		
	    // set the Content-Type header to multipart/mixed
	    headers[ "Content-Type" ] = 
		String.Format( "multipart/mixed;\r\n   boundary={0}" , boundary );
		
	    // write the headers
	    // and start writing the multipart body
	    smtp.WriteHeaders( headers );
		
	    // write the first part text part
	    // before the attachments
	    smtp.WriteBoundary( boundary );
		
	    Hashtable partHeaders = new Hashtable();
	    partHeaders[ "Content-Type" ] = "text/plain";
		
	    smtp.WriteHeaders( partHeaders );
	    		
	    smtp.WriteLine( msg.Body );

	    smtp.WriteBoundary( boundary );
	    
	    // now start to write the attachments

	    for( int i=0; i< msg.Attachments.Count ; i++ ) {
		MailAttachment a = (MailAttachment)msg.Attachments[ i ];
		FileStream file = 
		    new FileStream( a.Filename , FileMode.Open );
		    		    
		Hashtable aHeaders = new Hashtable();
		
		aHeaders[ "Content-Type" ] = 
		    String.Format( "unknown/unknown; name=\"{0}\"", a.Filename );
		
		aHeaders[ "Content-Disposition" ] = 
		    String.Format( "attachment; filename=\"{0}\"" , a.Filename );
		
		aHeaders[ "Content-Transfer-Encoding" ] = "base64";
			
		smtp.WriteHeaders( aHeaders );
		    
		smtp.WriteBase64( file );
		    
		smtp.WriteLine( "" );
		
		// if it is the last attachment write
		// the final boundary otherwise write
		// a normal one.
		if( i < (msg.Attachments.Count - 1) ) { 
		    smtp.WriteBoundary( boundary );
		} else {
		    smtp.WriteFinalBoundary( boundary );
		}
		    
		file.Close();
	    }
	       
	}
	
	// send the standard headers
	// and the custom in MailMessage
	// FIXME: more headers needs to be added so
	// that all properties from MailMessage are sent..
	// missing: Priority , UrlContentBase,UrlContentLocation
	private IDictionary CreateHeaders( MailMessage msg ) {
	    Hashtable headers = new Hashtable(); 
	    
	    headers[ "From" ] = msg.From;
	    headers[ "To" ] = msg.To;
	    	    
	    if( HasData( msg.Cc ) ) headers[ "Cc" ] = msg.Cc;
			    
	    if( HasData( msg.Bcc ) ) headers[ "Bcc" ] = msg.Bcc;
	    
	    if( HasData( msg.Subject ) ) headers[ "Subject" ] = msg.Subject;
	    
	    if( HasData( msg.UrlContentBase ) ) 
		headers[ "Content-Base" ] = msg.UrlContentBase;
	    
	    if( HasData( msg.UrlContentLocation ) ) 
		headers[ "Content-Location" ] = msg.UrlContentLocation;
	    
	    // set body the content type
	    switch( msg.BodyFormat ) {
		
	    case MailFormat.Html: 
		headers[ "Content-Type" ] = "text/html"; 
		break;
	    
	    case MailFormat.Text: 
		headers[ "Content-Type" ] = "text/plain"; 
		break;
	    
	    default: 
		headers[ "Content-Type" ] = "text/plain"; 
		break;

	    }
	    
	    // set the priority as in the same way as .NET sdk does
	    switch( msg.Priority ) {
		
	    case MailPriority.High: 
		headers[ "Importance" ] = "high";
		break;
	    
	    case MailPriority.Low: 
		headers[ "Importance" ] = "low";
		break;
		
	    case MailPriority.Normal: 
		headers[ "Importance" ] = "normal";
		break;
		
	    default: 
		headers[ "Importance" ] = "normal";
		break;

	    }
	    
	    // .NET sdk allways sets this to normal
	    headers[ "Priority" ] = "normal";
	    

	    // add mime version
	    headers[ "Mime-Version" ] = "1.0";
	    
	    // set the mailer -- should probably be changed
	    headers[ "X-Mailer" ] = "Mono (System.Web.Mail.SmtpMail.Send)";
	    
	    // add the custom headers they will overwrite
	    // the earlier ones if they are the same
	    foreach( string key in msg.Headers.Keys )
		headers[ key ] = (string)msg.Headers[ key ];
		
	    

	    return headers;
	}
	
	// returns true if str is not null and not
	// empty
	private bool HasData( string str ) {
	    bool hasData = false;
	    if( str != null ) {
		if( str.Length > 0 ) {
		    hasData = true;
		}
	    }
	    return hasData;
	}
	
	
	// send quit command and
	// closes the connection
	public void Close() {
	    
	    smtp.WriteQuit();
	    tcpConnection.Close();
	
	}
	
		
    }

}
// SmtpResponse.cs
// author: Per Arneng <[EMAIL PROTECTED]>
using System;

namespace SmtpClient {

    /// this class represents the response from the smtp server
    public class SmtpResponse {
	
	private string rawResponse;
	private int statusCode;
	private string[] parts;

	/// use the Parse method to create instances
	protected SmtpResponse() {}

	/// the smtp status code FIXME: change to Enumeration?
	public int StatusCode {
	    get { return statusCode; }
	    set { statusCode = value; }
	}
	
	/// the response as it was recieved
	public string RawResponse {
	    get { return rawResponse; }
	    set { rawResponse = value; }
	}

	/// the response as parts where ; was used as delimiter
	public string[] Parts {
	    get { return parts; }
	    set { parts = value; }
	}

	/// parses a new response object from a response string
	public static SmtpResponse Parse( string line ) {
	    SmtpResponse response = new SmtpResponse();
	    
	    if( line == null )
		throw new ArgumentNullException( "Null is not allowed " + 
						 "as a response string.");

	    if( line.Length < 4 ) 
		throw new FormatException( "Response is to short " + 
					   line.Length + ".");
	    
	    if( line[ 3 ] != ' ' )
		throw new FormatException( "Response format is wrong.");
	    
	    // parse the response code
	    response.StatusCode = Int32.Parse( line.Substring( 0 , 3 ) );
	    
	    // set the rawsponse
	    response.RawResponse = line;

	    // set the response parts
	    response.Parts = line.Substring( 0 , 3 ).Split( ';' );

	    return response;
	}
    }

}
// SmtpStream.cs
// author: Per Arneng <[EMAIL PROTECTED]>
using System;
using System.IO;
using System.Collections;
using System.Text;
using System.Security.Cryptography;

namespace SmtpClient {

    public class SmtpStream {
	
	protected Stream stream;
	protected Encoding encoding;
	protected SmtpResponse lastResponse;
	protected string command = "";

	public SmtpStream( Stream stream ) {
	    this.stream = stream;
	    encoding = new ASCIIEncoding();
	}
	
	public SmtpResponse LastResponse {
	    get { return lastResponse; }
	}
	
	public void WriteRset() {
	    command = "RSET";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	
	}
	
	public void WriteHelo( string hostName ) { 
	    command = "HELO " + hostName;
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    
	}
	
	public void WriteMailFrom( string from ) {
	    command = "MAIL FROM: " + from;
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    
	}
	
	public void WriteRcptTo( string to ) {
	    command = "RCPT TO: " + to;  
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    	    
	}
	

	public void WriteData() {
	    command = "DATA";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 354 );
	
	}
	
	public void WriteQuit() {
	    command = "QUIT";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 221 );
	
	}
		
	public void WriteBoundary( string boundary ) {
	
	    WriteLine( "--{0}" , boundary );
	
	}
	
	public void WriteFinalBoundary( string boundary ) {
	
	    WriteLine( "--{0}--" , boundary );
	
	}
	
	public void WriteDataEndTag() {
	    command = ".";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	
	}
	
	
	public void WriteHeaders( IDictionary headers ) {
	    // write the headers
	    foreach( string key in headers.Keys )
		WriteLine( "{0}: {1}" , key , (string)headers[ key ] );
	    
	    // write the header end tag
	    WriteLine( "" );
	}
	
	public void CheckForStatusCode( int statusCode ) {
	    
	    if( LastResponse.StatusCode != statusCode ) {
		
		string msg = "" + 
		    "Server reponse: '" + lastResponse.RawResponse + "';" +
		    "Status code: '" +  lastResponse.StatusCode + "';" + 
		    "Expected status code: '" + statusCode + "';" + 
		    "Last command: '" + command + "'";
		
		throw new SmtpException( msg ); 
					
	    }
	}
	
	// writes a formatted line to the server
	public void WriteLine( string format ,  params object[] args ) {
	    WriteLine( String.Format( format , args ) );
	}
	
	// writes a line to the server
	public void WriteLine( string line ) {
	    byte[] buffer = encoding.GetBytes( line + "\r\n" );
	    
	    stream.Write( buffer , 0 , buffer.Length );
	
	    #if DEBUG 
	      DebugPrint( line );
            #endif
	}
	
	// read a line from the server
	public void ReadResponse( ) {
	    string line = null;
	    
	    byte[] buffer = new byte[ 4096 ];
	    
	    int readLength = stream.Read( buffer , 0 , buffer.Length );
	    
	    if( readLength > 0 ) { 
	    
		line = encoding.GetString( buffer , 0 , readLength );
		
		line = line.TrimEnd( new Char[] { '\r' , '\n' , ' ' } );
			
	    }
	   
	    // parse the line to the lastResponse object
	    lastResponse = SmtpResponse.Parse( line );
	   
	    #if DEBUG
	      DebugPrint( line );
	    #endif
	}


	// reads bytes from a stream and writes the encoded
	// as base64 encoded characters. ( 60 chars on each row)
	public void WriteBase64( Stream inStream ) {
	
	    ICryptoTransform base64 = new ToBase64Transform();
	    ASCIIEncoding encoding = new ASCIIEncoding();
	
	    // the buffers
	    byte[] plainText = new byte[ base64.InputBlockSize ];
	    byte[] cipherText = new byte[ base64.OutputBlockSize ];

	    int readLength = 0;
	    int trLength = 0;
	
	    StringBuilder row = new StringBuilder( 60 );
	
	    // read through the stream until there 
	    // are no more bytes left
	    while( true ) {
		readLength = inStream.Read( plainText , 0 , plainText.Length );
	    
		// break when there is no more data
		if( readLength < 1 ) break;
	    
		// transfrom and write the blocks. If the block size
		// is less than the InputBlockSize then write the final block
		if( readLength == plainText.Length ) {
		
		    trLength = base64.TransformBlock( plainText , 0 , 
						      plainText.Length ,
						      cipherText , 0 );
		
		    // trLength must be the same size as the cipherText
		    // length otherwise something is wrong
		    if( trLength != cipherText.Length )
			throw new Exception( "All of the plaintext bytes where not converted" );
		
		    // convert the bytes to a string and then add it to the
		    // current row
		    string cipherString = encoding.GetString( cipherText , 0 , trLength ); 
		    row.Append( cipherString );
		
		    // when a row is full write it and begin
		    // on the next row
		    if( row.Length == 60 ) {
			WriteLine( row.ToString() );
			row.Remove( 0 , 60 );
		    }
		
		} else {
		    // convert the final blocks of bytes
		    cipherText = base64.TransformFinalBlock( plainText , 0 , readLength );
		
		    // convert the bytes to a string and then write it
		    string cipherString = encoding.GetString( cipherText , 0 , 
							      cipherText.Length );
		    row.Append( cipherString );
		    WriteLine( row.ToString() );
		
		}
	    
	    } 
    
	}
	
	/// debug printing 
	private void DebugPrint( string line ) {
	    Console.WriteLine( "smtp: {0}" , line );
	}

    }


}

Reply via email to