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 using System
.ComponentModel
;
37 using System
.Net
.Mime
;
38 using System
.Net
.Sockets
;
40 using System
.Threading
;
42 namespace System
.Net
.Mail
{
43 public class SmtpClient
: IDisposable
//, IGetContextAwareResult
50 ICredentialsByHost credentials
;
51 bool useDefaultCredentials
;
59 Mutex mutex
= new Mutex ();
61 const string MimeVersion
= "1.0 (produced by Mono System.Net.Mail.SmtpClient)";
72 public SmtpClient (string host
)
77 [MonoTODO ("Load default settings from configuration.")]
78 public SmtpClient (string host
, int port
)
84 #endregion // Constructors
88 public ICredentialsByHost Credentials
{
89 get { return credentials; }
90 set { credentials = value; }
100 [MonoTODO ("Check to make sure an email is not being sent.")]
103 throw new ArgumentOutOfRangeException ();
109 public ServicePoint ServicePoint
{
110 get { throw new NotImplementedException (); }
114 get { return timeout; }
115 [MonoTODO ("Check to make sure an email is not being sent.")]
118 throw new ArgumentOutOfRangeException ();
123 public bool UseDefaultCredentials
{
124 get { return useDefaultCredentials; }
125 set { useDefaultCredentials = value; }
128 #endregion // Properties
132 public event SendCompletedEventHandler SendCompleted
;
139 public void Dispose ()
143 private void EndSection (string section
)
145 SendData (String
.Format ("--{0}--", section
));
148 private string GenerateBoundary ()
150 string output
= GenerateBoundary (boundaryIndex
);
155 private static string GenerateBoundary (int index
)
157 return String
.Format ("--boundary_{0}_{1}", index
, Guid
.NewGuid ().ToString ("D"));
160 private bool IsError (SmtpResponse status
)
162 return ((int) status
.StatusCode
) >= 400;
165 protected void OnSendCompleted (AsyncCompletedEventArgs e
)
167 if (SendCompleted
!= null)
168 SendCompleted (this, e
);
171 private SmtpResponse
Read ()
173 SmtpResponse response
;
175 char[] buf
= new char [3];
176 reader
.Read (buf
, 0, 3);
179 response
.StatusCode
= (SmtpStatusCode
) Int32
.Parse (new String (buf
));
180 response
.Description
= reader
.ReadLine ();
185 [MonoTODO ("Need to work on message attachments.")]
186 public void Send (MailMessage message
)
188 // Block while sending
193 client
= new TcpClient (host
, port
);
194 stream
= client
.GetStream ();
195 writer
= new StreamWriter (stream
);
196 reader
= new StreamReader (stream
);
198 string boundary
= GenerateBoundary ();
200 bool hasAlternateViews
= (message
.AlternateViews
.Count
> 0);
201 bool hasAttachments
= (message
.Attachments
.Count
> 0);
204 if (IsError (status
))
205 throw new SmtpException (status
.StatusCode
);
208 status
= SendCommand (Command
.Helo
, Dns
.GetHostName ());
209 if (IsError (status
))
210 throw new SmtpException (status
.StatusCode
);
213 status
= SendCommand (Command
.MailFrom
, message
.From
.Address
);
214 if (IsError (status
))
215 throw new SmtpException (status
.StatusCode
);
217 // Send RCPT TO: for all recipients
218 SmtpFailedRecipientsException sfre
= new SmtpFailedRecipientsException ();
220 for (int i
= 0; i
< message
.To
.Count
; i
+= 1) {
221 status
= SendCommand (Command
.RcptTo
, message
.To
[i
].Address
);
222 if (IsError (status
))
223 sfre
.AddFailedRecipient (new SmtpException (status
.StatusCode
), message
.To
[i
].Address
);
225 for (int i
= 0; i
< message
.CC
.Count
; i
+= 1) {
226 status
= SendCommand (Command
.RcptTo
, message
.CC
[i
].Address
);
227 if (IsError (status
))
228 sfre
.AddFailedRecipient (new SmtpException (status
.StatusCode
), message
.CC
[i
].Address
);
230 for (int i
= 0; i
< message
.Bcc
.Count
; i
+= 1) {
231 status
= SendCommand (Command
.RcptTo
, message
.Bcc
[i
].Address
);
232 if (IsError (status
))
233 sfre
.AddFailedRecipient (new SmtpException (status
.StatusCode
), message
.Bcc
[i
].Address
);
236 if (sfre
.FailedRecipients
.Length
> 0)
240 status
= SendCommand (Command
.Data
);
241 if (IsError (status
))
242 throw new SmtpException (status
.StatusCode
);
244 // Figure out the message content type
245 ContentType messageContentType
= message
.BodyContentType
;
246 if (hasAttachments
|| hasAlternateViews
) {
247 messageContentType
= new ContentType ();
248 messageContentType
.Boundary
= boundary
;
251 messageContentType
.MediaType
= "multipart/mixed";
253 messageContentType
.MediaType
= "multipart/alternative";
256 // Send message headers
257 SendHeader (HeaderName
.From
, message
.From
.ToString ());
258 SendHeader (HeaderName
.To
, message
.To
.ToString ());
259 if (message
.CC
.Count
> 0)
260 SendHeader (HeaderName
.Cc
, message
.CC
.ToString ());
261 if (message
.Bcc
.Count
> 0)
262 SendHeader (HeaderName
.Bcc
, message
.Bcc
.ToString ());
263 SendHeader (HeaderName
.Subject
, message
.Subject
);
265 foreach (string s
in message
.Headers
.AllKeys
)
266 SendHeader (s
, message
.Headers
[s
]);
268 SendHeader ("Content-Type", messageContentType
.ToString ());
271 if (hasAlternateViews
) {
272 string innerBoundary
= boundary
;
274 // The body is *technically* an alternative view. The body text goes FIRST because
275 // that is most compatible with non-MIME readers.
277 // If there are attachments, then the main content-type is multipart/mixed and
278 // the subpart has type multipart/alternative. Then all of the views have their
281 // If there are no attachments, then the main content-type is multipart/alternative
282 // and we don't need this subpart.
284 if (hasAttachments
) {
285 innerBoundary
= GenerateBoundary ();
286 ContentType contentType
= new ContentType ("multipart/alternative");
287 contentType
.Boundary
= innerBoundary
;
288 StartSection (boundary
, contentType
);
291 // Start the section for the body text. This is either section "1" or "0" depending
292 // on whether there are attachments.
294 StartSection (innerBoundary
, message
.BodyContentType
, TransferEncoding
.QuotedPrintable
);
295 SendData (message
.Body
);
297 // Send message attachments.
298 SendAttachments (message
.AlternateViews
, innerBoundary
);
301 EndSection (innerBoundary
);
304 // If this is multipart then we need to send a boundary before the body.
306 StartSection (boundary
, message
.BodyContentType
, TransferEncoding
.QuotedPrintable
);
307 SendData (message
.Body
);
311 if (hasAttachments
) {
312 string innerBoundary
= boundary
;
314 // If we have alternate views and attachments then we need to nest this part inside another
315 // boundary. Otherwise, we are cool with the boundary we have.
317 if (hasAlternateViews
) {
318 innerBoundary
= GenerateBoundary ();
319 ContentType contentType
= new ContentType ("multipart/mixed");
320 contentType
.Boundary
= innerBoundary
;
321 StartSection (boundary
, contentType
);
324 SendAttachments (message
.Attachments
, innerBoundary
);
326 if (hasAlternateViews
)
327 EndSection (innerBoundary
);
333 if (IsError (status
))
334 throw new SmtpException (status
.StatusCode
);
336 status
= SendCommand (Command
.Quit
);
343 // Release the mutex to allow other threads access
344 mutex
.ReleaseMutex ();
347 public void Send (string from, string to
, string subject
, string body
)
349 Send (new MailMessage (from, to
, subject
, body
));
352 private void SendData (string data
)
354 writer
.WriteLine (data
);
359 public void SendAsync (MailMessage message
, object userToken
)
362 OnSendCompleted (new AsyncCompletedEventArgs (null, false, userToken
));
365 public void SendAsync (string from, string to
, string subject
, string body
, object userToken
)
367 SendAsync (new MailMessage (from, to
, subject
, body
), userToken
);
371 public void SendAsyncCancel ()
373 throw new NotImplementedException ();
376 private void SendAttachments (AttachmentCollection attachments
, string boundary
)
378 for (int i
= 0; i
< attachments
.Count
; i
+= 1) {
379 StartSection (boundary
, attachments
[i
].ContentType
, attachments
[i
].TransferEncoding
);
381 switch (attachments
[i
].TransferEncoding
) {
382 case TransferEncoding
.Base64
:
383 StreamReader reader
= new StreamReader (attachments
[i
].ContentStream
);
384 byte[] content
= new byte [attachments
[i
].ContentStream
.Length
];
385 attachments
[i
].ContentStream
.Read (content
, 0, content
.Length
);
386 SendData (Convert
.ToBase64String (content
, Base64FormattingOptions
.InsertLineBreaks
));
388 case TransferEncoding
.QuotedPrintable
:
389 SendData (ToQuotedPrintable (attachments
[i
].ContentString
));
392 SendData ("TO BE IMPLEMENTED");
398 private SmtpResponse
SendCommand (string command
, string data
)
400 SmtpResponse response
;
401 writer
.Write (command
);
407 private SmtpResponse
SendCommand (string command
)
409 writer
.WriteLine (command
);
414 private void SendHeader (string name
, string value)
416 SendData (String
.Format ("{0}: {1}", name
, value));
419 private void StartSection (string section
, ContentType sectionContentType
)
421 SendData (String
.Format ("--{0}", section
));
422 SendHeader ("content-type", sectionContentType
.ToString ());
426 private void StartSection (string section
, ContentType sectionContentType
, TransferEncoding transferEncoding
)
428 SendData (String
.Format ("--{0}", section
));
429 SendHeader ("content-type", sectionContentType
.ToString ());
430 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding
));
434 private string ToQuotedPrintable (string input
)
436 StringReader reader
= new StringReader (input
);
437 StringWriter writer
= new StringWriter ();
440 while ((i
= reader
.Read ()) > 0) {
443 writer
.Write (Convert
.ToString (i
, 16).ToUpper ());
446 writer
.Write (Convert
.ToChar (i
));
449 return writer
.GetStringBuilder ().ToString ();
452 private static string GetTransferEncodingName (TransferEncoding encoding
)
455 case TransferEncoding
.QuotedPrintable
:
456 return "quoted-printable";
457 case TransferEncoding
.EightBit
:
459 case TransferEncoding
.SevenBit
:
461 case TransferEncoding
.Base64
:
463 case TransferEncoding
.Binary
:
471 private sealed ContextAwareResult IGetContextAwareResult.GetContextAwareResult ()
473 throw new NotImplementedException ();
476 #endregion // Methods
478 // The Command struct is used to store constant string values representing SMTP commands.
479 private struct Command
{
480 public const string Data
= "DATA";
481 public const string Helo
= "HELO";
482 public const string MailFrom
= "MAIL FROM:";
483 public const string Quit
= "QUIT";
484 public const string RcptTo
= "RCPT TO:";
487 // The HeaderName struct is used to store constant string values representing mail headers.
488 private struct HeaderName
{
489 public const string ContentTransferEncoding
= "Content-Transfer-Encoding";
490 public const string ContentType
= "Content-Type";
491 public const string Bcc
= "Bcc";
492 public const string Cc
= "Cc";
493 public const string From
= "From";
494 public const string Subject
= "Subject";
495 public const string To
= "To";
496 public const string MimeVersion
= "MIME-Version";
497 public const string MessageId
= "Message-ID";
500 // This object encapsulates the status code and description of an SMTP response.
501 private struct SmtpResponse
{
502 public SmtpStatusCode StatusCode
;
503 public string Description
;