From 8324513fd6a996a48dbcc5f180f2d7cb4dea2d17 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Sat, 4 Nov 2023 11:05:07 -0400 Subject: [PATCH] Added virtual Multipart.TryGetValue(TextFormat, out TextPart) This cleans up the old logic internal to MimeMessage for getting the message body in a specified format to the Multipart subclasses. Should also fix issue #963 --- MimeKit/MimeMessage.cs | 60 +++------------------------------ MimeKit/Multipart.cs | 46 +++++++++++++++++++++++++ MimeKit/MultipartAlternative.cs | 51 ++++++++++++++++------------ MimeKit/MultipartRelated.cs | 34 +++++++++++++++++++ 4 files changed, 114 insertions(+), 77 deletions(-) diff --git a/MimeKit/MimeMessage.cs b/MimeKit/MimeMessage.cs index ed42145fde..3b1ff630c7 100644 --- a/MimeKit/MimeMessage.cs +++ b/MimeKit/MimeMessage.cs @@ -1023,57 +1023,6 @@ public MimeEntity Body { get; set; } - static bool TryGetMultipartBody (Multipart multipart, TextFormat format, out string body) - { - if (multipart is MultipartAlternative alternative) { - body = alternative.GetTextBody (format); - return body != null; - } - - if (multipart is MultipartRelated related) { - // Note: If the multipart/related root document is HTML, then this is the droid we are looking for. - var root = related.Root; - - if (root is TextPart text) { - body = text.IsFormat (format) ? text.Text : null; - return body != null; - } - - // maybe the root is another multipart (like multipart/alternative)? - if (root is Multipart multi) - return TryGetMultipartBody (multi, format, out body); - } else { - // Note: This is probably a multipart/mixed... and if not, we can still treat it like it is. - for (int i = 0; i < multipart.Count; i++) { - // descend into nested multiparts, if there are any... - if (multipart[i] is Multipart multi) { - if (TryGetMultipartBody (multi, format, out body)) - return true; - - // The text body should never come after a multipart. - break; - } - - // Look for the first non-attachment text part (realistically, the body text will - // precede any attachments, but I'm not sure we can rely on that assumption). - if (multipart[i] is TextPart text && !text.IsAttachment) { - if (text.IsFormat (format)) { - body = MultipartAlternative.GetText (text); - return true; - } - - // Note: the first text/* part in a multipart/mixed is the text body. - // If it's not in the format we're looking for, then it doesn't exist. - break; - } - } - } - - body = null; - - return false; - } - /// /// Get the text body of the message if it exists. /// @@ -1108,11 +1057,10 @@ public string HtmlBody { public string GetTextBody (TextFormat format) { if (Body is Multipart multipart) { - if (TryGetMultipartBody (multipart, format, out var text)) - return text; - } else { - if (Body is TextPart body && body.IsFormat (format) && !body.IsAttachment) - return body.Text; + if (multipart.TryGetValue (format, out var body)) + return MultipartAlternative.GetText (body); + } else if (Body is TextPart text && text.IsFormat (format) && !text.IsAttachment) { + return MultipartAlternative.GetText (text); } return null; diff --git a/MimeKit/Multipart.cs b/MimeKit/Multipart.cs index f90935e700..7bd0fc9019 100644 --- a/MimeKit/Multipart.cs +++ b/MimeKit/Multipart.cs @@ -34,6 +34,7 @@ using System.Security.Cryptography; using MimeKit.IO; +using MimeKit.Text; using MimeKit.Utils; using MimeKit.Encodings; @@ -343,6 +344,51 @@ public override void Accept (MimeVisitor visitor) visitor.VisitMultipart (this); } + /// + /// Get the preferred message body if it exists. + /// + /// + /// Gets the preferred message body if it exists. + /// + /// The preferred text format. + /// The MIME part containing the message body in the preferred text format. + /// true if the body part is found; otherwise, false. + /// + /// The has been disposed. + /// + public virtual bool TryGetValue (TextFormat format, out TextPart body) + { + CheckDisposed (); + + for (int i = 0; i < Count; i++) { + // Descend into nested multiparts if there are any... + if (this[i] is Multipart multipart) { + if (multipart.TryGetValue (format, out body)) + return true; + + // The text body should never come after a multipart. + break; + } + + // Look for the first non-attachment text part (realistically, the body text will + // precede any attachments, but I'm not sure we can rely on that assumption). + if (this[i] is TextPart text && !text.IsAttachment) { + if (text.IsFormat (format)) { + body = text; + return true; + } + + // Note: the first text/* part in a multipart/mixed is the text body. + // If it's not in the format we're looking for, then it doesn't exist. + break; + } + } + + body = null; + + return false; + } + internal static string FoldPreambleOrEpilogue (FormatOptions options, string text, bool isEpilogue) { var builder = new ValueStringBuilder (256); diff --git a/MimeKit/MultipartAlternative.cs b/MimeKit/MultipartAlternative.cs index e8f7a69c73..744dacb954 100644 --- a/MimeKit/MultipartAlternative.cs +++ b/MimeKit/MultipartAlternative.cs @@ -170,34 +170,43 @@ internal static string GetText (TextPart text) /// public string GetTextBody (TextFormat format) { - CheckDisposed (); + if (TryGetValue (format, out var body)) + return GetText (body); - // walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful - for (int i = Count - 1; i >= 0; i--) { - if (this[i] is MultipartAlternative alternative) { - // Note: nested multipart/alternative parts make no sense... yet here we are. - return alternative.GetTextBody (format); - } - - TextPart text; + return null; + } - if (this[i] is MultipartRelated related) { - var root = related.Root; + /// + /// Get the preferred message body if it exists. + /// + /// + /// Gets the preferred message body if it exists. + /// + /// The preferred text format. + /// The MIME part containing the message body in the preferred text format. + /// true if the body part is found; otherwise, false. + /// + /// The has been disposed. + /// + public override bool TryGetValue (TextFormat format, out TextPart body) + { + CheckDisposed (); - alternative = root as MultipartAlternative; - if (alternative != null) - return alternative.GetTextBody (format); + // Walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful. + for (int i = Count - 1; i >= 0; i--) { + // Descend into child multiparts. + if (this[i] is Multipart multipart && multipart.TryGetValue (format, out body)) + return true; - text = root as TextPart; - } else { - text = this[i] as TextPart; + if (this[i] is TextPart text && text.IsFormat (format)) { + body = text; + return true; } - - if (text != null && text.IsFormat (format)) - return GetText (text); } - return null; + body = null; + + return false; } } } diff --git a/MimeKit/MultipartRelated.cs b/MimeKit/MultipartRelated.cs index e099dd471e..8584a17a41 100644 --- a/MimeKit/MultipartRelated.cs +++ b/MimeKit/MultipartRelated.cs @@ -28,6 +28,7 @@ using System.IO; using System.Linq; +using MimeKit.Text; using MimeKit.Utils; namespace MimeKit { @@ -214,6 +215,39 @@ public override void Accept (MimeVisitor visitor) visitor.VisitMultipartRelated (this); } + /// + /// Get the preferred message body if it exists. + /// + /// + /// Gets the preferred message body if it exists. + /// + /// The preferred text format. + /// The MIME part containing the message body in the preferred text format. + /// true if the body part is found; otherwise, false. + /// + /// The has been disposed. + /// + public override bool TryGetValue (TextFormat format, out TextPart body) + { + CheckDisposed (); + + // Note: If the multipart/related root document is HTML, then this is the droid we are looking for. + var root = Root; + + if (root is TextPart text) { + body = text.IsFormat (format) ? text : null; + return body != null; + } + + // The root may be a multipart such as a multipart/alternative. + if (root is Multipart multipart) + return multipart.TryGetValue (format, out body); + + body = null; + + return false; + } + /// /// Check if the contains a part matching the specified URI. ///