pmengal / MailSystem.NET

Great email library for C#.
GNU Lesser General Public License v3.0
270 stars 136 forks source link

Difference between MailSystem.NET and other IMAP libs? #6

Closed jgboggs closed 8 years ago

jgboggs commented 8 years ago

Hi,

I was wondering what the difference between these projects were (aenetmail, MailKit, S22.Imap, MailSystem.NET, etc) and whether maybe it would be a good idea for all of these projects to work together instead of reinventing the wheel over and over again?

I'm trying to make a decision on which one to use. At first glance, it would appear that MailKit might be the way to go (it has 1012 stars) while aenetmail has 269, S22.Imap has 117 and MailSystem.NET has 17, but I've learned the hard way that more popularity doesn't mean "best".

I plan to post this same question on each of these projects with the hope of getting thoughts of the both the developers and the users (have users used the other libraries? how did they compare? why did you choose the one you settled on?)

Any feedback would be hugely helpful in saving me a lot of time and effort!

reinaldocoelho commented 8 years ago

Hello jgboggs.

Being quite honest, are competing and complementary libraries at the same time, since all will have greater benefits at some point.

For example, I work with MailSystem.NET and the MailKit.

Both work with POP / IMAP / SMTP, but the parse system is different.

If, for my use, the MailSystem.NET does a better parse in case of some error treatments. But MailKit library works with emails that have a type TNFE format(http://www.dwheeler.com/essays/microsoft-outlook-tnef.html), eg not meeting the MailSystem.NET the current version.

Another difference is the simplicity of use, particularly I think MailSystem.NET library is easier to use than MailKit.

But you will have to make a choice and neither of them is perfect, all have advantages and disadvantages.

If you ask me a suggestion, I obviously suggest MailSystem.NET and put ourselves available to assist in case of any problem.

I hope this information has been helpful.

reinaldocoelho commented 8 years ago

I will close the issue, but fell free for make new questions. :-)

jgboggs commented 8 years ago

Thanks for getting back to me!

Don't feel bad for suggesting your own library as being your ideal choice. If it wasn't, you wouldn't be working on it! :-)

Besides, MailKit author felt his was the best too :-)

I actually started working with MailKit today at work to try and evaluate it since the aenetmail author suggested I use it and so did the MailKit author (obviously).

I'm not sure if we'll actually need TNEF support or not, but it will probably be a nice checkmark to have if we discover we need it (how common is it, do you know?)

You mentioned that MailKit didn't parse some messages correctly? Could you explain that more? Obviously that's something that's pretty important to get right.

I'll try to play with MailSystem.NET a bit tomorrow to try and compare ease of use. I haven't felt like MailKit has been that difficult to use, but I don't (yet!) have anything to compare it to and I'm also fairly early in my investigating stages so haven't delved too deep.

Thanks!

reinaldocoelho commented 8 years ago

A while ago, I made a test to try to load the MailKit library some unit tests we have in MailSystem, and some gave error.

The test was very basic, but I noticed some differences in the solution of the two libraries as said before.

If you want the EMLs I used in the test, just check the unit test cases we have: https://github.com/pmengal/MailSystem.NET/tree/master/Class%20Library/ActiveUp.Net.Tests/resource

Any questions, just let me know :-)

jgboggs commented 8 years ago

I tried to verify this but I gave up trying because I am way too newb at both libraries :-)

jstedfast commented 8 years ago

Well, I ported the ParserTests (assuming the "bugs" were there). I found some bugs in MailSystem.NET, but no bugs in MimeKit.

using System;
using System.IO;
using System.Linq;
using System.Text;

using NUnit.Framework;

using MimeKit;
using MimeKit.Utils;

namespace UnitTests
{
    [TestFixture]
    public class ActiveUpParserTests
    {
        [Test]
        public void should_parse_simple_date ()
        {
            var buffer = Encoding.ASCII.GetBytes ("Mon, 24 Jun 2013 10:37:36 +0100");
            DateTimeOffset date;

            DateUtils.TryParse (buffer, out date);

            Assert.AreEqual (date, new DateTimeOffset (2013, 06, 24, 10, 37, 36, new TimeSpan (1, 0, 0)));
        }

        [Test]
        public void should_clean_input ()
        {
            var buffer = Encoding.ASCII.GetBytes ("(noise\\input)Mon, 24 Jun 2013 10:37:36 +0100");
            DateTimeOffset date;

            DateUtils.TryParse (buffer, out date);

            Assert.AreEqual (date, new DateTimeOffset (2013, 06, 24, 10, 37, 36, new TimeSpan (1, 0, 0)));
        }

