// 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);
}
}
}