(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / System / System.Net.Mail / SmtpClient.cs
blob16d2c283b61eb512a99d08013520e4ad2c7e89e9
1 //
2 // System.Net.Mail.SmtpClient.cs
3 //
4 // Author:
5 // Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) Tim Coleman, 2004
8 //
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:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
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.
31 #if NET_2_0
33 using System;
34 using System.ComponentModel;
35 using System.IO;
36 using System.Net;
37 using System.Net.Mime;
38 using System.Net.Sockets;
39 using System.Text;
40 using System.Threading;
42 namespace System.Net.Mail {
43 public class SmtpClient : IDisposable //, IGetContextAwareResult
45 #region Fields
47 string host;
48 int port;
49 int timeout;
50 ICredentialsByHost credentials;
51 bool useDefaultCredentials;
53 TcpClient client;
54 NetworkStream stream;
55 StreamWriter writer;
56 StreamReader reader;
57 int boundaryIndex;
59 Mutex mutex = new Mutex ();
61 const string MimeVersion = "1.0 (produced by Mono System.Net.Mail.SmtpClient)";
63 #endregion // Fields
65 #region Constructors
67 public SmtpClient ()
68 : this (null, 0)
72 public SmtpClient (string host)
73 : this (host, 0)
77 [MonoTODO ("Load default settings from configuration.")]
78 public SmtpClient (string host, int port)
80 Host = host;
81 Port = port;
84 #endregion // Constructors
86 #region Properties
88 public ICredentialsByHost Credentials {
89 get { return credentials; }
90 set { credentials = value; }
93 public string Host {
94 get { return host; }
95 set { host = value; }
98 public int Port {
99 get { return port; }
100 [MonoTODO ("Check to make sure an email is not being sent.")]
101 set {
102 if (value <= 0)
103 throw new ArgumentOutOfRangeException ();
104 port = value;
108 [MonoTODO]
109 public ServicePoint ServicePoint {
110 get { throw new NotImplementedException (); }
113 public int Timeout {
114 get { return timeout; }
115 [MonoTODO ("Check to make sure an email is not being sent.")]
116 set {
117 if (value <= 0)
118 throw new ArgumentOutOfRangeException ();
119 timeout = value;
123 public bool UseDefaultCredentials {
124 get { return useDefaultCredentials; }
125 set { useDefaultCredentials = value; }
128 #endregion // Properties
130 #region Events
132 public event SendCompletedEventHandler SendCompleted;
134 #endregion // Events
136 #region Methods
138 [MonoTODO]
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);
151 boundaryIndex += 1;
152 return output;
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);
177 reader.Read ();
179 response.StatusCode = (SmtpStatusCode) Int32.Parse (new String (buf));
180 response.Description = reader.ReadLine ();
182 return response;
185 [MonoTODO ("Need to work on message attachments.")]
186 public void Send (MailMessage message)
188 // Block while sending
189 mutex.WaitOne ();
191 SmtpResponse status;
193 client = new TcpClient (host, port);
194 stream = client.GetStream ();
195 writer = new StreamWriter (stream);
196 reader = new StreamReader (stream);
197 boundaryIndex = 0;
198 string boundary = GenerateBoundary ();
200 bool hasAlternateViews = (message.AlternateViews.Count > 0);
201 bool hasAttachments = (message.Attachments.Count > 0);
203 status = Read ();
204 if (IsError (status))
205 throw new SmtpException (status.StatusCode);
207 // HELO
208 status = SendCommand (Command.Helo, Dns.GetHostName ());
209 if (IsError (status))
210 throw new SmtpException (status.StatusCode);
212 // MAIL FROM:
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)
237 throw sfre;
239 // DATA
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;
250 if (hasAttachments)
251 messageContentType.MediaType = "multipart/mixed";
252 else
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 ());
269 SendData ("");
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
279 // own types.
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);
300 if (hasAttachments)
301 EndSection (innerBoundary);
303 else {
304 // If this is multipart then we need to send a boundary before the body.
305 if (hasAttachments)
306 StartSection (boundary, message.BodyContentType, TransferEncoding.QuotedPrintable);
307 SendData (message.Body);
310 // Send attachments
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);
330 SendData (".");
332 status = Read ();
333 if (IsError (status))
334 throw new SmtpException (status.StatusCode);
336 status = SendCommand (Command.Quit);
338 writer.Close ();
339 reader.Close ();
340 stream.Close ();
341 client.Close ();
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);
355 writer.Flush ();
358 [MonoTODO]
359 public void SendAsync (MailMessage message, object userToken)
361 Send (message);
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);
370 [MonoTODO]
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));
387 break;
388 case TransferEncoding.QuotedPrintable:
389 SendData (ToQuotedPrintable (attachments [i].ContentString));
390 break;
391 default:
392 SendData ("TO BE IMPLEMENTED");
393 break;
398 private SmtpResponse SendCommand (string command, string data)
400 SmtpResponse response;
401 writer.Write (command);
402 writer.Write (" ");
403 SendData (data);
404 return Read ();
407 private SmtpResponse SendCommand (string command)
409 writer.WriteLine (command);
410 writer.Flush ();
411 return Read ();
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 ());
423 SendData ("");
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));
431 SendData ("");
434 private string ToQuotedPrintable (string input)
436 StringReader reader = new StringReader (input);
437 StringWriter writer = new StringWriter ();
438 int i;
440 while ((i = reader.Read ()) > 0) {
441 if (i > 127) {
442 writer.Write ("=");
443 writer.Write (Convert.ToString (i, 16).ToUpper ());
445 else
446 writer.Write (Convert.ToChar (i));
449 return writer.GetStringBuilder ().ToString ();
452 private static string GetTransferEncodingName (TransferEncoding encoding)
454 switch (encoding) {
455 case TransferEncoding.QuotedPrintable:
456 return "quoted-printable";
457 case TransferEncoding.EightBit:
458 return "8bit";
459 case TransferEncoding.SevenBit:
460 return "7bit";
461 case TransferEncoding.Base64:
462 return "base64";
463 case TransferEncoding.Binary:
464 return "binary";
466 return "unknown";
470 [MonoTODO]
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;
508 #endif // NET_2_0