Bladeren bron

2007-10-16 Atsushi Enomoto <[email protected]>

	* MailMessage.cs : Some entire refactory on BodyEncoding and
	  IsBodyHtml. BodyEncoding and SubjectEncoding are guessed when
	  Body and Subject are set for each.
	* SmtpClient.cs :
	  Subject header is encoded according to RFC 2047.
	  Body is encoded according to RFC 2821.
	  Output Date header.
	  ToQuotedPrintable() should take encoding into consideration.
	  For SevenBit/Unknown TransferEncoding, just decode with ASCII.
	  In set_UseDefaultCredentials(), raise NIE only when value is true.
	  In set_Timeout(), raise an error when Send() is in progress.

	* MailMessageTest.cs : added test for encoding guess.


svn path=/trunk/mcs/; revision=87585
Atsushi Eno 18 jaren geleden
bovenliggende
commit
b0b846d5bb

+ 14 - 0
mcs/class/System/System.Net.Mail/ChangeLog

@@ -1,3 +1,17 @@
+2007-10-16  Atsushi Enomoto  <[email protected]>
+
+	* MailMessage.cs : Some entire refactory on BodyEncoding and
+	  IsBodyHtml. BodyEncoding and SubjectEncoding are guessed when
+	  Body and Subject are set for each.
+	* SmtpClient.cs :
+	  Subject header is encoded according to RFC 2047.
+	  Body is encoded according to RFC 2821.
+	  Output Date header.
+	  ToQuotedPrintable() should take encoding into consideration.
+	  For SevenBit/Unknown TransferEncoding, just decode with ASCII.
+	  In set_UseDefaultCredentials(), raise NIE only when value is true.
+	  In set_Timeout(), raise an error when Send() is in progress.
+
 2007-10-16  Atsushi Enomoto  <[email protected]>
 
 	* SmtpClient.cs : Replace every \r and \n with \r\n per RFC 2821 

+ 42 - 16
mcs/class/System/System.Net.Mail/MailMessage.cs

