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 string pickupDirectoryLocation
;
68 SmtpDeliveryMethod deliveryMethod
;
71 X509CertificateCollection clientCertificates
;
79 MailAddress defaultFrom
;
81 MailMessage messageInProcess
;
83 BackgroundWorker worker
;
84 object user_async_state
;
98 class CancellationException
: Exception
102 AuthMechs authMechs
= AuthMechs
.None
;
103 Mutex mutex
= new Mutex ();
114 public SmtpClient (string host
)
119 public SmtpClient (string host
, int port
) {
120 #if CONFIGURATION_DEP
121 SmtpSection cfg
= (SmtpSection
) ConfigurationManager
.GetSection ("system.net/mailSettings/smtp");
124 this.host
= cfg
.Network
.Host
;
125 this.port
= cfg
.Network
.Port
;
126 TargetName
= cfg
.Network
.TargetName
;
127 if (this.TargetName
== null)
128 TargetName
= "SMTPSVC/" + (host
!= null ? host
: "");
131 if (cfg
.Network
.UserName
!= null) {
132 string password
= String
.Empty
;
134 if (cfg
.Network
.Password
!= null)
135 password
= cfg
.Network
.Password
;
137 Credentials
= new CCredentialsByHost (cfg
.Network
.UserName
, password
);
140 if (cfg
.From
!= null)
141 defaultFrom
= new MailAddress (cfg
.From
);
144 // Just to eliminate the warning, this codepath does not end up in production.
148 if (!String
.IsNullOrEmpty (host
))
155 #endregion // Constructors
160 [MonoTODO("Client certificates not used")]
161 public X509CertificateCollection ClientCertificates
{
163 if (clientCertificates
== null)
164 clientCertificates
= new X509CertificateCollection ();
165 return clientCertificates
;
173 string TargetName { get; set; }
175 public ICredentialsByHost Credentials
{
176 get { return credentials; }
183 public SmtpDeliveryMethod DeliveryMethod
{
184 get { return deliveryMethod; }
187 deliveryMethod
= value;
191 public bool EnableSsl
{
192 get { return enableSsl; }
203 throw new ArgumentNullException ("value");
204 if (value.Length
== 0)
205 throw new ArgumentException ("An empty string is not allowed.", "value");
211 public string PickupDirectoryLocation
{
212 get { return pickupDirectoryLocation; }
213 set { pickupDirectoryLocation = value; }
220 throw new ArgumentOutOfRangeException ("value");
227 public ServicePoint ServicePoint
{
228 get { throw new NotImplementedException (); }
232 get { return timeout; }
235 throw new ArgumentOutOfRangeException ("value");
241 public bool UseDefaultCredentials
{
242 get { return false; }
243 [MonoNotSupported ("no DefaultCredential support in Mono")]
246 throw new NotImplementedException ("Default credentials are not supported");
251 #endregion // Properties
255 public event SendCompletedEventHandler SendCompleted
;
261 private void CheckState ()
263 if (messageInProcess
!= null)
264 throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
267 private static string EncodeAddress(MailAddress address
)
269 string encodedDisplayName
= ContentType
.EncodeSubjectRFC2047 (address
.DisplayName
, Encoding
.UTF8
);
270 return "\"" + encodedDisplayName
+ "\" <" + address
.Address
+ ">";
273 private static string EncodeAddresses(MailAddressCollection addresses
)
275 StringBuilder sb
= new StringBuilder();
277 foreach (MailAddress address
in addresses
) {
281 sb
.Append(EncodeAddress(address
));
284 return sb
.ToString();
287 private string EncodeSubjectRFC2047 (MailMessage message
)
289 return ContentType
.EncodeSubjectRFC2047 (message
.Subject
, message
.SubjectEncoding
);
292 private string EncodeBody (MailMessage message
)
294 string body
= message
.Body
;
295 Encoding encoding
= message
.BodyEncoding
;
297 switch (message
.ContentTransferEncoding
) {
298 case TransferEncoding
.SevenBit
:
300 case TransferEncoding
.Base64
:
301 return Convert
.ToBase64String (encoding
.GetBytes (body
), Base64FormattingOptions
.InsertLineBreaks
);
303 return ToQuotedPrintable (body
, encoding
);
307 private string EncodeBody (AlternateView av
)
309 //Encoding encoding = av.ContentType.CharSet != null ? Encoding.GetEncoding (av.ContentType.CharSet) : Encoding.UTF8;
311 byte [] bytes
= new byte [av
.ContentStream
.Length
];
312 av
.ContentStream
.Read (bytes
, 0, bytes
.Length
);
315 switch (av
.TransferEncoding
) {
316 case TransferEncoding
.SevenBit
:
317 return Encoding
.ASCII
.GetString (bytes
);
318 case TransferEncoding
.Base64
:
319 return Convert
.ToBase64String (bytes
, Base64FormattingOptions
.InsertLineBreaks
);
321 return ToQuotedPrintable (bytes
);
326 private void EndSection (string section
)
328 SendData (String
.Format ("--{0}--", section
));
329 SendData (string.Empty
);
332 private string GenerateBoundary ()
334 string output
= GenerateBoundary (boundaryIndex
);
339 private static string GenerateBoundary (int index
)
341 return String
.Format ("--boundary_{0}_{1}", index
, Guid
.NewGuid ().ToString ("D"));
344 private bool IsError (SmtpResponse status
)
346 return ((int) status
.StatusCode
) >= 400;
349 protected void OnSendCompleted (AsyncCompletedEventArgs e
)
352 if (SendCompleted
!= null)
353 SendCompleted (this, e
);
356 user_async_state
= null;
360 private void CheckCancellation ()
362 if (worker
!= null && worker
.CancellationPending
)
363 throw new CancellationException ();
366 private SmtpResponse
Read () {
367 byte [] buffer
= new byte [512];
369 bool lastLine
= false;
372 CheckCancellation ();
374 int readLength
= stream
.Read (buffer
, position
, buffer
.Length
- position
);
375 if (readLength
> 0) {
376 int available
= position
+ readLength
- 1;
377 if (available
> 4 && (buffer
[available
] == '\n' || buffer
[available
] == '\r'))
378 for (int index
= available
- 3; ; index
--) {
379 if (index
< 0 || buffer
[index
] == '\n' || buffer
[index
] == '\r') {
380 lastLine
= buffer
[index
+ 4] == ' ';
386 position
+= readLength
;
388 // check if buffer is full
389 if (position
== buffer
.Length
) {
390 byte [] newBuffer
= new byte [buffer
.Length
* 2];
391 Array
.Copy (buffer
, 0, newBuffer
, 0, buffer
.Length
);
401 Encoding encoding
= new ASCIIEncoding ();
403 string line
= encoding
.GetString (buffer
, 0, position
- 1);
405 // parse the line to the lastResponse object
406 SmtpResponse response
= SmtpResponse
.Parse (line
);
410 throw new System
.IO
.IOException ("Connection closed");
414 void ResetExtensions()
416 authMechs
= AuthMechs
.None
;
419 void ParseExtensions (string extens
)
421 char[] delims
= new char [1] { ' ' }
;
422 string[] parts
= extens
.Split ('\n');
424 foreach (string part
in parts
) {
428 string start
= part
.Substring (4);
429 if (start
.StartsWith ("AUTH ", StringComparison
.Ordinal
)) {
430 string[] options
= start
.Split (delims
);
431 for (int k
= 1; k
< options
.Length
; k
++) {
432 string option
= options
[k
].Trim();
435 authMechs
|= AuthMechs
.CramMD5
;
438 authMechs
|= AuthMechs
.DigestMD5
;
441 authMechs
|= AuthMechs
.GssAPI
;
444 authMechs
|= AuthMechs
.Kerberos4
;
447 authMechs
|= AuthMechs
.Login
;
450 authMechs
|= AuthMechs
.Plain
;
458 public void Send (MailMessage message
)
461 throw new ArgumentNullException ("message");
463 if (deliveryMethod
== SmtpDeliveryMethod
.Network
&& (Host
== null || Host
.Trim ().Length
== 0))
464 throw new InvalidOperationException ("The SMTP host was not specified");
465 else if (deliveryMethod
== SmtpDeliveryMethod
.PickupDirectoryFromIis
)
466 throw new NotSupportedException("IIS delivery is not supported");
471 // Block while sending
474 messageInProcess
= message
;
475 if (deliveryMethod
== SmtpDeliveryMethod
.SpecifiedPickupDirectory
)
476 SendToFile (message
);
478 SendInternal (message
);
479 } catch (CancellationException
) {
480 // This exception is introduced for convenient cancellation process.
481 } catch (SmtpException
) {
483 } catch (Exception ex
) {
484 throw new SmtpException ("Message could not be sent.", ex
);
486 // Release the mutex to allow other threads access
487 mutex
.ReleaseMutex ();
488 messageInProcess
= null;
492 private void SendInternal (MailMessage message
)
494 CheckCancellation ();
497 client
= new TcpClient (host
, port
);
498 stream
= client
.GetStream ();
499 // FIXME: this StreamWriter creation is bogus.
500 // It expects as if a Stream were able to switch to SSL
501 // mode (such behavior is only in Mainsoft Socket API).
502 writer
= new StreamWriter (stream
);
503 reader
= new StreamReader (stream
);
518 // FIXME: simple implementation, could be brushed up.
519 private void SendToFile (MailMessage message
)
521 if (!Path
.IsPathRooted (pickupDirectoryLocation
))
522 throw new SmtpException("Only absolute directories are allowed for pickup directory.");
524 string filename
= Path
.Combine (pickupDirectoryLocation
,
525 Guid
.NewGuid() + ".eml");
528 writer
= new StreamWriter(filename
);
530 MailAddress
from = message
.From
;
535 SendHeader (HeaderName
.Date
, DateTime
.Now
.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo
.InvariantInfo
));
536 SendHeader (HeaderName
.From
, from.ToString ());
537 SendHeader (HeaderName
.To
, message
.To
.ToString ());
538 if (message
.CC
.Count
> 0)
539 SendHeader (HeaderName
.Cc
, message
.CC
.ToString ());
540 SendHeader (HeaderName
.Subject
, EncodeSubjectRFC2047 (message
));
542 foreach (string s
in message
.Headers
.AllKeys
)
543 SendHeader (s
, message
.Headers
[s
]);
545 AddPriorityHeader (message
);
548 if (message
.Attachments
.Count
> 0)
549 SendWithAttachments (message
);
551 SendWithoutAttachments (message
, null, false);
555 if (writer
!= null) writer
.Close(); writer
= null;
559 private void SendCore (MailMessage message
)
564 if (IsError (status
))
565 throw new SmtpException (status
.StatusCode
, status
.Description
);
569 // FIXME: parse the list of extensions so we don't bother wasting
570 // our time trying commands if they aren't supported.
571 status
= SendCommand ("EHLO " + Dns
.GetHostName ());
573 if (IsError (status
)) {
574 status
= SendCommand ("HELO " + Dns
.GetHostName ());
576 if (IsError (status
))
577 throw new SmtpException (status
.StatusCode
, status
.Description
);
579 // Parse ESMTP extensions
580 string extens
= status
.Description
;
583 ParseExtensions (extens
);
587 InitiateSecureConnection ();
589 writer
= new StreamWriter (stream
);
590 reader
= new StreamReader (stream
);
591 status
= SendCommand ("EHLO " + Dns
.GetHostName ());
593 if (IsError (status
)) {
594 status
= SendCommand ("HELO " + Dns
.GetHostName ());
596 if (IsError (status
))
597 throw new SmtpException (status
.StatusCode
, status
.Description
);
599 // Parse ESMTP extensions
600 string extens
= status
.Description
;
602 ParseExtensions (extens
);
606 if (authMechs
!= AuthMechs
.None
)
609 MailAddress
from = message
.From
;
615 status
= SendCommand ("MAIL FROM:<" + from.Address
+ '>');
616 if (IsError (status
)) {
617 throw new SmtpException (status
.StatusCode
, status
.Description
);
620 // Send RCPT TO: for all recipients
621 List
<SmtpFailedRecipientException
> sfre
= new List
<SmtpFailedRecipientException
> ();
623 for (int i
= 0; i
< message
.To
.Count
; i
++) {
624 status
= SendCommand ("RCPT TO:<" + message
.To
[i
].Address
+ '>');
625 if (IsError (status
))
626 sfre
.Add (new SmtpFailedRecipientException (status
.StatusCode
, message
.To
[i
].Address
));
628 for (int i
= 0; i
< message
.CC
.Count
; i
++) {
629 status
= SendCommand ("RCPT TO:<" + message
.CC
[i
].Address
+ '>');
630 if (IsError (status
))
631 sfre
.Add (new SmtpFailedRecipientException (status
.StatusCode
, message
.CC
[i
].Address
));
633 for (int i
= 0; i
< message
.Bcc
.Count
; i
++) {
634 status
= SendCommand ("RCPT TO:<" + message
.Bcc
[i
].Address
+ '>');
635 if (IsError (status
))
636 sfre
.Add (new SmtpFailedRecipientException (status
.StatusCode
, message
.Bcc
[i
].Address
));
639 #if TARGET_JVM // List<T>.ToArray () is not supported
640 if (sfre
.Count
> 0) {
641 SmtpFailedRecipientException
[] xs
= new SmtpFailedRecipientException
[sfre
.Count
];
643 throw new SmtpFailedRecipientsException ("failed recipients", xs
);
647 throw new SmtpFailedRecipientsException ("failed recipients", sfre
.ToArray ());
651 status
= SendCommand ("DATA");
652 if (IsError (status
))
653 throw new SmtpException (status
.StatusCode
, status
.Description
);
655 // Send message headers
656 string dt
= DateTime
.Now
.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo
.InvariantInfo
);
657 // remove ':' from time zone offset (e.g. from "+01:00")
658 dt
= dt
.Remove (dt
.Length
- 3, 1);
659 SendHeader (HeaderName
.Date
, dt
);
661 SendHeader (HeaderName
.From
, EncodeAddress (from));
662 SendHeader (HeaderName
.To
, EncodeAddresses (message
.To
));
663 if (message
.CC
.Count
> 0)
664 SendHeader (HeaderName
.Cc
, EncodeAddresses (message
.CC
));
665 SendHeader (HeaderName
.Subject
, EncodeSubjectRFC2047 (message
));
669 switch (message
.Priority
){
670 case MailPriority
.Normal
:
674 case MailPriority
.Low
:
678 case MailPriority
.High
:
682 SendHeader ("Priority", v
);
683 if (message
.Sender
!= null)
684 SendHeader ("Sender", EncodeAddress (message
.Sender
));
685 if (message
.ReplyToList
.Count
> 0)
686 SendHeader ("Reply-To", EncodeAddresses (message
.ReplyToList
));
689 foreach (string s
in message
.Headers
.AllKeys
)
690 SendHeader (s
, ContentType
.EncodeSubjectRFC2047 (message
.Headers
[s
], message
.HeadersEncoding
));
692 foreach (string s
in message
.Headers
.AllKeys
)
693 SendHeader (s
, message
.Headers
[s
]);
696 AddPriorityHeader (message
);
699 if (message
.Attachments
.Count
> 0)
700 SendWithAttachments (message
);
702 SendWithoutAttachments (message
, null, false);
707 if (IsError (status
))
708 throw new SmtpException (status
.StatusCode
, status
.Description
);
711 status
= SendCommand ("QUIT");
712 } catch (System
.IO
.IOException
) {
713 // We excuse server for the rude connection closing as a response to QUIT
717 public void Send (string from, string to
, string subject
, string body
)
719 Send (new MailMessage (from, to
, subject
, body
));
722 private void SendDot()
724 writer
.Write(".\r\n");
728 private void SendData (string data
)
730 if (String
.IsNullOrEmpty (data
)) {
731 writer
.Write("\r\n");
736 StringReader sr
= new StringReader (data
);
738 bool escapeDots
= deliveryMethod
== SmtpDeliveryMethod
.Network
;
739 while ((line
= sr
.ReadLine ()) != null) {
740 CheckCancellation ();
744 for (i
= 0; i
< line
.Length
; i
++) {
748 if (i
> 0 && i
== line
.Length
) {
753 writer
.Write ("\r\n");
758 public void SendAsync (MailMessage message
, object userToken
)
761 throw new InvalidOperationException ("Another SendAsync operation is in progress");
763 worker
= new BackgroundWorker ();
764 worker
.DoWork
+= delegate (object o
, DoWorkEventArgs ea
) {
766 user_async_state
= ea
.Argument
;
768 } catch (Exception ex
) {
773 worker
.WorkerSupportsCancellation
= true;
774 worker
.RunWorkerCompleted
+= delegate (object o
, RunWorkerCompletedEventArgs ea
) {
775 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
776 OnSendCompleted (new AsyncCompletedEventArgs (ea
.Error
, ea
.Cancelled
, user_async_state
));
778 worker
.RunWorkerAsync (userToken
);
781 public void SendAsync (string from, string to
, string subject
, string body
, object userToken
)
783 SendAsync (new MailMessage (from, to
, subject
, body
), userToken
);
786 public void SendAsyncCancel ()
789 throw new InvalidOperationException ("SendAsync operation is not in progress");
790 worker
.CancelAsync ();
793 private void AddPriorityHeader (MailMessage message
) {
794 switch (message
.Priority
) {
795 case MailPriority
.High
:
796 SendHeader (HeaderName
.Priority
, "Urgent");
797 SendHeader (HeaderName
.Importance
, "high");
798 SendHeader (HeaderName
.XPriority
, "1");
800 case MailPriority
.Low
:
801 SendHeader (HeaderName
.Priority
, "Non-Urgent");
802 SendHeader (HeaderName
.Importance
, "low");
803 SendHeader (HeaderName
.XPriority
, "5");
808 private void SendSimpleBody (MailMessage message
) {
809 SendHeader (HeaderName
.ContentType
, message
.BodyContentType
.ToString ());
810 if (message
.ContentTransferEncoding
!= TransferEncoding
.SevenBit
)
811 SendHeader (HeaderName
.ContentTransferEncoding
, GetTransferEncodingName (message
.ContentTransferEncoding
));
812 SendData (string.Empty
);
814 SendData (EncodeBody (message
));
817 private void SendBodylessSingleAlternate (AlternateView av
) {
818 SendHeader (HeaderName
.ContentType
, av
.ContentType
.ToString ());
819 if (av
.TransferEncoding
!= TransferEncoding
.SevenBit
)
820 SendHeader (HeaderName
.ContentTransferEncoding
, GetTransferEncodingName (av
.TransferEncoding
));
821 SendData (string.Empty
);
823 SendData (EncodeBody (av
));
826 private void SendWithoutAttachments (MailMessage message
, string boundary
, bool attachmentExists
)
828 if (message
.Body
== null && message
.AlternateViews
.Count
== 1)
829 SendBodylessSingleAlternate (message
.AlternateViews
[0]);
830 else if (message
.AlternateViews
.Count
> 0)
831 SendBodyWithAlternateViews (message
, boundary
, attachmentExists
);
833 SendSimpleBody (message
);
837 private void SendWithAttachments (MailMessage message
) {
838 string boundary
= GenerateBoundary ();
840 // first "multipart/mixed"
841 ContentType messageContentType
= new ContentType ();
842 messageContentType
.Boundary
= boundary
;
843 messageContentType
.MediaType
= "multipart/mixed";
844 messageContentType
.CharSet
= null;
846 SendHeader (HeaderName
.ContentType
, messageContentType
.ToString ());
847 SendData (String
.Empty
);
850 Attachment body
= null;
852 if (message
.AlternateViews
.Count
> 0)
853 SendWithoutAttachments (message
, boundary
, true);
855 body
= Attachment
.CreateAttachmentFromString (message
.Body
, null, message
.BodyEncoding
, message
.IsBodyHtml
? "text/html" : "text/plain");
856 message
.Attachments
.Insert (0, body
);
860 SendAttachments (message
, body
, boundary
);
863 message
.Attachments
.Remove (body
);
866 EndSection (boundary
);
869 private void SendBodyWithAlternateViews (MailMessage message
, string boundary
, bool attachmentExists
)
871 AlternateViewCollection alternateViews
= message
.AlternateViews
;
873 string inner_boundary
= GenerateBoundary ();
875 ContentType messageContentType
= new ContentType ();
876 messageContentType
.Boundary
= inner_boundary
;
877 messageContentType
.MediaType
= "multipart/alternative";
879 if (!attachmentExists
) {
880 SendHeader (HeaderName
.ContentType
, messageContentType
.ToString ());
881 SendData (String
.Empty
);
885 AlternateView body
= null;
886 if (message
.Body
!= null) {
887 body
= AlternateView
.CreateAlternateViewFromString (message
.Body
, message
.BodyEncoding
, message
.IsBodyHtml
? "text/html" : "text/plain");
888 alternateViews
.Insert (0, body
);
889 StartSection (boundary
, messageContentType
);
893 // alternate view sections
894 foreach (AlternateView av
in alternateViews
) {
896 string alt_boundary
= null;
897 ContentType contentType
;
898 if (av
.LinkedResources
.Count
> 0) {
899 alt_boundary
= GenerateBoundary ();
900 contentType
= new ContentType ("multipart/related");
901 contentType
.Boundary
= alt_boundary
;
903 contentType
.Parameters
["type"] = av
.ContentType
.ToString ();
904 StartSection (inner_boundary
, contentType
);
905 StartSection (alt_boundary
, av
.ContentType
, av
.TransferEncoding
);
907 contentType
= new ContentType (av
.ContentType
.ToString ());
908 StartSection (inner_boundary
, contentType
, av
.TransferEncoding
);
911 switch (av
.TransferEncoding
) {
912 case TransferEncoding
.Base64
:
913 byte [] content
= new byte [av
.ContentStream
.Length
];
914 av
.ContentStream
.Read (content
, 0, content
.Length
);
916 SendData (Convert
.ToBase64String (content
));
918 SendData (Convert
.ToBase64String (content
, Base64FormattingOptions
.InsertLineBreaks
));
921 case TransferEncoding
.QuotedPrintable
:
922 byte [] bytes
= new byte [av
.ContentStream
.Length
];
923 av
.ContentStream
.Read (bytes
, 0, bytes
.Length
);
924 SendData (ToQuotedPrintable (bytes
));
926 case TransferEncoding
.SevenBit
:
927 case TransferEncoding
.Unknown
:
928 content
= new byte [av
.ContentStream
.Length
];
929 av
.ContentStream
.Read (content
, 0, content
.Length
);
930 SendData (Encoding
.ASCII
.GetString (content
));
934 if (av
.LinkedResources
.Count
> 0) {
935 SendLinkedResources (message
, av
.LinkedResources
, alt_boundary
);
936 EndSection (alt_boundary
);
939 if (!attachmentExists
)
940 SendData (string.Empty
);
945 alternateViews
.Remove (body
);
947 EndSection (inner_boundary
);
950 private void SendLinkedResources (MailMessage message
, LinkedResourceCollection resources
, string boundary
)
952 foreach (LinkedResource lr
in resources
) {
953 StartSection (boundary
, lr
.ContentType
, lr
.TransferEncoding
, lr
);
955 switch (lr
.TransferEncoding
) {
956 case TransferEncoding
.Base64
:
957 byte [] content
= new byte [lr
.ContentStream
.Length
];
958 lr
.ContentStream
.Read (content
, 0, content
.Length
);
960 SendData (Convert
.ToBase64String (content
));
962 SendData (Convert
.ToBase64String (content
, Base64FormattingOptions
.InsertLineBreaks
));
965 case TransferEncoding
.QuotedPrintable
:
966 byte [] bytes
= new byte [lr
.ContentStream
.Length
];
967 lr
.ContentStream
.Read (bytes
, 0, bytes
.Length
);
968 SendData (ToQuotedPrintable (bytes
));
970 case TransferEncoding
.SevenBit
:
971 case TransferEncoding
.Unknown
:
972 content
= new byte [lr
.ContentStream
.Length
];
973 lr
.ContentStream
.Read (content
, 0, content
.Length
);
974 SendData (Encoding
.ASCII
.GetString (content
));
980 private void SendAttachments (MailMessage message
, Attachment body
, string boundary
) {
981 foreach (Attachment att
in message
.Attachments
) {
982 ContentType contentType
= new ContentType (att
.ContentType
.ToString ());
983 if (att
.Name
!= null) {
984 contentType
.Name
= att
.Name
;
985 if (att
.NameEncoding
!= null)
986 contentType
.CharSet
= att
.NameEncoding
.HeaderName
;
987 att
.ContentDisposition
.FileName
= att
.Name
;
989 StartSection (boundary
, contentType
, att
.TransferEncoding
, att
== body
? null : att
.ContentDisposition
);
991 byte [] content
= new byte [att
.ContentStream
.Length
];
992 att
.ContentStream
.Read (content
, 0, content
.Length
);
993 switch (att
.TransferEncoding
) {
994 case TransferEncoding
.Base64
:
996 SendData (Convert
.ToBase64String (content
));
998 SendData (Convert
.ToBase64String (content
, Base64FormattingOptions
.InsertLineBreaks
));
1001 case TransferEncoding
.QuotedPrintable
:
1002 SendData (ToQuotedPrintable (content
));
1004 case TransferEncoding
.SevenBit
:
1005 case TransferEncoding
.Unknown
:
1006 SendData (Encoding
.ASCII
.GetString (content
));
1010 SendData (string.Empty
);
1014 private SmtpResponse
SendCommand (string command
)
1016 writer
.Write (command
);
1017 // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
1018 writer
.Write ("\r\n");
1023 private void SendHeader (string name
, string value)
1025 SendData (String
.Format ("{0}: {1}", name
, value));
1028 private void StartSection (string section
, ContentType sectionContentType
)
1030 SendData (String
.Format ("--{0}", section
));
1031 SendHeader ("content-type", sectionContentType
.ToString ());
1032 SendData (string.Empty
);
1035 private void StartSection (string section
, ContentType sectionContentType
,TransferEncoding transferEncoding
)
1037 SendData (String
.Format ("--{0}", section
));
1038 SendHeader ("content-type", sectionContentType
.ToString ());
1039 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding
));
1040 SendData (string.Empty
);
1043 private void StartSection(string section
, ContentType sectionContentType
, TransferEncoding transferEncoding
, LinkedResource lr
)
1045 SendData (String
.Format("--{0}", section
));
1046 SendHeader ("content-type", sectionContentType
.ToString ());
1047 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding
));
1049 if (lr
.ContentId
!= null && lr
.ContentId
.Length
> 0)
1050 SendHeader("content-ID", "<" + lr
.ContentId
+ ">");
1052 SendData (string.Empty
);
1055 private void StartSection (string section
, ContentType sectionContentType
, TransferEncoding transferEncoding
, ContentDisposition contentDisposition
) {
1056 SendData (String
.Format ("--{0}", section
));
1057 SendHeader ("content-type", sectionContentType
.ToString ());
1058 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding
));
1059 if (contentDisposition
!= null)
1060 SendHeader ("content-disposition", contentDisposition
.ToString ());
1061 SendData (string.Empty
);
1064 // use proper encoding to escape input
1065 private string ToQuotedPrintable (string input
, Encoding enc
)
1067 byte [] bytes
= enc
.GetBytes (input
);
1068 return ToQuotedPrintable (bytes
);
1071 private string ToQuotedPrintable (byte [] bytes
)
1073 StringWriter writer
= new StringWriter ();
1074 int charsInLine
= 0;
1076 StringBuilder sb
= new StringBuilder("=", 3);
1077 byte equalSign
= (byte)'=';
1080 foreach (byte i
in bytes
) {
1081 if (i
> 127 || i
== equalSign
) {
1083 sb
.Append(Convert
.ToString (i
, 16).ToUpperInvariant ());
1086 c
= Convert
.ToChar (i
);
1087 if (c
== '\r' || c
== '\n') {
1095 charsInLine
+= curLen
;
1096 if (charsInLine
> 75) {
1097 writer
.Write ("=\r\n");
1098 charsInLine
= curLen
;
1103 writer
.Write (sb
.ToString ());
1106 return writer
.ToString ();
1108 private static string GetTransferEncodingName (TransferEncoding encoding
)
1111 case TransferEncoding
.QuotedPrintable
:
1112 return "quoted-printable";
1113 case TransferEncoding
.SevenBit
:
1115 case TransferEncoding
.Base64
:
1122 RemoteCertificateValidationCallback callback
= delegate (object sender
,
1123 X509Certificate certificate
,
1125 SslPolicyErrors sslPolicyErrors
) {
1126 // honor any exciting callback defined on ServicePointManager
1127 if (ServicePointManager
.ServerCertificateValidationCallback
!= null)
1128 return ServicePointManager
.ServerCertificateValidationCallback (sender
, certificate
, chain
, sslPolicyErrors
);
1129 // otherwise provide our own
1130 if (sslPolicyErrors
!= SslPolicyErrors
.None
)
1131 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors
);
1136 private void InitiateSecureConnection () {
1137 SmtpResponse response
= SendCommand ("STARTTLS");
1139 if (IsError (response
)) {
1140 throw new SmtpException (SmtpStatusCode
.GeneralFailure
, "Server does not support secure connections.");
1144 ((NetworkStream
) stream
).ChangeToSSLSocket ();
1146 SslStream sslStream
= new SslStream (stream
, false, callback
, null);
1147 CheckCancellation ();
1148 sslStream
.AuthenticateAsClient (Host
, this.ClientCertificates
, SslProtocols
.Default
, false);
1152 throw new SystemException ("You are using an incomplete System.dll build");
1156 void Authenticate ()
1158 string user
= null, pass
= null;
1160 if (UseDefaultCredentials
) {
1161 user
= CredentialCache
.DefaultCredentials
.GetCredential (new System
.Uri ("smtp://" + host
), "basic").UserName
;
1162 pass
= CredentialCache
.DefaultCredentials
.GetCredential (new System
.Uri ("smtp://" + host
), "basic").Password
;
1163 } else if (Credentials
!= null) {
1164 user
= Credentials
.GetCredential (host
, port
, "smtp").UserName
;
1165 pass
= Credentials
.GetCredential (host
, port
, "smtp").Password
;
1170 Authenticate (user
, pass
);
1173 void Authenticate (string Username
, string Password
)
1175 // FIXME: use the proper AuthMech
1176 SmtpResponse status
= SendCommand ("AUTH LOGIN");
1177 if (((int) status
.StatusCode
) != 334) {
1178 throw new SmtpException (status
.StatusCode
, status
.Description
);
1181 status
= SendCommand (Convert
.ToBase64String (Encoding
.ASCII
.GetBytes (Username
)));
1182 if (((int) status
.StatusCode
) != 334) {
1183 throw new SmtpException (status
.StatusCode
, status
.Description
);
1186 status
= SendCommand (Convert
.ToBase64String (Encoding
.ASCII
.GetBytes (Password
)));
1187 if (IsError (status
)) {
1188 throw new SmtpException (status
.StatusCode
, status
.Description
);
1192 #endregion // Methods
1194 // The HeaderName struct is used to store constant string values representing mail headers.
1195 private struct HeaderName
{
1196 public const string ContentTransferEncoding
= "Content-Transfer-Encoding";
1197 public const string ContentType
= "Content-Type";
1198 public const string Bcc
= "Bcc";
1199 public const string Cc
= "Cc";
1200 public const string From
= "From";
1201 public const string Subject
= "Subject";
1202 public const string To
= "To";
1203 public const string MimeVersion
= "MIME-Version";
1204 public const string MessageId
= "Message-ID";
1205 public const string Priority
= "Priority";
1206 public const string Importance
= "Importance";
1207 public const string XPriority
= "X-Priority";
1208 public const string Date
= "Date";
1211 // This object encapsulates the status code and description of an SMTP response.
1212 private struct SmtpResponse
{
1213 public SmtpStatusCode StatusCode
;
1214 public string Description
;
1216 public static SmtpResponse
Parse (string line
) {
1217 SmtpResponse response
= new SmtpResponse ();
1219 if (line
.Length
< 4)
1220 throw new SmtpException ("Response is to short " +
1223 if ((line
[3] != ' ') && (line
[3] != '-'))
1224 throw new SmtpException ("Response format is wrong.(" +
1227 // parse the response code
1228 response
.StatusCode
= (SmtpStatusCode
) Int32
.Parse (line
.Substring (0, 3));
1230 // set the raw response
1231 response
.Description
= line
;
1238 class CCredentialsByHost
: ICredentialsByHost
1240 public CCredentialsByHost (string userName
, string password
) {
1241 this.userName
= userName
;
1242 this.password
= password
;
1245 public NetworkCredential
GetCredential (string host
, int port
, string authenticationType
) {
1246 return new NetworkCredential (userName
, password
);
1249 private string userName
;
1250 private string password
;