        // Note: This MailSystem.NET unit test makes no sense for MimeKit because MimeKit uses DateTimeOffset
        // rather than DateTime.
        //[Test]
        //public void should_return_resulting_date_in_utc()
        //{
        //  var utcDate = Parser.ParseAsUniversalDateTime("Mon, 24 Jun 2013 10:37:36 +0100");
        //
        //  utcDate.Kind.ShouldEqual(DateTimeKind.Utc);
        //}

        [Test]
        public void should_parse_date_with_no_day_of_week ()
        {
            var buffer = Encoding.ASCII.GetBytes ("24 Jun 2013 10:37:36 +0100");
            DateTimeOffset date;

            DateUtils.TryParse (buffer, out date);

            Assert.AreEqual (date, new DateTimeOffset (2013, 06, 24, 10, 37, 36, new TimeSpan (1, 0, 0)));
        }

        [Test]
        public void should_parse_date_with_two_digits_year ()
        {
            var buffer = Encoding.ASCII.GetBytes ("Mon, 24 Jun 13 10:37:36 +0100");
            DateTimeOffset date;

            DateUtils.TryParse (buffer, out date);

            Assert.AreEqual (date, new DateTimeOffset (2013, 06, 24, 10, 37, 36, new TimeSpan (1, 0, 0)));
        }

        [Test]
        public void should_parse_date_with_no_seconds ()
        {
            var buffer = Encoding.ASCII.GetBytes ("Mon, 24 Jun 2013 10:37 +0100");
            DateTimeOffset date;

            DateUtils.TryParse (buffer, out date);

            Assert.AreEqual (date, new DateTimeOffset (2013, 06, 24, 10, 37, 0, new TimeSpan (1, 0, 0)));
        }

        [Test]
        public void should_parse_basic_address ()
        {
            var address = MailboxAddress.Parse ("here@there.com");
            Assert.AreEqual ("here@there.com", address.Address);
            Assert.AreEqual (string.Empty, address.Name);
        }

        [Test]
        public void should_parse_quoted_address ()
        {
            var address = MailboxAddress.Parse ("<\"here@there.com\">");
            Assert.AreEqual ("\"here@there.com\"", address.Address);
            Assert.AreEqual (string.Empty, address.Name);
        }

        [Test]
        public void should_parse_address_with_quoted_display_name ()
        {
            var address = MailboxAddress.Parse ("\"Display Name\" <display@name.de>");
            Assert.AreEqual ("display@name.de", address.Address);
            Assert.AreEqual ("Display Name", address.Name);
        }

        [Test]
        public void should_parse_address_with_non_quoted_display_name ()
        {
            var address = MailboxAddress.Parse ("DisplayName <display@name.de>");
            Assert.AreEqual ("display@name.de", address.Address);
            Assert.AreEqual ("DisplayName", address.Name);
        }

        [Test]
        public void should_parse_address_with_chevrons_in_display_name ()
        {
            var address = MailboxAddress.Parse ("\"Display Name <with Chevrons>\" <Chevrons@displayname.de>");
            Assert.AreEqual ("Chevrons@displayname.de", address.Address);
            Assert.AreEqual ("Display Name <with Chevrons>", address.Name);
        }

        // Note: This is testing a very specific brokenness with a very specific assumption of how
        // it should be interpreted. MimeKit does not make the same assumption. More on this
        // later.
        //[Test]
        //public void should_parse_address_with_no_closing_quote_after_display_name ()
        //{
        //  var address = MailboxAddress.Parse ("\"Display Name only one quote <Chevrons@displayname.de>");
        //  Assert.AreEqual ("Chevrons@displayname.de", address.Address);
        //  Assert.AreEqual ("Display Name only one quote", address.Name);
        //}

        // Note: MailSystem.NET's parser seems to combine inline text parts, but that's wrong. MimeKit
        // does the right thing which is to treat each inline MimePart as separate entities, allowing
        // developers to combine them after the fact.
        [Test]
        public void should_append_text_parts_with_inline_disposition ()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "text_multipart_email.eml");
            var message = MimeMessage.Load (path);
            string text = string.Empty;

            foreach (var bodyPart in message.BodyParts.OfType<TextPart> ())
                text += bodyPart.Text;

            text = text.Replace ("\r\n", "\n");

