--- /dev/null
+// SmtpClient.cs
+// author: Per Arneng <pt99par@student.bth.se>
+using System;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Collections;
+using System.Net.Sockets;
+
+namespace System.Web.Mail {
+
+ /// represents a conntection to a smtp server
+ internal 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();
+
+ }
+
+
+ }
+
+}
--- /dev/null
+using System.IO;
+
+namespace System.Web.Mail {
+
+ internal class SmtpException : IOException {
+ public SmtpException( string message ) : base( message ) {}
+ }
+
+}
--- /dev/null
+// SmtpResponse.cs
+// author: Per Arneng <pt99par@student.bth.se>
+using System;
+
+namespace System.Web.Mail {
+
+ /// this class represents the response from the smtp server
+ internal 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;
+ }
+ }
+
+}
--- /dev/null
+// SmtpStream.cs
+// author: Per Arneng <pt99par@student.bth.se>
+using System;
+using System.IO;
+using System.Collections;
+using System.Text;
+using System.Security.Cryptography;
+
+namespace System.Web.Mail {
+
+ internal 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 );
+ }
+
+ }
+
+
+}