PageRenderTime 23ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Postal/EmailParser.cs

http://github.com/andrewdavey/postal
C# | 280 lines | 245 code | 19 blank | 16 comment | 32 complexity | 4774ffa3c896f30f2722747228fafd8b MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net.Mail;
  6. using System.Net.Mime;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. namespace Postal
  10. {
  11. /// <summary>
  12. /// Converts the raw string output of a view into a <see cref="MailMessage"/>.
  13. /// </summary>
  14. public class EmailParser : IEmailParser
  15. {
  16. /// <summary>
  17. /// Creates a new <see cref="EmailParser"/>.
  18. /// </summary>
  19. public EmailParser(IEmailViewRenderer alternativeViewRenderer)
  20. {
  21. this.alternativeViewRenderer = alternativeViewRenderer;
  22. }
  23. readonly IEmailViewRenderer alternativeViewRenderer;
  24. /// <summary>
  25. /// Parses the email view output into a <see cref="MailMessage"/>.
  26. /// </summary>
  27. /// <param name="emailViewOutput">The email view output.</param>
  28. /// <param name="email">The <see cref="Email"/> used to generate the output.</param>
  29. /// <returns>A <see cref="MailMessage"/> containing the email headers and content.</returns>
  30. public MailMessage Parse(string emailViewOutput, Email email)
  31. {
  32. var message = new MailMessage();
  33. InitializeMailMessage(message, emailViewOutput, email);
  34. return message;
  35. }
  36. void InitializeMailMessage(MailMessage message, string emailViewOutput, Email email)
  37. {
  38. using (var reader = new StringReader(emailViewOutput))
  39. {
  40. ParserUtils.ParseHeaders(reader, (key, value) => ProcessHeader(key, value, message, email));
  41. AssignCommonHeaders(message, email);
  42. if (message.AlternateViews.Count == 0)
  43. {
  44. var messageBody = reader.ReadToEnd().Trim();
  45. if (email.ImageEmbedder.HasImages)
  46. {
  47. var view = AlternateView.CreateAlternateViewFromString(messageBody, new ContentType("text/html"));
  48. email.ImageEmbedder.AddImagesToView(view);
  49. message.AlternateViews.Add(view);
  50. message.Body = "Plain text not available.";
  51. message.IsBodyHtml = false;
  52. }
  53. else
  54. {
  55. message.Body = messageBody;
  56. if (message.Body.StartsWith("<")) message.IsBodyHtml = true;
  57. }
  58. }
  59. AddAttachments(message, email);
  60. }
  61. }
  62. void AssignCommonHeaders(MailMessage message, Email email)
  63. {
  64. if (message.To.Count == 0)
  65. {
  66. AssignCommonHeader<string>(email, "to", to => message.To.Add(to));
  67. AssignCommonHeader<MailAddress>(email, "to", to => message.To.Add(to));
  68. }
  69. if (message.From == null)
  70. {
  71. AssignCommonHeader<string>(email, "from", from => message.From = new MailAddress(from));
  72. AssignCommonHeader<MailAddress>(email, "from", from => message.From = from);
  73. }
  74. if (message.CC.Count == 0)
  75. {
  76. AssignCommonHeader<string>(email, "cc", cc => message.CC.Add(cc));
  77. AssignCommonHeader<MailAddress>(email, "cc", cc => message.CC.Add(cc));
  78. }
  79. if (message.Bcc.Count == 0)
  80. {
  81. AssignCommonHeader<string>(email, "bcc", bcc => message.Bcc.Add(bcc));
  82. AssignCommonHeader<MailAddress>(email, "bcc", bcc => message.Bcc.Add(bcc));
  83. }
  84. if (message.ReplyToList.Count == 0)
  85. {
  86. AssignCommonHeader<string>(email, "replyto", replyTo => message.ReplyToList.Add(replyTo));
  87. AssignCommonHeader<MailAddress>(email, "replyto", replyTo => message.ReplyToList.Add(replyTo));
  88. }
  89. if (message.Sender == null)
  90. {
  91. AssignCommonHeader<string>(email, "sender", sender => message.Sender = new MailAddress(sender));
  92. AssignCommonHeader<MailAddress>(email, "sender", sender => message.Sender = sender);
  93. }
  94. if (string.IsNullOrEmpty(message.Subject))
  95. {
  96. AssignCommonHeader<string>(email, "subject", subject => message.Subject = subject);
  97. }
  98. }
  99. void AssignCommonHeader<T>(Email email, string header, Action<T> assign)
  100. where T : class
  101. {
  102. object value;
  103. if (email.ViewData.TryGetValue(header, out value))
  104. {
  105. var typedValue = value as T;
  106. if (typedValue != null) assign(typedValue);
  107. }
  108. }
  109. void ProcessHeader(string key, string value, MailMessage message, Email email)
  110. {
  111. if (IsAlternativeViewsHeader(key))
  112. {
  113. foreach (var view in CreateAlternativeViews(value, email))
  114. {
  115. message.AlternateViews.Add(view);
  116. }
  117. }
  118. else
  119. {
  120. AssignEmailHeaderToMailMessage(key, value, message);
  121. }
  122. }
  123. IEnumerable<AlternateView> CreateAlternativeViews(string deliminatedViewNames, Email email)
  124. {
  125. var viewNames = deliminatedViewNames.Split(new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
  126. return from viewName in viewNames
  127. select CreateAlternativeView(email, viewName);
  128. }
  129. AlternateView CreateAlternativeView(Email email, string alternativeViewName)
  130. {
  131. var fullViewName = GetAlternativeViewName(email, alternativeViewName);
  132. var output = alternativeViewRenderer.Render(email, fullViewName);
  133. string contentType;
  134. string body;
  135. using (var reader = new StringReader(output))
  136. {
  137. contentType = ParseHeadersForContentType(reader);
  138. body = reader.ReadToEnd();
  139. }
  140. if (string.IsNullOrWhiteSpace(contentType))
  141. {
  142. if (alternativeViewName.Equals("text", StringComparison.OrdinalIgnoreCase))
  143. {
  144. contentType = "text/plain";
  145. }
  146. else if (alternativeViewName.Equals("html", StringComparison.OrdinalIgnoreCase))
  147. {
  148. contentType = "text/html";
  149. }
  150. else
  151. {
  152. throw new Exception("The 'Content-Type' header is missing from the alternative view '" + fullViewName + "'.");
  153. }
  154. }
  155. var stream = CreateStreamOfBody(body);
  156. var alternativeView = new AlternateView(stream, contentType);
  157. if (alternativeView.ContentType.CharSet == null)
  158. {
  159. // Must set a charset otherwise mail readers seem to guess the wrong one!
  160. // Strings are unicode by default in .net.
  161. alternativeView.ContentType.CharSet = Encoding.Unicode.WebName;
  162. // A different charset can be specified in the Content-Type header.
  163. // e.g. Content-Type: text/html; charset=utf-8
  164. }
  165. email.ImageEmbedder.AddImagesToView(alternativeView);
  166. return alternativeView;
  167. }
  168. static string GetAlternativeViewName(Email email, string alternativeViewName)
  169. {
  170. if (email.ViewName.StartsWith("~"))
  171. {
  172. var index = email.ViewName.LastIndexOf('.');
  173. return email.ViewName.Insert(index + 1, alternativeViewName + ".");
  174. }
  175. else
  176. {
  177. return email.ViewName + "." + alternativeViewName;
  178. }
  179. }
  180. MemoryStream CreateStreamOfBody(string body)
  181. {
  182. var stream = new MemoryStream();
  183. var writer = new StreamWriter(stream);
  184. writer.Write(body);
  185. writer.Flush();
  186. stream.Position = 0;
  187. return stream;
  188. }
  189. string ParseHeadersForContentType(StringReader reader)
  190. {
  191. string contentType = null;
  192. ParserUtils.ParseHeaders(reader, (key, value) =>
  193. {
  194. if (key.Equals("content-type", StringComparison.OrdinalIgnoreCase))
  195. {
  196. contentType = value;
  197. }
  198. });
  199. return contentType;
  200. }
  201. bool IsAlternativeViewsHeader(string headerName)
  202. {
  203. return headerName.Equals("views", StringComparison.OrdinalIgnoreCase);
  204. }
  205. void AssignEmailHeaderToMailMessage(string key, string value, MailMessage message)
  206. {
  207. switch (key)
  208. {
  209. case "to":
  210. message.To.Add(value);
  211. break;
  212. case "from":
  213. message.From = new MailAddress(value);
  214. break;
  215. case "subject":
  216. message.Subject = value;
  217. break;
  218. case "cc":
  219. message.CC.Add(value);
  220. break;
  221. case "bcc":
  222. message.Bcc.Add(value);
  223. break;
  224. case "reply-to":
  225. message.ReplyToList.Add(value);
  226. break;
  227. case "sender":
  228. message.Sender = new MailAddress(value);
  229. break;
  230. case "priority":
  231. MailPriority priority;
  232. if (Enum.TryParse(value, true, out priority))
  233. {
  234. message.Priority = priority;
  235. }
  236. else
  237. {
  238. throw new ArgumentException(string.Format("Invalid email priority: {0}. It must be High, Medium or Low.", value));
  239. }
  240. break;
  241. case "content-type":
  242. var charsetMatch = Regex.Match(value, @"\bcharset\s*=\s*(.*)$");
  243. if (charsetMatch.Success)
  244. {
  245. message.BodyEncoding = Encoding.GetEncoding(charsetMatch.Groups[1].Value);
  246. }
  247. break;
  248. default:
  249. message.Headers[key] = value;
  250. break;
  251. }
  252. }
  253. void AddAttachments(MailMessage message, Email email)
  254. {
  255. foreach (var attachment in email.Attachments)
  256. {
  257. message.Attachments.Add(attachment);
  258. }
  259. }
  260. }
  261. }