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

To/From InternetAddresses are not being returned in case they only have a name #712

Open
Dethras opened this issue Sep 15, 2021 · 8 comments
Labels
enhancement New feature or request

Comments

@Dethras
Copy link

Dethras commented Sep 15, 2021

I don't know if this is a bug or not, but I have an email (Problematic Email.zip) that in the To/From field it only has the Names and when parsing the mime, I don't get anything in the MimeMessage.To/From.

Describe the bug
A clear and concise description of what the bug is.

Platform (please complete the following information):

  • OS: [e.g. Windows, Linux, MacOS, iOS, Android, Windows Phone, etc.]
  • .NET Runtime: [e.g. CoreCLR, Mono]
  • .NET Framework: [e.g. .Net Core, .NET 4.5, UWP, etc.]
  • MimeKit Version:

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

@jstedfast
Copy link
Owner

It's technically by design, but I can see how it could be argued either way.

The reason I chose not to include incomplete email addresses is because a MailboxAddress insinuates that it has an address, but these cases do not.

API's in MimeKit and MailKit that take a MailboxAddress argument all expect that the mailbox has a syntactically valid address (even if it's not valid because it doesn't exist or something).

Maybe I can have an IncompleteAddress type that is just the name, but I'm not sure how complicated it would be to do that.

@Dethras
Copy link
Author

Dethras commented Sep 15, 2021

Thanks a lot for the swift update. I assumed that it was by design, but didn't know for sure. The IncompleteAddress would help if it possible.

@jstedfast jstedfast added the enhancement New feature or request label Sep 17, 2021
@Olby2000
Copy link

Olby2000 commented Mar 2, 2023

It would be a great enhancement to know if an email address is incomplete or invalid. Just the other day I encountered a customer who is sending emails where the From header contains an invalid email address. Exchange server actually displays it as is. Below is a mock example header (notice the dot):

From: <[email protected]>

In such cases the from property contains no email addresses whatsoever and I have to manually parse the from header, which is not ideal.

@jstedfast
Copy link
Owner

@Olby2000 For that particular case, I believe you can use:

var options = ParserOptions.Default.Clone ();
options.AddressParserCompliance = RfcCompliance.Loosest;

var message = MimeMessage.Load (options, fileName);

Or at the very least, you can use those ParserOptions to parse the raw From header:

var options = ParserOptions.Default.Clone ();
options.AddressParserCompliance = RfcCompliance.Loosest;

foreach (var header in message.Headers) {
    if (header.Id == HeaderId.From) {
        var from = InternetAddressList.Parse (options, header.RawValue);
    }
}

@sliekens
Copy link

sliekens commented Jul 28, 2023

Just as an FYI, I have a similar case where the address is a phone number, not an e-mail address.

From: +12065550100 <tel:+12065550100>

InternetAddress.Parse("+12065550100 <tel:+12065550100>") throws MimeKit.ParseException : Invalid addr-spec token at offset 14.

This format is used by Office 365 when you miss a call and the caller leaves a voicemail. You get an e-mail with the voicemail as an audio.mp3 and the phone number as the originator.

@jstedfast
Copy link
Owner

@sliekens that is definitely an illegal syntax.

@sliekens
Copy link

I'm not surprised 😄 still thought it's worth mentioning

@jstedfast
Copy link
Owner

jstedfast commented Jul 30, 2023

FWIW, I looked into the IncompleteAddress idea a year or two ago and concluded it was going to be more difficult than I had hoped because it broke support for old-style UNIX mailbox addresses (e.g. From: bob which should be treated the same as From: <bob@localhost>).

diff --git a/MimeKit/InternetAddress.cs b/MimeKit/InternetAddress.cs
index 9326ebc7..145267d0 100644
--- a/MimeKit/InternetAddress.cs
+++ b/MimeKit/InternetAddress.cs
@@ -27,7 +27,6 @@
 using System;
 using System.Text;
 using System.Globalization;
-using System.Collections.Generic;
 
 using MimeKit.Utils;
 
