[geary/wip/791275-mailsploit-mitigation: 5/8] Ensure encoded mailbox addresses are decoded correctly.



commit 8a1906fa96239110b2948bab4ff0b98d20f0f766
Author: Michael James Gratton <mike vee net>
Date:   Wed Jan 31 11:41:03 2018 +1030

    Ensure encoded mailbox addresses are decoded correctly.
    
    Both RFC mailbox address names and mailboxes/local-names may by RFC 2047
    be Quoted-Printable or Base64 encoded. This patch ensures these parts are
    correctly decoded when parsing a RFC 822 message, so that they are
    displayed to the user in human-readable form.
    
    Part 2 of Mailsploit mitigation.
    
    * src/engine/rfc822/rfc822-message.vala (Message): Since GMime.Message's
      convenience properties for accessing header values such as senders,
      recipients, etc. in string form are presented as human-readable, not
      RFC822 compliant strings, we can't re-parse them for use by this class
      when it is being constructed from a GMime-based source. Instead,
      iterate over all headers to get the raw values and parse those we are
      interested in instead. Add unit tests.
    
    * src/engine/rfc822/rfc822-mailbox-address.vala (BaseObject): Add gmime
      constructor so we can handle construction and decoding from a GMime
      InternetAddressMailbox object in a consistent way. Ensure both names
      are decoded correctly, and mailboxes are decoded at all, from both
      GMime and IMAP sources. If a GMime source's address has no @-symbol,
      try to decode the whole thing first it in case the whole address is
      encoded. Add unit tests.
    
    * src/engine/rfc822/rfc822-mailbox-addresses.vala (MailboxAddresses):
      Add append method and handle group addresses here instead of in Message
      to simplify updated Message implementation.
    
    * src/engine/rfc822/rfc822-message-data.vala (MessageData): Add append
      method to simplify updated Message implementation.

 bindings/vapi/gmime-2.6.vapi                    |    2 +-
 src/engine/rfc822/rfc822-mailbox-address.vala   |  105 +++++++++-----
 src/engine/rfc822/rfc822-mailbox-addresses.vala |   44 +++++-
 src/engine/rfc822/rfc822-message-data.vala      |   12 ++-
 src/engine/rfc822/rfc822-message.vala           |  181 ++++++++++-------------
 test/engine/rfc822-mailbox-address-test.vala    |   82 ++++++++++
 test/engine/rfc822-message-test.vala            |   98 ++++++++++++-
 7 files changed, 378 insertions(+), 146 deletions(-)
---
diff --git a/bindings/vapi/gmime-2.6.vapi b/bindings/vapi/gmime-2.6.vapi
index 0bb1991..ae9458d 100644
--- a/bindings/vapi/gmime-2.6.vapi
+++ b/bindings/vapi/gmime-2.6.vapi
@@ -1376,7 +1376,7 @@ namespace GMime {
        [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_structured_header_fold")]
        public static string utils_structured_header_fold (string header);
        [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_text_is_8bit")]
-       public static bool utils_text_is_8bit (uint text, size_t len);
+       public static bool utils_text_is_8bit (string text, size_t len);
        [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_unquote_string")]
        public static void utils_unquote_string (string str);
        [CCode (cheader_filename = "gmime/gmime.h", cname = "g_mime_utils_unstructured_header_fold")]
diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala
index 26df61e..87733f7 100644
--- a/src/engine/rfc822/rfc822-mailbox-address.vala
+++ b/src/engine/rfc822/rfc822-mailbox-address.vala
@@ -34,6 +34,47 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
         }
     }
 
+    private static string decode_name(string name) {
+        return GMime.utils_header_decode_phrase(prepare_header_text_part(name));
+    }
+
+    private static string decode_address_part(string mailbox) {
+        return GMime.utils_header_decode_text(prepare_header_text_part(mailbox));
+    }
+
+    private static string prepare_header_text_part(string part) {
+        // Borrowed liberally from GMime's internal
+        // _internet_address_decode_name() function.
+
+        // see if a broken mailer has sent raw 8-bit information
+        string text = GMime.utils_text_is_8bit(part, part.length)
+            ? part : GMime.utils_decode_8bit(part, part.length);
+
+        // unquote the string then decode the text
+        GMime.utils_unquote_string(text);
+
+        // Sometimes quoted printables contain unencoded spaces which trips up GMime, so we want to
+        // encode them all here.
+        int offset = 0;
+        int start;
+        while ((start = text.index_of("=?", offset)) != -1) {
+            // Find the closing marker.
+            int end = text.index_of("?=", start + 2) + 2;
+            if (end == -1) {
+                end = text.length;
+            }
+
+            // Replace any spaces inside the encoded string.
+            string encoded = text.substring(start, end - start);
+            if (encoded.contains("\x20")) {
+                text = text.replace(encoded, encoded.replace("\x20", "_"));
+            }
+            offset = end;
+        }
+
+        return text;
+    }
+
 
     internal delegate string ListToStringDelegate(MailboxAddress address);
 
@@ -84,26 +125,24 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
 
     public MailboxAddress(string? name, string address) {
         this.name = name;
+        this.source_route = null;
         this.address = address;
 
-        source_route = null;
-
         int atsign = address.last_index_of_char('@');
         if (atsign > 0) {
-            mailbox = address.slice(0, atsign);
-            domain = address.slice(atsign + 1, address.length);
+            this.mailbox = address[0:atsign];
+            this.domain = address[atsign + 1:address.length];
         } else {
-            mailbox = "";
-            domain = "";
+            this.mailbox = "";
+            this.domain = "";
         }
     }
 
     public MailboxAddress.imap(string? name, string? source_route, string mailbox, string domain) {
         this.name = (name != null) ? decode_name(name) : null;
         this.source_route = source_route;
-        this.mailbox = mailbox;
+        this.mailbox = decode_address_part(mailbox);
         this.domain = domain;
-
         this.address = "%s@%s".printf(mailbox, domain);
     }
 
@@ -119,41 +158,39 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
             // TODO: Handle group lists
             InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox;
             if (mbox_addr != null) {
-                this(mbox_addr.get_name(), mbox_addr.get_addr());
+                this.gmime(mbox_addr);
                 return;
             }
         }
         throw new RFC822Error.INVALID("Could not parse RFC822 address: %s", rfc822);
     }
 