@@ -53,8 +53,8 @@ namespace System.Net.Mail {
 		NameValueCollection headers;
 		MailAddressCollection to;
 		string subject;
-		Encoding subjectEncoding;
-		ContentType bodyContentType;
+		Encoding subjectEncoding, bodyEncoding;
+		bool isHtml;
 
 		#endregion // Fields
 
@@ -128,20 +128,39 @@ namespace System.Net.Mail {
 
 		public string Body {
 			get { return body; }
-			set { body = value; }
+			set {
+				// autodetect suitable body encoding (ASCII or UTF-8), if it is not initialized yet.
+				if (value != null && bodyEncoding == null)
+					bodyEncoding = GuessEncoding (value);
+				body = value;
+			}
 		}
 
 		internal ContentType BodyContentType {
 			get {
-				if (bodyContentType == null)
-					bodyContentType = new ContentType ("text/plain; charset=us-ascii");
-				return bodyContentType;
+				ContentType ct = new ContentType (isHtml ? "text/html" : "text/plain");
+				ct.CharSet = (BodyEncoding ?? Encoding.ASCII).HeaderName;
+				return ct;
+			}
+		}
+
+		internal TransferEncoding ContentTransferEncoding {
+			get {
+				Encoding enc = BodyEncoding;
+				if (Encoding.ASCII.Equals (enc))
+					return TransferEncoding.SevenBit;
+				else if (Encoding.UTF8.CodePage == enc.CodePage ||
+				    Encoding.Unicode.CodePage == enc.CodePage ||
+				    Encoding.UTF32.CodePage == enc.CodePage)
+					return TransferEncoding.Base64;
+				else
+					return TransferEncoding.QuotedPrintable;
 			}
 		}
 
 		public Encoding BodyEncoding {
-			get { return Encoding.GetEncoding (BodyContentType.CharSet); }
-			set { BodyContentType.CharSet = value.WebName; }
+			get { return bodyEncoding; }
+			set { bodyEncoding = value; }
 		}
 
 		public MailAddressCollection CC {
@@ -163,13 +182,8 @@ namespace System.Net.Mail {
 		}
 
 		public bool IsBodyHtml {
-			get { return String.Compare (BodyContentType.MediaType, "text/html", true, CultureInfo.InvariantCulture) == 0; }
-			set {
-				if (value)
-					BodyContentType.MediaType = "text/html";
-				else
-					BodyContentType.MediaType = "text/plain";
-			}
+			get { return isHtml; }
+			set { isHtml = value; }
 		}
 
 		public MailPriority Priority {
@@ -189,7 +203,11 @@ namespace System.Net.Mail {
 
 		public string Subject {
 			get { return subject; }
-			set { subject = value; }
+			set {
+				if (value != null && subjectEncoding == null)
+					subjectEncoding = GuessEncoding (value);
+				subject = value;
+			}
 		}
 
 		public Encoding SubjectEncoding {
@@ -215,6 +233,14 @@ namespace System.Net.Mail {
 		{
 		}
 
+		private Encoding GuessEncoding (string s)
+		{
+			for (int i = 0; i < s.Length; i++)
+				if (s [i] >= '\u0080')
+					return Encoding.UTF8;
+			return Encoding.ASCII;
+		}
+
 		#endregion // Methods
 	}
 }

+ 62 - 28
mcs/class/System/System.Net.Mail/SmtpClient.cs

@@ -33,6 +33,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Globalization;
 using System.IO;
 using System.Net;
 using System.Net.Mime;
@@ -66,6 +67,8 @@ namespace System.Net.Mail {
 		int boundaryIndex;
 		MailAddress defaultFrom;
 
+		MailMessage messageInProcess;
+
 		// ESMTP state
 		enum AuthMechs {
 			None        = 0,
@@ -185,17 +188,22 @@ namespace System.Net.Mail {
 
 		public int Timeout {
 			get { return timeout; }
-			// FIXME: Check to make sure an email is not being sent.
 			set { 
 				if (value < 0)
 					throw new ArgumentOutOfRangeException ();
+				if (messageInProcess != null)
+					throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
 				timeout = value; 
 			}
 		}
 
 		public bool UseDefaultCredentials {
 			get { return useDefaultCredentials; }
-			set { throw new NotImplementedException ("Default credentials are not supported"); }
+			[MonoNotSupported ("no DefaultCredential support in Mono")]
+			set {
+				if (value)
+					throw new NotImplementedException ("Default credentials are not supported");
+			}
 		}
 
 		#endregion // Properties
@@ -208,6 +216,28 @@ namespace System.Net.Mail {
 
 		#region Methods
 
+		private string EncodeSubjectRFC2047 (MailMessage message)
+		{
+			if (message.SubjectEncoding == null || Encoding.ASCII.Equals (message.SubjectEncoding))
+				return message.Subject;
+			string b64 = Convert.ToBase64String (message.SubjectEncoding.GetBytes (message.Subject));
+			return String.Concat ("=?", message.SubjectEncoding.HeaderName, "?B?", b64, "?=");
+		}
+
+		private string EncodeBody (MailMessage message)
+		{
+			string body = message.Body;
+			// RFC 2045 encoding
+			switch (message.ContentTransferEncoding) {
+			case TransferEncoding.SevenBit:
+				return body;
+			case TransferEncoding.Base64:
+				return Convert.ToBase64String (message.BodyEncoding.GetBytes (body));
+			default:
+				return ToQuotedPrintable (body, message.BodyEncoding);
+			}
+		}
+
 		private void EndSection (string section)
 		{
 			SendData (String.Format ("--{0}--", section));
@@ -333,6 +363,9 @@ namespace System.Net.Mail {
 
 		public void Send (MailMessage message)
 		{
+			if (message == null)
+				throw new ArgumentNullException ("message");
+
 			if (String.IsNullOrEmpty (Host))
 				throw new InvalidOperationException ("The SMTP host was not specified");
 			
@@ -341,6 +374,7 @@ namespace System.Net.Mail {
 			
 			// Block while sending
 			mutex.WaitOne ();
+			messageInProcess = message;
 
 			SmtpResponse status;
 
@@ -436,11 +470,12 @@ namespace System.Net.Mail {
 				throw new SmtpException (status.StatusCode, status.Description);
 
 			// Send message headers
+			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, message.Subject);
+			SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
 
 			foreach (string s in message.Headers.AllKeys)
 				SendHeader (s, message.Headers [s]);
@@ -475,6 +510,7 @@ namespace System.Net.Mail {
 
 			// Release the mutex to allow other threads access
 			mutex.ReleaseMutex ();
+			messageInProcess = null;
 		}
 
 		public void Send (string from, string to, string subject, string body)
@@ -524,10 +560,12 @@ namespace System.Net.Mail {
 		}
 
 		private void SendSimpleBody (MailMessage message) {
-			SendHeader ("Content-Type", message.BodyContentType.ToString ());
+			SendHeader (HeaderName.ContentType, message.BodyContentType.ToString ());
+			if (message.ContentTransferEncoding != TransferEncoding.SevenBit)
+				SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (message.ContentTransferEncoding));
 			SendData (string.Empty);
 
-			SendData (message.Body);
+			SendData (EncodeBody (message));
 		}
 
 		private void SendMultipartBody (MailMessage message) {
@@ -539,10 +577,12 @@ namespace System.Net.Mail {
 			messageContentType.Boundary = boundary;
 			messageContentType.MediaType = "multipart/mixed";
 
-			SendHeader ("Content-Type", messageContentType.ToString ());
+			SendHeader (HeaderName.ContentType, messageContentType.ToString ());
+			if (message.ContentTransferEncoding != TransferEncoding.SevenBit)
+				SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (message.ContentTransferEncoding));
 			SendData (string.Empty);
 
-			SendData (message.Body);
+			SendData (EncodeBody (message));
 			SendData (string.Empty);
 
 			message.AlternateViews.Add (AlternateView.CreateAlternateViewFromString (message.Body, new ContentType ("text/plain")));
@@ -585,12 +625,12 @@ namespace System.Net.Mail {
 					break;
 				case TransferEncoding.QuotedPrintable:
 					StreamReader sr = new StreamReader (alternateViews [i].ContentStream);
-					SendData (ToQuotedPrintable (sr.ReadToEnd ()));
+					SendData (ToQuotedPrintable (sr.ReadToEnd (), Encoding.GetEncoding (contentType.CharSet)));
 					break;
-				//case TransferEncoding.SevenBit:
-				//case TransferEncoding.Unknown:
-				default:
-					SendData ("TO BE IMPLEMENTED");
+				case TransferEncoding.SevenBit:
+				case TransferEncoding.Unknown:
+					content = new byte [alternateViews [i].ContentStream.Length];
+					SendData (Encoding.ASCII.GetString (content));
 					break;
 				}
 
@@ -620,12 +660,12 @@ namespace System.Net.Mail {
 					break;
 				case TransferEncoding.QuotedPrintable:
 					StreamReader sr = new StreamReader (attachments [i].ContentStream);
-					SendData (ToQuotedPrintable (sr.ReadToEnd ()));
+					SendData (ToQuotedPrintable (sr.ReadToEnd (), Encoding.GetEncoding (contentType.CharSet)));
 					break;
-				//case TransferEncoding.SevenBit:
-				//case TransferEncoding.Unknown:
-				default:
-					SendData ("TO BE IMPLEMENTED");
+				case TransferEncoding.SevenBit:
+				case TransferEncoding.Unknown:
+					content = new byte [attachments [i].ContentStream.Length];
+					SendData (Encoding.ASCII.GetString (content));
 					break;
 				}
 
@@ -671,12 +711,11 @@ namespace System.Net.Mail {
 			SendData (string.Empty);
 		}
 
-		private string ToQuotedPrintable (string input) {
-			StringReader reader = new StringReader (input);
+		// use proper encoding to escape input
+		private string ToQuotedPrintable (string input, Encoding enc) {
+			byte [] bytes = enc.GetBytes (input);
 			StringWriter writer = new StringWriter ();
-			int i;
-
-			while ((i = reader.Read ()) > 0) {
+			foreach (byte i in bytes) {
 				if (i > 127) {
 					writer.Write ("=");
 					writer.Write (Convert.ToString (i, 16).ToUpper ());
@@ -756,12 +795,6 @@ namespace System.Net.Mail {
 			}
 		}
 		
-		/*[MonoTODO]
-		private sealed ContextAwareResult IGetContextAwareResult.GetContextAwareResult ()
-		{
-			throw new NotImplementedException ();
-		}*/
-		
 		#endregion // Methods
 		
 		// The HeaderName struct is used to store constant string values representing mail headers.
@@ -778,6 +811,7 @@ namespace System.Net.Mail {
 			public const string Priority = "Priority";
 			public const string Importance = "Importance";
 			public const string XPriority = "X-Priority";
+			public const string Date = "Date";
 		}
 
 		// This object encapsulates the status code and description of an SMTP response.

+ 4 - 0
mcs/class/System/Test/System.Net.Mail/ChangeLog

@@ -1,3 +1,7 @@
+2007-10-16  Atsushi Enomoto  <[email protected]>
+
+	* MailMessageTest.cs : added test for encoding guess.
+
 2007-06-17  Gert Driesen  <[email protected]>
 
 	* MailAddressTest.cs: Added tests for ctors and specifying display

+ 28 - 0
mcs/class/System/Test/System.Net.Mail/MailMessageTest.cs

@@ -135,6 +135,34 @@ namespace MonoTests.System.Net.Mail
 			Assert.AreEqual (msg.To[0].Address, "[email protected]", "#B2");
 			Assert.AreEqual (msg.To[1].Address, "[email protected]", "#B3");
 		}
+
+		[Test]
+		public void BodyAndEncoding ()
+		{
+			MailMessage msg = new MailMessage ("[email protected]", "[email protected]");
+			Assert.AreEqual (null, msg.BodyEncoding, "#1");
+			msg.Body = "test";
+			Assert.AreEqual (Encoding.ASCII, msg.BodyEncoding, "#2");
+			msg.Body = "test\u3067\u3059";
+			Assert.AreEqual (Encoding.ASCII, msg.BodyEncoding, "#3");
+			msg.BodyEncoding = null;
+			msg.Body = "test\u3067\u3059";
+			Assert.AreEqual (Encoding.UTF8, msg.BodyEncoding, "#4");
+		}
+
+		[Test]
+		public void SubjectAndEncoding ()
+		{
+			MailMessage msg = new MailMessage ("[email protected]", "[email protected]");
+			Assert.AreEqual (null, msg.SubjectEncoding, "#1");
+			msg.Subject = "test";
+			Assert.AreEqual (Encoding.ASCII, msg.SubjectEncoding, "#2");
+			msg.Subject = "test\u3067\u3059";
+			Assert.AreEqual (Encoding.ASCII, msg.SubjectEncoding, "#3");
+			msg.SubjectEncoding = null;
+			msg.Subject = "test\u3067\u3059";
+			Assert.AreEqual (Encoding.UTF8, msg.SubjectEncoding, "#4");
+		}
 	}
 }
 #endif