SMTP Internal classes added to make SmtpMail.Send work
authorPer Arneng <per@mono-cvs.ximian.com>
Sun, 23 Feb 2003 19:50:45 +0000 (19:50 -0000)
committerPer Arneng <per@mono-cvs.ximian.com>
Sun, 23 Feb 2003 19:50:45 +0000 (19:50 -0000)
svn path=/trunk/mcs/; revision=11885

mcs/class/System.Web/System.Web.Mail/SmtpClient.cs [new file with mode: 0644]
mcs/class/System.Web/System.Web.Mail/SmtpException.cs [new file with mode: 0644]
mcs/class/System.Web/System.Web.Mail/SmtpResponse.cs [new file with mode: 0644]
mcs/class/System.Web/System.Web.Mail/SmtpStream.cs [new file with mode: 0644]

diff --git a/mcs/class/System.Web/System.Web.Mail/SmtpClient.cs b/mcs/class/System.Web/System.Web.Mail/SmtpClient.cs
new file mode 100644 (file)
index 0000000..c0e0193
--- /dev/null
@@ -0,0 +1,267 @@
+// 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();
+       
+       }
+       
+               
+    }
+
+}
diff --git a/mcs/class/System.Web/System.Web.Mail/SmtpException.cs b/mcs/class/System.Web/System.Web.Mail/SmtpException.cs
new file mode 100644 (file)
index 0000000..d5d9a44
--- /dev/null
@@ -0,0 +1,9 @@
+using System.IO;
+
+namespace System.Web.Mail {
+
+    internal class SmtpException : IOException {
+       public SmtpException( string message ) : base( message ) {}
+    }
+
+}
diff --git a/mcs/class/System.Web/System.Web.Mail/SmtpResponse.cs b/mcs/class/System.Web/System.Web.Mail/SmtpResponse.cs
new file mode 100644 (file)
index 0000000..7c763e9
--- /dev/null
@@ -0,0 +1,63 @@
+// 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;
+       }
+    }
+
+}
diff --git a/mcs/class/System.Web/System.Web.Mail/SmtpStream.cs b/mcs/class/System.Web/System.Web.Mail/SmtpStream.cs
new file mode 100644 (file)
index 0000000..31c865a
--- /dev/null
@@ -0,0 +1,235 @@
+// 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 );
+       }
+
+    }
+
+
+}