diff --git a/MimeKit/InternetAddressList.cs b/MimeKit/InternetAddressList.cs
index 5b3c4ff4..6b71d3b7 100644
--- a/MimeKit/InternetAddressList.cs
+++ b/MimeKit/InternetAddressList.cs
@@ -589,10 +589,33 @@ namespace MimeKit {
 				if (isGroup && text[index] == (byte) ';')
 					break;
 
+				int startIndex = index;
+
 				if (!InternetAddress.TryParse (options, text, ref index, endIndex, groupDepth, flags, out var address)) {
+					Encoding encoding;
+					string name;
+
 					// skip this address...
 					while (index < endIndex && text[index] != (byte) ',' && (!isGroup || text[index] != (byte) ';'))
 						index++;
+
+					if (index > startIndex) {
+						name = Rfc2047.DecodePhrase (options, text, startIndex, index - startIndex, out int codepage);
+
+						if (codepage == -1)
+							codepage = 65001;
+
+						try {
+							encoding = Encoding.GetEncoding (codepage);
+						} catch {
+							encoding = Encoding.UTF8;
+						}
+					} else {
+						encoding = Encoding.UTF8;
+						name = string.Empty;
+					}
+
+					list.Add (new IncompleteAddress (encoding, MimeUtils.Unquote (name)));
 				} else {
 					list.Add (address);
 				}
diff --git a/MimeKit/MimeKit.csproj b/MimeKit/MimeKit.csproj
index 724579a2..aed9fe37 100644
--- a/MimeKit/MimeKit.csproj
+++ b/MimeKit/MimeKit.csproj
@@ -281,6 +281,7 @@
     <Compile Include="HeaderListChangedEventArgs.cs" />
     <Compile Include="HeaderListCollection.cs" />
     <Compile Include="IMimeContent.cs" />
+    <Compile Include="IncompleteAddress.cs" />
     <Compile Include="InternetAddress.cs" />
     <Compile Include="InternetAddressList.cs" />
     <Compile Include="MailboxAddress.cs" />
diff --git a/UnitTests/InternetAddressListTests.cs b/UnitTests/InternetAddressListTests.cs
index 906ac0ab..6db07d81 100644
--- a/UnitTests/InternetAddressListTests.cs
+++ b/UnitTests/InternetAddressListTests.cs
@@ -234,7 +234,14 @@ namespace UnitTests {
 		public void TestParseNameLessThan ()
 		{
 			AssertParseFails ("\"Name\" <");
-			AssertTryParse ("\"Name\" <", "", new InternetAddressList ());
+			AssertTryParse ("\"Name\" <", "\"Name <\"", new InternetAddressList { new IncompleteAddress (Encoding.UTF8, "Name <") });
+		}
+
+		[Test]
+		public void TestParseIncompleteAddress ()
+		{
+			AssertParseFails ("John Craig Taylor");
+			AssertTryParse ("John Craig Taylor", "John Craig Taylor", new InternetAddressList { new IncompleteAddress (Encoding.UTF8, "John Craig Taylor") });
 		}
 
 		[Test]

IncompleteAddress.cs:

//
// IncompleteAddress.cs
//
// Author: Jeffrey Stedfast <[email protected]>
//
// Copyright (c) 2013-2022 .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

using System;
using System.Text;

using MimeKit.Utils;

namespace MimeKit {
	/// <summary>
	/// An incomplete address.
	/// </summary>
	/// <remarks>
	/// An incomplete address.
	/// </remarks>
	public class IncompleteAddress : InternetAddress
	{
		internal IncompleteAddress (Encoding encoding, string displayName) : base (encoding, displayName)
		{
		}

		/// <summary>
		/// Clone the address.
		/// </summary>
		/// <remarks>
		/// Clones the address.
		/// </remarks>
		/// <returns>The cloned address.</returns>
		public override InternetAddress Clone ()
		{
			return new IncompleteAddress (Encoding, Name);
		}

		/// <summary>
		/// Determines whether the specified <see cref="InternetAddress"/> is equal to the current <see cref="InternetAddress"/>.
		/// </summary>
		/// <remarks>
		/// Compares two internet addresses to determine if they are identical or not.
		/// </remarks>
		/// <param name="other">The <see cref="InternetAddress"/> to compare with the current <see cref="InternetAddress"/>.</param>
		/// <returns><c>true</c> if the specified <see cref="InternetAddress"/> is equal to the current
		/// <see cref="InternetAddress"/>; otherwise, <c>false</c>.</returns>
		public override bool Equals (InternetAddress other)
		{
			var incomplete = other as IncompleteAddress;

			if (incomplete == null)
				return false;

			return Name == incomplete.Name;
		}

		/// <summary>
		/// Returns a string representation of the <see cref="MailboxAddress"/>,
		/// optionally encoding it for transport.
		/// </summary>
		/// <remarks>
		/// Returns a string containing the formatted mailbox address. If the <paramref name="encode"/>
		/// parameter is <c>true</c>, then the mailbox name will be encoded according to the rules defined
		/// in rfc2047, otherwise the name will not be encoded at all and will therefor only be suitable
		/// for display purposes.
		/// </remarks>
		/// <returns>A string representing the <see cref="MailboxAddress"/>.</returns>
		/// <param name="options">The formatting options.</param>
		/// <param name="encode">If set to <c>true</c>, the <see cref="MailboxAddress"/> will be encoded.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="options"/> is <c>null</c>.
		/// </exception>
		public override string ToString (FormatOptions options, bool encode)
		{
			if (options == null)
				throw new ArgumentNullException (nameof (options));

			if (encode) {
				var builder = new StringBuilder ();
				int lineLength = 0;

				Encode (options, builder, true, ref lineLength);

				return builder.ToString ();
			}

			if (!string.IsNullOrEmpty (Name))
				return MimeUtils.Quote (Name);

			return string.Empty;
		}

		internal override void Encode (FormatOptions options, StringBuilder builder, bool firstToken, ref int lineLength)
		{
			if (!string.IsNullOrEmpty (Name)) {
				string name;

				if (!options.International) {
					var encoded = Rfc2047.EncodePhrase (options, Encoding, Name);
					name = Encoding.ASCII.GetString (encoded, 0, encoded.Length);
				} else {
					name = Name;
				}

				if (lineLength + name.Length > options.MaxLineLength) {
					if (name.Length > options.MaxLineLength) {
						// we need to break up the name...
						builder.AppendFolded (options, firstToken, name, ref lineLength);
					} else {
						// the name itself is short enough to fit on a single line,
						// but only if we write it on a line by itself
						if (!firstToken && lineLength > 1) {
							builder.LineWrap (options);
							lineLength = 1;
						}

						lineLength += name.Length;
						builder.Append (name);
					}
				} else {
					// we can safely fit the name on this line...
					lineLength += name.Length;
					builder.Append (name);
				}
			}
		}
	}
}

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

No branches or pull requests

4 participants