            Assert.AreEqual ("Good morning,\nThis is the body of the message.\n\nThis is the attached disclamer\n", text);
        }

        // Note: MailSystem.NET's parser seems to combine inline text parts, but that's wrong. MimeKit
        // does the right thing which is to treat each inline MimePart as separate entities, allowing
        // developers to combine them after the fact.
        [Test]
        public void should_append_html_parts_with_inline_disposition ()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "html_multipart_email.eml");
            var message = MimeMessage.Load (path);
            string text = string.Empty;

            foreach (var bodyPart in message.BodyParts.OfType<TextPart> ())
                text += bodyPart.Text;

            text = text.Replace ("\r\n", "\n");

            Assert.AreEqual ("Good morning,\n<em>This is the body of the message.</em>\n\nThis is the <em>attached</em> disclamer\n", text);
        }

        [Test]
        public void should_decode_content_name ()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "japanese_email.eml");
            var message = MimeMessage.Load (path);
            var attachment = message.Attachments.OfType<MimePart> ().FirstOrDefault ();

            Assert.AreEqual ("大阪瓦斯9532.pdf", attachment.FileName);
        }

        /// <summary>
        ///  https://tools.ietf.org/html/rfc2387
        /// </summary>
        [Test (Description = "LineBreak \r or \n only fail.")]
        public void should_recognize_line_break_of_notepad_text_in_body ()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "quoted-printable-notepad-linebreak.eml");
            var message = MimeMessage.Load (path);
            var text = message.TextBody.Replace ("\r\n", "\n");

            Assert.AreEqual ("customer,\r\rFoi criada uma nova solicitação para TESTE SOLICITANTE.\r\rCliente: TESTE HOTEL\rEmpresa: TESTE\rC. Custo: TESTE TESTE\r\r\r>>> PASSAGEM AÉREA\rDescrição.: (GRU) Cumbica / (LAS) Las Vegas 04/Jan Manhã (06:00 às 12:00) (Econômica)\rHorário...: considerando saída\rPagamento.: FATURADO\r\rDescrição.: (LAS) Las Vegas / (GRU) Cumbica 07/Jan Manhã (06:00 às 12:00) (Econômica)\rHorário...: considerando saída\rPagamento.: FATURADO\r\r\r>>> SOLICITANTE\rteste solicitante (yyyyyyy@xxxxx.com)\r\r\rDestinatários que estão recebendo esse email: \rtms@argoit.com.br (tms@argoit.com.br)\rteste solicitante (customerteste@yyyyyy.com)\rteste@xpto.com.br (teste@xxxxx.com.br)\rteste@xxxxx.com.br (teste@xxxxxxx.com.br)\r\rPara acessá-la clique em: \r<https://arb.xpto.com/xxxpppp/default.aspx?Id=5a03cdaf-1503-e611-9406-90b11c25f027&LinkId=FLXMfbCeRo72PRAkakfyOg%3d%3d> \r\rEMAIL AUTOMÁTICO, NÃO RESPONDA ESSA MENSAGEM\n", text);
        }

        [Test (Description = "Attachment without filename")]
        public void ParseAttachmentWitoutFilename ()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "attachment-witout-file-name.eml");
            var message = MimeMessage.Load (path);
            var attachments = message.Attachments.OfType<MimePart> ().ToList ();

            Assert.AreEqual (2, attachments.Count);

            // Note: MailSystem.NET's test checks that both attachments have a non-null FileName, but that's wrong!
            //
            // The first attachment is missing the 'filename' parameter in the 'Content-Disposition' header, but it
            // has a 'name' parameter in the 'Content-Type' header.
            //
            // The second attachment has neither a 'filename' parameter in the 'Content-Disposition' header, nor a
            // 'name' parameter in the 'Content-Type' header!
            Assert.AreEqual ("ues31654.txt", attachments[0].FileName);
            Assert.IsNull (attachments[1].FileName);
        }

        /// <summary>
        /// Fields: Confirm-Reading-To, Return-Receipt-To, Disposition-Notification-To was indicated without e-mail address.
        /// RFC3798 has more information about details of parse https://tools.ietf.org/html/rfc3798
        /// NOTE: Without address the system will work with a null return on parse.
        /// </summary>
        [Test (Description = "ConfirmRead, DispositionNotificationTo and ReturnReceiptTo having exception.")]
        public void MustParseInvalidConfirmReadReturnReceipt()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "confirm_read_parse_problem.eml");
            var message = MimeMessage.Load (path);

            // Note: MailSystem.NET offers these headers as Address properties on their Message class,
            // while MimeKit does not (but I could easily do so if people cared), but it's also easy to
            // just do this:
            var confirmReadingTo = MailboxAddress.Parse (message.Headers["X-Confirm-Reading-To"]);
            var returnReceiptTo = MailboxAddress.Parse (message.Headers["Return-Receipt-To"]);
            var dispositionNotificationTo = MailboxAddress.Parse (message.Headers["Disposition-Notification-To"]);

            // Note: MailSystem.NET asserts that these addresses are null, but they are not!
            Assert.AreEqual ("Anonymous", confirmReadingTo.Address);
            Assert.AreEqual ("Anonymous", returnReceiptTo.Address);
            Assert.AreEqual ("Anonymous", dispositionNotificationTo.Address);
        }

        [Test (Description = "")]
        public void MustParseEmlWithWrongImageAsPartOfEmailBody()
        {
            var path = Path.Combine ("..", "..", "TestData", "ActiveUp", "image-as-body-part.eml");
            var message = MimeMessage.Load (path);
            var related = (MultipartRelated) message.Body;
            var alternative = (MultipartAlternative) related[0];
            var html = message.HtmlBody.Replace ("\r\n", "\n");
            var text = message.TextBody.Replace ("\r\n", "\n");

            Assert.AreEqual("CAM3z=h1WB0qSZPU+PWL5VqxsL1k1gmh0pmJivD1G+LjNC5jTLA@mail.serverhost.com", message.MessageId);
            Assert.AreEqual("Boa tarde roger,\n\nAgradeço a atenção e atendimento. Pode fechar o pedido com 2 cápsulas no\nvalor passado de $123.312.313,04.\n\nMoro em Abracox, eu busco pessoalmente ou recebo no meu endereço? E qual o\nprazo de entrega e formas de pagamento?\n\nObrigado,\nJosé roger\n\n\nEm 23 de julho de 2016 09:00, roger Munes <\nroger@destinataryhost.com> escreveu:\n\n> Com 2 cáp deu $123.312.313,04 fico no seu aguardo para finalizar o pedido..\n>\n>\n> Atenciosamente, roger Mussa\n>  [image: Customer Supplier]*roger de Souza Nunes* /\n> Atendimento\n> roger@destinataryhost.com <%7bEmail%7d>\n>\n>\n> *Customer supplier*\n> 0800 116 7284 -  (99) 9376-8104\n> http://www.destinataryhost.com\n>\n>\n>\n> [image: Twitter] <https://www.twitter.com/customersupplier>  [image:\n> Facebook] <https://www.facebook.com/custsupplier>  [image: Instagram]\n> <https://www.instagram.com/custsupplier>\n> Antes de imprimir este e-mail veja se é necessário e pense em sua\n> responsabilidade com o *Meio Ambiente*.\n>\n>\n>\n> *De:* rogerneto@serverhost.com\n> *Enviada em:* 22/07/2016 19:13:51\n> *Para:* roger Munes\n> *Assunto:* Re: Re: Contact\n> Olá roger, esse valor é com 90 cápsulas, correto? Veja por gentileza com\n> 2 aproveito para comprar logo mais.\n>\n> Obrigado pela atenção.\n>\n> José roger\n>\n> Em 22 de julho de 2016 16:05, roger Munes <\n> roger@destinataryhost.com> escreveu:\n>\n> Boa tarde tudo bem ? orçamento 345788 consegui por $ 2.222,00\n> fico no seu aguardo.\n>\n>\n> Atenciosamente, roger Mussa\n>  [image: Customer Supplier]*roger Munes* /\n> Atendimento\n> roger@destinataryhost.com <%7bEmail%7d>\n>\n>\n> *Customer supplier*\n> 0800 116 7284 -  (99) 9376-8104\n> http://www.destinataryhost.com\n>\n>\n>\n> [image: Twitter] <https://www.twitter.com/customersupplier>  [image:\n> Facebook] <https://www.facebook.com/custsupplier>  [image: Instagram]\n> <https://www.instagram.com/custsupplier>\n> Antes de imprimir este e-mail veja se é necessário e pense em sua\n> responsabilidade com o *Meio Ambiente*.\n>\n>\n>\n> *De:* rogerneto@serverhost.com\n> *Enviada em:* 22/07/2016 14:55:08\n> *Para:* roger Munes\n> *Assunto:* Re: Contact\n> Boa tarde roger,\n>\n> Agradeço o contato. Ainda não comprei porém tenho o orçamento abaixo que\n> infelizmente está abaixo da Miligrama. Caso consiga cobrir, prefiro comprar\n> com vocês por já ser cliente e ter outras compras com sucesso no histórico.\n>\n>\n> Obrigado,\n> José roger\n>\n>\n>\n> Em 22 de julho de 2016 14:49, roger Munes <\n> roger@destinataryhost.com> escreveu:\n>\n> Boa tarde amigo, como vai ?\n>\n> Chegou a finalizar o pedido, comprou em outro lugar ? que achou do meu\n> orçamento vamos negociar cubro a oferta de qualquer concorrente.\n>\n>\n> Atenciosamente, roger Mussa\n>  [image: Customer Supplier]*roger de Souza Nunes* /\n> Atendimento\n> roger@destinataryhost.com <%7bEmail%7d>\n>\n>\n> *Customer supplier*\n> 0800 116 7284 -  (99) 9376-8104\n> http://www.destinataryhost.com\n>\n>\n>\n> [image: Twitter] <https://www.twitter.com/customersupplier>  [image:\n> Facebook] <https://www.facebook.com/custsupplier>  [image: Instagram]\n> <https://www.instagram.com/custsupplier>\n> Antes de imprimir este e-mail veja se é necessário e pense em sua\n> responsabilidade com o *Meio Ambiente*.\n>\n>\n>\n>\n", text);
            Assert.AreEqual("<div dir=\"ltr\">Boa tarde roger,<div><br></div><div>Agradeço a atenção e atendimento. Pode fechar o pedido com 2 cápsulas no valor passado de\u00a0<span style=\"font-size:12.8px\">$ 2.222,00.</span></div><div><br></div><div>Moro em Cubivila, eu busco pessoalmente ou recebo no meu endereço? E qual o prazo de entrega e formas de pagamento?</div><div><br></div><div>Obrigado,</div><div>José roger</div><div><br></div></div><div class=\"gmail_extra\"><br><div class=\"gmail_quote\">Em 23 de julho de 2016 09:00, roger Munes <span dir=\"ltr\">&lt;<a href=\"mailto:roger@destinataryhost.com\" target=\"_blank\">roger@custsupplier.com..br</a&gt;</span> escreveu:<br><blockquote class=\"gmail_quote\" style=\"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\"><div>Com 2 cáp deu $ 2.222,00 fico no seu aguardo para finalizar o pedido.\u00a0\u00a0\u00a0\u00a0\n<span class=\"\"><div>\n<p><br>\nAtenciosamente, roger Mussa<br>\n\u00a0<img alt=\"Customer Supplier\" src=\"http://www.customerhost.com/assinatura/logo.png\"><strong>roger de Souza Nunes</strong>\u00a0/ Atendimento<br>\n<a href=\"mailto:%7bEmail%7d\" target=\"_blank\">roger@destinataryhost.com</a><br>\n<br>\n<br>\n<strong>Customer supplier</strong>\u00a0<br>\n0800 116 7284 -\u00a0<img alt=\"\" src=\"http://customerhost.com/assinatura/whatsappm.png\">\u00a0(99) 9376-8104<br>\n<a href=\"http://www.destinataryhost.com/\" target=\"_blank\">http://www.destinataryhost.com</a>\u00a0\u00a0<br>\n<br>\n<br>\n<br>\n<a href=\"https://www.twitter.com/customersupplier\" target=\"_blank\"><img alt=\"Twitter\" src=\"http://www.customerhost.com/assinatura/twitterm.png\"></a>\u00a0 <a href=\"https://www.facebook.com/custsupplier\" target=\"_blank\"><img alt=\"Facebook\" src=\"http://www.customerhost.com/assinatura/facebookm.png\"></a>\u00a0\u00a0<a href=\"https://www.instagram.com/custsupplier\" target=\"_blank\"><img alt=\"Instagram\" src=\"http://www.customerhost.com/assinatura/instagramm.png\"></a><br>\nAntes de imprimir este e-mail veja se é necessário e pense em sua responsabilidade com o <strong>Meio Ambiente</strong>.</p>\n\n<p><img alt=\"\" src=\"cid:31391411\" style=\"border:0px solid black;min-height:200px;margin-bottom:0px;margin-left:0px;margin-right:0px;margin-top:0px;width:600px\"></p>\n\n<p>\u00a0</p>\n</div>\n\u00a0\n\n</span><div style=\"text-align:left\"><strong>De:</strong> <a href=\"mailto:rogerneto@serverhost.com\" target=\"_blank\">rogerneto@serverhost.com</a><br>\n<strong>Enviada em:</strong> 22/07/2016 19:13:51<span class=\"\"><br>\n<strong>Para:</strong> roger Munes<br>\n</span><strong>Assunto:</strong> Re: Re: Contact</div><div><div class=\"h5\">\n\n<div>Olá roger, esse valor é com 90 cápsulas, correto? Veja por gentileza com 2 aproveito para comprar logo mais.\n<div>\u00a0</div>\n\n<div>Obrigado pela atenção.</div>\n\n<div>\u00a0</div>\n\n<div>José roger</div>\n</div>\n\n<div>\u00a0\n<div>Em 22 de julho de 2016 16:05, roger Munes &lt;<a href=\"mailto:roger@destinataryhost.com\" target=\"_blank\">roger@destinataryhost.com</a>&gt; escreveu:\n\n<blockquote>\n<div>Boa tarde tudo bem ? orçamento\u00a0345788\u00a0consegui por $ 2.222,00<br>\nfico no seu aguardo.\n<div>\n<p><br>\nAtenciosamente, roger Mussa<br>\n\u00a0<img alt=\"Customer Supplier\" src=\"http://www.customerhost.com/assinatura/logo.png\"><strong>roger de Souza Nunes</strong>\u00a0/ Atendimento<br>\n<a href=\"mailto:%7bEmail%7d\" target=\"_blank\">roger@destinataryhost.com</a><br>\n<br>\n<br>\n<strong>Customer supplier</strong>\u00a0<br>\n0800 116 7284 -\u00a0<img alt=\"\" src=\"http://customerhost.com/assinatura/whatsappm.png\">\u00a0(99) 9376-8104<br>\n<a href=\"http://www.destinataryhost.com/\" target=\"_blank\">http://www.fmiligrama.com.br</a>\u00a0\u00a0<br>\n<br>\n<br>\n<br>\n<a href=\"https://www.twitter.com/customersupplier\" target=\"_blank\"><img alt=\"Twitter\" src=\"http://www.customerhost.com/assinatura/twitterm.png\"></a>\u00a0 <a href=\"https://www.facebook.com/custsupplier\" target=\"_blank\"><img alt=\"Facebook\" src=\"http://www.customerhost.com/assinatura/facebookm.png\"></a>\u00a0\u00a0<a href=\"https://www.instagram.com/custsupplier\" target=\"_blank\"><img alt=\"Instagram\" src=\"http://www.customerhost.com/assinatura/instagramm.png\"></a><br>\nAntes de imprimir este e-mail veja se é necessário e pense em sua responsabilidade com o <strong>Meio Ambiente</strong>.</p>\n\n<p><img alt=\"\" src=\"cid:16636849\" style=\"border:0px solid black;margin-bottom:0px;margin-left:0px;margin-right:0px;margin-top:0px;min-height:200px;width:600px\"></p>\n\n<p>\u00a0</p>\n</div>\n\u00a0\n\n<div style=\"text-align:left\"><strong>De:</strong> <a href=\"mailto:ramalhoneto@serverhost.com\" target=\"_blank\">rogerneto@serverhost.com</a><br>\n<strong>Enviada em:</strong> 22/07/2016 14:55:08<br>\n<strong>Para:</strong> roger Munes<br>\n<strong>Assunto:</strong> Re: Contact</div>\n\n<div>\n<div>\n<div>Boa tarde roger,\n<div>\u00a0</div>\n\n<div>Agradeço o contato. Ainda não comprei porém tenho o orçamento abaixo que infelizmente está abaixo da Miligrama. Caso consiga cobrir, prefiro comprar com vocês por já ser cliente e ter outras compras com sucesso no histórico.</div>\n\n<div>\u00a0</div>\n\n<div>\u00a0</div>\n\n<div>Obrigado,</div>\n\n<div>José roger</div>\n\n<div>\u00a0</div>\n\n<div>\u00a0</div>\n\n<div>\u00a0</div>\n\n<div>\n<div>Em 22 de julho de 2016 14:49, roger Munes &lt;<a href=\"mailto:roger@destinataryhost.com\" target=\"_blank\">roger@destinataryhost.com</a>&gt; escreveu:\n\n<blockquote>\n<div>Boa tarde amigo, como vai ?<br>\n<br>\nChegou a finalizar o pedido, comprou em outro lugar ? que achou do meu orçamento vamos negociar cubro a oferta de qualquer concorrente.\n<div>\n<p><br>\nAtenciosamente, roger Mussa<br>\n\u00a0<img alt=\"Customer Supplier\" src=\"http://www.customerhost.com/assinatura/logo.png\"><strong>roger de Souza Nunes</strong>\u00a0/ Atendimento<br>\n<a href=\"mailto:%7bEmail%7d\" target=\"_blank\">roger@destinataryhost.com</a><br>\n<br>\n<br>\n<strong>Customer supplier</strong>\u00a0<br>\n0800 116 7284 -\u00a0<img alt=\"\" src=\"http://customerhost.com/assinatura/whatsappm.png\">\u00a0(99) 9376-8104<br>\n<a href=\"http://www.destinataryhost.com/\" target=\"_blank\">http://www.fmiligrama.com.br</a>\u00a0\u00a0<br>\n<br>\n<br>\n<br>\n<a href=\"https://www.twitter.com/customersupplier\" target=\"_blank\"><img alt=\"Twitter\" src=\"http://www.customerhost.com/assinatura/twitterm.png\"></a>\u00a0 <a href=\"https://www.facebook.com/custsupplier\" target=\"_blank\"><img alt=\"Facebook\" src=\"http://www.customerhost.cor/assinatura/facebookm.png\"></a>\u00a0\u00a0<a href=\"https://www.instagram.com/custsupplier\" target=\"_blank\"><img alt=\"Instagram\" src=\"http://www.customerhost.com/assinatura/instagramm.png\"></a><br>\nAntes de imprimir este e-mail veja se é necessário e pense em sua responsabilidade com o <strong>Meio Ambiente</strong>.</p>\n\n<p><img alt=\"\" src=\"cid:16636849\" style=\"border:0px solid black;margin-bottom:0px;margin-left:0px;margin-right:0px;margin-top:0px;min-height:200px;width:600px\"></p>\n\n<p>\u00a0</p>\n</div>\n</div>\n</blockquote>\n</div>\n</div>\n</div>\n</div>\n</div>\n</div>\n</blockquote>\n</div>\n</div>\n</div></div></div></blockquote></div><br></div>\n", html);
            Assert.AreEqual("Re: Re: Re: Contact", message.Subject);
            Assert.AreEqual(1, message.To.Count);
            Assert.AreEqual(3, related.Count);
            Assert.AreEqual(0, message.Attachments.Count ());
        }
    }
}

