1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 using System.Diagnostics.CodeAnalysis; 5 using System.IO; 6 using System.Net; 7 using System.Net.Mail; 8 using System.Text; 9 using System.Web.Helpers.Resources; 10 using System.Web.WebPages.Scope; 11 using Microsoft.Internal.Web.Utils; 12 13 namespace System.Web.Helpers 14 { 15 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "WebMail", Justification = "The name of this class is consistent with the naming convention followed in other helpers")] 16 public static class WebMail 17 { 18 internal static readonly object SmtpServerKey = new object(); 19 internal static readonly object SmtpPortKey = new object(); 20 internal static readonly object SmtpUseDefaultCredentialsKey = new object(); 21 internal static readonly object EnableSslKey = new object(); 22 internal static readonly object PasswordKey = new object(); 23 internal static readonly object UserNameKey = new object(); 24 internal static readonly object FromKey = new object(); 25 internal static readonly Lazy<IDictionary<object, object>> SmtpDefaults = new Lazy<IDictionary<object, object>>(ReadSmtpDefaults); 26 27 /// <summary> 28 /// MailMessage dictates that headers values that have equivalent properties would be discarded or overwritten. The list of values is available at 29 /// http://msdn.microsoft.com/en-us/library/system.net.mail.mailmessage.aspx 30 /// </summary> 31 private static readonly Dictionary<string, Action<MailMessage, string>> _actionableHeaders = new Dictionary<string, Action<MailMessage, string>>(StringComparer.OrdinalIgnoreCase) 32 { 33 { "Bcc", (message, value) => message.Bcc.Add(value) }, 34 { "Cc", (message, value) => message.CC.Add(value) }, 35 { "From", (mailMessage, value) => 36 { 37 mailMessage.From = new MailAddress(value); 38 } 39 }, 40 { "Priority", SetPriority }, 41 { "Reply-To", (mailMessage, value) => 42 { 43 mailMessage.ReplyToList.Add(value); 44 } 45 }, 46 { "Sender", (mailMessage, value) => 47 { 48 mailMessage.Sender = new MailAddress(value); 49 } 50 }, 51 { "To", (mailMessage, value) => 52 { 53 mailMessage.To.Add(value); 54 } 55 }, 56 }; 57 58 /////////////////////////////////////////////////////////////////////////// 59 // Public Properties 60 [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "SmtpServer is more descriptive as compared to the actual argument \"value\"")] 61 public static string SmtpServer 62 { 63 get { return ReadValue<string>(SmtpServerKey); } 64 set 65 { 66 if (String.IsNullOrEmpty(value)) 67 { 68 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "SmtpServer"); 69 } 70 ScopeStorage.CurrentScope[SmtpServerKey] = value; 71 } 72 } 73 74 public static int SmtpPort 75 { 76 get { return ReadValue<int>(SmtpPortKey); } 77 set { ScopeStorage.CurrentScope[SmtpPortKey] = value; } 78 } 79 80 public static string From 81 { 82 get { return ReadValue<string>(FromKey); } 83 set { ScopeStorage.CurrentScope[FromKey] = value; } 84 } 85 86 public static bool SmtpUseDefaultCredentials 87 { 88 get { return ReadValue<bool>(SmtpUseDefaultCredentialsKey); } 89 set { ScopeStorage.CurrentScope[SmtpUseDefaultCredentialsKey] = value; } 90 } 91 92 public static bool EnableSsl 93 { 94 get { return ReadValue<bool>(EnableSslKey); } 95 set { ScopeStorage.CurrentScope[EnableSslKey] = value; } 96 } 97 98 public static string UserName 99 { 100 get { return ReadValue<string>(UserNameKey); } 101 set { ScopeStorage.CurrentScope[UserNameKey] = value; } 102 } 103 104 public static string Password 105 { 106 get { return ReadValue<string>(PasswordKey); } 107 set { ScopeStorage.CurrentScope[PasswordKey] = value; } 108 } 109 Send(string to, string subject, string body, string from = null, string cc = null, IEnumerable<string> filesToAttach = null, bool isBodyHtml = true, IEnumerable<string> additionalHeaders = null, string bcc = null, string contentEncoding = null, string headerEncoding = null, string priority = null, string replyTo = null)110 public static void Send(string to, 111 string subject, 112 string body, 113 string from = null, 114 string cc = null, 115 IEnumerable<string> filesToAttach = null, 116 bool isBodyHtml = true, 117 IEnumerable<string> additionalHeaders = null, 118 string bcc = null, 119 string contentEncoding = null, 120 string headerEncoding = null, 121 string priority = null, 122 string replyTo = null) 123 { 124 if (filesToAttach != null) 125 { 126 foreach (string fileName in filesToAttach) 127 { 128 if (String.IsNullOrEmpty(fileName)) 129 { 130 throw new ArgumentException(HelpersResources.WebMail_ItemInCollectionIsNull, "filesToAttach"); 131 } 132 } 133 } 134 135 if (additionalHeaders != null) 136 { 137 foreach (string header in additionalHeaders) 138 { 139 if (String.IsNullOrEmpty(header)) 140 { 141 throw new ArgumentException(HelpersResources.WebMail_ItemInCollectionIsNull, "additionalHeaders"); 142 } 143 } 144 } 145 146 MailPriority priorityValue = MailPriority.Normal; 147 if (!String.IsNullOrEmpty(priority) && !ConversionUtil.TryFromStringToEnum(priority, out priorityValue)) 148 { 149 throw new ArgumentException(HelpersResources.WebMail_InvalidPriority, "priority"); 150 } 151 152 if (String.IsNullOrEmpty(SmtpServer)) 153 { 154 throw new InvalidOperationException(HelpersResources.WebMail_SmtpServerNotSpecified); 155 } 156 157 using (MailMessage message = new MailMessage()) 158 { 159 SetPropertiesOnMessage(message, to, subject, body, from, cc, bcc, replyTo, contentEncoding, headerEncoding, priorityValue, 160 filesToAttach, isBodyHtml, additionalHeaders); 161 using (SmtpClient client = new SmtpClient()) 162 { 163 SetPropertiesOnClient(client); 164 client.Send(message); 165 } 166 } 167 } 168 ReadValue(object key)169 private static TValue ReadValue<TValue>(object key) 170 { 171 return (TValue)(ScopeStorage.CurrentScope[key] ?? SmtpDefaults.Value[key]); 172 } 173 ReadSmtpDefaults()174 private static IDictionary<object, object> ReadSmtpDefaults() 175 { 176 Dictionary<object, object> smtpDefaults = new Dictionary<object, object>(); 177 try 178 { 179 // Create a new SmtpClient object: this will read config & tell us what the default value is 180 using (SmtpClient client = new SmtpClient()) 181 { 182 smtpDefaults[SmtpServerKey] = client.Host; 183 smtpDefaults[SmtpPortKey] = client.Port; 184 smtpDefaults[EnableSslKey] = client.EnableSsl; 185 smtpDefaults[SmtpUseDefaultCredentialsKey] = client.UseDefaultCredentials; 186 187 var credentials = client.Credentials as NetworkCredential; 188 if (credentials != null) 189 { 190 smtpDefaults[UserNameKey] = credentials.UserName; 191 smtpDefaults[PasswordKey] = credentials.Password; 192 } 193 else 194 { 195 smtpDefaults[UserNameKey] = null; 196 smtpDefaults[PasswordKey] = null; 197 } 198 using (MailMessage message = new MailMessage()) 199 { 200 smtpDefaults[FromKey] = (message.From != null) ? message.From.Address : null; 201 } 202 } 203 } 204 catch (InvalidOperationException) 205 { 206 // Due to Bug Dev10 PS 337470 ("SmtpClient reports InvalidOperationException when disposed"), we need to ignore the spurious InvalidOperationException 207 } 208 return smtpDefaults; 209 } 210 SetPropertiesOnClient(SmtpClient client)211 internal static void SetPropertiesOnClient(SmtpClient client) 212 { 213 // If no value has been assigned to these properties, at the very worst we will simply 214 // write back the values we just read from the SmtpClient 215 if (SmtpServer != null) 216 { 217 client.Host = SmtpServer; 218 } 219 client.Port = SmtpPort; 220 client.UseDefaultCredentials = SmtpUseDefaultCredentials; 221 client.EnableSsl = EnableSsl; 222 if (!String.IsNullOrEmpty(UserName)) 223 { 224 client.Credentials = new NetworkCredential(UserName, Password); 225 } 226 } 227 SetPropertiesOnMessage(MailMessage message, string to, string subject, string body, string from, string cc, string bcc, string replyTo, string contentEncoding, string headerEncoding, MailPriority priority, IEnumerable<string> filesToAttach, bool isBodyHtml, IEnumerable<string> additionalHeaders)228 internal static void SetPropertiesOnMessage(MailMessage message, string to, string subject, 229 string body, string from, string cc, string bcc, string replyTo, 230 string contentEncoding, string headerEncoding, MailPriority priority, 231 IEnumerable<string> filesToAttach, bool isBodyHtml, 232 IEnumerable<string> additionalHeaders) 233 { 234 message.Subject = subject; 235 message.Body = body; 236 message.IsBodyHtml = isBodyHtml; 237 238 if (additionalHeaders != null) 239 { 240 AssignHeaderValues(message, additionalHeaders); 241 } 242 243 if (to != null) 244 { 245 message.To.Add(to); 246 } 247 248 if (!String.IsNullOrEmpty(cc)) 249 { 250 message.CC.Add(cc); 251 } 252 253 if (!String.IsNullOrEmpty(bcc)) 254 { 255 message.Bcc.Add(bcc); 256 } 257 258 if (!String.IsNullOrEmpty(replyTo)) 259 { 260 message.ReplyToList.Add(replyTo); 261 } 262 263 if (!String.IsNullOrEmpty(contentEncoding)) 264 { 265 message.BodyEncoding = Encoding.GetEncoding(contentEncoding); 266 } 267 268 if (!String.IsNullOrEmpty(headerEncoding)) 269 { 270 message.HeadersEncoding = Encoding.GetEncoding(headerEncoding); 271 } 272 273 message.Priority = priority; 274 275 if (from != null) 276 { 277 message.From = new MailAddress(from); 278 } 279 else if (!String.IsNullOrEmpty(From)) 280 { 281 message.From = new MailAddress(From); 282 } 283 else if (message.From == null || String.IsNullOrEmpty(message.From.Address)) 284 { 285 var httpContext = HttpContext.Current; 286 if (httpContext != null) 287 { 288 message.From = new MailAddress("DoNotReply@" + httpContext.Request.Url.Host); 289 } 290 else 291 { 292 throw new InvalidOperationException(HelpersResources.WebMail_UnableToDetermineFrom); 293 } 294 } 295 296 if (filesToAttach != null) 297 { 298 foreach (string file in filesToAttach) 299 { 300 if (!Path.IsPathRooted(file) && HttpRuntime.AppDomainAppPath != null) 301 { 302 message.Attachments.Add(new Attachment(Path.Combine(HttpRuntime.AppDomainAppPath, file))); 303 } 304 else 305 { 306 message.Attachments.Add(new Attachment(file)); 307 } 308 } 309 } 310 } 311 AssignHeaderValues(MailMessage message, IEnumerable<string> headerValues)312 internal static void AssignHeaderValues(MailMessage message, IEnumerable<string> headerValues) 313 { 314 // Parse the header value. If this 315 foreach (var header in headerValues) 316 { 317 string key, value; 318 if (TryParseHeader(header, out key, out value)) 319 { 320 // Verify if the header key maps to a property on MailMessage. 321 Action<MailMessage, string> action; 322 if (_actionableHeaders.TryGetValue(key, out action)) 323 { 324 try 325 { 326 action(message, value); 327 } 328 catch (FormatException) 329 { 330 // If the mail address is invalid, swallow the exception. 331 } 332 } 333 message.Headers.Add(key, value); 334 } 335 } 336 } 337 338 /// <summary> 339 /// Parses a SMTP Mail header of the format "name: value" 340 /// </summary> 341 /// <returns>True if the header was parsed.</returns> TryParseHeader(string header, out string key, out string value)342 internal static bool TryParseHeader(string header, out string key, out string value) 343 { 344 int pos = header.IndexOf(':'); 345 if (pos > 0) 346 { 347 key = header.Substring(0, pos).TrimEnd(); 348 value = header.Substring(pos + 1).TrimStart(); 349 return key.Length > 0 && value.Length > 0; 350 } 351 key = null; 352 value = null; 353 return false; 354 } 355 SetPriority(MailMessage message, string priority)356 private static void SetPriority(MailMessage message, string priority) 357 { 358 MailPriority priorityValue; 359 if (!String.IsNullOrEmpty(priority) && ConversionUtil.TryFromStringToEnum(priority, out priorityValue)) 360 { 361 // If we can parse it, set it. Do nothing otherwise 362 message.Priority = priorityValue; 363 } 364 } 365 } 366 } 367