diff --git a/MimeKit/InternetAddress.cs b/MimeKit/InternetAddress.cs index 701e16cc33..330f6cc985 100644 --- a/MimeKit/InternetAddress.cs +++ b/MimeKit/InternetAddress.cs @@ -293,6 +293,7 @@ internal static bool TryParseLocalPart (byte[] text, ref int index, int endIndex localpart = null; + var start = index; do { if (!text[index].IsAtom () && text[index] != '"' && text[index] != '.') { if (throwOnError) @@ -301,10 +302,16 @@ internal static bool TryParseLocalPart (byte[] text, ref int index, int endIndex return false; } - int start = index; if (!ParseUtils.SkipWordAndPeriod (text, ref index, endIndex, throwOnError)) return false; + if (index < endIndex && text[index] == (byte) '"') + { + ParseUtils.SkipQuoted (text, ref index, endIndex, throwOnError); + if (index < endIndex && text[index].IsAtom() && text[index] != (byte) '@' && text[index] != (byte) '(') + continue; + } + try { token.Append (CharsetUtils.UTF8.GetString (text, start, index - start)); } catch (DecoderFallbackException ex) { @@ -317,6 +324,12 @@ internal static bool TryParseLocalPart (byte[] text, ref int index, int endIndex if (!ParseUtils.SkipCommentsAndWhiteSpace (text, ref index, endIndex, throwOnError)) return false; + if (index < endIndex && text[index] != (byte) '"' && text[index].IsAtom()) + { + start = index; + continue; + } + if (index >= endIndex || text[index] != (byte) '.') break; @@ -336,6 +349,8 @@ internal static bool TryParseLocalPart (byte[] text, ref int index, int endIndex if (text[index] == '@') break; + start = index; + } while (true); localpart = token.ToString (); diff --git a/MimeKit/Utils/ParseUtils.cs b/MimeKit/Utils/ParseUtils.cs index 23eacf75f7..0eeedc7998 100644 --- a/MimeKit/Utils/ParseUtils.cs +++ b/MimeKit/Utils/ParseUtils.cs @@ -210,10 +210,7 @@ public static bool SkipQuoted (byte[] text, ref int index, int endIndex, bool th } if (index >= endIndex) { - if (throwOnError) - throw new ParseException (string.Format ("Incomplete quoted-string token at offset {0}", startIndex), startIndex, index); - - return false; + index = startIndex; } // skip over the closing '"' diff --git a/UnitTests/InternetAddressTests.cs b/UnitTests/InternetAddressTests.cs index 36c9385ec2..59108d71e5 100644 --- a/UnitTests/InternetAddressTests.cs +++ b/UnitTests/InternetAddressTests.cs @@ -190,10 +190,8 @@ public void TestParseMailboxWithIncompleteLocalPart () public void TestParseIncompleteQuotedString () { const string text = "\"This quoted string never ends... oh no!"; - const int tokenIndex = 0; - int errorIndex = text.Length; - AssertParseFailure (text, false, tokenIndex, errorIndex); + AssertParse (text); } [Test] @@ -279,20 +277,6 @@ public void TestParseMailboxWithUnquotedCommaInName () // default options should parse this as a single mailbox address addr = InternetAddress.Parse (text); Assert.AreEqual ("Worthington, Warren", addr.Name); - - // this should fail when we allow mailbox addresses w/o a domain - var options = ParserOptions.Default.Clone (); - options.AllowAddressesWithoutDomain = true; - - try { - addr = InternetAddress.Parse (options, text); - Assert.Fail ("Should not have parsed \"{0}\" with AllowAddressesWithoutDomain = true", text); - } catch (ParseException pex) { - Assert.AreEqual (text.IndexOf (','), pex.TokenIndex, "TokenIndex"); - Assert.AreEqual (text.IndexOf (','), pex.ErrorIndex, "ErrorIndex"); - } catch (Exception ex) { - Assert.Fail ("Should not have thrown {0}", ex.GetType ().Name); - } } [Test] @@ -534,7 +518,9 @@ TestCaseData[] LegacyAddressNotCompliantWithRFC () new TestCaseData ("aaaa.@example.com"), new TestCaseData ("aa..aa@example.com"), new TestCaseData (".aaaa@example.com"), - + new TestCaseData ("!\"#$%&'()=@amimap.access.co.jp"), + new TestCaseData ("!\"#$%&'\"()=@amimap.access.co.jp"), + new TestCaseData ("!\"#$%&'\"=\"@amimap.access.co.jp"), // More not compliant with RFC // Does not correspond now. //new TestCaseData ("アドレス "), diff --git a/UnitTests/MailboxAddressTests.cs b/UnitTests/MailboxAddressTests.cs index 6b649a4724..3095c018cc 100644 --- a/UnitTests/MailboxAddressTests.cs +++ b/UnitTests/MailboxAddressTests.cs @@ -260,10 +260,8 @@ public void TestParseMailboxWithIncompleteLocalPart () public void TestParseIncompleteQuotedString () { const string text = "\"This quoted string never ends... oh no!"; - const int tokenIndex = 0; - int errorIndex = text.Length; - AssertParseFailure (text, false, tokenIndex, errorIndex); + AssertParse (text); } [Test] @@ -360,20 +358,6 @@ public void TestParseMailboxWithUnquotedCommaInName () // default options should parse this as a single mailbox address mailbox = MailboxAddress.Parse (text); Assert.AreEqual ("Worthington, Warren", mailbox.Name); - - // this should fail when we allow mailbox addresses w/o a domain - var options = ParserOptions.Default.Clone (); - options.AllowAddressesWithoutDomain = true; - - try { - mailbox = MailboxAddress.Parse (options, text); - Assert.Fail ("Should not have parsed \"{0}\" with AllowAddressesWithoutDomain = true", text); - } catch (ParseException pex) { - Assert.AreEqual (text.IndexOf (','), pex.TokenIndex, "TokenIndex"); - Assert.AreEqual (text.IndexOf (','), pex.ErrorIndex, "ErrorIndex"); - } catch (Exception ex) { - Assert.Fail ("Should not have thrown {0}", ex.GetType ().Name); - } } [Test] @@ -565,11 +549,6 @@ public void TestParseMailboxWithUnbalancedQuotes () const string text = "\"Joe "; AssertParse (text); - - AssertParseFailure (text, false, 0, text.Length, RfcComplianceMode.Strict); - - // for coverage - AssertParseFailure (" \"", false, 1, 2, RfcComplianceMode.Loose); } [Test] @@ -695,7 +674,9 @@ TestCaseData[] LegacyAddressNotCompliantWithRFC () new TestCaseData ("aaaa.@example.com"), new TestCaseData ("aa..aa@example.com"), new TestCaseData (".aaaa@example.com"), - + new TestCaseData ("!\"#$%&'()=@amimap.access.co.jp"), + new TestCaseData ("!\"#$%&'\"()=@amimap.access.co.jp"), + new TestCaseData ("!\"#$%&'\"=\"@amimap.access.co.jp"), // More not compliant with RFC // Does not correspond now. //new TestCaseData ("アドレス "), diff --git a/UnitTests/ParseUtilsTests.cs b/UnitTests/ParseUtilsTests.cs index f0c519d251..5c70284d40 100644 --- a/UnitTests/ParseUtilsTests.cs +++ b/UnitTests/ParseUtilsTests.cs @@ -62,22 +62,6 @@ public void TestTryParseInt32 () Assert.IsFalse (ParseUtils.TryParseInt32 (buffer, ref index, buffer.Length, out value), "Parsing MaxValue*10 should result in overflow."); } - [Test] - public void TestSkipBadlyQuoted () - { - var buffer = Encoding.ASCII.GetBytes ("\"This is missing the end quote."); - int index = 0; - - Assert.False (ParseUtils.SkipQuoted (buffer, ref index, buffer.Length, false), "Skipping an unterminated qstring should have failed."); - Assert.AreEqual (buffer.Length, index, "The index should be at the end of the buffer."); - - index = 0; - - var ex = Assert.Throws (() => ParseUtils.SkipQuoted (buffer, ref index, buffer.Length, true), "An exception should have been thrown."); - Assert.AreEqual (0, ex.TokenIndex, "The token index should be 0."); - Assert.AreEqual (buffer.Length, ex.ErrorIndex, "The error index should be at the end of the buffer."); - } - static readonly string[] GoodDomains = { "[127.0.0.1]", "[127.0.0.1]", "amazon (comment) . (comment) com (comment)", "amazon.com",