[Mono-list] SMTP into CVS
Per Arneng
pt99par@student.bth.se
Sun, 23 Feb 2003 01:41:00 +0100
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/plain;
charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Hi!
Here are the smtp classes. They have now a namespace that is SmtpClient a=
nd=20
they should probably be in another namespace. I have designed the classes=
in=20
souch a way so that they will be easy to incorperate in the SmtpMail clas=
s.=20
It would be nice if someone could insert them into the cvs or tell me of =
where=20
to insert them. I think that a new assembly would be good since the class=
es=20
expose more functionallity than the SmtpMail do. SmtpStream ex: provides=20
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 =3D 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=
=20
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
=20
Best regards
Per Arneng
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/x-makefile;
charset="us-ascii";
name="Makefile"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="Makefile"
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)
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/x-c++src;
charset="us-ascii";
name="SmtpException.cs"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="SmtpException.cs"
using System.IO;
namespace SmtpClient {
public class SmtpException : IOException {
public SmtpException( string message ) : base( message ) {}
}
}
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/x-c++src;
charset="us-ascii";
name="SmtpClient.cs"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="SmtpClient.cs"
// 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;
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();
}
}
}
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/x-c++src;
charset="us-ascii";
name="SmtpResponse.cs"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="SmtpResponse.cs"
// SmtpResponse.cs
// author: Per Arneng <pt99par@student.bth.se>
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;
}
}
}
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK
Content-Type: text/x-c++src;
charset="us-ascii";
name="SmtpStream.cs"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="SmtpStream.cs"
// 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 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 );
}
}
}
--------------Boundary-00=_CKKQ6KQ4H56G5CMEJPMK--