From 87f82166a74b86deab0fc3c1ee1c05e4ed1a5132 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Mon, 17 Aug 2020 13:59:43 +1000 Subject: [PATCH 1/8] Geary.RFC822.Mailbox: Fix typo in doc comment --- src/engine/rfc822/rfc822-mailbox-address.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index 173312a7..68663b4b 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -206,7 +206,7 @@ public class Geary.RFC822.MailboxAddress : * * The given name (if any) and address parts will be used * verbatim, and quoted or encoded if needed when serialising to - * an RFC 833 mailbox address string. + * an RFC 822 mailbox address string. */ public MailboxAddress(string? name, string address) { this.name = name; From 747df10b9dec2609ee8e11ba2a1e0525313825f2 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Tue, 18 Aug 2020 11:15:27 +1000 Subject: [PATCH 2/8] Geary.RFC822.MailboxAddress: Compile is-valid regex only once --- src/engine/rfc822/rfc822-mailbox-address.vala | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index 68663b4b..80cb0112 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -22,6 +22,8 @@ public class Geary.RFC822.MailboxAddress : Gee.Hashable, DecodedMessageData { + private static Regex? email_regex = null; + private static unichar[] ATEXT = { '!', '#', '$', '%', '&', '\'', '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~' @@ -29,17 +31,20 @@ public class Geary.RFC822.MailboxAddress : /** Determines if a string contains a valid RFC822 mailbox address. */ public static bool is_valid_address(string address) { - try { - // http://www.regular-expressions.info/email.html - // matches john@dep.aol.museum not john@aol...com - Regex email_regex = - new Regex("[A-Z0-9._%+-]+@((?:[A-Z0-9-]+\\.)+[A-Z]{2}|localhost)", - RegexCompileFlags.CASELESS); - return email_regex.match(address); - } catch (RegexError e) { - debug("Regex error validating email address: %s", e.message); - return false; + if (MailboxAddress.email_regex == null) { + try { + // http://www.regular-expressions.info/email.html + // matches john@dep.aol.museum not john@aol...com + MailboxAddress.email_regex = new Regex( + "[A-Z0-9._%+-]+@((?:[A-Z0-9-]+\\.)+[A-Z]{2}|localhost)", + RegexCompileFlags.CASELESS + ); + } catch (RegexError e) { + warning("Regex error validating email address: %s", e.message); + return false; + } } + return MailboxAddress.email_regex.match(address); } private static string decode_name(string name) { From 1c22eb4ebdb0b7e86a32b32b15800c08f589da2a Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Mon, 17 Aug 2020 13:57:15 +1000 Subject: [PATCH 3/8] Geary.RFC822.MailboxAddress: Handle empty mailbox and domain parts better If an address does not have an `@`, or if the IMAP constructor is called with empty mailbox or domain parts, then these properties will be empty. This is not uncommon, especially on UNIX hosts where system accounts send email. Ensure this is handled correctly in the constructors and are round-tripped correctly by `to_rfc822_address` and hence `to_rfc822_string`. --- src/engine/rfc822/rfc822-mailbox-address.vala | 57 ++++-- .../rfc822/rfc822-mailbox-address-test.vala | 177 +++++++++++------- 2 files changed, 155 insertions(+), 79 deletions(-) diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index 80cb0112..1e23fca4 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -233,7 +233,18 @@ public class Geary.RFC822.MailboxAddress : this.source_route = source_route; this.mailbox = decode_address_part(mailbox); this.domain = domain; - this.address = "%s@%s".printf(mailbox, domain); + + bool empty_mailbox = String.is_empty_or_whitespace(mailbox); + bool empty_domain = String.is_empty_or_whitespace(domain); + if (!empty_mailbox && !empty_domain) { + this.address = "%s@%s".printf(mailbox, domain); + } else if (empty_mailbox) { + this.address = domain; + } else if (empty_domain) { + this.address = mailbox; + } else { + this.address = ""; + } } public MailboxAddress.from_rfc822_string(string rfc822) throws Error { @@ -266,9 +277,11 @@ public class Geary.RFC822.MailboxAddress : // GMime strips source route for us, so the address part // should only ever contain a single '@' string? name = mailbox.get_name(); - if (name != "") { - this.name = decode_name(name); - } + this.name = ( + !String.is_empty_or_whitespace(name) + ? decode_name(name) + : null + ); string address = mailbox.get_addr(); int atsign = Ascii.last_index_of(address, '@'); @@ -286,7 +299,7 @@ public class Geary.RFC822.MailboxAddress : } else { this.mailbox = ""; this.domain = ""; - this.address = address; + this.address = decode_address_part(address); } } @@ -502,15 +515,33 @@ public class Geary.RFC822.MailboxAddress : // GMime.utils_header_encode_text will use MIME encoding, // which is disallowed in mailboxes by RFC 2074 §5. So quote // manually. - string local_part = this.mailbox; - if (local_part_needs_quoting(local_part)) { - local_part = quote_string(local_part); + var address = ""; + if (this.mailbox != "") { + address = this.mailbox; + if (local_part_needs_quoting(address)) { + address = quote_string(address); + } } - return "%s@%s".printf( - local_part, - // XXX Need to punycode international domains. - this.domain - ); + if (this.domain != "") { + address = "%s@%s".printf( + address, + // XXX Need to punycode international domains. + this.domain + ); + } + if (address == "") { + // Both mailbox and domain are empty, i.e. there was no + // '@' symbol in the address, so just assume the address + // is a mailbox since this is not uncommon practice on + // UNIX systems where mail is sent from a local account, + // and it supports a greater range of characters than the + // domain component + address = this.address; + if (local_part_needs_quoting(address)) { + address = quote_string(address); + } + } + return address; } /** diff --git a/test/engine/rfc822/rfc822-mailbox-address-test.vala b/test/engine/rfc822/rfc822-mailbox-address-test.vala index 8718636d..fdef5b0b 100644 --- a/test/engine/rfc822/rfc822-mailbox-address-test.vala +++ b/test/engine/rfc822/rfc822-mailbox-address-test.vala @@ -9,6 +9,7 @@ class Geary.RFC822.MailboxAddressTest : TestCase { public MailboxAddressTest() { base("Geary.RFC822.MailboxAddressTest"); + add_test("imap_address", imap_address); add_test("is_valid_address", is_valid_address); add_test("unescaped_constructor", unescaped_constructor); add_test("from_rfc822_string_encoded", from_rfc822_string_encoded); @@ -24,6 +25,25 @@ class Geary.RFC822.MailboxAddressTest : TestCase { add_test("equal_to", equal_to); } + public void imap_address() throws GLib.Error { + assert_equal( + new MailboxAddress.imap(null, null, "test", "example.com").address, + "test@example.com" + ); + assert_equal( + new MailboxAddress.imap(null, null, "test", "").address, + "test" + ); + assert_equal( + new MailboxAddress.imap(null, null, "", "example.com").address, + "example.com" + ); + assert_equal( + new MailboxAddress.imap(null, null, "", "").address, + "" + ); + } + public void is_valid_address() throws GLib.Error { assert(Geary.RFC822.MailboxAddress.is_valid_address("john@dep.aol.museum") == true); assert(Geary.RFC822.MailboxAddress.is_valid_address("test@example.com") == true); @@ -73,84 +93,93 @@ class Geary.RFC822.MailboxAddressTest : TestCase { } public void from_rfc822_string_encoded() throws GLib.Error { - try { - MailboxAddress addr = new MailboxAddress.from_rfc822_string("test@example.com"); - assert(addr.name == null); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + var encoded = "test@example.com"; + var addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test", encoded); + assert_equal(addr.domain, "example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("\"test\"@example.com"); - assert(addr.name == null); - assert(addr.address == "test@example.com"); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + encoded = "\"test\"@example.com"; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("=?UTF-8?b?dGVzdA==?=@example.com"); - assert(addr.name == null); - assert(addr.address == "test@example.com"); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + encoded = "=?UTF-8?b?dGVzdA==?=@example.com"; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("\"=?UTF-8?b?dGVzdA==?=\"@example.com"); - assert(addr.name == null); - assert(addr.address == "test@example.com"); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + encoded = "\"=?UTF-8?b?dGVzdA==?=\"@example.com"; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string(""); - assert(addr.name == null); - assert(addr.address == "test@example.com"); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + encoded = ""; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test"); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("<\"test\"@example.com>"); - assert(addr.name == null); - assert(addr.address == "test@example.com"); - assert(addr.mailbox == "test"); - assert(addr.domain == "example.com"); + encoded = "<\"test\"@example.com>"; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_null(addr.name, encoded); + assert_equal(addr.mailbox, "test", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("Test 1 "); - assert(addr.name == "Test 1"); - assert(addr.address == "test2@example.com"); - assert(addr.mailbox == "test2"); - assert(addr.domain == "example.com"); + encoded = "Test 1 "; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "Test 1", encoded); + assert_equal(addr.mailbox, "test2", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test2@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("\"Test 1\" "); - assert(addr.name == "Test 1"); - assert(addr.address == "test2@example.com"); - assert(addr.mailbox == "test2"); - assert(addr.domain == "example.com"); + encoded = "\"Test 1\" "; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "Test 1", encoded); + assert_equal(addr.mailbox, "test2", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test2@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("Test 1 <\"test2\"@example.com>"); - assert(addr.name == "Test 1"); - assert(addr.address == "test2@example.com"); - assert(addr.mailbox == "test2"); - assert(addr.domain == "example.com"); + encoded = "Test 1 <\"test2\"@example.com>"; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "Test 1", encoded); + assert_equal(addr.mailbox, "test2", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test2@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("=?UTF-8?b?VGVzdCAx?= "); - assert(addr.name == "Test 1"); - assert(addr.address == "test2@example.com"); - assert(addr.mailbox == "test2"); - assert(addr.domain == "example.com"); + encoded = "=?UTF-8?b?VGVzdCAx?= "; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "Test 1", encoded); + assert_equal(addr.mailbox, "test2", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test2@example.com", encoded); - addr = new MailboxAddress.from_rfc822_string("\"=?UTF-8?b?VGVzdCAx?=\" "); - assert(addr.name == "Test 1"); - assert(addr.address == "test2@example.com"); - assert(addr.mailbox == "test2"); - assert(addr.domain == "example.com"); + encoded = "\"=?UTF-8?b?VGVzdCAx?=\" "; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "Test 1", encoded); + assert_equal(addr.mailbox, "test2", encoded); + assert_equal(addr.domain, "example.com", encoded); + assert_equal(addr.address, "test2@example.com", encoded); - // Courtesy Mailsploit https://www.mailsploit.com - addr = new MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" "); - assert(addr.name == "test ?"); - assert(addr.address == "demo@mailsploit.com"); + // Courtesy Mailsploit https://www.mailsploit.com + encoded = "\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" "; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, "test ?", encoded); + assert_equal(addr.address, "demo@mailsploit.com", encoded); - // Courtesy Mailsploit https://www.mailsploit.com - addr = new MailboxAddress.from_rfc822_string("\"=?utf-8?Q?=42=45=47=49=4E=20=2F=20=28=7C=29=7C=3C=7C=3E=7C=40=7C=2C=7C=3B=7C=3A=7C=5C=7C=22=7C=2F=7C=5B=7C=5D=7C=3F=7C=2E=7C=3D=20=2F=20=00=20=50=41=53=53=45=44=20=4E=55=4C=4C=20=42=59=54=45=20=2F=20=0D=0A=20=50=41=53=53=45=44=20=43=52=4C=46=20=2F=20?==?utf-8?b?RU5E=?=\""); - assert(addr.name == null); - assert(addr.address == "BEGIN / (|)|<|>|@|,|;|:|\\|\"|/|[|]|?|.|= / ? PASSED NULL BYTE / \r\n PASSED CRLF / END"); - } catch (Error err) { - assert_not_reached(); - } + // Courtesy Mailsploit https://www.mailsploit.com + encoded = "\"=?utf-8?Q?=42=45=47=49=4E=20=2F=20=28=7C=29=7C=3C=7C=3E=7C=40=7C=2C=7C=3B=7C=3A=7C=5C=7C=22=7C=2F=7C=5B=7C=5D=7C=3F=7C=2E=7C=3D=20=2F=20=00=20=50=41=53=53=45=44=20=4E=55=4C=4C=20=42=59=54=45=20=2F=20=0D=0A=20=50=41=53=53=45=44=20=43=52=4C=46=20=2F=20?==?utf-8?b?RU5E=?=\""; + addr = new MailboxAddress.from_rfc822_string(encoded); + assert_equal(addr.name, null, encoded); + assert_equal(addr.address, "BEGIN / (|)|<|>|@|,|;|:|\\|\"|/|[|]|?|.|= / ? PASSED NULL BYTE / \r\n PASSED CRLF / END", encoded); } public void prepare_header_text_part() throws GLib.Error { @@ -286,6 +315,22 @@ class Geary.RFC822.MailboxAddressTest : TestCase { "😸@example.com" ); + assert_equal( + new MailboxAddress(null, "example1").to_rfc822_address(), + "example1" + ); + assert_equal( + new MailboxAddress.imap(null, null, "example2", "").to_rfc822_address(), + "example2" + ); + assert_equal( + new MailboxAddress.imap(null, null, "", "example3").to_rfc822_address(), + "@example3" + ); + assert_equal( + new MailboxAddress.imap(null, null, "", "").to_rfc822_address(), + "" + ); } public void to_rfc822_string() throws GLib.Error { From 15a87be780405de06f4114067bfca74359d2942f Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Tue, 18 Aug 2020 15:10:20 +1000 Subject: [PATCH 4/8] Geary.Imap.ClientSession: Add property for the IMAP server's greeting --- .../imap/transport/imap-client-session.vala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/engine/imap/transport/imap-client-session.vala b/src/engine/imap/transport/imap-client-session.vala index 68e6f06e..27e84837 100644 --- a/src/engine/imap/transport/imap-client-session.vala +++ b/src/engine/imap/transport/imap-client-session.vala @@ -245,6 +245,14 @@ public class Geary.Imap.ClientSession : BaseObject, Logging.Source { get { return this.capabilities.has_capability(Capabilities.IDLE); } } + /** + * The server's greeting, if any. + * + * This will be null up until the session has successfully + * connected and the server has responded with a greeting. + */ + public StatusResponse? server_greeting { get; private set; default = null; } + /** The currently selected mailbox, if any. */ public MailboxSpecifier? selected_mailbox = null; @@ -846,9 +854,12 @@ public class Geary.Imap.ClientSession : BaseObject, Logging.Source { new_state = State.LOGOUT; } + this.server_greeting = status_response; + debug("Server greeting: %s", status_response.get_text()); + try { - connect_waiter.notify(); - } catch (Error err) { + this.connect_waiter.notify(); + } catch (GLib.Error err) { warning( "Unable to notify connect_waiter of connection: %s", err.message From ebf7a8ad1dc54bf3397e8b85b7f7052caa893b68 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Tue, 18 Aug 2020 15:12:26 +1000 Subject: [PATCH 5/8] Geary.Imap: Update IMAP quirks based on server greeting rather type Use server greeting to update IMAP quirks, so that if e.g. a GMail account is configured as a generic account, it still gets the right quirks. --- src/engine/api/geary-engine.vala | 5 +-- src/engine/imap/api/imap-client-service.vala | 34 +-------------- src/engine/imap/api/imap-quirks.vala | 42 +++++++++++++++++++ .../transport/imap-deserializer-test.vala | 10 +++-- test/integration/imap/client-session.vala | 4 +- 5 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala index 749f0ad5..69089c08 100644 --- a/src/engine/api/geary-engine.vala +++ b/src/engine/api/geary-engine.vala @@ -284,10 +284,7 @@ public class Geary.Engine : BaseObject { (security, cx) => account.untrusted_host(service, security, cx) ); - var client = new Imap.ClientSession( - endpoint, - Imap.ClientService.new_quirks_for_provider(account.service_provider) - ); + var client = new Imap.ClientSession(endpoint, new Imap.Quirks()); GLib.Error? imap_err = null; try { yield client.connect_async( diff --git a/src/engine/imap/api/imap-client-service.vala b/src/engine/imap/api/imap-client-service.vala index 21a2e4aa..044a7c05 100644 --- a/src/engine/imap/api/imap-client-service.vala +++ b/src/engine/imap/api/imap-client-service.vala @@ -39,36 +39,6 @@ public class Geary.Imap.ClientService : Geary.ClientService { private const int CHECK_NOOP_THRESHOLD_SEC = 5; - public static Quirks new_quirks_for_provider(ServiceProvider provider) { - var quirks = new Quirks(); - switch (provider) { - case GMAIL: - // As of 2020-05-02, GMail doesn't seem to quote flag - // atoms containing reserved characters, and at least one - // use of both `]` and ` ` have been found. This works - // around the former. See #746 - quirks.flag_atom_exceptions = "]"; - break; - - case ServiceProvider.OUTLOOK: - // As of June 2016, outlook.com's IMAP servers have a bug - // where a large number (~50) of pipelined STATUS commands - // on mailboxes with many messages will eventually cause - // it to break command parsing and return a BAD response, - // causing us to drop the connection. Limit the number of - // pipelined commands per batch to work around this. See - // b.g.o Bug 766552 - quirks.max_pipeline_batch_size = 25; - break; - - default: - // noop - break; - } - return quirks; - } - - /** * Set to zero or negative value if keepalives should be disabled when a connection has not * selected a mailbox. (This is not recommended.) @@ -131,7 +101,7 @@ public class Geary.Imap.ClientService : Geary.ClientService { get { return LOGGING_DOMAIN; } } - private Quirks quirks; + private Quirks quirks = new Quirks(); private Nonblocking.Mutex sessions_mutex = new Nonblocking.Mutex(); private Gee.Set all_sessions = @@ -147,7 +117,6 @@ public class Geary.Imap.ClientService : Geary.ClientService { ServiceInformation configuration, Endpoint remote) { base(account, configuration, remote); - this.quirks = new_quirks_for_provider(account.service_provider); } /** @@ -403,6 +372,7 @@ public class Geary.Imap.ClientService : Geary.ClientService { // An error was thrown, so close the pool this.close_pool.begin(true); } else { + this.quirks.update_for_server(new_session); try { yield this.sessions_mutex.execute_locked(() => { this.all_sessions.add(new_session); diff --git a/src/engine/imap/api/imap-quirks.vala b/src/engine/imap/api/imap-quirks.vala index 79ef3267..61fe9cd7 100644 --- a/src/engine/imap/api/imap-quirks.vala +++ b/src/engine/imap/api/imap-quirks.vala @@ -39,4 +39,46 @@ public class Geary.Imap.Quirks : BaseObject { public uint max_pipeline_batch_size { get; set; default = 0; } + + public void update_for_server(ClientSession session) { + if (session.server_greeting != null) { + var greeting = session.server_greeting.get_text() ?? ""; + if (greeting.has_prefix("Gimap")) { + update_for_gmail(); + } else if (greeting.has_prefix("The Microsoft Exchange")) { + update_for_outlook(); + } + } + } + + /** + * Updates this quirks object with known quirks for GMail. + * + * As of 2020-05-02, GMail doesn't seem to quote flag + * atoms containing reserved characters, and at least one + * use of both `]` and ` ` have been found. This works + * around the former. + * + * See [[https://gitlab.gnome.org/GNOME/geary/-/issues/746]] + */ + public void update_for_gmail() { + this.flag_atom_exceptions = "]"; + } + + /** + * Updates this quirks object with known quirks for Outlook.com. + * + * As of June 2016, outlook.com's IMAP servers have a bug where a + * large number (~50) of pipelined STATUS commands on mailboxes + * with many messages will eventually cause it to break command + * parsing and return a BAD response, causing us to drop the + * connection. Limit the number of pipelined commands per batch to + * work around this. + * + * See [[https://bugzilla.gnome.org/show_bug.cgi?id=766552]] + */ + public void update_for_outlook() { + this.max_pipeline_batch_size = 25; + } + } diff --git a/test/engine/imap/transport/imap-deserializer-test.vala b/test/engine/imap/transport/imap-deserializer-test.vala index b9c456d8..e5e642df 100644 --- a/test/engine/imap/transport/imap-deserializer-test.vala +++ b/test/engine/imap/transport/imap-deserializer-test.vala @@ -202,7 +202,6 @@ class Geary.Imap.DeserializerTest : TestCase { string greeting = "* OK Gimap ready for requests from 115.187.245.46 c194mb399904375ivc"; this.stream.add_data(greeting.data); this.stream.add_data(EOL.data); - this.deser.quirks = ClientService.new_quirks_for_provider(GMAIL); this.process.begin(Expect.MESSAGE, this.async_completion); RootParameters? message = this.process.end(async_result()); @@ -249,7 +248,8 @@ class Geary.Imap.DeserializerTest : TestCase { string flags = """* FLAGS (\Answered \Flagged \Draft \Deleted \Seen $NotPhishing $Phishing)"""; this.stream.add_data(flags.data); this.stream.add_data(EOL.data); - this.deser.quirks = ClientService.new_quirks_for_provider(GMAIL); + this.deser.quirks = new Imap.Quirks(); + this.deser.quirks.update_for_gmail(); this.process.begin(Expect.MESSAGE, this.async_completion); RootParameters? message = this.process.end(async_result()); @@ -261,7 +261,8 @@ class Geary.Imap.DeserializerTest : TestCase { string flags = """* OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen $NotPhishing $Phishing \*)] Flags permitted."""; this.stream.add_data(flags.data); this.stream.add_data(EOL.data); - this.deser.quirks = ClientService.new_quirks_for_provider(GMAIL); + this.deser.quirks = new Imap.Quirks(); + this.deser.quirks.update_for_gmail(); this.process.begin(Expect.MESSAGE, this.async_completion); RootParameters? message = this.process.end(async_result()); @@ -275,7 +276,8 @@ class Geary.Imap.DeserializerTest : TestCase { string flags = """* FLAGS (\Answered \Flagged \Draft \Deleted \Seen $Forwarded $MDNSent $NotPhishing $Phishing Junk LoadRemoteImages NonJunk OIB-Seen-INBOX OIB-Seen-Unsubscribe OIB-Seen-[Gmail]/Important OIB-Seen-[Gmail]/Spam OIB-Seen-[Gmail]/Tous les messages)"""; this.stream.add_data(flags.data); this.stream.add_data(EOL.data); - this.deser.quirks = ClientService.new_quirks_for_provider(GMAIL); + this.deser.quirks = new Imap.Quirks(); + this.deser.quirks.update_for_gmail(); this.process.begin(Expect.MESSAGE, this.async_completion); RootParameters? message = this.process.end(async_result()); diff --git a/test/integration/imap/client-session.vala b/test/integration/imap/client-session.vala index ad48e545..ca813b79 100644 --- a/test/integration/imap/client-session.vala +++ b/test/integration/imap/client-session.vala @@ -29,9 +29,7 @@ class Integration.Imap.ClientSession : TestCase { public override void set_up() { this.session = new Geary.Imap.ClientSession( this.config.target, - Geary.Imap.ClientService.new_quirks_for_provider( - this.config.provider - ) + new Geary.Imap.Quirks() ); } From 757c0542dc748c0b22208a93a5744ad9b38fee95 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Mon, 17 Aug 2020 13:05:31 +1000 Subject: [PATCH 6/8] Geary.Imap.ServerResponse: Add quirks property Add a quirks object as a property so code that parses the response has access to it. Simplify constructing server responses slightly and pass the IMAP connection's quirks when doing so. --- .../response/imap-continuation-response.vala | 14 +++--- .../imap/response/imap-server-data.vala | 9 ++-- .../imap/response/imap-server-response.vala | 43 ++++++------------- .../imap/response/imap-status-response.vala | 14 +++--- .../transport/imap-client-connection.vala | 24 +++++------ .../imap-namespace-response-test.vala | 2 +- 6 files changed, 46 insertions(+), 60 deletions(-) diff --git a/src/engine/imap/response/imap-continuation-response.vala b/src/engine/imap/response/imap-continuation-response.vala index 8fc0a7cc..d15fe45f 100644 --- a/src/engine/imap/response/imap-continuation-response.vala +++ b/src/engine/imap/response/imap-continuation-response.vala @@ -15,8 +15,10 @@ */ public class Geary.Imap.ContinuationResponse : ServerResponse { - private ContinuationResponse() { - base (Tag.get_continuation()); + + + private ContinuationResponse(Quirks quirks) { + base(Tag.get_continuation(), quirks); } /** @@ -25,9 +27,9 @@ public class Geary.Imap.ContinuationResponse : ServerResponse { * The supplied root is "stripped" of its children. This may happen even if an exception is * thrown. It's recommended to use {@link is_continuation_response} prior to this call. */ - public ContinuationResponse.migrate(RootParameters root) throws ImapError { - base.migrate(root); - + public ContinuationResponse.migrate(RootParameters root, Quirks quirks) + throws ImapError { + base.migrate(root, quirks); if (!tag.is_continuation()) throw new ImapError.INVALID("Tag %s is not a continuation", tag.to_string()); } @@ -37,8 +39,6 @@ public class Geary.Imap.ContinuationResponse : ServerResponse { */ public static bool is_continuation_response(RootParameters root) { Tag? tag = root.get_tag(); - return tag != null ? tag.is_continuation() : false; } } - diff --git a/src/engine/imap/response/imap-server-data.vala b/src/engine/imap/response/imap-server-data.vala index 29aee4b5..f9e0edc7 100644 --- a/src/engine/imap/response/imap-server-data.vala +++ b/src/engine/imap/response/imap-server-data.vala @@ -13,8 +13,8 @@ public class Geary.Imap.ServerData : ServerResponse { public ServerDataType server_data_type { get; private set; } - private ServerData(Tag tag, ServerDataType server_data_type) { - base (tag); + private ServerData(Tag tag, ServerDataType server_data_type, Quirks quirks) { + base(tag, quirks); this.server_data_type = server_data_type; } @@ -25,8 +25,9 @@ public class Geary.Imap.ServerData : ServerResponse { * The supplied root is "stripped" of its children. This may happen even if an exception is * thrown. It's recommended to use {@link is_server_data} prior to this call. */ - public ServerData.migrate(RootParameters root) throws ImapError { - base.migrate(root); + public ServerData.migrate(RootParameters root, Quirks quirks) + throws ImapError { + base.migrate(root, quirks); server_data_type = ServerDataType.from_response(this); } diff --git a/src/engine/imap/response/imap-server-response.vala b/src/engine/imap/response/imap-server-response.vala index 7d2ace51..5f33568f 100644 --- a/src/engine/imap/response/imap-server-response.vala +++ b/src/engine/imap/response/imap-server-response.vala @@ -14,10 +14,15 @@ */ public abstract class Geary.Imap.ServerResponse : RootParameters { - public Tag tag { get; private set; } - protected ServerResponse(Tag tag) { + + public Tag tag { get; private set; } + public Quirks quirks { get; private set; } + + + protected ServerResponse(Tag tag, Quirks quirks) { this.tag = tag; + this.quirks = quirks; } /** @@ -25,36 +30,16 @@ public abstract class Geary.Imap.ServerResponse : RootParameters { * * The supplied root is "stripped" of its children. */ - protected ServerResponse.migrate(RootParameters root) throws ImapError { + protected ServerResponse.migrate(RootParameters root, + Quirks quirks) + throws ImapError { base.migrate(root); + this.quirks = quirks; - if (!has_tag()) + if (!has_tag()) { throw new ImapError.INVALID("Server response does not have a tag token: %s", to_string()); - - tag = get_tag(); + } + this.tag = get_tag(); } - /** - * Migrate the contents of RootParameters into a new, properly-typed ServerResponse. - * - * The returned ServerResponse may be a {@link ContinuationResponse}, {@link ServerData}, - * or a generic {@link StatusResponse}. - * - * The RootParameters will be migrated and stripped clean upon exit. - * - * @throws ImapError.PARSE_ERROR if not a known form of ServerResponse. - */ - public static ServerResponse migrate_from_server(RootParameters root) throws ImapError { - if (ContinuationResponse.is_continuation_response(root)) - return new ContinuationResponse.migrate(root); - - if (StatusResponse.is_status_response(root)) - return new StatusResponse.migrate(root); - - if (ServerData.is_server_data(root)) - return new ServerData.migrate(root); - - throw new ImapError.PARSE_ERROR("Unknown server response: %s", root.to_string()); - } } - diff --git a/src/engine/imap/response/imap-status-response.vala b/src/engine/imap/response/imap-status-response.vala index 2b5c7336..25e2a545 100644 --- a/src/engine/imap/response/imap-status-response.vala +++ b/src/engine/imap/response/imap-status-response.vala @@ -11,8 +11,6 @@ * StatusResponses may be tagged or untagged, depending on their nature. * * See [[http://tools.ietf.org/html/rfc3501#section-7.1]] for more information. - * - * @see ServerResponse.migrate_from_server */ public class Geary.Imap.StatusResponse : ServerResponse { @@ -34,8 +32,11 @@ public class Geary.Imap.StatusResponse : ServerResponse { */ public ResponseCode? response_code { get; private set; } - private StatusResponse(Tag tag, Status status, ResponseCode? response_code) { - base (tag); + private StatusResponse(Tag tag, + Status status, + ResponseCode? response_code, + Quirks quirks) { + base(tag, quirks); this.status = status; this.response_code = response_code; @@ -48,8 +49,9 @@ public class Geary.Imap.StatusResponse : ServerResponse { * The supplied root is "stripped" of its children. This may happen even if an exception is * thrown. It's recommended to use {@link is_status_response} prior to this call. */ - public StatusResponse.migrate(RootParameters root) throws ImapError { - base.migrate(root); + public StatusResponse.migrate(RootParameters root, Quirks quirks) + throws ImapError { + base.migrate(root, quirks); status = Status.from_parameter(get_as_string(1)); response_code = get_if_list(2) as ResponseCode; diff --git a/src/engine/imap/transport/imap-client-connection.vala b/src/engine/imap/transport/imap-client-connection.vala index 756d7bb7..927ac3bd 100644 --- a/src/engine/imap/transport/imap-client-connection.vala +++ b/src/engine/imap/transport/imap-client-connection.vala @@ -467,26 +467,24 @@ public class Geary.Imap.ClientConnection : BaseObject, Logging.Source { private void on_parameters_ready(RootParameters root) { try { - ServerResponse response = ServerResponse.migrate_from_server(root); - GLib.Type type = response.get_type(); - if (type == typeof(StatusResponse)) { - on_status_response((StatusResponse) response); - } else if (type == typeof(ServerData)) { - on_server_data((ServerData) response); - } else if (type == typeof(ContinuationResponse)) { - on_continuation_response((ContinuationResponse) response); + // Important! The order of these tests matters. + if (ContinuationResponse.is_continuation_response(root)) { + on_continuation_response( + new ContinuationResponse.migrate(root, this.quirks) + ); + } else if (StatusResponse.is_status_response(root)) { + on_status_response(new StatusResponse.migrate(root, this.quirks)); + } else if (ServerData.is_server_data(root)) { + on_server_data(new ServerData.migrate(root, this.quirks)); } else { - warning( - "Unknown ServerResponse of type %s received: %s:", - response.get_type().name(), - response.to_string() + throw new ImapError.PARSE_ERROR( + "Unknown server response: %s", root.to_string() ); } } catch (ImapError err) { received_bad_response(root, err); } - if (this.pending_queue.is_empty && this.sent_queue.is_empty) { // There's nothing remaining to send, and every sent // command has been dealt with, so ready an IDLE command. diff --git a/test/engine/imap/response/imap-namespace-response-test.vala b/test/engine/imap/response/imap-namespace-response-test.vala index d0ea7761..2d8d18f5 100644 --- a/test/engine/imap/response/imap-namespace-response-test.vala +++ b/test/engine/imap/response/imap-namespace-response-test.vala @@ -126,7 +126,7 @@ class Geary.Imap.NamespaceResponseTest : TestCase { else root.add(shared); - return new ServerData.migrate(root); + return new ServerData.migrate(root, new Quirks()); } private ListParameter newNamespace(string prefix, string? delim) { From 31f10e2787ea08e2544606cd8f3a0b39c154b6f3 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Tue, 18 Aug 2020 11:40:14 +1000 Subject: [PATCH 7/8] Geary.Imap: Add quirk for Envelope address structure placeholders Some servers (e.g. Dovecot) use placeholder strings instead of the empty string (e.g. "MISSING_DOMAIN") in FETCH Envelope responses when a required address part (e.g. local part, domain) are empty. This adds a quirk that can be enabled for such servers to check for the placeholders and if found, replace them with empty strings. --- src/engine/imap/api/imap-quirks.vala | 20 +++ .../message/imap-fetch-data-specifier.vala | 4 +- .../imap/parameter/imap-list-parameter.vala | 11 ++ .../response/imap-fetch-data-decoder.vala | 28 +++- .../imap/response/imap-fetched-data.vala | 2 +- .../imap-fetch-data-decoder-test.vala | 135 ++++++++++++++++++ test/meson.build | 1 + test/test-engine.vala | 1 + 8 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 test/engine/imap/response/imap-fetch-data-decoder-test.vala diff --git a/src/engine/imap/api/imap-quirks.vala b/src/engine/imap/api/imap-quirks.vala index 61fe9cd7..b4443a95 100644 --- a/src/engine/imap/api/imap-quirks.vala +++ b/src/engine/imap/api/imap-quirks.vala @@ -38,6 +38,26 @@ public class Geary.Imap.Quirks : BaseObject { */ public uint max_pipeline_batch_size { get; set; default = 0; } + /** + * The value sent by the server for missing envelope mailbox local parts. + * + * IMAP FETCH ENVELOPE structures use NIL for the "mailbox name" + * part (in addition to a NIL "host name" part) as an end-of-list + * marker for RFC822 group syntax. To indicate a missing + * local-part in a non-group mailbox some mail servers use a + * string such as "MISSING_MAILBOX" rather than the empty string. + */ + public string empty_envelope_mailbox_name { get; set; default = ""; } + + /** + * The value sent by the server for missing envelope mailbox domains. + * + * IMAP FETCH ENVELOPE structures use NIL for the "host name" + * argument to indicate RFC822 group syntax. To indicate a missing + * some mail servers use a string such as "MISSING_DOMAIN" rather + * than the empty string. + */ + public string empty_envelope_host_name { get; set; default = ""; } public void update_for_server(ClientSession session) { diff --git a/src/engine/imap/message/imap-fetch-data-specifier.vala b/src/engine/imap/message/imap-fetch-data-specifier.vala index 5d05c674..7f60acc7 100644 --- a/src/engine/imap/message/imap-fetch-data-specifier.vala +++ b/src/engine/imap/message/imap-fetch-data-specifier.vala @@ -142,7 +142,7 @@ public enum Geary.Imap.FetchDataSpecifier { * * @return null if no FetchDataDecoder is associated with this value, or an invalid value. */ - public FetchDataDecoder? get_decoder() { + public FetchDataDecoder? get_decoder(Quirks quirks) { switch (this) { case UID: return new UIDDecoder(); @@ -151,7 +151,7 @@ public enum Geary.Imap.FetchDataSpecifier { return new MessageFlagsDecoder(); case ENVELOPE: - return new EnvelopeDecoder(); + return new EnvelopeDecoder(quirks); case INTERNALDATE: return new InternalDateDecoder(); diff --git a/src/engine/imap/parameter/imap-list-parameter.vala b/src/engine/imap/parameter/imap-list-parameter.vala index d580830a..777d42fc 100644 --- a/src/engine/imap/parameter/imap-list-parameter.vala +++ b/src/engine/imap/parameter/imap-list-parameter.vala @@ -29,6 +29,17 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter { private Gee.List list = new Gee.ArrayList(); + /** Constructs a new, empty list. */ + public ListParameter() { + // noop + } + + /** Constructs a new list wit a single parameter. */ + public ListParameter.single(Parameter param) { + base(); + add(param); + } + /** * Appends a parameter to the end of this list. * diff --git a/src/engine/imap/response/imap-fetch-data-decoder.vala b/src/engine/imap/response/imap-fetch-data-decoder.vala index 1c35d99a..4701a1da 100644 --- a/src/engine/imap/response/imap-fetch-data-decoder.vala +++ b/src/engine/imap/response/imap-fetch-data-decoder.vala @@ -124,8 +124,14 @@ public class Geary.Imap.RFC822SizeDecoder : Geary.Imap.FetchDataDecoder { } public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { - public EnvelopeDecoder() { - base (FetchDataSpecifier.ENVELOPE); + + + private Quirks quirks; + + + public EnvelopeDecoder(Quirks quirks) { + base(FetchDataSpecifier.ENVELOPE); + this.quirks = quirks; } protected override MessageData decode_list(ListParameter listp) throws ImapError { @@ -150,7 +156,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { try { sent_date = new RFC822.Date.from_rfc822_string(sent.ascii); } catch (GLib.Error err) { - debug( + warning( "Error parsing sent date from FETCH envelope: %s", err.message ); @@ -179,14 +185,22 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { ListParameter fields = listp.get_as_empty_list(ctr); StringParameter? name = fields.get_as_nullable_string(0); StringParameter? source_route = fields.get_as_nullable_string(1); - StringParameter mailbox = fields.get_as_empty_string(2); - StringParameter domain = fields.get_as_empty_string(3); + StringParameter? mailbox = fields.get_as_empty_string(2); + StringParameter? domain = fields.get_as_empty_string(3); + + if (mailbox.ascii == this.quirks.empty_envelope_mailbox_name) { + mailbox = null; + } + if (domain.ascii == this.quirks.empty_envelope_host_name) { + domain = null; + } Geary.RFC822.MailboxAddress addr = new Geary.RFC822.MailboxAddress.imap( (name != null) ? name.nullable_ascii : null, (source_route != null) ? source_route.nullable_ascii : null, - mailbox.ascii, - domain.ascii); + mailbox != null ? mailbox.ascii : "", + domain != null ? domain.ascii : "" + ); list.add(addr); } diff --git a/src/engine/imap/response/imap-fetched-data.vala b/src/engine/imap/response/imap-fetched-data.vala index 7bc47d57..7f377396 100644 --- a/src/engine/imap/response/imap-fetched-data.vala +++ b/src/engine/imap/response/imap-fetched-data.vala @@ -73,7 +73,7 @@ public class Geary.Imap.FetchedData : Object { fetched_data.body_data_map.set(specifier, Memory.EmptyBuffer.instance); } else { FetchDataSpecifier data_item = FetchDataSpecifier.from_parameter(data_item_param); - FetchDataDecoder? decoder = data_item.get_decoder(); + FetchDataDecoder? decoder = data_item.get_decoder(server_data.quirks); if (decoder == null) { debug("Unable to decode fetch response for \"%s\": No decoder available", data_item.to_string()); diff --git a/test/engine/imap/response/imap-fetch-data-decoder-test.vala b/test/engine/imap/response/imap-fetch-data-decoder-test.vala new file mode 100644 index 00000000..c80c91ee --- /dev/null +++ b/test/engine/imap/response/imap-fetch-data-decoder-test.vala @@ -0,0 +1,135 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.Imap.FetchDataDecoderTest : TestCase { + + + public FetchDataDecoderTest() { + base("Geary.Imap.FetchDataDecoderTest"); + add_test("envelope_basic", envelope_basic); + add_test( + "envelope_mailbox_missing_mailbox_name_quirk", + envelope_mailbox_missing_mailbox_name_quirk + ); + add_test( + "envelope_mailbox_missing_host_name_quirk", + envelope_mailbox_missing_host_name_quirk + ); + } + + public void envelope_basic() throws GLib.Error { + ListParameter env = new ListParameter(); + env.add(new QuotedStringParameter("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)")); + env.add(new QuotedStringParameter("Test subject")); + + // From + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + + // Sender + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + + // Reply-To + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + + env.add(new ListParameter.single(new_mailbox_structure("To", "to", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("Cc", "cc", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("Bcc", "bcc", "example.com"))); + + // In-Reply-To + env.add(new QuotedStringParameter("<1234@example.com>")); + + // Message-Id + env.add(new QuotedStringParameter("<5678@example.com>")); + + var test_article = new EnvelopeDecoder(new Quirks()); + var decoded_generic = test_article.decode(env); + var decoded = decoded_generic as Envelope; + + assert_non_null(decoded, "decoded type"); + assert_non_null(decoded.sent, "decoded sent"); + assert_equal(decoded.subject.value, "Test subject"); + assert_equal(decoded.from.to_rfc822_string(), "From "); + assert_equal(decoded.sender.to_rfc822_string(), "From "); + assert_equal(decoded.reply_to.to_rfc822_string(), "From "); + + assert_non_null(decoded.to, "to"); + assert_equal(decoded.to.to_rfc822_string(), "To "); + + assert_non_null(decoded.cc, "cc"); + assert_equal(decoded.cc.to_rfc822_string(), "Cc "); + + assert_non_null(decoded.bcc, "bcc"); + assert_equal(decoded.bcc.to_rfc822_string(), "Bcc "); + + assert_non_null(decoded.in_reply_to, "in_reply_to"); + assert_equal(decoded.in_reply_to.to_rfc822_string(), "<1234@example.com>"); + + assert_non_null(decoded.message_id, "message_id"); + assert_equal(decoded.message_id.to_rfc822_string(), "<5678@example.com>"); + } + + public void envelope_mailbox_missing_mailbox_name_quirk() throws GLib.Error { + ListParameter env = new ListParameter(); + env.add(new QuotedStringParameter("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)")); + env.add(new QuotedStringParameter("Test subject")); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + + env.add(new ListParameter.single(new_mailbox_structure("To", "BOGUS", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("Cc", "cc", "example.com"))); + env.add(NilParameter.instance); + env.add(NilParameter.instance); + env.add(NilParameter.instance); + + var quirks = new Quirks(); + quirks.empty_envelope_mailbox_name = "BOGUS"; + + var test_article = new EnvelopeDecoder(quirks); + var decoded = test_article.decode(env) as Envelope; + + assert_non_null(decoded.to, "to"); + assert_equal(decoded.to.to_rfc822_string(), "To <@example.com>"); + assert_non_null(decoded.cc, "cc"); + assert_equal(decoded.cc.to_rfc822_string(), "Cc "); + } + + public void envelope_mailbox_missing_host_name_quirk() throws GLib.Error { + ListParameter env = new ListParameter(); + env.add(new QuotedStringParameter("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)")); + env.add(new QuotedStringParameter("Test subject")); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + env.add(new ListParameter.single(new_mailbox_structure("From", "from", "example.com"))); + + env.add(new ListParameter.single(new_mailbox_structure("To name", "to", "BOGUS"))); + env.add(new ListParameter.single(new_mailbox_structure("Cc", "cc", "example.com"))); + env.add(NilParameter.instance); + env.add(NilParameter.instance); + env.add(NilParameter.instance); + + var quirks = new Quirks(); + quirks.empty_envelope_host_name = "BOGUS"; + + var test_article = new EnvelopeDecoder(quirks); + var decoded = test_article.decode(env) as Envelope; + + assert_non_null(decoded.to, "to"); + assert_equal(decoded.to.to_rfc822_string(), "To name "); + assert_non_null(decoded.cc, "cc"); + assert_equal(decoded.cc.to_rfc822_string(), "Cc "); + } + + private ListParameter new_mailbox_structure(string name, string local, string domain) { + ListParameter mailbox = new ListParameter(); + mailbox.add(new QuotedStringParameter(name)); + mailbox.add(NilParameter.instance); + mailbox.add(new QuotedStringParameter(local)); + mailbox.add(new QuotedStringParameter(domain)); + return mailbox; + } +} diff --git a/test/meson.build b/test/meson.build index a2fc8a62..6ea5e27a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -43,6 +43,7 @@ test_engine_sources = [ 'engine/imap/message/imap-data-format-test.vala', 'engine/imap/message/imap-mailbox-specifier-test.vala', 'engine/imap/parameter/imap-list-parameter-test.vala', + 'engine/imap/response/imap-fetch-data-decoder-test.vala', 'engine/imap/response/imap-namespace-response-test.vala', 'engine/imap/transport/imap-client-connection-test.vala', 'engine/imap/transport/imap-client-session-test.vala', diff --git a/test/test-engine.vala b/test/test-engine.vala index 483163de..0260ef6a 100644 --- a/test/test-engine.vala +++ b/test/test-engine.vala @@ -56,6 +56,7 @@ int main(string[] args) { engine.add_suite(new Geary.Imap.CreateCommandTest().suite); engine.add_suite(new Geary.Imap.FetchCommandTest().suite); + engine.add_suite(new Geary.Imap.FetchDataDecoderTest().suite); engine.add_suite(new Geary.Imap.ListParameterTest().suite); engine.add_suite(new Geary.Imap.MailboxSpecifierTest().suite); engine.add_suite(new Geary.Imap.NamespaceResponseTest().suite); From 50f2ca98b519836eb4e8526748d6859c83fa4abf Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Tue, 18 Aug 2020 15:13:56 +1000 Subject: [PATCH 8/8] Geary.Imap.Quirks: Add quirks for Dovecot Dovecot 2.3.4.1 and earlier uses "MISSING_MAILBOX" and "MISSING_DOMAIN" in the address structures of FETCH ENVELOPE replies when the mailbox or domain is missing. See https://dovecot.org/pipermail/dovecot/2020-August/119658.html --- src/engine/imap/api/imap-quirks.vala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/engine/imap/api/imap-quirks.vala b/src/engine/imap/api/imap-quirks.vala index b4443a95..c24ae55b 100644 --- a/src/engine/imap/api/imap-quirks.vala +++ b/src/engine/imap/api/imap-quirks.vala @@ -67,6 +67,8 @@ public class Geary.Imap.Quirks : BaseObject { update_for_gmail(); } else if (greeting.has_prefix("The Microsoft Exchange")) { update_for_outlook(); + } else if (greeting.has_prefix("Dovecot")) { + update_for_dovecot(); } } } @@ -101,4 +103,18 @@ public class Geary.Imap.Quirks : BaseObject { this.max_pipeline_batch_size = 25; } + /** + * Updates this quirks object with known quirks for Dovecot + * + * Dovecot 2.3.4.1 and earlier uses "MISSING_MAILBOX" and + * "MISSING_DOMAIN" in the address structures of FETCH ENVELOPE + * replies when the mailbox or domain is missing. + * + * See [[https://dovecot.org/pipermail/dovecot/2020-August/119658.html]] + */ + public void update_for_dovecot() { + this.empty_envelope_mailbox_name = "MISSING_MAILBOX"; + this.empty_envelope_host_name = "MISSING_DOMAIN"; + } + }