Notes:

  1. MailSystem.NET's parser combines text/* parts with a Content-Disposition value of inline. This is wrong. The parser should do no such thing. This should be an operation that the consumer of the library does after the fact (if they so desire). By combining things in the parser, you lose information.
  2. In ParseAttachmentWitoutFilename(), MailSystem.NET's unit test checks that both attachments have a non-null FileName. This is WRONG! Only the first attachment has a non-null FileName, the second attachment has neither a filename parameter nor a name parameter, so how could it have a non-null FileName? Looks like a bug in MailSystem.NET's parser.
  3. In MustParseInvalidConfirmReadReturnReceipt(), MailSystem.NET tests that the parser returns null Address objects for each of the receipt headers. Since the header values are simply Anonymous, MailSystem.NET seems to have decided that those addresses are invalid. MimeKit, on the other hand, was written to parse those values as a mailbox with the value Anonymous (i.e. a local-part token w/o the @domain). This is because in the real-world, Unix users on a multi-user machine can choose to send messages to each other using only the local-part's of their email addresses (and in fact, MimeKit has a number of unit test messages from the real-world that illustrate this). Bug in MailSystem.NET? Well, I suppose that is open for interpretation...
  4. In should_parse_address_with_no_closing_quote_after_display_name(), MailSystem.NET makes the assumption that the leading " character should be dropped while MimeKit throws a ParseException with token offsets that provide developers with the ability to fix the problem. One might assume that MailSystem.NET's approach is the better approach at first glance, but that is arguable at best and, in fact, I'd argue that MimeKit's approach is better because it clues developers in that the address string that they are trying to parse is broken so that they can take action to fix said address before they go sending it out into the wild.

