Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / net / System / Net / mail / SmtpClient.cs
1 namespace System.Net.Mail
2 {
3
4     using System;
5     using System.IO;
6     using System.Net;
7     using System.ComponentModel;
8     using System.Net.Configuration;
9     using System.Threading;
10     using System.Threading.Tasks;
11     using System.Security;
12     using System.Security.Permissions;
13     using System.Security.Authentication;
14     using System.Security.Cryptography.X509Certificates;
15     using System.Net.NetworkInformation;
16     using System.Runtime.Versioning;
17     using System.Text;
18     using System.Globalization;
19
20     public delegate void SendCompletedEventHandler(object sender, AsyncCompletedEventArgs e);
21
22     public enum SmtpDeliveryMethod {
23         Network,
24         SpecifiedPickupDirectory,
25 #if !FEATURE_PAL
26         PickupDirectoryFromIis
27 #endif
28     }
29
30     // EAI Settings
31     public enum SmtpDeliveryFormat {
32         SevenBit = 0, // Legacy
33         International = 1, // SMTPUTF8 - Email Address Internationalization (EAI)
34     }
35
36     public class SmtpClient : IDisposable {
37
38         string host;
39         int port;
40         bool inCall;
41         bool cancelled;
42         bool timedOut;
43         string targetName = null;
44         SmtpDeliveryMethod deliveryMethod = SmtpDeliveryMethod.Network;
45         SmtpDeliveryFormat deliveryFormat = SmtpDeliveryFormat.SevenBit; // Non-EAI default
46         string pickupDirectoryLocation = null;
47         SmtpTransport transport;
48         MailMessage message; //required to prevent premature finalization
49         MailWriter writer;
50         MailAddressCollection recipients;
51         SendOrPostCallback onSendCompletedDelegate;
52         Timer timer;
53         static volatile MailSettingsSectionGroupInternal mailConfiguration;
54         ContextAwareResult operationCompletedResult = null;
55         AsyncOperation asyncOp = null;
56         static AsyncCallback _ContextSafeCompleteCallback = new AsyncCallback(ContextSafeCompleteCallback);
57         static int defaultPort = 25;
58         internal string clientDomain = null;
59         bool disposed = false;
60         // true if the host and port change between calls to send or GetServicePoint
61         bool servicePointChanged = false;
62         ServicePoint servicePoint = null;
63         // (async only) For when only some recipients fail.  We still send the e-mail to the others.
64         SmtpFailedRecipientException failedRecipientException;
65         // ports above this limit are invalid
66         const int maxPortValue = 65535;
67
68         public event SendCompletedEventHandler SendCompleted;
69
70         public SmtpClient() {
71             if (Logging.On) Logging.Enter(Logging.Web, "SmtpClient", ".ctor", "");
72             try {
73                 Initialize();
74             } finally {
75                 if (Logging.On) Logging.Exit(Logging.Web, "SmtpClient", ".ctor", this);
76             }
77         }
78
79         public SmtpClient(string host) {
80             if (Logging.On) Logging.Enter(Logging.Web, "SmtpClient", ".ctor", "host=" + host);
81             try {
82                 this.host = host;
83                 Initialize();
84             } finally {
85                 if (Logging.On) Logging.Exit(Logging.Web, "SmtpClient", ".ctor", this);
86             }
87         }
88
89         //?? should port throw or just default on 0?
90         public SmtpClient(string host, int port) {
91             if (Logging.On) Logging.Enter(Logging.Web, "SmtpClient", ".ctor", "host=" + host + ", port=" + port);
92             try {
93                 if (port < 0) {
94                     throw new ArgumentOutOfRangeException("port");
95                 }
96
97                 this.host = host;
98                 this.port = port;
99                 Initialize();
100             } finally {
101                 if (Logging.On) Logging.Exit(Logging.Web, "SmtpClient", ".ctor", this);
102             }
103         }
104
105         void Initialize() {
106             if (port == defaultPort || port == 0) {
107                 new SmtpPermission(SmtpAccess.Connect).Demand();
108             }
109             else {
110                 new SmtpPermission(SmtpAccess.ConnectToUnrestrictedPort).Demand();
111             }
112
113             transport = new SmtpTransport(this);
114             if (Logging.On) Logging.Associate(Logging.Web, this, transport);
115             onSendCompletedDelegate = new SendOrPostCallback(SendCompletedWaitCallback);
116
117             if (MailConfiguration.Smtp != null) 
118             {
119                 if (MailConfiguration.Smtp.Network != null) 
120                 {
121                     if (host == null || host.Length == 0) {
122                         host = MailConfiguration.Smtp.Network.Host;
123                     }
124                     if (port == 0) {
125                         port = MailConfiguration.Smtp.Network.Port;
126                     }
127
128                     transport.Credentials = MailConfiguration.Smtp.Network.Credential;
129                     transport.EnableSsl = MailConfiguration.Smtp.Network.EnableSsl;
130
131                     if (MailConfiguration.Smtp.Network.TargetName != null)
132                         targetName = MailConfiguration.Smtp.Network.TargetName;
133
134                     // If the config file contains a domain to be used for the 
135                     // domain element in the client's EHLO or HELO message, 
136                     // use it.
137                     //
138                     // We do not validate whether the domain specified is valid.
139                     // It is up to the administrators or user to use the right
140                     // value for their scenario. 
141                     //
142                     // Note: per section 4.1.4 of RFC2821, the domain element of 
143                     // the HELO/EHLO should be used for logging purposes. An 
144                     // SMTP server should not decide to route an email based on
145                     // this value.
146                     clientDomain = MailConfiguration.Smtp.Network.ClientDomain;
147                 }
148
149                 deliveryFormat = MailConfiguration.Smtp.DeliveryFormat;
150
151                 deliveryMethod = MailConfiguration.Smtp.DeliveryMethod;
152                 if (MailConfiguration.Smtp.SpecifiedPickupDirectory != null)
153                     pickupDirectoryLocation = MailConfiguration.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation;
154             }
155
156             if (host != null && host.Length != 0) {
157                 host = host.Trim();
158             }
159
160             if (port == 0) {
161                 port = defaultPort;
162             }
163
164             if (this.targetName == null)
165                 targetName = "SMTPSVC/" + host;
166
167             if (clientDomain == null) {
168                 // We use the local host name as the default client domain
169                 // for the client's EHLO or HELO message. This limits the 
170                 // information about the host that we share. Additionally, the 
171                 // FQDN is not available to us or useful to the server (internal
172                 // machine connecting to public server).
173
174                 // SMTP RFC's require ASCII only host names in the HELO/EHLO message.
175                 string clientDomainRaw = IPGlobalProperties.InternalGetIPGlobalProperties().HostName;
176                 IdnMapping mapping = new IdnMapping();
177                 try
178                 {
179                     clientDomainRaw = mapping.GetAscii(clientDomainRaw);
180                 }
181                 catch (ArgumentException) { }
182
183                 // For some inputs GetAscii may fail (bad Unicode, etc).  If that happens
184                 // we must strip out any non-ASCII characters.
185                 // If we end up with no characters left, we use the string "LocalHost".  This 
186                 // matches Outlook behavior.
187                 StringBuilder sb = new StringBuilder();
188                 char ch;
189                 for (int i = 0; i < clientDomainRaw.Length; i++) {
190                     ch = clientDomainRaw[i];
191                     if ((ushort)ch <= 0x7F)
192                         sb.Append(ch);
193                 }
194                 if (sb.Length > 0)
195                     clientDomain = sb.ToString();
196                 else
197                     clientDomain = "LocalHost";
198             }
199         }
200
201
202         public string Host {
203             get {
204                 return host;
205             }
206             set {
207
208                 if (InCall) {
209                     throw new InvalidOperationException(SR.GetString(SR.SmtpInvalidOperationDuringSend));
210                 }
211
212                 if (value == null) 
213                 {
214                     throw new ArgumentNullException("value");
215                 }
216
217                 if (value == String.Empty) 
218                 {
219                     throw new ArgumentException(SR.GetString(SR.net_emptystringset), "value");
220                 }
221
222                 value = value.Trim();
223
224                 if (value != host)
225                 {
226                     host = value;
227                     servicePointChanged = true;
228                 }
229             }
230         }
231
232
233         public int Port {
234             get {
235                 return port;
236             }
237             set {
238                 if (InCall) {
239                     throw new InvalidOperationException(SR.GetString(SR.SmtpInvalidOperationDuringSend));
240                 }
241
242                 if (value <= 0) {
243                     throw new ArgumentOutOfRangeException("value");
244                 }
245
246                 if (value != defaultPort) {
247                     new SmtpPermission(SmtpAccess.ConnectToUnrestrictedPort).Demand();
248                 }
249
250                 if (value != port) {
251                     port = value;
252                     servicePointChanged = true;
253                 }
254             }
255         }
256
257
258         public bool UseDefaultCredentials {
259             get {
260                 return (transport.Credentials is SystemNetworkCredential) ? true : false;
261             }
262             set {
263                 if (InCall) {
264                     throw new InvalidOperationException(SR.GetString(SR.SmtpInvalidOperationDuringSend));
265                 }
266
267                 transport.Credentials = value ? CredentialCache.DefaultNetworkCredentials : null;
268             }
269         }
270
271
272         public ICredentialsByHost Credentials {
273             get {
274                 return transport.Credentials;
275             }
276             set {
277                 if (InCall) {
278                     throw new InvalidOperationException(SR.GetString(SR.SmtpInvalidOperationDuringSend));
279                 }
280
281                 transport.Credentials = value;
282             }
283         }
284
285
286
287         public int Timeout {
288             get {
289                 return transport.Timeout;
290             }
291             set {
292                 if (InCall) {
293                     throw new InvalidOperationException(SR.GetString(SR.SmtpInvalidOperationDuringSend));
294                 }
295
296                 if (value < 0) 
297                 {
298                     throw new ArgumentOutOfRangeException("value");
299                 }
300
301                 transport.Timeout = value;
302             }
303         }
304
305
306         public ServicePoint ServicePoint {
307             get {
308                 CheckHostAndPort();
309                 if (servicePoint == null || servicePointChanged) {
310                     servicePoint = ServicePointManager.FindServicePoint(host, port);
311                     // servicePoint is now correct for current host and port
312                     servicePointChanged = false;
313                 }
314                 return servicePoint;
315             }
316         }
317
318         public SmtpDeliveryMethod DeliveryMethod {
319             get {
320                 return deliveryMethod;
321             }
322             set {
323                 deliveryMethod = value;
324             }
325         }
326
327         // Should we use EAI formats?
328         public SmtpDeliveryFormat DeliveryFormat {
329             get {
330                 return deliveryFormat;
331             }
332             set {
333                 deliveryFormat = value;
334             }
335         }
336
337         public string PickupDirectoryLocation {
338             [ResourceExposure(ResourceScope.Machine)]
339             [ResourceConsumption(ResourceScope.Machine)]
340             get {
341                 return pickupDirectoryLocation;
342             }
343             [ResourceExposure(ResourceScope.Machine)]
344             [ResourceConsumption(ResourceScope.Machine)]
345             set {
346                 pickupDirectoryLocation = value;
347             }
348         }
349
350         /// <summary>
351         ///    <para>Set to true if we need SSL</para>
352         /// </summary>
353         public bool EnableSsl {
354             get {
355                 return transport.EnableSsl;
356             }
357             set {
358                 transport.EnableSsl = value;
359             }
360         }
361
362         /// <summary>
363         /// Certificates used by the client for establishing an SSL connection with the server. 
364         /// </summary>
365         public X509CertificateCollection ClientCertificates {
366             get {
367                 return transport.ClientCertificates;
368             }
369         }
370
371         public string TargetName {
372             set { this.targetName = value; }
373             get { return this.targetName; }
374         }
375
376         private bool ServerSupportsEai {
377             get { 
378                 return transport.ServerSupportsEai; 
379             }
380         }
381
382         private bool IsUnicodeSupported() {
383             if (DeliveryMethod == SmtpDeliveryMethod.Network) {
384                 return (ServerSupportsEai && (DeliveryFormat == SmtpDeliveryFormat.International));
385             }
386             else { // Pickup directories
387                 return (DeliveryFormat == SmtpDeliveryFormat.International);
388             }
389         }
390
391         [ResourceExposure(ResourceScope.Machine)]
392         [ResourceConsumption(ResourceScope.Machine)]
393         internal MailWriter GetFileMailWriter(string pickupDirectory) 
394         {
395             if (Logging.On) Logging.PrintInfo(Logging.Web, "SmtpClient.Send() pickupDirectory=" + pickupDirectory);
396             if (!Path.IsPathRooted(pickupDirectory))
397                 throw new SmtpException(SR.GetString(SR.SmtpNeedAbsolutePickupDirectory));
398             string filename;
399             string pathAndFilename;
400             while (true) {
401                 filename = Guid.NewGuid().ToString() + ".eml";
402                 pathAndFilename = Path.Combine(pickupDirectory, filename);
403                 if (!File.Exists(pathAndFilename))
404                     break;
405             }
406
407             FileStream fileStream = new FileStream(pathAndFilename, FileMode.CreateNew);
408             return new MailWriter(fileStream);
409         }
410
411         protected void OnSendCompleted(AsyncCompletedEventArgs e) 
412         {
413             if (SendCompleted != null) {
414                 SendCompleted(this, e);
415             }
416         }
417
418         void SendCompletedWaitCallback(object operationState) {
419             OnSendCompleted((AsyncCompletedEventArgs)operationState);
420         }
421
422
423         public void Send(string from, string recipients, string subject, string body) {
424             if (disposed) {
425                 throw new ObjectDisposedException(this.GetType().FullName);
426             }
427             //validation happends in MailMessage constructor
428             MailMessage mailMessage = new MailMessage(from, recipients, subject, body);
429             Send(mailMessage);
430         }
431
432
433         [ResourceExposure(ResourceScope.None)]
434         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
435         public void Send(MailMessage message) {
436             if (Logging.On) Logging.Enter(Logging.Web, this, "Send", message);
437             if (disposed) {
438                 throw new ObjectDisposedException(this.GetType().FullName);
439             }           
440             try {
441                 if (Logging.On) Logging.PrintInfo(Logging.Web, this, "Send", "DeliveryMethod=" + DeliveryMethod.ToString());
442                 if (Logging.On) Logging.Associate(Logging.Web, this, message);
443                 SmtpFailedRecipientException recipientException = null;
444
445                 if (InCall) {
446                     throw new InvalidOperationException(SR.GetString(SR.net_inasync));
447                 }
448
449                 if (message == null) {
450                     throw new ArgumentNullException("message");
451                 }
452
453                 if (DeliveryMethod == SmtpDeliveryMethod.Network)
454                     CheckHostAndPort();
455
456                 MailAddressCollection recipients = new MailAddressCollection();
457
458                 if (message.From == null) {
459                     throw new InvalidOperationException(SR.GetString(SR.SmtpFromRequired));
460                 }
461
462                 if (message.To != null) {
463                     foreach (MailAddress address in message.To) {
464                         recipients.Add(address);
465                     }
466                 }
467                 if (message.Bcc != null) {
468                     foreach (MailAddress address in message.Bcc) {
469                         recipients.Add(address);
470                     }
471                 }
472                 if (message.CC != null) {
473                     foreach (MailAddress address in message.CC) {
474                         recipients.Add(address);
475                     }
476                 }
477
478                 if (recipients.Count == 0) {
479                     throw new InvalidOperationException(SR.GetString(SR.SmtpRecipientRequired));
480                 }
481
482                 transport.IdentityRequired = false;  // everything completes on the same thread.
483
484                 try {
485                     InCall = true;
486                     timedOut = false;
487                     timer = new Timer(new TimerCallback(this.TimeOutCallback), null, Timeout, Timeout);
488                     bool allowUnicode = false;
489                     string pickupDirectory = PickupDirectoryLocation;
490
491                     MailWriter writer;
492                     switch (DeliveryMethod) {
493 #if !FEATURE_PAL
494                         case SmtpDeliveryMethod.PickupDirectoryFromIis:
495                             pickupDirectory = IisPickupDirectory.GetPickupDirectory();
496                             goto case SmtpDeliveryMethod.SpecifiedPickupDirectory;
497 #endif // !FEATURE_PAL
498                         case SmtpDeliveryMethod.SpecifiedPickupDirectory:
499                             if (EnableSsl)
500                                 throw new SmtpException(SR.GetString(SR.SmtpPickupDirectoryDoesnotSupportSsl));
501                             allowUnicode = IsUnicodeSupported(); // Determend by the DeliveryFormat paramiter
502                             ValidateUnicodeRequirement(message, recipients, allowUnicode);
503                             writer = GetFileMailWriter(pickupDirectory);
504                             break;
505
506                         case SmtpDeliveryMethod.Network:
507                         default:
508                             GetConnection();
509                             // Detected durring GetConnection(), restrictable using the DeliveryFormat paramiter
510                             allowUnicode = IsUnicodeSupported();
511                             ValidateUnicodeRequirement(message, recipients, allowUnicode);
512                             writer = transport.SendMail(message.Sender ?? message.From, recipients, 
513                                 message.BuildDeliveryStatusNotificationString(), allowUnicode, out recipientException);
514                             break;
515                     }
516                     this.message = message;
517                     message.Send(writer, DeliveryMethod != SmtpDeliveryMethod.Network, allowUnicode);
518                     writer.Close();
519                     transport.ReleaseConnection();
520
521                     //throw if we couldn't send to any of the recipients
522                     if (DeliveryMethod == SmtpDeliveryMethod.Network && recipientException != null) {
523                         throw recipientException;
524                     }
525                 }
526                 catch (Exception e) {
527
528                     if (Logging.On) Logging.Exception(Logging.Web, this, "Send", e);
529
530
531                     if (e is SmtpFailedRecipientException && !((SmtpFailedRecipientException)e).fatal) {
532                         throw;
533                     }
534
535
536                     Abort();
537                     if (timedOut) {
538                         throw new SmtpException(SR.GetString(SR.net_timeout));
539                     }
540
541                     if (e is SecurityException ||
542                         e is AuthenticationException ||
543                         e is SmtpException) 
544                     {
545                         throw;
546                     }
547
548                     throw new SmtpException(SR.GetString(SR.SmtpSendMailFailure), e);
549                 } 
550                 finally {
551                     InCall = false;
552                     if (timer != null) {
553                         timer.Dispose();
554                     }
555                 }
556             } finally {
557                 if (Logging.On) Logging.Exit(Logging.Web, this, "Send", null);
558             }
559         }
560
561         [HostProtection(ExternalThreading = true)]
562         public void SendAsync(string from, string recipients, string subject, string body, object userToken) {
563             if (disposed) {
564                 throw new ObjectDisposedException(this.GetType().FullName);
565             }
566             SendAsync(new MailMessage(from, recipients, subject, body), userToken);
567         }
568
569
570         [HostProtection(ExternalThreading = true)]
571         [ResourceExposure(ResourceScope.None)]
572         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
573         public void SendAsync(MailMessage message, object userToken) {
574             if (disposed) {
575                 throw new ObjectDisposedException(this.GetType().FullName);
576             }
577             if (Logging.On) Logging.Enter(Logging.Web, this, "SendAsync", "DeliveryMethod=" + DeliveryMethod.ToString());
578             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::SendAsync Transport#" + ValidationHelper.HashString(transport));
579             try {
580                 if (InCall) {
581                     throw new InvalidOperationException(SR.GetString(SR.net_inasync));
582                 }
583
584                 if (message == null) {
585                     throw new ArgumentNullException("message");
586                 }
587
588                 if (DeliveryMethod == SmtpDeliveryMethod.Network)
589                     CheckHostAndPort();
590
591                 recipients = new MailAddressCollection();
592
593                 if (message.From == null) {
594                     throw new InvalidOperationException(SR.GetString(SR.SmtpFromRequired));
595                 }
596
597                 if (message.To != null) {
598                     foreach (MailAddress address in message.To) {
599                         recipients.Add(address);
600                     }
601                 }
602                 if (message.Bcc != null) {
603                     foreach (MailAddress address in message.Bcc) {
604                         recipients.Add(address);
605                     }
606                 }
607                 if (message.CC != null) {
608                     foreach (MailAddress address in message.CC) {
609                         recipients.Add(address);
610                     }
611                 }
612
613                 if (recipients.Count == 0) {
614                     throw new InvalidOperationException(SR.GetString(SR.SmtpRecipientRequired));
615                 }
616
617                 try {
618                     InCall = true;
619                     cancelled = false;
620                     this.message = message;
621                     string pickupDirectory = PickupDirectoryLocation;
622
623 #if !FEATURE_PAL
624                     CredentialCache cache;
625                     // Skip token capturing if no credentials are used or they don't include a default one.
626                     // Also do capture the token if ICredential is not of CredentialCache type so we don't know what the exact credential response will be.
627                     transport.IdentityRequired = Credentials != null && (Credentials is SystemNetworkCredential || (cache = Credentials as CredentialCache) == null || cache.IsDefaultInCache);
628 #endif // !FEATURE_PAL
629
630                     asyncOp = AsyncOperationManager.CreateOperation(userToken);
631                     switch (DeliveryMethod) {
632 #if !FEATURE_PAL
633                         case SmtpDeliveryMethod.PickupDirectoryFromIis:
634                             pickupDirectory = IisPickupDirectory.GetPickupDirectory();
635                             goto case SmtpDeliveryMethod.SpecifiedPickupDirectory;
636 #endif // !FEATURE_PAL
637                         case SmtpDeliveryMethod.SpecifiedPickupDirectory: 
638                             {
639                                 if (EnableSsl)
640                                     throw new SmtpException(SR.GetString(SR.SmtpPickupDirectoryDoesnotSupportSsl));
641                                 writer = GetFileMailWriter(pickupDirectory);
642                                 bool allowUnicode = IsUnicodeSupported();
643                                 ValidateUnicodeRequirement(message, recipients, allowUnicode);
644                                 message.Send(writer, true, allowUnicode);
645
646                                 if (writer != null)
647                                     writer.Close();
648
649                                 transport.ReleaseConnection();
650                                 AsyncCompletedEventArgs eventArgs = new AsyncCompletedEventArgs(null, false, asyncOp.UserSuppliedState);
651                                 InCall = false;
652                                 asyncOp.PostOperationCompleted(onSendCompletedDelegate, eventArgs);
653                                 break;
654                             }
655
656                         case SmtpDeliveryMethod.Network:
657                         default:
658                             operationCompletedResult = new ContextAwareResult(transport.IdentityRequired, true, null, this, _ContextSafeCompleteCallback);
659                             lock (operationCompletedResult.StartPostingAsyncOp()) 
660                             {
661                                 GlobalLog.Print("SmtpClient#" + ValidationHelper.HashString(this) + "::SendAsync calling BeginConnect.  Transport#" + ValidationHelper.HashString(transport));
662                                 transport.BeginGetConnection(ServicePoint, operationCompletedResult, ConnectCallback, operationCompletedResult);
663                                 operationCompletedResult.FinishPostingAsyncOp();
664                             }
665                             break;
666                     }
667
668                 }
669                 catch (Exception e) {
670                     InCall = false;
671
672                     if (Logging.On) Logging.Exception(Logging.Web, this, "Send", e);
673
674                     if (e is SmtpFailedRecipientException && !((SmtpFailedRecipientException)e).fatal) {
675                         throw;
676                     }
677
678                     Abort();
679                     if (timedOut) {
680                         throw new SmtpException(SR.GetString(SR.net_timeout));
681                     }
682
683                     if (e is SecurityException ||
684                         e is AuthenticationException ||
685                         e is SmtpException) 
686                     {
687                         throw;
688                     }
689
690                     throw new SmtpException(SR.GetString(SR.SmtpSendMailFailure), e);
691                 }
692             } finally {
693                 if (Logging.On) Logging.Exit(Logging.Web, this, "SendAsync", null);
694                 GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::SendAsync");
695             }
696         }
697
698
699         public void SendAsyncCancel() {
700             if (disposed) {
701                 throw new ObjectDisposedException(this.GetType().FullName);
702             }
703             if (Logging.On) Logging.Enter(Logging.Web, this, "SendAsyncCancel", null);
704             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::SendAsyncCancel");
705             try {
706                 if (!InCall || cancelled) {
707                     return;
708                 }
709
710                 cancelled = true;
711                 Abort();
712             } finally {
713                 if (Logging.On) Logging.Exit(Logging.Web, this, "SendAsyncCancel", null);
714                 GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::SendAsyncCancel");
715             }
716         }
717
718
719         //************* Task-based async public methods *************************
720         [HostProtection(ExternalThreading = true)]
721         public Task SendMailAsync(string from, string recipients, string subject, string body)
722         {
723             var message = new MailMessage(from, recipients, subject, body);
724             return SendMailAsync(message);
725         }
726
727         [HostProtection(ExternalThreading = true)]
728         public Task SendMailAsync(MailMessage message)
729         {
730             // Create a TaskCompletionSource to represent the operation
731             var tcs = new TaskCompletionSource<object>();
732
733             // Register a handler that will transfer completion results to the TCS Task
734             SendCompletedEventHandler handler = null;
735             handler = (sender, e) => HandleCompletion(tcs, e, handler);
736             this.SendCompleted += handler;
737
738             // Start the async operation.
739             try { this.SendAsync(message, tcs); }
740             catch
741             {
742                 this.SendCompleted -= handler;
743                 throw;
744             }
745
746             // Return the task to represent the asynchronous operation
747             return tcs.Task;
748         }
749
750         private void HandleCompletion(TaskCompletionSource<object> tcs, AsyncCompletedEventArgs e, SendCompletedEventHandler handler)
751         {
752             if (e.UserState == tcs)
753             {
754                 try { this.SendCompleted -= handler; }
755                 finally
756                 {
757                     if (e.Error != null) tcs.TrySetException(e.Error);
758                     else if (e.Cancelled) tcs.TrySetCanceled();
759                     else tcs.TrySetResult(null);
760                 }
761             }
762         }
763
764
765         //*********************************
766         // private methods
767         //********************************
768         internal bool InCall {
769             get {
770                 return inCall;
771             }
772             set {
773                 inCall = value;
774             }
775         }
776
777         internal static MailSettingsSectionGroupInternal MailConfiguration {
778             get {
779                 if (mailConfiguration == null) {
780                     mailConfiguration = MailSettingsSectionGroupInternal.GetSection();
781                 }
782                 return mailConfiguration;
783             }
784         }
785
786
787         void CheckHostAndPort() {
788
789             if (host == null || host.Length == 0) {
790                 throw new InvalidOperationException(SR.GetString(SR.UnspecifiedHost));
791             }
792             if (port <= 0 || port > maxPortValue) {
793                 throw new InvalidOperationException(SR.GetString(SR.InvalidPort));
794             }
795         }
796
797
798         void TimeOutCallback(object state) {
799             if (!timedOut) {
800                 timedOut = true;
801                 Abort();
802             }
803         }
804
805
806         void Complete(Exception exception, IAsyncResult result) {
807             ContextAwareResult operationCompletedResult = (ContextAwareResult)result.AsyncState;
808             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::Complete");
809             try {
810                 if (cancelled) {
811                     //any exceptions were probably caused by cancellation, clear it.
812                     exception = null;
813                     Abort();
814                 }
815                 // An individual failed recipient exception is benign, only abort here if ALL the recipients failed.
816                 else if (exception != null && (!(exception is SmtpFailedRecipientException) || ((SmtpFailedRecipientException)exception).fatal)) 
817                 {
818                     GlobalLog.Print("SmtpClient#" + ValidationHelper.HashString(this) + "::Complete Exception: " + exception.ToString());
819                     Abort();
820
821                     if (!(exception is SmtpException)) {
822                         exception = new SmtpException(SR.GetString(SR.SmtpSendMailFailure), exception);
823                     }
824                 }
825                 else {
826                     if (writer != null) {
827                         try {
828                             writer.Close();
829                         }
830                         // Close may result in a DataStopCommand and the server may return error codes at this time.
831                         catch (SmtpException se) {
832                             exception = se;
833                         }
834                     }
835                     transport.ReleaseConnection();
836                 }
837             }
838             finally {
839                 operationCompletedResult.InvokeCallback(exception);
840             }
841             GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::Complete");
842         }
843
844         static void ContextSafeCompleteCallback(IAsyncResult ar) 
845         {
846             ContextAwareResult result = (ContextAwareResult)ar;
847             SmtpClient client = (SmtpClient)ar.AsyncState;
848             Exception exception = result.Result as Exception;
849             AsyncOperation asyncOp = client.asyncOp;
850             AsyncCompletedEventArgs eventArgs = new AsyncCompletedEventArgs(exception, client.cancelled, asyncOp.UserSuppliedState);
851             client.InCall = false;
852             client.failedRecipientException = null; // Reset before the next send.
853             asyncOp.PostOperationCompleted(client.onSendCompletedDelegate, eventArgs);
854         }
855
856         void SendMessageCallback(IAsyncResult result) {
857             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::SendMessageCallback");
858             try {
859                 message.EndSend(result);
860                 // If some recipients failed but not others, throw AFTER sending the message.
861                 Complete(failedRecipientException, result);
862             }
863             catch (Exception e) {
864                 Complete(e, result);
865             }
866             GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::SendMessageCallback");
867         }
868
869
870         void SendMailCallback(IAsyncResult result) {
871             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::SendMailCallback");
872             try {
873                 writer = transport.EndSendMail(result);
874                 // If some recipients failed but not others, send the e-mail anyways, but then return the
875                 // "Non-fatal" exception reporting the failures.  The sync code path does it this way.
876                 // Fatal exceptions would have thrown above at transport.EndSendMail(...)
877                 SendMailAsyncResult sendResult = (SendMailAsyncResult)result;
878                 // Save these and throw them later in SendMessageCallback, after the message has sent.
879                 failedRecipientException = sendResult.GetFailedRecipientException();
880             }
881             catch (Exception e) 
882             {
883                 Complete(e, result);
884                 GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::SendMailCallback");
885                 return;
886             }
887             try {
888                 if (cancelled) {
889                     Complete(null, result);
890                 }
891                 else {
892                     message.BeginSend(writer, DeliveryMethod != SmtpDeliveryMethod.Network,
893                         ServerSupportsEai, new AsyncCallback(SendMessageCallback), result.AsyncState);
894                 }
895             }
896             catch (Exception e) {
897                 Complete(e, result);
898             }
899             GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::SendMailCallback");
900         }
901
902
903         void ConnectCallback(IAsyncResult result) {
904             GlobalLog.Enter("SmtpClient#" + ValidationHelper.HashString(this) + "::ConnectCallback");
905             try {
906                 transport.EndGetConnection(result);
907                 if (cancelled) {
908                     Complete(null, result);
909                 }
910                 else {
911                     // Detected durring Begin/EndGetConnection, restrictable using DeliveryFormat
912                     bool allowUnicode = IsUnicodeSupported(); 
913                     ValidateUnicodeRequirement(message, recipients, allowUnicode);
914                     transport.BeginSendMail(message.Sender ?? message.From, recipients,
915                         message.BuildDeliveryStatusNotificationString(), allowUnicode,
916                         new AsyncCallback(SendMailCallback), result.AsyncState);
917                 }
918             }
919             catch (Exception e) {
920                 Complete(e, result);
921             }
922             GlobalLog.Leave("SmtpClient#" + ValidationHelper.HashString(this) + "::ConnectCallback");
923         }
924
925         // After we've estabilished a connection and initilized ServerSupportsEai,
926         // check all the addresses for one that contains unicode in the username/localpart.
927         // The localpart is the only thing we cannot succesfully downgrade.
928         private void ValidateUnicodeRequirement(MailMessage message, 
929             MailAddressCollection recipients, bool allowUnicode)
930         {
931             // Check all recipients, to, from, sender, bcc, cc, etc...
932             // GetSmtpAddress will throw if !allowUnicode and the username contains non-ascii
933             foreach (MailAddress address in recipients)
934             {
935                 address.GetSmtpAddress(allowUnicode);
936             }
937             if (message.Sender != null)
938             {
939                 message.Sender.GetSmtpAddress(allowUnicode);
940             }
941             message.From.GetSmtpAddress(allowUnicode);
942         }
943
944
945         void GetConnection() {
946             if (!transport.IsConnected) {
947                 transport.GetConnection(ServicePoint);
948             }
949         }
950
951
952         void Abort() {
953             try {
954                 transport.Abort();
955             }
956             catch {
957             }
958         }
959
960         public void Dispose() {
961             Dispose(true);
962             GC.SuppressFinalize(this);
963         }
964
965         protected virtual void Dispose(bool disposing) {
966             if (disposing && !disposed ) {
967                 if (InCall && !cancelled) {
968                     cancelled = true;
969                     Abort();
970                 }
971
972                 if ((transport != null) && (servicePoint != null)) {
973                     transport.CloseIdleConnections(servicePoint);
974                 }
975                 
976                 if (timer != null) {
977                     timer.Dispose();
978                 }
979               
980                 disposed = true;
981             }
982         }
983     }
984 }