// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; // COM "Microsoft CDO For Windows 2000 Library" add refs ADODB, CDO // have to load all messgae just header posible? copy out with line reader if file over certian size!!!!!! using System.IO; using System.Data; namespace Spludlow.Email { /// /// Read SMTP mail files using ye olde CDO /// public class CDOSYS { private static DateTime CDOBadDate = new DateTime(1899, 12, 30); public static CDO.Message ReadMessage(string emailFilename) { CDO.Message message = new CDO.Message(); ADODB.Stream stream = new ADODB.Stream(); stream.Charset = "us-ascii"; // Otherwise ADO sometimes inserts an unwanted 2 byte BOM, which will corrupt the first mail header line stream.Open(Type.Missing, ADODB.ConnectModeEnum.adModeUnknown, ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, String.Empty, String.Empty); try { stream.LoadFromFile(emailFilename); stream.Flush(); message.DataSource.OpenObject(stream, "_Stream"); message.DataSource.Save(); } finally { stream.Close(); } return message; } public static void ReadSimpleMailMessage(Spludlow.Email.SimpleMailMessage simpleMessage, string filename, string attachmentsDirectory) { CDO.Message message = ReadMessage(filename); simpleMessage.XSender = ""; simpleMessage.XReceiver = ""; simpleMessage.Subject = message.Subject; simpleMessage.HTMLBody = message.HTMLBody; simpleMessage.TextBody = message.TextBody; simpleMessage.BCC = message.BCC; simpleMessage.CC = message.CC; simpleMessage.To = message.To; // MATCH:to (sometimes missing) simpleMessage.From = message.From; // MATCH:from simpleMessage.ReplyTo = message.ReplyTo; // MATCH:reply-to (optional) simpleMessage.ReturnPath = ""; simpleMessage.ReceivedTime = message.ReceivedTime; // must come from "received" not in NDR simpleMessage.SentOn = message.SentOn; // MATCH:date for (int index = 0; index < message.BodyPart.Fields.Count; ++index) { ADODB.Field field = message.BodyPart.Fields[index]; if (field.Value == null) continue; switch (field.Name) { case "urn:schemas:mailheader:x-sender": simpleMessage.XSender = field.Value; if (simpleMessage.From == "") simpleMessage.From = field.Value; break; case "urn:schemas:mailheader:x-receiver": simpleMessage.XReceiver = field.Value; if (simpleMessage.To == "") simpleMessage.To = field.Value; break; case "urn:schemas:mailheader:return-path": if (simpleMessage.ReturnPath == "") simpleMessage.ReturnPath = field.Value; break; } } if (simpleMessage.ReplyTo == "") simpleMessage.ReplyTo = simpleMessage.From; if (message.ReceivedTime == CDOBadDate) { if (message.SentOn == CDOBadDate) simpleMessage.ReceivedTime = File.GetCreationTime(filename); else simpleMessage.ReceivedTime = message.SentOn.AddMinutes(1); } if (message.SentOn == CDOBadDate) simpleMessage.SentOn = message.ReceivedTime.AddMinutes(-1); simpleMessage.BCCAddress = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.BCC); simpleMessage.CCAddress = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.CC); simpleMessage.ToAddress = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.To); simpleMessage.FromAddress = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.From); simpleMessage.ReplyToAddress = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.ReplyTo); simpleMessage.ReturnPaths = Spludlow.Net.Smtp.ParseMimeEmailAddresses(simpleMessage.ReturnPath); List attachments = new List(); if (attachmentsDirectory != null) { // ContentMediaType text/plain, application/pdf, image/png, image/jpeg // ContentTransferEncoding base64, quoted-printable // Attachments [0] = Store Filename, [1] Filename, [2] = MIME simpleMessage.AttachmentsTable = SaveAttachments(filename, attachmentsDirectory).Tables[0]; foreach (DataRow row in simpleMessage.AttachmentsTable.Rows) { string path; if (row.IsNull("Path") == true) continue; path = (string)row["Path"]; string name; if (row.IsNull("Filename") == true) { File.Delete(path); continue; } name = (string)row["Filename"]; if (name == "") // sort out continue; string contentMediaType = (string)row["ContentMediaType"]; attachments.Add(new string[] { path, name, contentMediaType }); } } simpleMessage.Attachments = attachments.ToArray(); } public static DataSet SaveAttachments(string filename, string outputDirectory) { return SaveAttachments(filename, outputDirectory, null); } public static DataSet SaveAttachments(string filename, string outputDirectory, string prefix) { if (prefix == null) prefix = ""; DataTable table = Spludlow.Data.TextTable.ReadText(new string[] { "Path Size Depth Charset ContentClass ContentMediaType ContentTransferEncoding FileName BodyPartsCount", "String Int32 Int32 String String String String String Int32", }); CDO.Message message = ReadMessage(filename); ProcessBodyPart(message.BodyPart, 0, outputDirectory, table, prefix); DataSet dataSet = new DataSet(); dataSet.Tables.Add(table); return dataSet; } private static void ProcessBodyPart(CDO.IBodyPart bodyPart, int depth, string targetDirectory, DataTable table, string prefix) { string filename = "untitled.dat"; if (bodyPart.FileName != null && bodyPart.FileName.Trim().Length > 0) { filename = bodyPart.FileName; } else { if (bodyPart.ContentMediaType == "text/plain") filename = "_EmailBody.txt"; if (bodyPart.ContentMediaType == "text/html") filename = "_EmailBody.htm"; } filename = Spludlow.Io.Paths.LegalFileName(prefix + filename); string pathName = null; long size = 0; if (targetDirectory != null) { if (bodyPart.ContentMediaType == "multipart/appledouble") { pathName = ProcessAppledouble(bodyPart, targetDirectory, prefix); } else { if (bodyPart.BodyParts.Count == 0) { string extention = ""; if (bodyPart.ContentMediaType == "application/applefile") extention = ".applefile"; pathName = Spludlow.Io.Files.UniqueExistingName(targetDirectory + "\\" + filename + extention); bodyPart.SaveToFile(pathName); } } if (pathName != null) size = Spludlow.Io.Files.FileLength(pathName); } filename = bodyPart.FileName; string cid = ContentId(bodyPart); if (cid != null) filename = cid; // bodyPart.FileName -> filename table.Rows.Add(pathName, size, depth, bodyPart.Charset, bodyPart.ContentClass, bodyPart.ContentMediaType, bodyPart.ContentTransferEncoding, filename, bodyPart.BodyParts.Count); foreach (CDO.IBodyPart childBodyPart in bodyPart.BodyParts) ProcessBodyPart(childBodyPart, depth + 1, targetDirectory, table, prefix); } private static string ContentId(CDO.IBodyPart bodyPart) { string fieldName = "urn:schemas:mailheader:content-id"; // , string cid = bodyPart.Fields[fieldName].Value; if (cid == null) return null; return "cid:" + cid.Trim(new char[] { '<', '>' }); } private static string ProcessAppledouble(CDO.IBodyPart bodyPart, string targetDirectory, string prefix) { if (bodyPart.BodyParts.Count != 2) throw new ApplicationException("appledouble does not have 2 parts:\t" + bodyPart.BodyParts.Count); CDO.IBodyPart applefilePart = null; CDO.IBodyPart dataPart = null; foreach (CDO.IBodyPart childBodyPart in bodyPart.BodyParts) { if (childBodyPart.ContentMediaType == "application/applefile") applefilePart = childBodyPart; else dataPart = childBodyPart; } if (applefilePart == null || dataPart == null) throw new ApplicationException("did not find both parts of appledouble."); string filename = dataPart.FileName; if (filename == null || filename.Length == 0) throw new ApplicationException("No filename found in appledouble data."); filename = Spludlow.Io.Paths.LegalFileName(prefix + filename); filename = Spludlow.Io.Files.UniqueExistingName(targetDirectory + @"\" + filename + ".bin"); using (Spludlow.TempDirectory tempDir = new TempDirectory()) { string appleFilename = tempDir.Path + @"\applefile"; applefilePart.SaveToFile(appleFilename); string dataFilename = tempDir.Path + @"\data"; dataPart.SaveToFile(dataFilename); MakeMacBinary(appleFilename, dataFilename, filename); } return filename; } private static void MakeMacBinary(string appleFilename, string dataFilename, string macBinaryFilename) { Spludlow.Email.AppleDouble appleDouble = new AppleDouble(); appleDouble.Load(appleFilename); if (appleDouble.DataFork.Length != 0) throw new ApplicationException("appleFile contains a data fork."); appleDouble.DataFork = File.ReadAllBytes(dataFilename); Spludlow.Email.MacBinary macBinary = new MacBinary(); macBinary.Save(macBinaryFilename, appleDouble); } } }