New test.
[mono.git] / mcs / class / System / System.Net.Mail / SmtpClient.cs
index 99648257a08f5dd60a2e64f8223e4839a26cd8d1..8fe2b6ae83df9d688041155c05132b316e178951 100644 (file)
@@ -64,11 +64,12 @@ namespace System.Net.Mail {
                int port;
                int timeout = 100000;
                ICredentialsByHost credentials;
-               bool useDefaultCredentials = false;
                string pickupDirectoryLocation;
                SmtpDeliveryMethod deliveryMethod;
                bool enableSsl;
+#if SECURITY_DEP               
                X509CertificateCollection clientCertificates;
+#endif         
 
                TcpClient client;
                Stream stream;
@@ -99,8 +100,6 @@ namespace System.Net.Mail {
                }
 
                AuthMechs authMechs = AuthMechs.None;
-               bool canStartTLS = false;
-
                Mutex mutex = new Mutex ();
 
                #endregion // Fields
@@ -124,6 +123,11 @@ namespace System.Net.Mail {
                        if (cfg != null) {
                                this.host = cfg.Network.Host;
                                this.port = cfg.Network.Port;
+                               TargetName = cfg.Network.TargetName;
+                               if (this.TargetName == null)
+                                       TargetName = "SMTPSVC/" + (host != null ? host : "");
+
+                               
                                if (cfg.Network.UserName != null) {
                                        string password = String.Empty;
 
@@ -136,6 +140,9 @@ namespace System.Net.Mail {
                                if (cfg.From != null)
                                        defaultFrom = new MailAddress (cfg.From);
                        }
+#else
+                       // Just to eliminate the warning, this codepath does not end up in production.
+                       defaultFrom = null;
 #endif
 
                        if (!String.IsNullOrEmpty (host))
@@ -150,7 +157,7 @@ namespace System.Net.Mail {
                #region Properties
 
 #if SECURITY_DEP
-               [MonoTODO("STARTTLS is not supported yet")]
+               [MonoTODO("Client certificates not used")]
                public X509CertificateCollection ClientCertificates {
                        get {
                                if (clientCertificates == null)
@@ -160,6 +167,11 @@ namespace System.Net.Mail {
                }
 #endif
 
+#if NET_4_0
+               public
+#endif
+               string TargetName { get; set; }
+
                public ICredentialsByHost Credentials {
                        get { return credentials; }
                        set {
@@ -176,9 +188,7 @@ namespace System.Net.Mail {
                        }
                }
 
-               [MonoTODO("STARTTLS is not supported yet")]
                public bool EnableSsl {
-                       // FIXME: So... is this supposed to enable SSL port functionality? or STARTTLS? Or both?
                        get { return enableSsl; }
                        set {
                                CheckState ();
@@ -190,9 +200,9 @@ namespace System.Net.Mail {
                        get { return host; }
                        set {
                                if (value == null)
-                                       throw new ArgumentNullException ();
+                                       throw new ArgumentNullException ("value");
                                if (value.Length == 0)
-                                       throw new ArgumentException ();
+                                       throw new ArgumentException ("An empty string is not allowed.", "value");
                                CheckState ();
                                host = value;
                        }
@@ -206,8 +216,8 @@ namespace System.Net.Mail {
                public int Port {
                        get { return port; }
                        set { 
-                               if (value <= 0 || value > 65535)
-                                       throw new ArgumentOutOfRangeException ();
+                               if (value <= 0)
+                                       throw new ArgumentOutOfRangeException ("value");
                                CheckState ();
                                port = value;
                        }
@@ -222,14 +232,14 @@ namespace System.Net.Mail {
                        get { return timeout; }
                        set { 
                                if (value < 0)
-                                       throw new ArgumentOutOfRangeException ();
+                                       throw new ArgumentOutOfRangeException ("value");
                                CheckState ();
                                timeout = value; 
                        }
                }
 
                public bool UseDefaultCredentials {
-                       get { return useDefaultCredentials; }
+                       get { return false; }
                        [MonoNotSupported ("no DefaultCredential support in Mono")]
                        set {
                                if (value)
@@ -253,6 +263,26 @@ namespace System.Net.Mail {
                        if (messageInProcess != null)
                                throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
                }
+               
+               private static string EncodeAddress(MailAddress address)
+               {
+                       string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
+                       return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
+               }
+
+               private static string EncodeAddresses(MailAddressCollection addresses)
+               {
+                       StringBuilder sb = new StringBuilder();
+                       bool first = true;
+                       foreach (MailAddress address in addresses) {
+                               if (!first) {
+                                       sb.Append(", ");
+                               }
+                               sb.Append(EncodeAddress(address));
+                               first = false;
+                       }
+                       return sb.ToString();
+               }
 
                private string EncodeSubjectRFC2047 (MailMessage message)
                {
@@ -383,7 +413,6 @@ namespace System.Net.Mail {
 
                void ResetExtensions()
                {
-                       canStartTLS = false;
                        authMechs = AuthMechs.None;
                }
 
@@ -422,8 +451,6 @@ namespace System.Net.Mail {
                                                        break;
                                                }
                                        }
-                               } else if (start.StartsWith ("STARTTLS", StringComparison.Ordinal)) {
-                                       canStartTLS = true;
                                }
                        }
                }
@@ -433,9 +460,11 @@ namespace System.Net.Mail {
                        if (message == null)
                                throw new ArgumentNullException ("message");
 
-                       if (String.IsNullOrEmpty (Host))
+                       if (deliveryMethod == SmtpDeliveryMethod.Network && (Host == null || Host.Trim ().Length == 0))
                                throw new InvalidOperationException ("The SMTP host was not specified");
-                       
+                       else if (deliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
+                               throw new NotSupportedException("IIS delivery is not supported");
+
                        if (port == 0)
                                port = 25;
                        
@@ -443,9 +472,16 @@ namespace System.Net.Mail {
                        mutex.WaitOne ();
                        try {
                                messageInProcess = message;
-                               SendInternal (message);
+                               if (deliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
+                                       SendToFile (message);
+                               else
+                                       SendInternal (message);
                        } catch (CancellationException) {
                                // This exception is introduced for convenient cancellation process.
+                       } catch (SmtpException) {
+                               throw;
+                       } catch (Exception ex) {
+                               throw new SmtpException ("Message could not be sent.", ex);
                        } finally {
                                // Release the mutex to allow other threads access
                                mutex.ReleaseMutex ();
@@ -478,6 +514,47 @@ namespace System.Net.Mail {
                                        client.Close ();
                        }
                }
+               // FIXME: simple implementation, could be brushed up.
+               private void SendToFile (MailMessage message)
+               {
+                       if (!Path.IsPathRooted (pickupDirectoryLocation))
+                               throw new SmtpException("Only absolute directories are allowed for pickup directory.");
+
+                       string filename = Path.Combine (pickupDirectoryLocation,
+                               Guid.NewGuid() + ".eml");
+
+                       try {
+                               writer = new StreamWriter(filename);
+
+                               MailAddress from = message.From;
+
+                               if (from == null)
+                                       from = defaultFrom;
+                               
+                               SendHeader (HeaderName.Date, DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo));
+                               SendHeader (HeaderName.From, from.ToString ());
+                               SendHeader (HeaderName.To, message.To.ToString ());
+                               if (message.CC.Count > 0)
+                                       SendHeader (HeaderName.Cc, message.CC.ToString ());
+                               SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
+
+                               foreach (string s in message.Headers.AllKeys)
+                                       SendHeader (s, message.Headers [s]);
+
+                               AddPriorityHeader (message);
+
+                               boundaryIndex = 0;
+                               if (message.Attachments.Count > 0)
+                                       SendWithAttachments (message);
+                               else
+                                       SendWithoutAttachments (message, null, false);
+
+
+                       } finally {
+                               if (writer != null) writer.Close(); writer = null;
+                       }
+               }
 
                private void SendCore (MailMessage message)
                {
@@ -507,18 +584,6 @@ namespace System.Net.Mail {
                        }
                        
                        if (enableSsl) {
-#if old_comment
-                               // FIXME: I get the feeling from the docs that EnableSsl is meant
-                               // for using the SSL-port and not STARTTLS (or, if it includes
-                               // STARTTLS... only use STARTTLS if the SSL-type in the certificate
-                               // is TLS and not SSLv2 or SSLv3)
-                               
-                               // FIXME: even tho we have a canStartTLS flag... ignore it for now
-                               // so that the STARTTLS command can throw the appropriate
-                               // SmtpException if STARTTLS is unavailable
-#else
-                               // SmtpClient implements STARTTLS support.
-#endif
                                InitiateSecureConnection ();
                                ResetExtensions();
                                writer = new StreamWriter (stream);
@@ -593,15 +658,41 @@ namespace System.Net.Mail {
                        dt = dt.Remove (dt.Length - 3, 1);
                        SendHeader (HeaderName.Date, dt);
 
-                       SendHeader (HeaderName.From, from.ToString ());
-                       SendHeader (HeaderName.To, message.To.ToString ());
+                       SendHeader (HeaderName.From, EncodeAddress (from));
+                       SendHeader (HeaderName.To, EncodeAddresses (message.To));
                        if (message.CC.Count > 0)
-                               SendHeader (HeaderName.Cc, message.CC.ToString ());
+                               SendHeader (HeaderName.Cc, EncodeAddresses (message.CC));
                        SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
 
+                       string v = "normal";
+                               
+                       switch (message.Priority){
+                       case MailPriority.Normal:
+                               v = "normal";
+                               break;
+                               
+                       case MailPriority.Low:
+                               v = "non-urgent";
+                               break;
+                               
+                       case MailPriority.High:
+                               v = "urgent";
+                               break;
+                       }
+                       SendHeader ("Priority", v);
+                       if (message.Sender != null)
+                               SendHeader ("Sender", EncodeAddress (message.Sender));
+                       if (message.ReplyToList.Count > 0)
+                               SendHeader ("Reply-To", EncodeAddresses (message.ReplyToList));
+
+#if NET_4_0
+                       foreach (string s in message.Headers.AllKeys)
+                               SendHeader (s, ContentType.EncodeSubjectRFC2047 (message.Headers [s], message.HeadersEncoding));
+#else
                        foreach (string s in message.Headers.AllKeys)
                                SendHeader (s, message.Headers [s]);
-
+#endif
+       
                        AddPriorityHeader (message);
 
                        boundaryIndex = 0;
@@ -610,7 +701,7 @@ namespace System.Net.Mail {
                        else
                                SendWithoutAttachments (message, null, false);
 
-                       SendData (".");
+                       SendDot ();
 
                        status = Read ();
                        if (IsError (status))
@@ -628,17 +719,39 @@ namespace System.Net.Mail {
                        Send (new MailMessage (from, to, subject, body));
                }
 
+               private void SendDot()
+               {
+                       writer.Write(".\r\n");
+                       writer.Flush();
+               }
+
                private void SendData (string data)
                {
-                       CheckCancellation ();
+                       if (String.IsNullOrEmpty (data)) {
+                               writer.Write("\r\n");
+                               writer.Flush();
+                               return;
+                       }
 
-                       // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
-                       StringBuilder sb = new StringBuilder (data);
-                       sb.Replace ("\r\n", "\n");
-                       sb.Replace ('\r', '\n');
-                       sb.Replace ("\n", "\r\n");
-                       writer.Write (sb.ToString ());
-                       writer.Write ("\r\n");
+                       StringReader sr = new StringReader (data);
+                       string line;
+                       bool escapeDots = deliveryMethod == SmtpDeliveryMethod.Network;
+                       while ((line = sr.ReadLine ()) != null) {
+                               CheckCancellation ();
+
+                               if (escapeDots) {
+                                       int i;
+                                       for (i = 0; i < line.Length; i++) {
+                                               if (line[i] != '.')
+                                                       break;
+                                       }
+                                       if (i > 0 && i == line.Length) {
+                                               line += ".";
+                                       }
+                               }
+                               writer.Write (line);
+                               writer.Write ("\r\n");
+                       }
                        writer.Flush ();
                }
 
@@ -654,6 +767,7 @@ namespace System.Net.Mail {
                                        Send (message);
                                } catch (Exception ex) {
                                        ea.Result = ex;
+                                       throw ex;
                                }
                        };
                        worker.WorkerSupportsCancellation = true;
@@ -785,8 +899,10 @@ try {
                                        alt_boundary = GenerateBoundary ();
                                        contentType = new ContentType ("multipart/related");
                                        contentType.Boundary = alt_boundary;
-                                       contentType.Parameters ["type"] = "application/octet-stream";
+                                       
+                                       contentType.Parameters ["type"] = av.ContentType.ToString ();
                                        StartSection (inner_boundary, contentType);
+                                       StartSection (alt_boundary, av.ContentType, av.TransferEncoding);
                                } else {
                                        contentType = new ContentType (av.ContentType.ToString ());
                                        StartSection (inner_boundary, contentType, av.TransferEncoding);
@@ -810,6 +926,7 @@ try {
                                case TransferEncoding.SevenBit:
                                case TransferEncoding.Unknown:
                                        content = new byte [av.ContentStream.Length];
+                                       av.ContentStream.Read (content, 0, content.Length);
                                        SendData (Encoding.ASCII.GetString (content));
                                        break;
                                }
@@ -833,8 +950,7 @@ try {
                private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
                {
                        foreach (LinkedResource lr in resources) {
-                               ContentType contentType = new ContentType (lr.ContentType.ToString ());
-                               StartSection (boundary, contentType, lr.TransferEncoding);
+                               StartSection (boundary, lr.ContentType, lr.TransferEncoding, lr);
 
                                switch (lr.TransferEncoding) {
                                case TransferEncoding.Base64:
@@ -854,6 +970,7 @@ try {
                                case TransferEncoding.SevenBit:
                                case TransferEncoding.Unknown:
                                        content = new byte [lr.ContentStream.Length];
+                                       lr.ContentStream.Read (content, 0, content.Length);
                                        SendData (Encoding.ASCII.GetString (content));
                                        break;
                                }
@@ -923,6 +1040,18 @@ try {
                        SendData (string.Empty);
                }
 
+               private void StartSection(string section, ContentType sectionContentType, TransferEncoding transferEncoding, LinkedResource lr)
+               {
+                       SendData (String.Format("--{0}", section));
+                       SendHeader ("content-type", sectionContentType.ToString ());
+                       SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
+
+                       if (lr.ContentId != null && lr.ContentId.Length > 0)
+                               SendHeader("content-ID", "<" + lr.ContentId + ">");
+
+                       SendData (string.Empty);
+               }
+
                private void StartSection (string section, ContentType sectionContentType, TransferEncoding transferEncoding, ContentDisposition contentDisposition) {
                        SendData (String.Format ("--{0}", section));
                        SendHeader ("content-type", sectionContentType.ToString ());
@@ -994,6 +1123,10 @@ try {
                                                                         X509Certificate certificate,
                                                                         X509Chain chain,
                                                                         SslPolicyErrors sslPolicyErrors) {
+                       // honor any exciting callback defined on ServicePointManager
+                       if (ServicePointManager.ServerCertificateValidationCallback != null)
+                               return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
+                       // otherwise provide our own
                        if (sslPolicyErrors != SslPolicyErrors.None)
                                throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
                        return true;