-    // Borrowed liberally from GMime's internal _internet_address_decode_name() function.
-    private static string decode_name(string name) {
-        // see if a broken mailer has sent raw 8-bit information
-        string text = name.validate() ? name : GMime.utils_decode_8bit(name, name.length);
-
-        // unquote the string and decode the text
-        GMime.utils_unquote_string(text);
-
-        // Sometimes quoted printables contain unencoded spaces which trips up GMime, so we want to
-        // encode them all here.
-        int offset = 0;
-        int start;
-        while ((start = text.index_of("=?", offset)) != -1) {
-            // Find the closing marker.
-            int end = text.index_of("?=", start + 2) + 2;
-            if (end == -1) {
-                end = text.length;
-            }
+    public MailboxAddress.gmime(InternetAddressMailbox mailbox) {
+        // GMime strips source route for us, so the address part
+        // should only ever contain a single '@'
+        string? name = mailbox.get_name();
+        if (name != null) {
+            this.name = decode_name(name);
+        }
 
-            // Replace any spaces inside the encoded string.
-            string encoded = text.substring(start, end - start);
-            if (encoded.contains("\x20")) {
-                text = text.replace(encoded, encoded.replace("\x20", "_"));
-            }
-            offset = end;
+        string address = mailbox.get_addr();
+        int atsign = address.last_index_of_char('@');
+        if (atsign == -1) {
+            // No @ detected, try decoding in case a mailer (wrongly)
+            // encoded the whole thing and re-try
+            address = decode_address_part(address);
+            atsign = address.last_index_of_char('@');
         }
 
-        return GMime.utils_header_decode_text(text);
+        if (atsign >= 0) {
+            this.mailbox = decode_address_part(address[0:atsign]);
+            this.domain = address[atsign + 1:address.length];
+            this.address = "%s@%s".printf(this.mailbox, this.domain);
+        } else {
+            this.mailbox = "";
+            this.domain = "";
+            this.address = address;
+        }
     }
 
     /**
diff --git a/src/engine/rfc822/rfc822-mailbox-addresses.vala b/src/engine/rfc822/rfc822-mailbox-addresses.vala
index b5f39b1..2667ff1 100644
--- a/src/engine/rfc822/rfc822-mailbox-addresses.vala
+++ b/src/engine/rfc822/rfc822-mailbox-addresses.vala
@@ -4,6 +4,14 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
+/**
+ * An immutable representation an RFC 822 address list.
+ *
+ * This would typically be found as the value of the To, CC, BCC and
+ * other headers fields.
+ *
+ * See [[https://tools.ietf.org/html/rfc5322#section-3.4]]
+ */
 public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageData, 
     Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData, Gee.Hashable<MailboxAddresses> {
     
@@ -27,13 +35,26 @@ public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageDa
         int length = addrlist.length();
         for (int ctr = 0; ctr < length; ctr++) {
             InternetAddress? addr = addrlist.get_address(ctr);
-            
-            // TODO: Handle group lists
+
             InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox;
-            if (mbox_addr == null)
-                continue;
-            
-            addrs.add(new MailboxAddress(mbox_addr.get_name(), mbox_addr.get_addr()));
+            if (mbox_addr != null) {
+                this.addrs.add(new MailboxAddress.gmime(mbox_addr));
+            } else {
+                // XXX this is pretty bad - we just flatten the
+                // group's addresses into this list, merging lists and
+                // losing the group names.
+                InternetAddressGroup? mbox_group = addr as InternetAddressGroup;
+                if (mbox_group != null) {
+                    InternetAddressList group_list = mbox_group.get_members();
+                    for (int i = 0; i < group_list.length(); i++) {
+                        InternetAddressMailbox? group_addr =
+                            addrlist.get_address(i) as InternetAddressMailbox;
+                        if (group_addr != null) {
+                            this.addrs.add(new MailboxAddress.gmime(group_addr));
+                        }
+                    }
+                }
+            }
         }
     }
     
@@ -73,7 +94,16 @@ public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageDa
         
         return false;
     }
-    
+
+    /**
+     * Returns a new list with the given addresses appended to this list's.
+     */
+    public MailboxAddresses append(MailboxAddresses others) {
+        MailboxAddresses new_addrs = new MailboxAddresses(this.addrs);
+        new_addrs.addrs.add_all(others.addrs);
+        return new_addrs;
+    }
+
     /**
      * Returns the addresses suitable for insertion into an RFC822 message.  RFC822 quoting is
      * performed if required.
diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala
index 44593f8..adb9da9 100644
--- a/src/engine/rfc822/rfc822-message-data.vala
+++ b/src/engine/rfc822/rfc822-message-data.vala
@@ -142,7 +142,17 @@ public class Geary.RFC822.MessageIDList : Geary.MessageData.AbstractMessageData,
         // don't assert that list.size > 0; even though this method should generated a decoded ID
         // from any non-empty string, an empty Message-ID (i.e. "<>") won't.
     }
-    
+
+    /**
+     * Returns a new list with the given messages ids appended to this list's.
+     */
+    public MessageIDList append(MessageIDList others) {
+        MessageIDList new_ids = new MessageIDList();
+        new_ids.list.add_all(this.list);
+        new_ids.list.add_all(others.list);
+        return new_ids;
+    }
+
     public override string to_string() {
         return "MessageIDList (%d)".printf(list.size);
     }
diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala
index b374dc4..97ef351 100644
--- a/src/engine/rfc822/rfc822-message.vala
+++ b/src/engine/rfc822/rfc822-message.vala
@@ -416,106 +416,6 @@ public class Geary.RFC822.Message : BaseObject {
         return null;
     }
 
-    private void stock_from_gmime() {
-        // GMime calls the From address the "sender"
-        string? message_sender = message.get_sender();
-        if (message_sender != null) {
-            this.from = new RFC822.MailboxAddresses.from_rfc822_string(message_sender);
-        }
-
-        // And it doesn't provide a convenience method for Sender header
-        if (!String.is_empty(message.get_header(HEADER_SENDER))) {
-            string sender = GMime.utils_header_decode_text(message.get_header(HEADER_SENDER));
-            try {
-                this.sender = new RFC822.MailboxAddress.from_rfc822_string(sender);
-            } catch (RFC822Error e) {
-                debug("Invalid RDC822 Sender address: %s", sender);
-            }
-        }
-
-        if (!String.is_empty(message.get_reply_to()))
-            this.reply_to = new RFC822.MailboxAddresses.from_rfc822_string(message.get_reply_to());
-
-        Gee.List<RFC822.MailboxAddress>? converted = convert_gmime_address_list(
-            message.get_recipients(GMime.RecipientType.TO));
-        if (converted != null && converted.size > 0)
-            to = new RFC822.MailboxAddresses(converted);
-        
-        converted = convert_gmime_address_list(message.get_recipients(GMime.RecipientType.CC));
-        if (converted != null && converted.size > 0)
-            cc = new RFC822.MailboxAddresses(converted);
-        
-        converted = convert_gmime_address_list(message.get_recipients(GMime.RecipientType.BCC));
-        if (converted != null && converted.size > 0)
-            bcc = new RFC822.MailboxAddresses(converted);
-        
-        if (!String.is_empty(message.get_header(HEADER_IN_REPLY_TO)))
-            in_reply_to = new 
RFC822.MessageIDList.from_rfc822_string(message.get_header(HEADER_IN_REPLY_TO));
-        
-        if (!String.is_empty(message.get_header(HEADER_REFERENCES)))
-            references = new RFC822.MessageIDList.from_rfc822_string(message.get_header(HEADER_REFERENCES));
-        
-        if (!String.is_empty(message.get_subject()))
-            subject = new RFC822.Subject.decode(message.get_subject());
-        
-        if (!String.is_empty(message.get_header(HEADER_MAILER)))
-            mailer = message.get_header(HEADER_MAILER);
-        
-        if (!String.is_empty(message.get_date_as_string())) {
-            try {
-                date = new Geary.RFC822.Date(message.get_date_as_string());
-            } catch (Error error) {
-                debug("Could not get date from message: %s", error.message);
-            }
-        }
-    }
-    
-    private Gee.List<RFC822.MailboxAddress>? convert_gmime_address_list(InternetAddressList? addrlist,
-        int depth = 0) {
-        if (addrlist == null || addrlist.length() == 0)
-            return null;
-        
-        Gee.List<RFC822.MailboxAddress>? converted = new Gee.ArrayList<RFC822.MailboxAddress>();
-        
-        int length = addrlist.length();
-        for (int ctr = 0; ctr < length; ctr++) {
-            InternetAddress addr = addrlist.get_address(ctr);
-            
-            InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox;
-            if (mbox_addr != null) {
-                converted.add(new RFC822.MailboxAddress(mbox_addr.get_name(), mbox_addr.get_addr()));
-                
-                continue;
-            }
-            
-            // Two problems here:
-            //
-            // First, GMime crashes when parsing a malformed group list (the case seen in the
-            // wild is -- weirdly enough -- a date appended to the end of a cc: list on a spam
-            // email.  GMime interprets it as a group list but segfaults when destroying the
-            // InterneAddresses it generated from it.  See:
-            // https://bugzilla.gnome.org/show_bug.cgi?id=695319
-            //
-            // Second, RFC 822 6.2.6: "This  standard  does  not  permit  recursive  specification
-            // of groups within groups."  So don't do it.
-            InternetAddressGroup? group = addr as InternetAddressGroup;
-            if (group != null) {
-                if (depth == 0) {
-                    Gee.List<RFC822.MailboxAddress>? grouplist = convert_gmime_address_list(
-                        group.get_members(), depth + 1);
-                    if (grouplist != null)
-                        converted.add_all(grouplist);
-                }
-                
-                continue;
-            }
-            
-            warning("Unknown InternetAddress in list: %s", addr.get_type().name());
-        }
-        
-        return (converted.size > 0) ? converted : null;
-    }
-    
     public Gee.List<RFC822.MailboxAddress>? get_recipients() {
         Gee.List<RFC822.MailboxAddress> addrs = new Gee.ArrayList<RFC822.MailboxAddress>();
         
@@ -873,7 +773,86 @@ public class Geary.RFC822.Message : BaseObject {
         get_attachments_recursively(attachments, message.get_mime_part(), disposition);
         return attachments;
     }
-    
+
+    private void stock_from_gmime() {
+        this.message.get_header_list().foreach((name, value) => {
+                switch (name.down()) {
+                case "from":
+                    this.from = append_address(this.from, value);
+                    break;
+
+                case "sender":
+                    try {
+                        this.sender = new RFC822.MailboxAddress.from_rfc822_string(value);
+                    } catch (Error err) {
+                        debug("Could parse subject: %s", err.message);
+                    }
+                    break;
+
+                case "reply-to":
+                    this.reply_to = append_address(this.reply_to, value);
+                    break;
+
+                case "to":
+                    this.to = append_address(this.to, value);
+                    break;
+
+                case "cc":
+                    this.cc = append_address(this.cc, value);
+                    break;
+
+                case "bcc":
+                    this.bcc = append_address(this.bcc, value);
+                    break;
+
+                case "subject":
+                    this.subject = new RFC822.Subject.decode(value);
+                    break;
+
+                case "date":
+                    try {
+                        this.date = new Geary.RFC822.Date(value);
+                    } catch (Error err) {
+                        debug("Could not parse date: %s", err.message);
+                    }
+                    break;
+
+                case "in-reply-to":
+                    this.in_reply_to = append_message_id(this.in_reply_to, value);
+                    break;
+
+                case "references":
+                    this.references = append_message_id(this.references, value);
+                    break;
+
+                case "x-mailer":
+                    this.mailer = GMime.utils_header_decode_text(value);
+                    break;
+
+                default:
+                    break;
+                }
+            });
+    }
+
+    private MailboxAddresses append_address(MailboxAddresses? existing,
+                                            string header_value) {
+        MailboxAddresses addresses = new MailboxAddresses.from_rfc822_string(header_value);
+        if (existing != null) {
+            addresses = existing.append(addresses);
+        }
+        return addresses;
+    }
+
+    private MessageIDList append_message_id(MessageIDList? existing,
+                                            string header_value) {
+        MessageIDList ids = new MessageIDList.from_rfc822_string(header_value);
+        if (existing != null) {
+            ids = existing.append(ids);
+        }
+        return ids;
+    }
+
     private void get_attachments_recursively(Gee.List<GMime.Part> attachments, GMime.Object root,
         Mime.DispositionType requested_disposition) throws RFC822Error {
         // If this is a multipart container, dive into each of its children.
diff --git a/test/engine/rfc822-mailbox-address-test.vala b/test/engine/rfc822-mailbox-address-test.vala
index a7fe5a8..f30480b 100644
--- a/test/engine/rfc822-mailbox-address-test.vala
+++ b/test/engine/rfc822-mailbox-address-test.vala
@@ -11,6 +11,7 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
         base("Geary.RFC822.MailboxAddressTest");
         add_test("is_valid_address", is_valid_address);
         add_test("unescaped_constructor", unescaped_constructor);
+        add_test("from_rfc822_string_encoded", from_rfc822_string_encoded);
         add_test("is_spoofed", is_spoofed);
         add_test("has_distinct_name", has_distinct_name);
         add_test("to_full_display", to_full_display);
@@ -66,6 +67,87 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
         assert(addr5.domain == "");
     }
 
+    public void from_rfc822_string_encoded() {
+        try {
+            MailboxAddress addr = new MailboxAddress.from_rfc822_string("test example com");
+            assert(addr.name == null);
+            assert(addr.mailbox == "test");
+            assert(addr.domain == "example.com");
+
+            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");
+
+            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");
+
+            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");
+
+            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");
+
+            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");
+
+            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");
+
+            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");
+
+            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");
+
+            addr = new MailboxAddress.from_rfc822_string("=?UTF-8?b?VGVzdCAx?= <test2 example com>");
+            assert(addr.name == "Test 1");
+            assert(addr.address == "test2 example com");
+            assert(addr.mailbox == "test2");
+            assert(addr.domain == "example.com");
+
+            addr = new MailboxAddress.from_rfc822_string("\"=?UTF-8?b?VGVzdCAx?=\" <test2 example com>");
+            assert(addr.name == "Test 1");
+            assert(addr.address == "test2 example com");
+            assert(addr.mailbox == "test2");
+            assert(addr.domain == "example.com");
+
+            // Courtesy Mailsploit https://www.mailsploit.com
+            addr = new 
MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" 
<demo mailsploit com>");
+            assert(addr.name == "test <potus whitehouse gov>?\n");
+            assert(addr.address == "demo mailsploit com");
+
+            // 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();
+        }
+    }
+
     public void is_spoofed() {
         assert(new MailboxAddress(null, "example example com").is_spoofed() == false);
         assert(new MailboxAddress("", "example example com").is_spoofed() == false);
diff --git a/test/engine/rfc822-message-test.vala b/test/engine/rfc822-message-test.vala
index bf555ae..664e4b6 100644
--- a/test/engine/rfc822-message-test.vala
+++ b/test/engine/rfc822-message-test.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016 Michael Gratton <mike vee net>
+ * Copyright 2016-2018 Michael Gratton <mike vee net>
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later). See the COPYING file in this distribution.
@@ -9,7 +9,72 @@ class Geary.RFC822.MessageTest : Gee.TestCase {
 
     public MessageTest() {
         base("Geary.RFC822.MessageTest");
-        add_test("Message::get_preview", get_preview);
+        add_test("basic_message_from_buffer", basic_message_from_buffer);
+        add_test("encoded_recipient", encoded_recipient);
+        add_test("duplicate_mailbox", duplicate_mailbox);
+        add_test("duplicate_message_id", duplicate_message_id);
+        add_test("get_preview", get_preview);
+    }
+
+    public void basic_message_from_buffer() {
+        Message? basic = null;
+        try {
+            basic = string_to_message(BASIC_MESSAGE);
+        } catch (Error err) {
+            assert_no_error(err);
+        }
+        assert_data(basic.subject, "Re: Saying Hello");
+        assert_addresses(basic.from, "Mary Smith <mary example net>");
+        assert_address(basic.sender, "Mary Smith Sender <mary example net>");
+        assert_addresses(basic.reply_to, "\"Mary Smith: Personal Account\" <smith@home.example>");
+        assert_addresses(basic.to, "John Doe <jdoe@machine.example>");
+        assert_addresses(basic.cc, "John Doe CC <jdoe@machine.example>");
+        assert_addresses(basic.bcc, "John Doe BCC <jdoe@machine.example>");
+        //assert_data(basic.message_id, "<3456 example net>");
+        assert_message_id_list(basic.in_reply_to, "<1234@local.machine.example>");
+        assert_message_id_list(basic.references, "<1234@local.machine.example>");
+        assert_data(basic.date, "Fri, 21 Nov 1997 10:01:10 -0600");
+        assert(basic.mailer == "Geary Test Suite 1.0");
+    }
+
+    public void encoded_recipient() {
+        Message? enc = null;
+        try {
+            enc = string_to_message(ENCODED_TO);
+        } catch (Error err) {
+            assert_no_error(err);
+        }
+
+        // Courtesy Mailsploit https://www.mailsploit.com
+        assert(enc.to[0].name == "potus whitehouse gov <test>");
+    }
+
+    public void duplicate_mailbox() {
+        Message? dup = null;
+        try {
+            dup = string_to_message(DUPLICATE_TO);
+        } catch (Error err) {
+            assert_no_error(err);
+        }
+
+        assert(dup.to.size == 2);
+        assert_addresses(
+            dup.to, "John Doe 1 <jdoe1@machine.example>, John Doe 2 <jdoe2@machine.example>"
+        );
+    }
+
+    public void duplicate_message_id() {
+        Message? dup = null;
+        try {
+            dup = string_to_message(DUPLICATE_REFERENCES);
+        } catch (Error err) {
+            assert_no_error(err);
+        }
+
+        assert(dup.references.list.size == 2);
+        assert_message_id_list(
+            dup.references, "<1234@local.machine.example> <5678@local.machine.example>"
+        );
     }
 
     public void get_preview() {
@@ -27,6 +92,35 @@ class Geary.RFC822.MessageTest : Gee.TestCase {
         );
     }
 
+    private void assert_data(Geary.MessageData.AbstractMessageData? data, string expected) {
+        assert(data != null);
+        assert(data.to_string() == expected);
+    }
+
+    private void assert_address(Geary.RFC822.MailboxAddress? address, string expected) {
+        assert(address != null);
+        assert(address.to_rfc822_string() == expected);
+    }
+
+    private void assert_addresses(Geary.RFC822.MailboxAddresses? addresses, string expected) {
+        assert(addresses != null);
+        assert(addresses.to_rfc822_string() == expected);
+    }
+
+    private void assert_message_id_list(Geary.RFC822.MessageIDList? ids, string expected) {
+        assert(ids != null);
+        assert(ids.to_rfc822_string() == expected);
+    }
+
+    private static string BASIC_MESSAGE = "From: Mary Smith <mary example net>\r\nSender: Mary Smith Sender 
<mary example net>\r\nTo: John Doe <jdoe@machine.example>\r\nCC: John Doe CC <jdoe@machine.example>\r\nBCC: 
John Doe BCC <jdoe@machine.example>\r\nReply-To: \"Mary Smith: Personal Account\" 
<smith@home.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\nMessage-ID: 
<3456 example net>\r\nIn-Reply-To: <1234@local.machine.example>\r\nReferences: 
<1234@local.machine.example>\r\nX-Mailer: Geary Test Suite 1.0\r\n\r\nThis is a reply to your hello.\r\n\r\n";
+
+    // Courtesy Mailsploit https://www.mailsploit.com
+    private static string ENCODED_TO = "From: Mary Smith <mary example net>\r\nTo: 
=?utf-8?b?cG90dXNAd2hpdGVob3VzZS5nb3YiIDx0ZXN0Pg==?= <jdoe@machine.example>\r\nSubject: Re: Saying 
Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n";
+
+    private static string DUPLICATE_TO = "From: Mary Smith <mary example net>\r\nTo: John Doe 1 
<jdoe1@machine.example>\r\nTo: John Doe 2 <jdoe2@machine.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 
21 Nov 1997 10:01:10 -0600\r\n\r\nThis is a reply to your hello.\r\n\r\n";
+
+    private static string DUPLICATE_REFERENCES = "From: Mary Smith <mary example net>\r\nTo: John Doe 
<jdoe@machine.example>\r\nReferences: <1234@local.machine.example>\r\nReferences: 
<5678@local.machine.example>\r\nSubject: Re: Saying Hello\r\nDate: Fri, 21 Nov 1997 10:01:10 
-0600\r\n\r\nThis is a reply to your hello.\r\n\r\n";
+
     private static string MULTIPART_SIGNED_MESSAGE_TEXT = "Return-Path: <ubuntu-security-announce-bounces 
lists ubuntu com>\r\nReceived: from mogul.quuxo.net ([unix socket])\r\n       by mogul (Cyrus 
v2.4.12-Debian-2.4.12-2) with LMTPA;\r\n        Wed, 21 Dec 2016 06:54:03 +1030\r\nX-Sieve: CMU Sieve 
2.4\r\nReceived: from huckleberry.canonical.com (huckleberry.canonical.com [91.189.94.19])\r\n   by 
mogul.quuxo.net (8.14.4/8.14.4/Debian-2ubuntu2.1) with ESMTP id uBKKNtpt026727\r\n   for <mike vee net>; Wed, 
21 Dec 2016 06:53:57 +1030\r\nReceived: from localhost ([127.0.0.1] helo=huckleberry.canonical.com)\r\n       
 by huckleberry.canonical.com with esmtp (Exim 4.76)\r\n (envelope-from <ubuntu-security-announce-bounces 
lists ubuntu com>)\r\n id 1cJQwM-0003Xk-IO; Tue, 20 Dec 2016 20:23:14 +0000\r\nReceived: from 
208-151-246-43.dq1sn.easystreet.com ([208.151.246.43]\r\n helo=lizaveta.nxnw.org)\r\n by 
huckleberry.canonical.com with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\r\n (Exim 4.76) (envelope
 -from <steve beattie canonical com>)\r\n id 1cJQin-0000t2-G6\r\n for ubuntu-security-announce lists ubuntu 
com; Tue, 20 Dec 2016 20:09:14 +0000\r\nReceived: from kryten.nxnw.org (kryten.nxnw.org [10.19.96.254])\r\n 
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\r\n (Client CN 
\"kryten.int.wirex.com\", Issuer \"nxnw.org\" (not verified))\r\n by lizaveta.nxnw.org (Postfix) with ESMTPS 
id DD8C360941\r\n for <ubuntu-security-announce lists ubuntu com>;\r\n Tue, 20 Dec 2016 12:09:06 -0800 
(PST)\r\nReceived: by kryten.nxnw.org (Postfix, from userid 1000)\r\n id 84341342F6C; Tue, 20 Dec 2016 
12:09:06 -0800 (PST)\r\nDate: Tue, 20 Dec 2016 12:09:06 -0800\r\nFrom: Steve Beattie <steve beattie canonical 
com>\r\nTo: ubuntu-security-announce lists ubuntu com\r\nSubject: [USN-3159-1] Linux kernel 
vulnerability\r\nMessage-ID: <20161220200906 GF8251 nxnw org>\r\nMail-Followup-To: Ubuntu Security <security 
ubuntu com>\r\nMIME-Version: 1.0\r\nUser-Agent: Mutt/1.5.24 (2
 015-08-30)\r\nX-Mailman-Approved-At: Tue, 20 Dec 2016 20:23:12 +0000\r\nX-BeenThere: 
ubuntu-security-announce lists ubuntu com\r\nX-Mailman-Version: 2.1.14\r\nPrecedence: list\r\nReply-To: 
ubuntu-users lists ubuntu com, Ubuntu Security <security ubuntu com>\r\nList-Id: Ubuntu Security 
Announcements\r\n <ubuntu-security-announce.lists.ubuntu.com>\r\nList-Unsubscribe: 
<https://lists.ubuntu.com/mailman/options/ubuntu-security-announce>, \r\n 
<mailto:ubuntu-security-announce-request lists ubuntu com?subject=unsubscribe>\r\nList-Archive: 
<https://lists.ubuntu.com/archives/ubuntu-security-announce>\r\nList-Post: <mailto:ubuntu-security-announce 
lists ubuntu com>\r\nList-Help: <mailto:ubuntu-security-announce-request lists ubuntu 
com?subject=help>\r\nList-Subscribe: <https://lists.ubuntu.com/mailman/listinfo/ubuntu-security-announce>, 
\r\n <mailto:ubuntu-security-announce-request lists ubuntu com?subject=subscribe>\r\nContent-Type: 
multipart/mixed; boundary=\"===============7564301
 068935298617==\"\r\nErrors-To: ubuntu-security-announce-bounces lists ubuntu com\r\nSender: 
ubuntu-security-announce-bounces lists ubuntu com\r\nX-Greylist: Sender IP whitelisted by DNSRBL, not delayed 
by milter-greylist-4.3.9 (mogul.quuxo.net [203.18.245.241]); Wed, 21 Dec 2016 06:53:57 +1030 
(ACDT)\r\nX-Virus-Scanned: clamav-milter 0.99.2 at mogul\r\nX-Virus-Status: Clean\r\nX-Spam-Status: No, 
score=-4.2 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_MED\r\n    autolearn=ham 
version=3.3.2\r\nX-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on 
mogul.quuxo.net\r\n\r\n\r\n--===============7564301068935298617==\r\nContent-Type: multipart/signed; 
micalg=pgp-sha512;\r\n   protocol=\"application/pgp-signature\"; 
boundary=\"O98KdSgI27dgYlM5\"\r\nContent-Disposition: inline\r\n\r\n\r\n--O98KdSgI27dgYlM5\r\nContent-Type: 
text/plain; charset=us-ascii\r\nContent-Disposition: 
inline\r\n\r\n==========================================================================\r\nUbuntu Security 
Notice
  USN-3159-1\r\nDecember 20, 2016\r\n\r\nlinux 
vulnerability\r\n==========================================================================\r\n\r\nA security 
issue affects these releases of Ubuntu and its derivatives:\r\n\r\n- Ubuntu 12.04 
LTS\r\n\r\nSummary:\r\n\r\nThe system could be made to expose sensitive information.\r\n\r\nSoftware 
Description:\r\n- linux: Linux kernel\r\n\r\nDetails:\r\n\r\nIt was discovered that a race condition existed 
in the procfs\r\nenviron_read function in the Linux kernel, leading to an integer\r\nunderflow. A local 
attacker could use this to expose sensitive\r\ninformation (kernel memory).\r\n\r\nUpdate 
instructions:\r\n\r\nThe problem can be corrected by updating your system to the following\r\npackage 
versions:\r\n\r\nUbuntu 12.04 LTS:\r\n  linux-image-3.2.0-119-generic   3.2.0-119.162\r\n  
linux-image-3.2.0-119-generic-pae  3.2.0-119.162\r\n  linux-image-3.2.0-119-highbank  3.2.0-119.162\r\n  
linux-image-3.2.0-119-omap      3.2.0-119.162\r\n
   linux-image-3.2.0-119-powerpc-smp  3.2.0-119.162\r\n  linux-image-3.2.0-119-powerpc64-smp  
3.2.0-119.162\r\n  linux-image-3.2.0-119-virtual   3.2.0-119.162\r\n  linux-image-generic             
3.2.0.119.134\r\n  linux-image-generic-pae         3.2.0.119.134\r\n  linux-image-highbank            
3.2.0.119.134\r\n  linux-image-omap                3.2.0.119.134\r\n  linux-image-powerpc-smp         
3.2.0.119.134\r\n  linux-image-powerpc64-smp       3.2.0.119.134\r\n  linux-image-virtual             
3.2.0.119.134\r\n\r\nAfter a standard system update you need to reboot your computer to make\r\nall the 
necessary changes.\r\n\r\nATTENTION: Due to an unavoidable ABI change the kernel updates have\r\nbeen given a 
new version number, which requires you to recompile and\r\nreinstall all third party kernel modules you might 
have installed.\r\nUnless you manually uninstalled the standard kernel metapackages\r\n(e.g. linux-generic, 
linux-generic-lts-RELEASE, linux-virtual,\r\nlinux-power
 pc), a standard system upgrade will automatically perform\r\nthis as well.\r\n\r\nReferences:\r\n  
http://www.ubuntu.com/usn/usn-3159-1\r\n  CVE-2016-7916\r\n\r\nPackage Information:\r\n  
https://launchpad.net/ubuntu/+source/linux/3.2.0-119.162\r\n\r\n\r\n--O98KdSgI27dgYlM5\r\nContent-Type: 
application/pgp-signature; name=\"signature.asc\"\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG 
v1\r\n\r\niQIcBAEBCgAGBQJYWY/iAAoJEC8Jno0AXoH0gKUQAJ7UOWV591M8K+HGXHI3BVJi\r\n75LCUSBRrV2NZTpc32ZMCsssb4TSqQinzczQfWSNtlLsgucKTLdCYGJvbXYxd32z\r\nBzHHHH9D8EDC6X4Olx0byiDBTX76kVBVUjxsKJ1zkYBFeMZ6tx9Tmgsl7Rdr26lP\r\n9oe3nBadkP0vM7j/dG1913MdzOlFc/2YOnGRK6QKzy1HhM74XMQTzvj9Nsbgs8ea\r\nZFTzWgDiUXi9SbBDLmwkY2uFJ+zreIH/vRjZHZ5ofJz9ed91HDhMB7CmRzn4JG/b\r\nSPAmTk0IRzWVBWglb0hPA8NN194ijeQFa6OJt94+EIMYuIasjsi8zGr+o1yxM5aY\r\ngTiDLzrQVWfddcZWmoCw8WWVbHAjMW60ehAs+y6ly0tBAn7wailXFRDFir1Vt4i2\r\n1WRTnJR2JebfQN4YeJ7CAiw34+PO8+vi8qHcRqMGkRu5IYdBy8AvBucVO923jIIy\r\nJBRTVkZqacRVp4PLx7vrOXX02z7y38iQcP2QSe
 
apMoQjViYOVSMYhycO9zqGe3Tj\r\nAHMqp2HGj1uPp+3mM/yRBaE1X1j7lzjsKO1XZwjMUIYcFmAAsg2Gwi5S0FhhS+cD\r\nulCZ0A+r4wZ/1K6cZ2ZCEQoAZyMovwiVLNP+4q7pHhcQGTYAvCEgPksktQwD3YOe\r\nnSj5HG2dTMTOHDjVGSVV\r\n=qUGf\r\n-----END
 PGP 
SIGNATURE-----\r\n\r\n--O98KdSgI27dgYlM5--\r\n\r\n\r\n--===============7564301068935298617==\r\nContent-Type: 
text/plain; charset=\"us-ascii\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 
7bit\r\nContent-Disposition: inline\r\n\r\n-- \r\nubuntu-security-announce mailing 
list\r\nubuntu-security-announce lists ubuntu com\r\nModify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/ubuntu-security-announce\r\n\r\n--===============7564301068935298617==--\r\n";;
     private static string MULTIPART_SIGNED_MESSAGE_PREVIEW = "Ubuntu Security Notice USN-3159-1 December 20, 
2016 linux vulnerability A security issue affects these releases of Ubuntu and its derivatives: - Ubuntu 
12.04 LTS Summary: The system could be made to expose sensitive information. Software Description: - linux: 
Linux kernel Details: It was discovered that a race condition existed in the procfs environ_read function in 
the Linux kernel, leading to an integer underflow. A local attacker could use this to expose sensitive 
information (kernel memory). Update instructions: The problem can be corrected by updating your system to the 
following package versions: Ubuntu 12.04 LTS: linux-image-3.2.0-119-generic 3.2.0-119.162 
linux-image-3.2.0-119-generic-pae 3.2.0-119.162 linux-image-3.2.0-119-highbank 3.2.0-119.162 
linux-image-3.2.0-119-omap 3.2.0-119.162 linux-image-3.2.0-119-powerpc-smp 3.2.0-119.162 
linux-image-3.2.0-119-powerpc64-smp 3.2.0-119.162 linux-image-3.2.0-119-v
 irtual 3.2.0-119.162 linux-image-generic 3.2.0.119.134 linux-image-generic-pae 3.2.0.119.134 
linux-image-highbank 3.2.0.119.134 linux-image-omap 3.2.0.119.134 linux-image-powerpc-smp 3.2.0.119.134 
linux-image-powerpc64-smp 3.2.0.119.134 linux-image-virtual 3.2.0.119.134 After a standard system update you 
need to reboot your computer to make all the necessary changes. ATTENTION: Due to an unavoidable ABI change 
the kernel updates have been given a new version number, which requires you to recompile and reinstall all 
third party kernel modules you might have installed. Unless you manually uninstalled the standard kernel 
metapackages (e.g. linux-generic, linux-generic-lts-RELEASE, linux-virtual, linux-powerpc), a standard system 
upgrade will automatically perform this as well. References: http://www.ubuntu.com/usn/usn-3159-1 
CVE-2016-7916 Package Information: https://launchpad.net/ubuntu/+source/linux/3.2.0-119.162 
ubuntu-security-announce mailing list ubuntu-security-announce@
 lists.ubuntu.com Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/ubuntu-security-announce";;
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]