As a singleton, it could be argued that if the parser reaches the end of the header and does not find the closing quote, that the parser could/should back-track and ignore it, doing it's best to parse that invalid blob of text into something useful.

But let's dig deeper...

MailSystem.NET parses the following example into 2 distinct addresses:

"Joe <joe@inter.net>, "Bob <bob@inter.net>

Why? A parser written according to the specs would parse everything between the 2 quotes as a qstring token, so you'd expect, according to the spec, that you'd end up with only a single address. A user might tell me that I'm wrong, that this is clearly meant to be 2 addresses... but is it? What if I, as a malicious hacker, crafted that address in order to exploit a MailSystem.NET client into replying to 2 addresses instead of just mine (the second one)?

But things get even less clear in this next case:

"Joe without a closing quote <joe@inter.net>, Bob without an open quote" <bob@inter.net>

MailSystem.NET once again parses this as 2 distinct addresses, but according to the spec, once again, everything between the quotes is supposed to be treated as a qstring token!

Most properly written mail software will interpret that as a single mailbox (bob@inter.net), but MailSystem.NET treats it as 2. Again... open for malicious attackers to potentially exploit.

The way that MailSystem.NET's address parser appears to work is that it simply searches for @'s and parses from there. This will quickly land MailSystem.NET in trouble if people actually do embed their email address in the Display Name strings.

