diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index c4de9597..26df61ef 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -9,6 +9,11 @@ /** * An immutable representation of an RFC 822 mailbox address. * + * The properties of this class such as {@link name} and {@link + * address} are stores decoded UTF-8, thus they must be re-encoded + * using methods such as {@link to_rfc822_string} before being re-used + * in a message envelope. + * * See [[https://tools.ietf.org/html/rfc5322#section-3.4]] */ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageData, @@ -31,40 +36,52 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa internal delegate string ListToStringDelegate(MailboxAddress address); - + + /** - * The optional user-friendly name associated with the {@link MailboxAddress}. + * The optional human-readable part of the mailbox address. * * For "Dirk Gently ", this would be "Dirk Gently". + * + * The returned value has been decoded into UTF-8. */ public string? name { get; private set; } - + /** * The routing of the message (optional, obsolete). + * + * The returned value has been decoded into UTF-8. */ public string? source_route { get; private set; } - + /** - * The mailbox (local-part) portion of the {@link MailboxAddress}. + * The mailbox (local-part) portion of the mailbox's address. * * For "Dirk Gently ", this would be "dirk". + * + * The returned value has been decoded into UTF-8. */ public string mailbox { get; private set; } - + /** - * The domain portion of the {@link MailboxAddress}. + * The domain portion of the mailbox's address. * * For "Dirk Gently ", this would be "example.com". + * + * The returned value has been decoded into UTF-8. */ public string domain { get; private set; } - + /** - * The address specification of the {@link MailboxAddress}. + * The complete address part of the mailbox address. * * For "Dirk Gently ", this would be "dirk@example.com". + * + * The returned value has been decoded into UTF-8. */ public string address { get; private set; } + public MailboxAddress(string? name, string address) { this.name = name; this.address = address; @@ -293,14 +310,38 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa } /** - * Returns the address suitable for insertion into an RFC822 message. + * Returns the complete mailbox address, armoured for RFC 822 use. * - * @return the RFC822 quoted form of the full address. + * This method is similar to {@link to_full_display}, but only + * checks for a distinct address (per Postel's Law) and not for + * any spoofing, and does not strip extra white space or + * non-printing characters. + * + * @return the RFC822 encoded form of the full address. */ public string to_rfc822_string() { return has_distinct_name() - ? "%s <%s>".printf(GMime.utils_quote_string(this.name), this.address) - : this.address; + ? "%s <%s>".printf( + GMime.utils_header_encode_phrase(this.name), + to_rfc822_address() + ) + : to_rfc822_address(); + } + + /** + * Returns the address part only, armoured for RFC 822 use. + * + * @return the RFC822 encoded form of the address, without angle + * brackets. + */ + public string to_rfc822_address() { + return "%s@%s".printf( + // XXX utils_quote_string won't quote if spaces or quotes + // present, so need to do that manually + GMime.utils_quote_string(GMime.utils_header_encode_text(this.mailbox)), + // XXX Need to punycode international domains. + this.domain + ); } /** diff --git a/src/engine/smtp/smtp-request.vala b/src/engine/smtp/smtp-request.vala index c3db0139..fccf4355 100644 --- a/src/engine/smtp/smtp-request.vala +++ b/src/engine/smtp/smtp-request.vala @@ -58,7 +58,7 @@ public class Geary.Smtp.EhloRequest : Geary.Smtp.Request { public class Geary.Smtp.MailRequest : Geary.Smtp.Request { public MailRequest(Geary.RFC822.MailboxAddress from) { - base (Command.MAIL, { "from:%s".printf(from.to_address_display("<", ">")) }); + base (Command.MAIL, { "from:<%s>".printf(from.to_rfc822_address()) }); } public MailRequest.plain(string addr) { diff --git a/test/engine/rfc822-mailbox-address-test.vala b/test/engine/rfc822-mailbox-address-test.vala index 172bdda0..a7fe5a83 100644 --- a/test/engine/rfc822-mailbox-address-test.vala +++ b/test/engine/rfc822-mailbox-address-test.vala @@ -10,10 +10,13 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase { public MailboxAddressTest() { base("Geary.RFC822.MailboxAddressTest"); add_test("is_valid_address", is_valid_address); + add_test("unescaped_constructor", unescaped_constructor); add_test("is_spoofed", is_spoofed); add_test("has_distinct_name", has_distinct_name); add_test("to_full_display", to_full_display); add_test("to_short_display", to_short_display); + // latter depends on the former, so test that first + add_test("to_rfc822_address", to_rfc822_address); add_test("to_rfc822_string", to_rfc822_string); } @@ -35,6 +38,34 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase { assert(Geary.RFC822.MailboxAddress.is_valid_address("") == false); } + public void unescaped_constructor() { + MailboxAddress addr1 = new MailboxAddress("test1", "test2@example.com"); + assert(addr1.name == "test1"); + assert(addr1.address == "test2@example.com"); + assert(addr1.mailbox == "test2"); + assert(addr1.domain == "example.com"); + + MailboxAddress addr2 = new MailboxAddress(null, "test1@test2@example.com"); + assert(addr2.address == "test1@test2@example.com"); + assert(addr2.mailbox == "test1@test2"); + assert(addr2.domain == "example.com"); + + MailboxAddress addr3 = new MailboxAddress(null, "©@example.com"); + assert(addr3.address == "©@example.com"); + assert(addr3.mailbox == "©"); + assert(addr3.domain == "example.com"); + + MailboxAddress addr4 = new MailboxAddress(null, "😸@example.com"); + assert(addr4.address == "😸@example.com"); + assert(addr4.mailbox == "😸"); + assert(addr4.domain == "example.com"); + + MailboxAddress addr5 = new MailboxAddress(null, "example.com"); + assert(addr5.address == "example.com"); + assert(addr5.mailbox == ""); + assert(addr5.domain == ""); + } + public void is_spoofed() { assert(new MailboxAddress(null, "example@example.com").is_spoofed() == false); assert(new MailboxAddress("", "example@example.com").is_spoofed() == false); @@ -91,6 +122,23 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase { "example@example@example.com"); } + public void to_rfc822_address() { + assert(new MailboxAddress(null, "example@example.com").to_rfc822_address() == + "example@example.com"); + //assert(new MailboxAddress(null, "test test@example.com").to_rfc822_address() == + // "\"test test\"@example.com"); + //assert(new MailboxAddress(null, "test\" test@example.com").to_rfc822_address() == + // "\"test\" test\"@example.com"); + //assert(new MailboxAddress(null, "test\"test@example.com").to_rfc822_address() == + // "\"test\"test\"@example.com"); + assert(new MailboxAddress(null, "test@test@example.com").to_rfc822_address() == + "\"test@test\"@example.com"); + assert(new MailboxAddress(null, "©@example.com").to_rfc822_address() == + "\"=?iso-8859-1?b?qQ==?=\"@example.com"); + assert(new MailboxAddress(null, "😸@example.com").to_rfc822_address() == + "\"=?UTF-8?b?8J+YuA==?=\"@example.com"); + } + public void to_rfc822_string() { assert(new MailboxAddress("", "example@example.com").to_rfc822_string() == "example@example.com"); @@ -102,14 +150,16 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase { "test test "); assert(new MailboxAddress("example@example.com", "example@example.com").to_rfc822_string() == "example@example.com"); - // Technically, per - // https://tools.ietf.org/html/rfc5322#appendix-A.1.2 this - // would be fine as just "test? ", - // i.e. without the name being quoted, but I guess GMime is - // just being conservative here? assert(new MailboxAddress("test?", "example@example.com").to_rfc822_string() == - "\"test?\" "); + "test? "); + assert(new MailboxAddress("test@test", "example@example.com").to_rfc822_string() == + "\"test@test\" "); assert(new MailboxAddress(";", "example@example.com").to_rfc822_string() == "\";\" "); + assert(new MailboxAddress("©", "example@example.com").to_rfc822_string() == + "=?iso-8859-1?b?qQ==?= "); + assert(new MailboxAddress("😸", "example@example.com").to_rfc822_string() == + "=?UTF-8?b?8J+YuA==?= "); } + }