public static async Task TwoChildrenTest() { var stream = typeof(MimeTests).GetResource("testdata.two-children.eml"); MimeReader reader = new MimeReader(); var structure = await reader.ReadStructureAsync(stream, CancellationToken.None); await TestContent(structure, stream, new ExpectedPart( new Dictionary <string, string> { { "From", " From Name <*****@*****.**>" }, { "To", " To Name<*****@*****.**>" }, { "Subject", " Sample message" }, { "MIME-Version", " 1.0" }, { "Content-Type", " multipart/mixed; boundary=\"simple boundary\"" }, }, "This is the preamble. It is to be ignored, though it\r\nis a handy place for mail composers to include an\r\nexplanatory note to non-MIME compliant readers.", "This is the epilogue. It is also to be ignored.\r\n", new ExpectedPart( new Dictionary <string, string>(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak."), new ExpectedPart( new Dictionary <string, string> { { "Content-type", " text/plain; charset=us-ascii" }, }, "This is explicitly typed plain ASCII text.\r\nIt DOES end with a linebreak.\r\n")) ); }
public static async Task Nested() { var stream = typeof(MimeTests).GetResource("testdata.nested.eml"); MimeReader reader = new MimeReader(); var structure = await reader.ReadStructureAsync(stream, CancellationToken.None); await TestContent(structure, stream, new ExpectedPart( new Dictionary <string, string> { { "MIME-Version", " 1.0" }, { "From", " Nathaniel Borenstein <*****@*****.**>" }, { "To", " Ned Freed <*****@*****.**>" }, { "Subject", " A multipart example" }, { "Content-Type", " multipart/mixed;\r\n boundary=unique-boundary-1" }, }, "This is the preamble area of a multipart message.\r\nMail readers that understand multipart format\r\nshould ignore this preamble.\r\nIf you are reading this text, you might want to\r\nconsider changing to a mail reader that understands\r\nhow to properly display multipart messages.", "", new ExpectedPart( new Dictionary <string, string>(), "\r\n...Some text appears here...\r\n\r\n\r\n[Note that the preceding blank line means\r\nno header fields were given and this is text,\r\nwith charset US ASCII. It could have been\r\ndone with explicit typing as in the next part.]\r\n"), new ExpectedPart(new Dictionary <string, string> { { "Content-type", " text/plain; charset=US-ASCII" }, }, "This could have been part of the previous part,\r\nbut illustrates explicit versus implicit\r\ntyping of body parts.\r\n"), new ExpectedPart(new Dictionary <string, string> { { "Content-Type", " multipart/parallel;\r\n boundary=\"unique-\\\"boundary-2\\\"\"" }, }, "", "", new ExpectedPart(new Dictionary <string, string> { { "Content-Type", " audio/basic" }, { "Content-Transfer-Encoding", " base64" }, }, "\r\n... base64-encoded 8000 Hz single-channel\r\n mu-law-format audio data goes here....\r\n"), new ExpectedPart(new Dictionary <string, string> { { "Content-Type", " image/gif" }, { "Content-Transfer-Encoding", " base64" }, }, "\r\n... base64-encoded image data goes here....\r\n\r\n") ), new ExpectedPart(new Dictionary <string, string> { { "Content-type", " text/richtext" }, }, "This is <bold><italic>richtext.</italic></bold>\r\n<smaller>as defined in RFC 1341</smaller>\r\n<nl><nl>Isn\'t it\r\n<bigger><bigger>cool?</bigger></bigger>\r\n"), new ExpectedPart(new Dictionary <string, string> { { "Content-Type", " message/rfc822" }, }, "From: (mailbox in US-ASCII)\r\nTo: (address in US-ASCII)\r\nSubject: (subject in US-ASCII)\r\nContent-Type: Text/plain; charset=ISO-8859-1\r\nContent-Transfer-Encoding: Quoted-printable\r\n\r\n\r\n... Additional text in ISO-8859-1 goes here ...\r\n\r\n") ) ); }
public static async Task SimpleMimeTest() { var stream = typeof(MimeTests).GetResource("testdata.simple.eml"); MimeReader reader = new MimeReader(); var structure = await reader.ReadStructureAsync(stream, CancellationToken.None); await TestContent(structure, stream, new ExpectedPart( new Dictionary <string, string> { { "Return-Path", " <*****@*****.**>" }, { "From", " \"Sender Name\" <*****@*****.**>" }, { "To", " \"Recipient Name\" <*****@*****.**>,\r\n\t\"Other Name\" <*****@*****.**>" }, { "Subject", " Sample subject" }, { "MIME-Version", " 1.0" }, { "Content-Type", " text/plain;\r\n\tcharset=\"us-ascii\"" }, { "Content-Transfer-Encoding", " 7bit" }, }, "Test body\r\n\r\nTest paragraph\r\n" ) ); }
/// <summary> /// If "*****@*****.**" is replying to something sent to "*****@*****.**", /// replace the "me@example" in the mail /// </summary> public static async Task <(Stream, string)> ReplaceSenderAsync( IDictionary <string, IEnumerable <string> > headers, Stream bodyStream, string sender, CancellationToken token) { bodyStream.Seek(0, SeekOrigin.Begin); if (!headers.TryGetValue("References", out IEnumerable <string> referenes)) { return(bodyStream, sender); } foreach (var r in referenes) { Match match = ReferenceOriginalSender.Match(r); if (match.Success) { var senderParts = sender.Split('@', 2); if (senderParts.Length != 2) { // Not a useful email address... continue; } var headerSender = match.Groups[1].Value; var headerParts = headerSender.Split('@', 2); if (headerParts.Length != 2) { // Not a useful email address... continue; } if (senderParts[1] != headerParts[1]) { // Wrong domain... continue; } if (!headerParts[0].StartsWith(senderParts[0] + "-")) { // Doesn't start with correct extension root continue; } MemoryStream replacedStream = null; try { replacedStream = new MemoryStream(); MimeReader reader = new MimeReader(); var structure = await reader.ReadStructureAsync(bodyStream, token); // Copy the preamble bodyStream.Seek(0, SeekOrigin.Begin); BoundedStream pre = new BoundedStream(bodyStream, structure.HeaderSpan.Start); await pre.CopyToAsync(replacedStream, token); // Replace stuff in headers bodyStream.Seek(structure.HeaderSpan.Start, SeekOrigin.Begin); var headerBytes = await bodyStream.ReadExactlyAsync((int)structure.HeaderSpan.Length, token); string header = Encoding.ASCII.GetString(headerBytes); header = ReplaceFrom.Replace(header, $"$1<{headerSender}>"); header = ReplaceReferences.Replace(header, m => { if (String.IsNullOrWhiteSpace(m.Groups["pre"].Value) && String.IsNullOrWhiteSpace(m.Groups["post"].Value)) { // We were the ONLY references header, just remove the whole thing return(""); } string space = ""; if (!String.IsNullOrEmpty(m.Groups["preSpace"].Value) && !String.IsNullOrEmpty(m.Groups["postSpace"].Value)) { space = m.Groups["preSpace"].Value; } return($"References:{m.Groups["pre"].Value}{space}{m.Groups["post"].Value}"); }); using (StreamWriter writer = new StreamWriter(replacedStream, Encoding.ASCII, 1024, true)) { writer.Write(header); } // Copy message content bodyStream.Seek(structure.HeaderSpan.End, SeekOrigin.Begin); await bodyStream.CopyToAsync(replacedStream, token); bodyStream.Dispose(); replacedStream.Seek(0, SeekOrigin.Begin); return(replacedStream, headerSender); } catch { replacedStream?.Dispose(); throw; } } } return(bodyStream, sender); }