If MailSystem.NET ever adds support for the local-part-only mailbox addresses that MimeKit supports, it would most likely start failing to parse the above examples as 2 distinct addresses as well.

jstedfast commented 8 years ago

To expand on my argument about the addresses with missing begin/end quotes: let me just point out that no real-world email clients are sending out such addresses, those types of addresses only ever appear in spam messages and spam messages are designed to break and confuse mail client software that tries to be too "smart" and forgiving.

So I would argue that MimeKit has actually taken the more sane approach.

reinaldocoelho commented 8 years ago

@jstedfast thank you for the answer.

@jgboggs As I said, there are differences in behavior, some of which are presented by @jstedfast in the detailed description of it, unfortunately emails are received with many errors that the RFC could simply display error, but for use on a daily basis I do not agree that this is good. However, some points are opinions that in my view, give the specific features of each library that should be considered by you as a user to choose to find better :-)

I do not want to fight to say which is better, the choice is yours, it is important to find the one that meets your needs, as I mentioned, I use MailSystem.NET a large project we have and meet us perfectly well.

The MailKit also use in a dotnet core project, and is a good library, but in my personal opinion, I think it more difficult to understand and handle, probably following RFC perfectly as reported by @jstedfast, which should be great for who needs that need.

I hope that in both cases the answers have helped in your decision :-)

