Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multipart content sent as base64 with ContentEncoding set to Default #721

Closed
mortenn opened this issue Oct 27, 2021 · 11 comments
Closed

Multipart content sent as base64 with ContentEncoding set to Default #721

mortenn opened this issue Oct 27, 2021 · 11 comments

Comments

@mortenn
Copy link

mortenn commented Oct 27, 2021

Describe the bug
I upgraded MimeKit from 2.1.3 to 2.15.1 in a solution implementing RosettaNet, after this receipts were no longer coming from the partner receiving the messages.
It turns out that they had started receiving base64 rather than xml, which was not supported.

The parts of the message are defined like this;

var mimePart = new MimePart("application/XML")
{
	ContentLocation = new Uri($"RN-{part.Location}", UriKind.Relative),
	ContentTransferEncoding = ContentEncoding.Default
};
mimePart.Headers.Add(HeaderId.ContentDescription, $"RosettaNet-{part.Location}");
mimePart.ContentId = $"<{idDomain}.{part.Location.ToLower()}.{idUnique}>";
mimePart.Headers.Remove("Content-Transfer-Encoding");

var stream = new MemoryStream(part.Xml);
mimePart.Content = new MimeContent(stream);

According to ContentEncoding, the Default value should not encode at all, so base64 is unexpected.

Platform (please complete the following information):

  • OS: Windows Server
  • .NET Runtime: Microsoft
  • .NET Framework: .NET 4.8
  • MimeKit Version: 2.15.1

Expected behavior
Mime parts to be delivered unencoded

@jstedfast
Copy link
Owner

Default just means "no encoding specified".

When you call MimeMessage.Prepare() (or when MailKits SmtpClient calls it for you) it will check those unencoded MimeParts to make sure they won't break SMTP rules and force an encoding if they do (think: long lines > 998 characters, nil characters, 8bit characters, etc)

Also worth noting is that some SMTP servers will reencode parts so the receiving client really needs to be fixed to handle that.

@jstedfast jstedfast added the wontfix This will not be worked on label Oct 28, 2021
@mortenn
Copy link
Author

mortenn commented Oct 29, 2021

I see.
In this case, SMTP is not being used, RosettaNet is using MIME encoding over HTTP.
Can I force it to not use base64 by giving it some value other than Default so we can go back to 2.15.1?

@jstedfast
Copy link
Owner

You can set the ContentTransferEncoding to ContentEncoding.SevenBit or EightBit (as appropriate).

Setting EightBit is safe to use even when the content is ascii.

@mortenn
Copy link
Author

mortenn commented Nov 8, 2021

Unfortunately, that did not appear to fix the issue at hand, so I am forced to stay with the older version.

@jstedfast
Copy link
Owner

I took a closer look and SmtpClient calls SmtpClient.Prepare() (which you can override). The default implementation calls MimeMessage.Prepare().

MimeMessage.Prepare() iterates over all body parts calling Prepare() on them.

MimePart.Prepare() no-ops only if the ContentTransferEncoding property is set to Base64, QuotedPrintable, or UUEncode. It will also no-op if set to Binary, but only if the EncodingConstraint is None.

In all other cases, it calculates the best encoding to use for the provided EncodingConstraint.

TL;DR you can override SmtpClient.Prepare() to make it no-op and that should resolve your issue.

@mortenn
Copy link
Author

mortenn commented Nov 8, 2021

But the code isn't using SmtpClient

@jstedfast
Copy link
Owner

Oh, are you calling MimeMessage.Prepare()?

@mortenn
Copy link
Author

mortenn commented Nov 10, 2021

The code creates a MultipartRelated instance, puts a few MimePart objects into it, each with some xml content.
Next, it uses MultipartSigned.Create to sign the MultipartRelated object using a certificate.
Then it wraps that MimeEntity in a new MultipartRelated object with RosettaNet headers.
Finally, it calls WriteToAsync on that final MultipartRelated object, copying the content only to a memorystream with EnsureNewLine set to true.
The memorystream is then put into a HttpRequestMessage with type Post before being sent using HttpClient.

@jstedfast
Copy link
Owner

Thanks for the details.

Signing a MimeEntity forces MimeKit to enforce the same encoding rules that Prepare() enforces because the assumption is that the message or entity will be transmitted over SMTP.

Sounds like this assumption, at least in your case, is not the case.

@jstedfast jstedfast reopened this Nov 10, 2021
@jstedfast
Copy link
Owner

I'm reopening because maybe I can figure out a solution that will allow overriding that behavior such that it works for your needs as well as typical mail (i.e. SMTP) needs.

One possibility is to have a MultipartSigned.Create() method that takes a SignOptions argument that contains a bool CanonicalizeEntity property or something.

@jstedfast jstedfast removed the wontfix This will not be worked on label Nov 14, 2021
@jstedfast
Copy link
Owner

MimeKit v3.0.0 has been released with this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants