2 // System.Net.Mail.SmtpClient.cs
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2004
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 extern alias PrebuiltSystem;
38 using System.Collections.Generic;
39 using System.ComponentModel;
40 using System.Globalization;
43 using System.Net.Mime;
44 using System.Net.Sockets;
45 using System.Security.Cryptography.X509Certificates;
47 using System.Threading;
48 using System.Reflection;
49 using System.Net.Configuration;
50 using System.Configuration;
51 using System.Net.Security;
52 using System.Security.Authentication;
55 using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
58 namespace System.Net.Mail {
59 public class SmtpClient
66 ICredentialsByHost credentials;
67 bool useDefaultCredentials = false;
68 string pickupDirectoryLocation;
69 SmtpDeliveryMethod deliveryMethod;
71 X509CertificateCollection clientCertificates;
78 MailAddress defaultFrom;
80 MailMessage messageInProcess;
82 BackgroundWorker worker;
83 object user_async_state;
97 class CancellationException : Exception
101 AuthMechs authMechs = AuthMechs.None;
102 bool canStartTLS = false;
104 Mutex mutex = new Mutex ();
115 public SmtpClient (string host)
120 public SmtpClient (string host, int port) {
121 #if CONFIGURATION_DEP
122 SmtpSection cfg = (SmtpSection) ConfigurationManager.GetSection ("system.net/mailSettings/smtp");
125 this.host = cfg.Network.Host;
126 this.port = cfg.Network.Port;
127 if (cfg.Network.UserName != null) {
128 string password = String.Empty;
130 if (cfg.Network.Password != null)
131 password = cfg.Network.Password;
133 Credentials = new CCredentialsByHost (cfg.Network.UserName, password);
136 if (cfg.From != null)
137 defaultFrom = new MailAddress (cfg.From);
141 if (!String.IsNullOrEmpty (host))
148 #endregion // Constructors
153 [MonoTODO("STARTTLS is not supported yet")]
154 public X509CertificateCollection ClientCertificates {
156 if (clientCertificates == null)
157 clientCertificates = new X509CertificateCollection ();
158 return clientCertificates;
163 public ICredentialsByHost Credentials {
164 get { return credentials; }
171 public SmtpDeliveryMethod DeliveryMethod {
172 get { return deliveryMethod; }
175 deliveryMethod = value;
179 [MonoTODO("STARTTLS is not supported yet")]
180 public bool EnableSsl {
181 // FIXME: So... is this supposed to enable SSL port functionality? or STARTTLS? Or both?
182 get { return enableSsl; }
193 throw new ArgumentNullException ();
194 if (value.Length == 0)
195 throw new ArgumentException ();
201 public string PickupDirectoryLocation {
202 get { return pickupDirectoryLocation; }
203 set { pickupDirectoryLocation = value; }
209 if (value <= 0 || value > 65535)
210 throw new ArgumentOutOfRangeException ();
217 public ServicePoint ServicePoint {
218 get { throw new NotImplementedException (); }
222 get { return timeout; }
225 throw new ArgumentOutOfRangeException ();
231 public bool UseDefaultCredentials {
232 get { return useDefaultCredentials; }
233 [MonoNotSupported ("no DefaultCredential support in Mono")]
236 throw new NotImplementedException ("Default credentials are not supported");
241 #endregion // Properties
245 public event SendCompletedEventHandler SendCompleted;
251 private void CheckState ()
253 if (messageInProcess != null)
254 throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
257 private static string EncodeAddress(MailAddress address)
259 string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
260 return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
263 private static string EncodeAddresses(MailAddressCollection addresses)
265 StringBuilder sb = new StringBuilder();
267 foreach (MailAddress address in addresses) {
271 sb.Append(EncodeAddress(address));
274 return sb.ToString();
277 private string EncodeSubjectRFC2047 (MailMessage message)
279 return ContentType.EncodeSubjectRFC2047 (message.Subject, message.SubjectEncoding);
282 private string EncodeBody (MailMessage message)
284 string body = message.Body;
285 Encoding encoding = message.BodyEncoding;
287 switch (message.ContentTransferEncoding) {
288 case TransferEncoding.SevenBit:
290 case TransferEncoding.Base64:
291 return Convert.ToBase64String (encoding.GetBytes (body), Base64FormattingOptions.InsertLineBreaks);
293 return ToQuotedPrintable (body, encoding);
297 private string EncodeBody (AlternateView av)
299 //Encoding encoding = av.ContentType.CharSet != null ? Encoding.GetEncoding (av.ContentType.CharSet) : Encoding.UTF8;
301 byte [] bytes = new byte [av.ContentStream.Length];
302 av.ContentStream.Read (bytes, 0, bytes.Length);
305 switch (av.TransferEncoding) {
306 case TransferEncoding.SevenBit:
307 return Encoding.ASCII.GetString (bytes);
308 case TransferEncoding.Base64:
309 return Convert.ToBase64String (bytes, Base64FormattingOptions.InsertLineBreaks);
311 return ToQuotedPrintable (bytes);
316 private void EndSection (string section)
318 SendData (String.Format ("--{0}--", section));
319 SendData (string.Empty);
322 private string GenerateBoundary ()
324 string output = GenerateBoundary (boundaryIndex);
329 private static string GenerateBoundary (int index)
331 return String.Format ("--boundary_{0}_{1}", index, Guid.NewGuid ().ToString ("D"));
334 private bool IsError (SmtpResponse status)
336 return ((int) status.StatusCode) >= 400;
339 protected void OnSendCompleted (AsyncCompletedEventArgs e)
342 if (SendCompleted != null)
343 SendCompleted (this, e);
346 user_async_state = null;
350 private void CheckCancellation ()
352 if (worker != null && worker.CancellationPending)
353 throw new CancellationException ();
356 private SmtpResponse Read () {
357 byte [] buffer = new byte [512];
359 bool lastLine = false;
362 CheckCancellation ();
364 int readLength = stream.Read (buffer, position, buffer.Length - position);
365 if (readLength > 0) {
366 int available = position + readLength - 1;
367 if (available > 4 && (buffer [available] == '\n' || buffer [available] == '\r'))
368 for (int index = available - 3; ; index--) {
369 if (index < 0 || buffer [index] == '\n' || buffer [index] == '\r') {
370 lastLine = buffer [index + 4] == ' ';
376 position += readLength;
378 // check if buffer is full
379 if (position == buffer.Length) {
380 byte [] newBuffer = new byte [buffer.Length * 2];
381 Array.Copy (buffer, 0, newBuffer, 0, buffer.Length);
391 Encoding encoding = new ASCIIEncoding ();
393 string line = encoding.GetString (buffer, 0, position - 1);
395 // parse the line to the lastResponse object
396 SmtpResponse response = SmtpResponse.Parse (line);
400 throw new System.IO.IOException ("Connection closed");
404 void ResetExtensions()
407 authMechs = AuthMechs.None;
410 void ParseExtensions (string extens)
412 char[] delims = new char [1] { ' ' };
413 string[] parts = extens.Split ('\n');
415 foreach (string part in parts) {
419 string start = part.Substring (4);
420 if (start.StartsWith ("AUTH ", StringComparison.Ordinal)) {
421 string[] options = start.Split (delims);
422 for (int k = 1; k < options.Length; k++) {
423 string option = options[k].Trim();
426 authMechs |= AuthMechs.CramMD5;
429 authMechs |= AuthMechs.DigestMD5;
432 authMechs |= AuthMechs.GssAPI;
435 authMechs |= AuthMechs.Kerberos4;
438 authMechs |= AuthMechs.Login;
441 authMechs |= AuthMechs.Plain;
445 } else if (start.StartsWith ("STARTTLS", StringComparison.Ordinal)) {
451 public void Send (MailMessage message)
454 throw new ArgumentNullException ("message");
456 if (deliveryMethod == SmtpDeliveryMethod.Network && String.IsNullOrEmpty (Host))
457 throw new InvalidOperationException ("The SMTP host was not specified");
458 if (deliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory) {
459 if (String.IsNullOrEmpty(pickupDirectoryLocation))
460 throw new SmtpException("The pickup directory was not specified");
461 if (false && pickupDirectoryLocation[0] != '/')
462 throw new SmtpException("Only absolute directories are allowed for pickup directory.");
464 if (deliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
465 throw new NotSupportedException("IIS delivery is not supported");
470 // Block while sending
473 messageInProcess = message;
474 if (deliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
475 SendToFile (message, pickupDirectoryLocation + "/" + Guid.NewGuid() + ".eml");
477 SendInternal (message);
478 } catch (CancellationException) {
479 // This exception is introduced for convenient cancellation process.
481 // Release the mutex to allow other threads access
482 mutex.ReleaseMutex ();
483 messageInProcess = null;
487 private void SendInternal (MailMessage message)
489 CheckCancellation ();
492 client = new TcpClient (host, port);
493 stream = client.GetStream ();
494 // FIXME: this StreamWriter creation is bogus.
495 // It expects as if a Stream were able to switch to SSL
496 // mode (such behavior is only in Mainsoft Socket API).
497 writer = new StreamWriter (stream);
498 reader = new StreamReader (stream);
513 // FIXME: simple implementation, could be brushed up.
514 private void SendToFile (MailMessage message, String filename)
517 writer = new StreamWriter(filename);
519 MailAddress from = message.From;
524 SendHeader (HeaderName.Date, DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo));
525 SendHeader (HeaderName.From, from.ToString ());
526 SendHeader (HeaderName.To, message.To.ToString ());
527 if (message.CC.Count > 0)
528 SendHeader (HeaderName.Cc, message.CC.ToString ());
529 SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
531 foreach (string s in message.Headers.AllKeys)
532 SendHeader (s, message.Headers [s]);
534 AddPriorityHeader (message);
537 if (message.Attachments.Count > 0)
538 SendWithAttachments (message);
540 SendWithoutAttachments (message, null, false);
544 if (writer != null) writer.Close(); writer = null;
548 private void SendCore (MailMessage message)
553 if (IsError (status))
554 throw new SmtpException (status.StatusCode, status.Description);
558 // FIXME: parse the list of extensions so we don't bother wasting
559 // our time trying commands if they aren't supported.
560 status = SendCommand ("EHLO " + Dns.GetHostName ());
562 if (IsError (status)) {
563 status = SendCommand ("HELO " + Dns.GetHostName ());
565 if (IsError (status))
566 throw new SmtpException (status.StatusCode, status.Description);
568 // Parse ESMTP extensions
569 string extens = status.Description;
572 ParseExtensions (extens);
577 // FIXME: I get the feeling from the docs that EnableSsl is meant
578 // for using the SSL-port and not STARTTLS (or, if it includes
579 // STARTTLS... only use STARTTLS if the SSL-type in the certificate
580 // is TLS and not SSLv2 or SSLv3)
582 // FIXME: even tho we have a canStartTLS flag... ignore it for now
583 // so that the STARTTLS command can throw the appropriate
584 // SmtpException if STARTTLS is unavailable
586 // SmtpClient implements STARTTLS support.
588 InitiateSecureConnection ();
590 writer = new StreamWriter (stream);
591 reader = new StreamReader (stream);
592 status = SendCommand ("EHLO " + Dns.GetHostName ());
594 if (IsError (status)) {
595 status = SendCommand ("HELO " + Dns.GetHostName ());
597 if (IsError (status))
598 throw new SmtpException (status.StatusCode, status.Description);
600 // Parse ESMTP extensions
601 string extens = status.Description;
603 ParseExtensions (extens);
607 if (authMechs != AuthMechs.None)
610 MailAddress from = message.From;
616 status = SendCommand ("MAIL FROM:<" + from.Address + '>');
617 if (IsError (status)) {
618 throw new SmtpException (status.StatusCode, status.Description);
621 // Send RCPT TO: for all recipients
622 List<SmtpFailedRecipientException> sfre = new List<SmtpFailedRecipientException> ();
624 for (int i = 0; i < message.To.Count; i ++) {
625 status = SendCommand ("RCPT TO:<" + message.To [i].Address + '>');
626 if (IsError (status))
627 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.To [i].Address));
629 for (int i = 0; i < message.CC.Count; i ++) {
630 status = SendCommand ("RCPT TO:<" + message.CC [i].Address + '>');
631 if (IsError (status))
632 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.CC [i].Address));
634 for (int i = 0; i < message.Bcc.Count; i ++) {
635 status = SendCommand ("RCPT TO:<" + message.Bcc [i].Address + '>');
636 if (IsError (status))
637 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.Bcc [i].Address));
640 #if TARGET_JVM // List<T>.ToArray () is not supported
641 if (sfre.Count > 0) {
642 SmtpFailedRecipientException[] xs = new SmtpFailedRecipientException[sfre.Count];
644 throw new SmtpFailedRecipientsException ("failed recipients", xs);
648 throw new SmtpFailedRecipientsException ("failed recipients", sfre.ToArray ());
652 status = SendCommand ("DATA");
653 if (IsError (status))
654 throw new SmtpException (status.StatusCode, status.Description);
656 // Send message headers
657 string dt = DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo);
658 // remove ':' from time zone offset (e.g. from "+01:00")
659 dt = dt.Remove (dt.Length - 3, 1);
660 SendHeader (HeaderName.Date, dt);
662 SendHeader (HeaderName.From, EncodeAddress (from));
663 SendHeader (HeaderName.To, EncodeAddresses (message.To));
664 if (message.CC.Count > 0)
665 SendHeader (HeaderName.Cc, EncodeAddresses (message.CC));
666 SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
668 foreach (string s in message.Headers.AllKeys)
669 SendHeader (s, message.Headers [s]);
671 AddPriorityHeader (message);
674 if (message.Attachments.Count > 0)
675 SendWithAttachments (message);
677 SendWithoutAttachments (message, null, false);
682 if (IsError (status))
683 throw new SmtpException (status.StatusCode, status.Description);
686 status = SendCommand ("QUIT");
687 } catch (System.IO.IOException) {
688 // We excuse server for the rude connection closing as a response to QUIT
692 public void Send (string from, string to, string subject, string body)
694 Send (new MailMessage (from, to, subject, body));
697 private void SendData (string data)
699 CheckCancellation ();
701 // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
702 StringBuilder sb = new StringBuilder (data);
703 sb.Replace ("\r\n", "\n");
704 sb.Replace ('\r', '\n');
705 sb.Replace ("\n", "\r\n");
706 writer.Write (sb.ToString ());
707 writer.Write ("\r\n");
711 public void SendAsync (MailMessage message, object userToken)
714 throw new InvalidOperationException ("Another SendAsync operation is in progress");
716 worker = new BackgroundWorker ();
717 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
719 user_async_state = ea.Argument;
721 } catch (Exception ex) {
725 worker.WorkerSupportsCancellation = true;
726 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
727 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
728 OnSendCompleted (new AsyncCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state));
730 worker.RunWorkerAsync (userToken);
733 public void SendAsync (string from, string to, string subject, string body, object userToken)
735 SendAsync (new MailMessage (from, to, subject, body), userToken);
738 public void SendAsyncCancel ()
741 throw new InvalidOperationException ("SendAsync operation is not in progress");
742 worker.CancelAsync ();
745 private void AddPriorityHeader (MailMessage message) {
746 switch (message.Priority) {
747 case MailPriority.High:
748 SendHeader (HeaderName.Priority, "Urgent");
749 SendHeader (HeaderName.Importance, "high");
750 SendHeader (HeaderName.XPriority, "1");
752 case MailPriority.Low:
753 SendHeader (HeaderName.Priority, "Non-Urgent");
754 SendHeader (HeaderName.Importance, "low");
755 SendHeader (HeaderName.XPriority, "5");
760 private void SendSimpleBody (MailMessage message) {
761 SendHeader (HeaderName.ContentType, message.BodyContentType.ToString ());
762 if (message.ContentTransferEncoding != TransferEncoding.SevenBit)
763 SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (message.ContentTransferEncoding));
764 SendData (string.Empty);
766 SendData (EncodeBody (message));
769 private void SendBodylessSingleAlternate (AlternateView av) {
770 SendHeader (HeaderName.ContentType, av.ContentType.ToString ());
771 if (av.TransferEncoding != TransferEncoding.SevenBit)
772 SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (av.TransferEncoding));
773 SendData (string.Empty);
775 SendData (EncodeBody (av));
778 private void SendWithoutAttachments (MailMessage message, string boundary, bool attachmentExists)
780 if (message.Body == null && message.AlternateViews.Count == 1)
781 SendBodylessSingleAlternate (message.AlternateViews [0]);
782 else if (message.AlternateViews.Count > 0)
783 SendBodyWithAlternateViews (message, boundary, attachmentExists);
785 SendSimpleBody (message);
789 private void SendWithAttachments (MailMessage message) {
790 string boundary = GenerateBoundary ();
792 // first "multipart/mixed"
793 ContentType messageContentType = new ContentType ();
794 messageContentType.Boundary = boundary;
795 messageContentType.MediaType = "multipart/mixed";
796 messageContentType.CharSet = null;
798 SendHeader (HeaderName.ContentType, messageContentType.ToString ());
799 SendData (String.Empty);
802 Attachment body = null;
804 if (message.AlternateViews.Count > 0)
805 SendWithoutAttachments (message, boundary, true);
807 body = Attachment.CreateAttachmentFromString (message.Body, null, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
808 message.Attachments.Insert (0, body);
812 SendAttachments (message, body, boundary);
815 message.Attachments.Remove (body);
818 EndSection (boundary);
821 private void SendBodyWithAlternateViews (MailMessage message, string boundary, bool attachmentExists)
823 AlternateViewCollection alternateViews = message.AlternateViews;
825 string inner_boundary = GenerateBoundary ();
827 ContentType messageContentType = new ContentType ();
828 messageContentType.Boundary = inner_boundary;
829 messageContentType.MediaType = "multipart/alternative";
831 if (!attachmentExists) {
832 SendHeader (HeaderName.ContentType, messageContentType.ToString ());
833 SendData (String.Empty);
837 AlternateView body = null;
838 if (message.Body != null) {
839 body = AlternateView.CreateAlternateViewFromString (message.Body, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
840 alternateViews.Insert (0, body);
841 StartSection (boundary, messageContentType);
845 // alternate view sections
846 foreach (AlternateView av in alternateViews) {
848 string alt_boundary = null;
849 ContentType contentType;
850 if (av.LinkedResources.Count > 0) {
851 alt_boundary = GenerateBoundary ();
852 contentType = new ContentType ("multipart/related");
853 contentType.Boundary = alt_boundary;
854 contentType.Parameters ["type"] = "application/octet-stream";
855 StartSection (inner_boundary, contentType);
857 contentType = new ContentType (av.ContentType.ToString ());
858 StartSection (inner_boundary, contentType, av.TransferEncoding);
861 switch (av.TransferEncoding) {
862 case TransferEncoding.Base64:
863 byte [] content = new byte [av.ContentStream.Length];
864 av.ContentStream.Read (content, 0, content.Length);
866 SendData (Convert.ToBase64String (content));
868 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
871 case TransferEncoding.QuotedPrintable:
872 byte [] bytes = new byte [av.ContentStream.Length];
873 av.ContentStream.Read (bytes, 0, bytes.Length);
874 SendData (ToQuotedPrintable (bytes));
876 case TransferEncoding.SevenBit:
877 case TransferEncoding.Unknown:
878 content = new byte [av.ContentStream.Length];
879 SendData (Encoding.ASCII.GetString (content));
883 if (av.LinkedResources.Count > 0) {
884 SendLinkedResources (message, av.LinkedResources, alt_boundary);
885 EndSection (alt_boundary);
888 if (!attachmentExists)
889 SendData (string.Empty);
894 alternateViews.Remove (body);
896 EndSection (inner_boundary);
899 private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
901 foreach (LinkedResource lr in resources) {
902 ContentType contentType = new ContentType (lr.ContentType.ToString ());
903 StartSection (boundary, contentType, lr.TransferEncoding);
905 switch (lr.TransferEncoding) {
906 case TransferEncoding.Base64:
907 byte [] content = new byte [lr.ContentStream.Length];
908 lr.ContentStream.Read (content, 0, content.Length);
910 SendData (Convert.ToBase64String (content));
912 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
915 case TransferEncoding.QuotedPrintable:
916 byte [] bytes = new byte [lr.ContentStream.Length];
917 lr.ContentStream.Read (bytes, 0, bytes.Length);
918 SendData (ToQuotedPrintable (bytes));
920 case TransferEncoding.SevenBit:
921 case TransferEncoding.Unknown:
922 content = new byte [lr.ContentStream.Length];
923 SendData (Encoding.ASCII.GetString (content));
929 private void SendAttachments (MailMessage message, Attachment body, string boundary) {
930 foreach (Attachment att in message.Attachments) {
931 ContentType contentType = new ContentType (att.ContentType.ToString ());
932 if (att.Name != null) {
933 contentType.Name = att.Name;
934 if (att.NameEncoding != null)
935 contentType.CharSet = att.NameEncoding.HeaderName;
936 att.ContentDisposition.FileName = att.Name;
938 StartSection (boundary, contentType, att.TransferEncoding, att == body ? null : att.ContentDisposition);
940 byte [] content = new byte [att.ContentStream.Length];
941 att.ContentStream.Read (content, 0, content.Length);
942 switch (att.TransferEncoding) {
943 case TransferEncoding.Base64:
945 SendData (Convert.ToBase64String (content));
947 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
950 case TransferEncoding.QuotedPrintable:
951 SendData (ToQuotedPrintable (content));
953 case TransferEncoding.SevenBit:
954 case TransferEncoding.Unknown:
955 SendData (Encoding.ASCII.GetString (content));
959 SendData (string.Empty);
963 private SmtpResponse SendCommand (string command)
965 writer.Write (command);
966 // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
967 writer.Write ("\r\n");
972 private void SendHeader (string name, string value)
974 SendData (String.Format ("{0}: {1}", name, value));
977 private void StartSection (string section, ContentType sectionContentType)
979 SendData (String.Format ("--{0}", section));
980 SendHeader ("content-type", sectionContentType.ToString ());
981 SendData (string.Empty);
984 private void StartSection (string section, ContentType sectionContentType,TransferEncoding transferEncoding)
986 SendData (String.Format ("--{0}", section));
987 SendHeader ("content-type", sectionContentType.ToString ());
988 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
989 SendData (string.Empty);
992 private void StartSection (string section, ContentType sectionContentType, TransferEncoding transferEncoding, ContentDisposition contentDisposition) {
993 SendData (String.Format ("--{0}", section));
994 SendHeader ("content-type", sectionContentType.ToString ());
995 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
996 if (contentDisposition != null)
997 SendHeader ("content-disposition", contentDisposition.ToString ());
998 SendData (string.Empty);
1001 // use proper encoding to escape input
1002 private string ToQuotedPrintable (string input, Encoding enc)
1004 byte [] bytes = enc.GetBytes (input);
1005 return ToQuotedPrintable (bytes);
1008 private string ToQuotedPrintable (byte [] bytes)
1010 StringWriter writer = new StringWriter ();
1011 int charsInLine = 0;
1013 StringBuilder sb = new StringBuilder("=", 3);
1014 byte equalSign = (byte)'=';
1017 foreach (byte i in bytes) {
1018 if (i > 127 || i == equalSign) {
1020 sb.Append(Convert.ToString (i, 16).ToUpperInvariant ());
1023 c = Convert.ToChar (i);
1024 if (c == '\r' || c == '\n') {
1032 charsInLine += curLen;
1033 if (charsInLine > 75) {
1034 writer.Write ("=\r\n");
1035 charsInLine = curLen;
1040 writer.Write (sb.ToString ());
1043 return writer.ToString ();
1045 private static string GetTransferEncodingName (TransferEncoding encoding)
1048 case TransferEncoding.QuotedPrintable:
1049 return "quoted-printable";
1050 case TransferEncoding.SevenBit:
1052 case TransferEncoding.Base64:
1059 RemoteCertificateValidationCallback callback = delegate (object sender,
1060 X509Certificate certificate,
1062 SslPolicyErrors sslPolicyErrors) {
1063 if (sslPolicyErrors != SslPolicyErrors.None)
1064 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
1069 private void InitiateSecureConnection () {
1070 SmtpResponse response = SendCommand ("STARTTLS");
1072 if (IsError (response)) {
1073 throw new SmtpException (SmtpStatusCode.GeneralFailure, "Server does not support secure connections.");
1077 ((NetworkStream) stream).ChangeToSSLSocket ();
1079 SslStream sslStream = new SslStream (stream, false, callback, null);
1080 CheckCancellation ();
1081 sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
1085 throw new SystemException ("You are using an incomplete System.dll build");
1089 void Authenticate ()
1091 string user = null, pass = null;
1093 if (UseDefaultCredentials) {
1094 user = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").UserName;
1095 pass = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").Password;
1096 } else if (Credentials != null) {
1097 user = Credentials.GetCredential (host, port, "smtp").UserName;
1098 pass = Credentials.GetCredential (host, port, "smtp").Password;
1103 Authenticate (user, pass);
1106 void Authenticate (string Username, string Password)
1108 // FIXME: use the proper AuthMech
1109 SmtpResponse status = SendCommand ("AUTH LOGIN");
1110 if (((int) status.StatusCode) != 334) {
1111 throw new SmtpException (status.StatusCode, status.Description);
1114 status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Username)));
1115 if (((int) status.StatusCode) != 334) {
1116 throw new SmtpException (status.StatusCode, status.Description);
1119 status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Password)));
1120 if (IsError (status)) {
1121 throw new SmtpException (status.StatusCode, status.Description);
1125 #endregion // Methods
1127 // The HeaderName struct is used to store constant string values representing mail headers.
1128 private struct HeaderName {
1129 public const string ContentTransferEncoding = "Content-Transfer-Encoding";
1130 public const string ContentType = "Content-Type";
1131 public const string Bcc = "Bcc";
1132 public const string Cc = "Cc";
1133 public const string From = "From";
1134 public const string Subject = "Subject";
1135 public const string To = "To";
1136 public const string MimeVersion = "MIME-Version";
1137 public const string MessageId = "Message-ID";
1138 public const string Priority = "Priority";
1139 public const string Importance = "Importance";
1140 public const string XPriority = "X-Priority";
1141 public const string Date = "Date";
1144 // This object encapsulates the status code and description of an SMTP response.
1145 private struct SmtpResponse {
1146 public SmtpStatusCode StatusCode;
1147 public string Description;
1149 public static SmtpResponse Parse (string line) {
1150 SmtpResponse response = new SmtpResponse ();
1152 if (line.Length < 4)
1153 throw new SmtpException ("Response is to short " +
1156 if ((line [3] != ' ') && (line [3] != '-'))
1157 throw new SmtpException ("Response format is wrong.(" +
1160 // parse the response code
1161 response.StatusCode = (SmtpStatusCode) Int32.Parse (line.Substring (0, 3));
1163 // set the raw response
1164 response.Description = line;
1171 class CCredentialsByHost : ICredentialsByHost
1173 public CCredentialsByHost (string userName, string password) {
1174 this.userName = userName;
1175 this.password = password;
1178 public NetworkCredential GetCredential (string host, int port, string authenticationType) {
1179 return new NetworkCredential (userName, password);
1182 private string userName;
1183 private string password;