If you need support, I'm sure that both I and @jstedfast will be on hand to help in our views and specialties :-)

jstedfast commented 8 years ago

FWIW, curiosity got the better of me and I wrote a unit test for MailSystem.NET that illustrates inconsistency in the behavior of the address parser:

[Test]
public void before_and_after_address_should_match()
{
    var original = new Address ("bob@inter.net", "Joe <joe@inter.net>, Bob");
    var parsed = Parser.ParseAddresses(original.Merged);

    parsed.Count.ShouldEqual (1);
    parsed[0].Name.ShouldEqual (original.Name);
    parsed[0].Email.ShouldEqual (original.Email);
}

The unit test fails because original.Merged gets parsed as 2 separate addresses.

I would consider this a bug because if you serialize something and then deserialize it, you should end up with exactly the same structure that you had before.

jstedfast commented 8 years ago

FWIW, I agree with @reinaldocoelho.

In the case of malformed email addresses (or anything else), no matter what MIME parser you choose, there will always be examples that the parser will not handle in the way that some users might expect. Every parser has to make design decisions and compromises in how it deals with malformed content.

MimeKit's parsers work around all of the most common breakages that are sent by real-world clients in order to try and interoperate as best it can, but when in doubt, it follows the spec.

In the case of the "partially-quoted" email address examples in MailSystem.NET's unit tests, since I've only ever seen examples like that in spam messages, I have not bothered to modify MimeKit's address parser to interpret it as these unit tests expect. If I start getting reports that Outlook or Thunderbird or something are producing similar addresses in the real-world, I'll probably need to adjust my parser.

