renamed

alphons <alphons@heijden.com> 23 Feb 2025, 16:05
b7a6435b84ca0f8847cbf34e355f436e4d281e32
9 files changed
  • Eml2Pdf.sln
  • Eml2Pdf/DataModel.cs
  • Eml2Pdf/EmailDecoder.cs
  • Eml2Pdf/Eml2Pdf.csproj
  • Eml2Pdf/MimePart2Pdf.csproj
  • Eml2Pdf/PdfHelper.cs
  • Eml2Pdf/PdfHelper2.cs
  • Eml2PdfWinApp/Eml2PdfWinApp.csproj
  • Eml2PdfWinApp/Form1.cs
diff --git a/Eml2Pdf.sln b/Eml2Pdf.sln
index 1af26b7..f92487d 100644
--- a/Eml2Pdf.sln
+++ b/Eml2Pdf.sln
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35728.132
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eml2Pdf", "Eml2Pdf\Eml2Pdf.csproj", "{D93A802A-2709-4781-B536-700ABCA34A1D}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MimePart2Pdf", "Eml2Pdf\MimePart2Pdf.csproj", "{D93A802A-2709-4781-B536-700ABCA34A1D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eml2PdfWinApp", "Eml2PdfWinApp\Eml2PdfWinApp.csproj", "{3B67B712-8408-4F5C-B40E-66F255A61EC7}"
EndProject
diff --git a/Eml2Pdf/DataModel.cs b/Eml2Pdf/DataModel.cs
deleted file mode 100644
index 1a22797..0000000
--- a/Eml2Pdf/DataModel.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace Eml2Pdf;
-
-public class EmailMessage
-{
- public List<(string Name, string Value)> Headers { get; set; } = [];
- public List<MimePart2> Parts { get; set; } = [];
- public List<Attachment> Attachments { get; set; } = [];
-
- // Optionele convenience properties voor veelgebruikte headers
- public string From => Headers.FirstOrDefault(h => h.Name.Equals("from", StringComparison.CurrentCultureIgnoreCase)).Value;
- public string To => Headers.FirstOrDefault(h => h.Name.Equals("to", StringComparison.CurrentCultureIgnoreCase)).Value;
- public string Subject => Headers.FirstOrDefault(h => h.Name.Equals("subject", StringComparison.CurrentCultureIgnoreCase)).Value;
- public DateTime? Date => DateTime.TryParse(Headers.FirstOrDefault(h => h.Name.Equals("date", StringComparison.CurrentCultureIgnoreCase)).Value, out DateTime date) ? date : (DateTime?)null;
- public string MessageId => Headers.FirstOrDefault(h => h.Name.Equals("message-id", StringComparison.CurrentCultureIgnoreCase)).Value;
-}
-
-public class MimePart2
-{
- public string ContentType { get; set; }
- public string Charset { get; set; }
- public string Content { get; set; }
- public string TransferEncoding { get; set; }
-}
-
-public class Attachment
-{
- public string FileName { get; set; }
- public string ContentType { get; set; }
- public string ContentId { get; set; }
- public byte[] Data { get; set; }
-}
-
diff --git a/Eml2Pdf/EmailDecoder.cs b/Eml2Pdf/EmailDecoder.cs
deleted file mode 100644
index eb83b47..0000000
--- a/Eml2Pdf/EmailDecoder.cs
+++ /dev/null
@@ -1,312 +0,0 @@
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace Eml2Pdf;
-
-public class EmailDecoder
-{
- public static async Task<EmailMessage> ParseEmlAsync(string emlPath)
- {
- var emlContent = await File.ReadAllTextAsync(emlPath);
- var email = new EmailMessage();
- var lines = emlContent.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
- bool inHeaders = true;
- string headerBuffer = "";
- int i = 0;
-
- while (i < lines.Length && inHeaders)
- {
- string line = lines[i];
- if (string.IsNullOrWhiteSpace(line) && !string.IsNullOrWhiteSpace(headerBuffer))
- {
- inHeaders = false;
- }
- else
- {
- if (line.StartsWith(" ") || line.StartsWith("\t"))
- {
- headerBuffer += " " + line.Trim();
- }
- else
- {
- ParseHeader(email, headerBuffer);
- headerBuffer = line;
- }
- }
- i++;
- }
-
- ParseHeader(email, headerBuffer);
-
- var headerContentTypeMatch = Regex.Match(emlContent, @"Content-Type: ([^\r\n]+(?:\r\n[ \t][^\r\n]+)*)", RegexOptions.Singleline);
- string contentType = null;
- string boundary = null;
- if (headerContentTypeMatch.Success)
- {
- string fullContentType = headerContentTypeMatch.Groups[1].Value;
- var (type, parameters) = ParseContentType(fullContentType);
- contentType = type;
- boundary = parameters.ContainsKey("boundary") ? "--" + parameters["boundary"] : null;
- }
-
- string remainingContent = string.Join("\r\n", lines, i, lines.Length - i);
-
- if (contentType != null && contentType.StartsWith("multipart/") && boundary != null)
- {
- var parts = SplitMimeParts(remainingContent, boundary);
- foreach (var subPart in parts)
- {
- ProcessMimePart(email, subPart, boundary);
- }
- }
- else
- {
- ProcessSinglePart(email, remainingContent, contentType ?? "text/plain", new Dictionary<string, string>(), null);
- }
-
- return email;
- }
-
- private static void ParseHeader(EmailMessage email, string header)
- {
- if (string.IsNullOrWhiteSpace(header)) return;
- var parts = header.Split(new[] { ": " }, 2, StringSplitOptions.None);
- if (parts.Length < 2)
- return;
-
- string name = parts[0];
- string value = DecodeMimeEncodedWord(parts[1]);
-
-
- if(name == "Date")
- {
- if (DateTime.TryParse(value, out DateTime dtm))
- value = dtm.ToLocalTime().ToString();
- }
- email.Headers.Add((name, value));
- }
-
-
- private static string DecodeMimeEncodedWord(string input)
- {
- if (string.IsNullOrEmpty(input)) return input;
-
- var regex = new Regex(@"\=\?([^?]+)\?([QB])\?([^?]+)\?\=", RegexOptions.IgnoreCase);
- return regex.Replace(input, match =>
- {
- string charset = match.Groups[1].Value;
- string encoding = match.Groups[2].Value.ToUpper();
- string encodedText = match.Groups[3].Value;
-
- try
- {
- Encoding enc = Encoding.GetEncoding(charset);
- if (encoding == "Q")
- {
- return DecodeQuotedPrintable(encodedText, enc);
- }
- else if (encoding == "B")
- {
- byte[] decodedBytes = Convert.FromBase64String(encodedText);
- return enc.GetString(decodedBytes);
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Failed to decode MIME encoded-word: {ex.Message}");
- }
- return encodedText; // Fallback: return as-is
- });
- }
-
- private static void ProcessMimePart(EmailMessage email, string part, string parentBoundary)
- {
- var contentTypeMatch = Regex.Match(part, @"Content-Type: ([^\r\n]+(?:\r\n[ \t][^\r\n]+)*)", RegexOptions.Singleline);
- if (!contentTypeMatch.Success)
- {
- ProcessSinglePart(email, part, "text/plain", new Dictionary<string, string>(), parentBoundary);
- return;
- }
-
- string fullContentType = contentTypeMatch.Groups[1].Value;
- var (contentType, parameters) = ParseContentType(fullContentType);
-
- string currentBoundary = parameters.ContainsKey("boundary") ? "--" + parameters["boundary"] : null;
-
- if (contentType.StartsWith("multipart/") && currentBoundary != null)
- {
- var parts = SplitMimeParts(part, currentBoundary);
- foreach (var subPart in parts)
- {
- ProcessMimePart(email, subPart, currentBoundary);
- }
- }
- else if (contentType.StartsWith("text/"))
- {
- ProcessSinglePart(email, part, contentType, parameters, parentBoundary);
- }
- else if (contentType.StartsWith("image/") || contentType.StartsWith("application/"))
- {
- ParseAttachment(email, part, contentType, parameters, parentBoundary);
- }
- }
-
- private static List<string> SplitMimeParts(string content, string boundary)
- {
- var parts = new List<string>();
- var lines = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
- string currentPart = "";
- bool inPart = false;
-
- for (int i = 0; i < lines.Length; i++)
- {
- if (lines[i].StartsWith(boundary))
- {
- if (inPart && !string.IsNullOrWhiteSpace(currentPart))
- {
- parts.Add(currentPart);
- }
- currentPart = "";
- inPart = true;
- if (lines[i].EndsWith("--")) continue;
- }
- if (inPart)
- {
- currentPart += lines[i] + "\r\n";
- }
- }
- if (inPart && !string.IsNullOrWhiteSpace(currentPart))
- {
- parts.Add(currentPart);
- }
-
- return parts;
- }
-
- private static void ProcessSinglePart(EmailMessage email, string part, string contentType, Dictionary<string, string> parameters, string parentBoundary = null)
- {
- var mimePart = new MimePart2
- {
- ContentType = contentType,
- Charset = parameters.ContainsKey("charset") ? parameters["charset"] : "utf-8",
- TransferEncoding = Regex.Match(part, @"Content-Transfer-Encoding: ([^\r\n]+)").Groups[1].Value.ToLower()
- };
-
- mimePart.Content = ExtractContent(part, mimePart.TransferEncoding, mimePart.Charset);
-
- email.Parts.Add(mimePart);
- }
-
- private static string ExtractContent(string part, string transferEncoding, string charset)
- {
- var contentMatch = Regex.Match(part, @"(?:\r\n\r\n|\n\n)([\s\S]+)$");
- if (!contentMatch.Success) return "";
-
- string content = contentMatch.Groups[1].Value.Trim();
- var enc = Encoding.GetEncoding(charset);
- if (transferEncoding == "quoted-printable")
- {
- return DecodeQuotedPrintable(content, enc);
- }
- else if (transferEncoding == "base64")
- {
- byte[] decodedBytes = Convert.FromBase64String(Regex.Replace(content, @"[\r\n]", ""));
- return Encoding.GetEncoding(charset).GetString(decodedBytes);
- }
- return content;
- }
-
- private static void ParseAttachment(EmailMessage email, string part, string contentType, Dictionary<string, string> parameters, string parentBoundary)
- {
- var attachment = new Attachment();
-
- if (parameters.ContainsKey("name"))
- attachment.FileName = parameters["name"];
-
- attachment.ContentType = contentType;
-
- var contentIdMatch = Regex.Match(part, @"Content-ID: <([^>]+)>");
- if (contentIdMatch.Success)
- attachment.ContentId = contentIdMatch.Groups[1].Value;
-
- string closingBoundary = parentBoundary + "--";
- string pattern = $@"Content-Transfer-Encoding: base64\s*([\s\S]+?)(?=\s*$)";
-
- var contentMatch = Regex.Match(part, pattern, RegexOptions.Singleline);
-
- if (contentMatch.Success)
- {
- string content = contentMatch.Groups[1].Value;
- string base64 = Regex.Replace(content, @"[\r\n]", "");
- try
- {
- attachment.Data = Convert.FromBase64String(base64);
- }
- catch (FormatException ex)
- {
- Console.WriteLine($"Base64 decoding mislukt voor {attachment.FileName}: {ex.Message}");
- }
- }
- else
- {
- Console.WriteLine($"Geen inhoud gevonden voor attachment {attachment.FileName}.");
- }
-
- email.Attachments.Add(attachment);
- }
-
- static string DecodeQuotedPrintable(string input, Encoding enc)
- {
- var bytes = new List<byte>();
- for (int i = 0; i < input.Length; i++)
- {
- if (input[i] == '=' && i + 2 < input.Length &&
- IsHexChar(input[i + 1]) && IsHexChar(input[i + 2]))
- {
- string hex = input.Substring(i + 1, 2);
- bytes.Add(Convert.ToByte(hex, 16));
- i += 2;
- }
- else if (input[i] == '=')
- {
- while (i + 1 < input.Length && (input[i + 1] == '\r' || input[i + 1] == '\n'))
- i++;
- }
- else
- {
- bytes.AddRange(enc.GetBytes(input[i].ToString())); // Unicode-ondersteuning
- }
- }
- return enc.GetString([.. bytes]);
- }
-
- static bool IsHexChar(char c) =>
- c is >= '0' and <= '9' or >= 'A' and <= 'F' or >= 'a' and <= 'f';
-
- private static (string contentType, Dictionary<string, string> parameters) ParseContentType(string fullContentType)
- {
- var parts = Regex.Split(fullContentType, @";(?=(?:[^""]*""[^""]*"")*[^""]*$)")
- .Select(p => p.Trim())
- .Where(p => !string.IsNullOrEmpty(p))
- .ToArray();
-
- if (parts.Length == 0)
- return ("text/plain", new Dictionary<string, string>());
-
- string contentType = parts[0].Trim();
- var parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- for (int i = 1; i < parts.Length; i++)
- {
- var paramMatch = Regex.Match(parts[i], @"([^=]+)=""?([^""]+)""?");
- if (paramMatch.Success)
- {
- string key = paramMatch.Groups[1].Value.Trim();
- string value = paramMatch.Groups[2].Value.Trim();
- parameters[key] = value;
- }
- }
-
- return (contentType, parameters);
- }
-}
diff --git a/Eml2Pdf/Eml2Pdf.csproj b/Eml2Pdf/MimePart2Pdf.csproj
similarity index 100%
rename from Eml2Pdf/Eml2Pdf.csproj
rename to Eml2Pdf/MimePart2Pdf.csproj
diff --git a/Eml2Pdf/PdfHelper.cs b/Eml2Pdf/PdfHelper.cs
index 60b5228..24442f3 100644
--- a/Eml2Pdf/PdfHelper.cs
+++ b/Eml2Pdf/PdfHelper.cs
@@ -1,80 +1,101 @@
-using PdfSharp.Pdf;
-using System.Net.Mail;
+using Eml2MimePart;
+using PdfSharp.Pdf;
using System.Text;
using TheArtOfDev.HtmlRenderer.PdfSharp;
-namespace Eml2Pdf;
+namespace MimePart2Pdf;
public class PdfHelper
{
private static string HtmlEscape(string text) => text.Replace("<", "&lt;").Replace(">", "&gt;");
- public static void CreatePdf(EmailMessage email, string pdfPath)
+
+ private static string GetHtml(MimePart email)
{
+ if (email["Content-Type"].Contains("text/html"))
+ return email.TextContent;
+ foreach (var part in email.Parts)
+ {
+ var html = GetHtml(part);
+ if (!string.IsNullOrEmpty(html))
+ return html;
+ }
+ return string.Empty;
+ }
- var htmlContent = new StringBuilder();
- htmlContent.AppendLine("<html><head><meta charset='UTF-8'></head><body style='font-family: Arial, sans-serif;'>");
+ public static void GetAttachements(List<MimePart> attachements, MimePart part)
+ {
+ var contentType = part["Content-Type"];
+ if (!string.IsNullOrWhiteSpace(contentType) && !contentType.StartsWith("text/") && !contentType.StartsWith("multipart/"))
+ attachements.Add(part);
- htmlContent.AppendLine("<table border='1' style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>");
- string[] importantHeaders = ["From", "Subject", "To", "Date"];
- foreach (var header in importantHeaders)
+ foreach (var subpart in part.Parts)
{
- htmlContent.AppendLine("<tr>");
- htmlContent.AppendLine($"<td style='padding: 5px;'>{header}</td>");
- htmlContent.AppendLine($"<td style='padding: 5px;'>{HtmlEscape(email.Headers.FirstOrDefault(h => h.Name == header).Value)}</td>");
- htmlContent.AppendLine("</tr>");
+ GetAttachements(attachements, subpart);
}
- htmlContent.AppendLine("</table>");
+ }
- var hasHtmlPart = email.Parts.Any(p => p.ContentType.Contains("text/html"));
+ public static void CreatePdf(MimePart email, string pdfPath)
+ {
+ var html = GetHtml(email);
- foreach (var part in email.Parts)
- {
- if (hasHtmlPart && part.ContentType.Contains("text/plain"))
- continue;
+ if (string.IsNullOrWhiteSpace(html))
+ return;
- if (part.ContentType.Contains("text/html"))
- {
- //File.WriteAllText("debug.html", part.Content, Encoding.GetEncoding(part.Charset));
- htmlContent.AppendLine(part.Content);
- }
- else
+ int index = html.IndexOf("<body");
+ if (index != -1)
+ {
+ int eindIndex = html.IndexOf('>', index);
+ if (eindIndex != -1)
{
- htmlContent.AppendLine("<pre>");
- htmlContent.AppendLine(part.Content);
- htmlContent.AppendLine("</pre>");
+ var htmlTable = new StringBuilder(Environment.NewLine);
+ htmlTable.AppendLine("<table border='1' style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>");
+ string[] importantHeaders = ["From", "Subject", "To", "Date"];
+ foreach (var header in importantHeaders)
+ {
+ htmlTable.AppendLine("<tr>");
+ htmlTable.AppendLine($"<td style='padding: 5px; width:60px'>{header}</td>");
+ htmlTable.AppendLine($"<td style='padding: 5px;'>{HtmlEscape(email[header])}</td>");
+ htmlTable.AppendLine("</tr>");
+ }
+ htmlTable.Append("</table>");
+ html = html.Insert(eindIndex + 1, htmlTable.ToString());
}
}
- htmlContent.AppendLine("</body></html>");
-
var dir = Path.Combine(AppContext.BaseDirectory, "tmp");
- if (email.Attachments.Count > 0)
+ List<MimePart> attachements = [];
+ GetAttachements(attachements, email);
+
+ if (attachements.Count > 0)
{
Directory.CreateDirectory(dir);
// save attachements temporary
- foreach (var attachment in email.Attachments)
+ foreach (var attachment in attachements)
{
var path = Path.Combine(dir, attachment.FileName);
- File.WriteAllBytes(path, attachment.Data);
- htmlContent.Replace($"cid:{attachment.ContentId}", $"file:///{path.Replace('\\','/')}");
+
+ File.WriteAllBytes(path, attachment.BinaryContent);
+
+ html = html.Replace($"cid:{attachment.ContentId}", $"file:///{path.Replace('\\', '/')}");
}
- File.WriteAllText("test.html", htmlContent.ToString());
}
+ // DEBUGGING
+ File.WriteAllText("test.html", html);
- PdfDocument pdf = PdfGenerator.GeneratePdf(htmlContent.ToString(), PdfSharp.PageSize.A4);
+ PdfDocument pdf = PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4);
pdf.Save(pdfPath);
- if (email.Attachments.Count > 0)
+ if (attachements.Count > 0)
{
// clear attachements
- foreach (var attachment in email.Attachments)
+ foreach (var attachment in attachements)
{
var path = Path.Combine(dir, attachment.FileName);
- if(File.Exists(path))
+ if (File.Exists(path))
File.Delete(path);
}
}
diff --git a/Eml2Pdf/PdfHelper2.cs b/Eml2Pdf/PdfHelper2.cs
deleted file mode 100644
index 938e392..0000000
--- a/Eml2Pdf/PdfHelper2.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using Eml2MimePart;
-using PdfSharp.Pdf;
-using System.Text;
-using TheArtOfDev.HtmlRenderer.PdfSharp;
-
-namespace Eml2Pdf2;
-
-public class PdfHelper2
-{
- private static string HtmlEscape(string text) => text.Replace("<", "&lt;").Replace(">", "&gt;");
-
- private static string GetHtml(MimePart email)
- {
- if (email["Content-Type"].Contains("text/html"))
- return email.TextContent;
- foreach(var part in email.Parts)
- {
- var html = GetHtml(part);
- if (!string.IsNullOrEmpty(html))
- return html;
- }
- return string.Empty;
- }
-
- public static void GetAttachements(List<MimePart> attachements, MimePart part)
- {
- var contentType = part["Content-Type"];
- if (!string.IsNullOrWhiteSpace(contentType) && !contentType.StartsWith("text/") && !contentType.StartsWith("multipart/"))
- attachements.Add(part);
-
- foreach (var subpart in part.Parts)
- {
- GetAttachements(attachements, subpart);
- }
- }
-
- public static void CreatePdf(MimePart email, string pdfPath)
- {
- var html = GetHtml(email);
-
- if (string.IsNullOrWhiteSpace(html))
- return;
-
- int index = html.IndexOf("<body");
- if (index != -1)
- {
- int eindIndex = html.IndexOf('>', index);
- if (eindIndex != -1)
- {
- var htmlTable = new StringBuilder(Environment.NewLine);
- htmlTable.AppendLine("<table border='1' style='width: 100%; border-collapse: collapse; margin-bottom: 20px;'>");
- string[] importantHeaders = ["From", "Subject", "To", "Date"];
- foreach (var header in importantHeaders)
- {
- htmlTable.AppendLine("<tr>");
- htmlTable.AppendLine($"<td style='padding: 5px; width:60px'>{header}</td>");
- htmlTable.AppendLine($"<td style='padding: 5px;'>{HtmlEscape(email[header])}</td>");
- htmlTable.AppendLine("</tr>");
- }
- htmlTable.Append("</table>");
- html = html.Insert(eindIndex + 1, htmlTable.ToString());
- }
- }
-
- var dir = Path.Combine(AppContext.BaseDirectory, "tmp");
-
- List<MimePart> attachements = [];
- GetAttachements(attachements, email);
-
- if (attachements.Count > 0)
- {
- Directory.CreateDirectory(dir);
-
- // save attachements temporary
- foreach (var attachment in attachements)
- {
- var path = Path.Combine(dir, attachment.FileName);
-
- File.WriteAllBytes(path, attachment.BinaryContent);
-
- html = html.Replace($"cid:{attachment.ContentId}", $"file:///{path.Replace('\\', '/')}");
- }
- }
-
- // DEBUGGING
- File.WriteAllText("test.html", html);
-
- PdfDocument pdf = PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4);
-
- pdf.Save(pdfPath);
-
- if (attachements.Count > 0)
- {
- // clear attachements
- foreach (var attachment in attachements)
- {
- var path = Path.Combine(dir, attachment.FileName);
- if (File.Exists(path))
- File.Delete(path);
- }
- }
-
- }
-
-
-}
diff --git a/Eml2PdfWinApp/Eml2PdfWinApp.csproj b/Eml2PdfWinApp/Eml2PdfWinApp.csproj
index fac6fbe..7a5c14f 100644
--- a/Eml2PdfWinApp/Eml2PdfWinApp.csproj
+++ b/Eml2PdfWinApp/Eml2PdfWinApp.csproj
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
- <ProjectReference Include="..\Eml2Pdf\Eml2Pdf.csproj" />
+ <ProjectReference Include="..\Eml2Pdf\MimePart2Pdf.csproj" />
</ItemGroup>
<ItemGroup>
diff --git a/Eml2PdfWinApp/Form1.cs b/Eml2PdfWinApp/Form1.cs
index f5f5267..c8bf357 100644
--- a/Eml2PdfWinApp/Form1.cs
+++ b/Eml2PdfWinApp/Form1.cs
@@ -1,4 +1,5 @@
using Eml2MimePart;
+using MimePart2Pdf;
using System.Diagnostics;
using System.Text;
@@ -27,7 +28,7 @@ namespace Eml2PdfWinApp
var email = await MimePart.ReadEmlAsync(emlPath);
- Eml2Pdf2.PdfHelper2.CreatePdf(email, pdfPath);
+ PdfHelper.CreatePdf(email, pdfPath);
}