Skip to content

Commit

Permalink
Added virtual Multipart.TryGetValue(TextFormat, out TextPart)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jstedfast committed Nov 4, 2023
1 parent 79046cd commit 8324513
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 77 deletions.
60 changes: 4 additions & 56 deletions MimeKit/MimeMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
/// Get the text body of the message if it exists.
/// </summary>
Expand Down Expand Up @@ -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;
Expand Down
46 changes: 46 additions & 0 deletions MimeKit/Multipart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using System.Security.Cryptography;

using MimeKit.IO;
using MimeKit.Text;
using MimeKit.Utils;
using MimeKit.Encodings;

Expand Down Expand Up @@ -343,6 +344,51 @@ public override void Accept (MimeVisitor visitor)
visitor.VisitMultipart (this);
}

/// <summary>
/// Get the preferred message body if it exists.
/// </summary>
/// <remarks>
/// Gets the preferred message body if it exists.
/// </remarks>
/// <param name="format">The preferred text format.</param>
/// <param name="body">The MIME part containing the message body in the preferred text format.</param>
/// <returns><c>true</c> if the body part is found; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Multipart"/> has been disposed.
/// </exception>
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);
Expand Down
51 changes: 30 additions & 21 deletions MimeKit/MultipartAlternative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,34 +170,43 @@ internal static string GetText (TextPart text)
/// </exception>
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;
/// <summary>
/// Get the preferred message body if it exists.
/// </summary>
/// <remarks>
/// Gets the preferred message body if it exists.
/// </remarks>
/// <param name="format">The preferred text format.</param>
/// <param name="body">The MIME part containing the message body in the preferred text format.</param>
/// <returns><c>true</c> if the body part is found; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Multipart"/> has been disposed.
/// </exception>
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;
}
}
}
34 changes: 34 additions & 0 deletions MimeKit/MultipartRelated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using System.IO;
using System.Linq;

using MimeKit.Text;
using MimeKit.Utils;

namespace MimeKit {
Expand Down Expand Up @@ -214,6 +215,39 @@ public override void Accept (MimeVisitor visitor)
visitor.VisitMultipartRelated (this);
}

/// <summary>
/// Get the preferred message body if it exists.
/// </summary>
/// <remarks>
/// Gets the preferred message body if it exists.
/// </remarks>
/// <param name="format">The preferred text format.</param>
/// <param name="body">The MIME part containing the message body in the preferred text format.</param>
/// <returns><c>true</c> if the body part is found; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Multipart"/> has been disposed.
/// </exception>
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;
}

/// <summary>
/// Check if the <see cref="MultipartRelated"/> contains a part matching the specified URI.
/// </summary>
Expand Down

0 comments on commit 8324513

Please sign in to comment.