Keep in mind, too, that MimeKit's address parser has 2 API's: TryParse() (which is what the MimeParser uses) and Parse() which is what I was using in the ported unit tests.

Parse() throws a ParseException and is a bit more strict than TryParse()

TryParse() works a bit harder at trying to interpret malformed addresses (although, in the mis-quoted examples discussed above, both fail).

The reason for this is that the MimeParser which parses messages can't realistically go throwing ParseException at every malformed token. But the Parse() methods that are intended for developers to use themselves for parsing textbox inputs and such have much more flexibility in being allowed to throw exceptions to bring malformed input to the attention of the developer.

jgboggs commented 8 years ago

Wow, thanks guys! My inbox is going crazy this morning with replies from you guys.

I am probably revealing my cluelessness here by saying that I thought parsing email addresses was a simple task: just look for '<' and '>' and everything in between is an email address! And maybe, if you are being pedantic, checking that an '@' exists.

How wrong I apparently was!

I see what you guys mean now about different parsers having different interpretations of stuff.

All I can say right now is that I'll have to continue to play with both and see which one meets our needs more I guess.

Again, thank you guys so much for helping out this newb!

jstedfast commented 8 years ago

MimeKit's address parser now handles:

  1. <<<user2@example.org>>>
  2. <another@example.net
  3. second@example.org>
  4. <third@example.net, fourth@example.net>
  5. "Joe <joe@example.com>
  6. "Joe <joe@example.com>, Bob <bob@example.com>

Just make sure that ParserOptions.AddressParserComplianceMode is set to RfcCompliance.Loose (which is the default). If you set it to RfcCompliance.Strict, it will fail (as it should).

jstedfast commented 8 years ago

Now even @reinaldocoelho has no excuse for not using MimeKit & MailKit :p