From 6e7631b8d30febe0f4c238e2e5a3670f3d9e52d4 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 6 May 2020 11:27:00 +1000 Subject: [PATCH] Geary.RFC822: Clean up message data interfaces and classes Split Geary.RFC822.MessageData interface up into DecodedMessageData and EncodedMessageData so the difference between the two is clear and they can't be used interchangeably. Add `DecodedMessageData::to_rfc822_string` to provide a common interface for round-tripping decoded data. Update all classes to implement one of these and follow the same general API patterns. --- src/engine/imap-db/imap-db-message-row.vala | 8 +- src/engine/imap/api/imap-folder-session.vala | 4 +- .../response/imap-fetch-data-decoder.vala | 4 +- src/engine/rfc822/rfc822-mailbox-address.vala | 5 +- .../rfc822/rfc822-mailbox-addresses.vala | 3 +- src/engine/rfc822/rfc822-message-data.vala | 176 ++++++++++++------ .../app/app-conversation-monitor-test.vala | 2 +- .../engine/app/app-conversation-set-test.vala | 2 +- test/engine/app/app-conversation-test.vala | 2 +- .../rfc822/rfc822-message-data-test.vala | 10 +- 10 files changed, 138 insertions(+), 78 deletions(-) diff --git a/src/engine/imap-db/imap-db-message-row.vala b/src/engine/imap-db/imap-db-message-row.vala index f7b065dc..0b09a8cd 100644 --- a/src/engine/imap-db/imap-db-message-row.vala +++ b/src/engine/imap-db/imap-db-message-row.vala @@ -106,7 +106,7 @@ private class Geary.ImapDB.MessageRow { if (fields.is_all_set(Geary.Email.Field.DATE)) { try { email.set_send_date( - !String.is_empty(date) ? new RFC822.Date(date) : null + !String.is_empty(date) ? new RFC822.Date.from_rfc822_string(date) : null ); } catch (GLib.Error err) { debug("Error loading message date from db: %s", err.message); @@ -133,7 +133,7 @@ private class Geary.ImapDB.MessageRow { } if (fields.is_all_set(Geary.Email.Field.SUBJECT)) - email.set_message_subject(new RFC822.Subject.decode(subject ?? "")); + email.set_message_subject(new RFC822.Subject.from_rfc822_string(subject ?? "")); if (fields.is_all_set(Geary.Email.Field.HEADER)) email.set_message_header(new RFC822.Header(header ?? Memory.EmptyBuffer.instance)); @@ -191,7 +191,7 @@ private class Geary.ImapDB.MessageRow { // null if empty if (email.fields.is_all_set(Geary.Email.Field.DATE)) { - date = (email.date != null) ? email.date.original : null; + date = (email.date != null) ? email.date.to_rfc822_string() : null; date_time_t = (email.date != null) ? email.date.value.to_unix() : -1; fields = fields.set(Geary.Email.Field.DATE); @@ -222,7 +222,7 @@ private class Geary.ImapDB.MessageRow { } if (email.fields.is_all_set(Geary.Email.Field.SUBJECT)) { - subject = (email.subject != null) ? email.subject.original : null; + subject = (email.subject != null) ? email.subject.to_rfc822_string() : null; fields = fields.set(Geary.Email.Field.SUBJECT); } diff --git a/src/engine/imap/api/imap-folder-session.vala b/src/engine/imap/api/imap-folder-session.vala index a934fecc..e0e37687 100644 --- a/src/engine/imap/api/imap-folder-session.vala +++ b/src/engine/imap/api/imap-folder-session.vala @@ -901,7 +901,7 @@ private class Geary.Imap.FolderSession : Geary.Imap.SessionObject { RFC822.Date? date = null; if (!String.is_empty(value)) { try { - date = new RFC822.Date(value); + date = new RFC822.Date.from_rfc822_string(value); } catch (GLib.Error err) { warning( "Error parsing date from FETCH response: %s", @@ -979,7 +979,7 @@ private class Geary.Imap.FolderSession : Geary.Imap.SessionObject { if (required_but_not_set(Geary.Email.Field.SUBJECT, required_fields, email)) { string? value = headers.get("Subject"); if (value != null) - email.set_message_subject(new RFC822.Subject.decode(value)); + email.set_message_subject(new RFC822.Subject.from_rfc822_string(value)); else email.set_message_subject(null); } diff --git a/src/engine/imap/response/imap-fetch-data-decoder.vala b/src/engine/imap/response/imap-fetch-data-decoder.vala index 8f9f42b9..2d3cfce6 100644 --- a/src/engine/imap/response/imap-fetch-data-decoder.vala +++ b/src/engine/imap/response/imap-fetch-data-decoder.vala @@ -148,7 +148,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { Geary.RFC822.Date? sent_date = null; if (sent != null) { try { - sent_date = new RFC822.Date(sent.ascii); + sent_date = new RFC822.Date.from_rfc822_string(sent.ascii); } catch (GLib.Error err) { debug( "Error parsing sent date from FETCH envelope: %s", @@ -159,7 +159,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder { return new Envelope( sent_date, - new Geary.RFC822.Subject.decode(subject.ascii), + new Geary.RFC822.Subject.from_rfc822_string(subject.ascii), parse_addresses(from), parse_addresses(sender), parse_addresses(reply_to), (to != null) ? parse_addresses(to) : null, (cc != null) ? parse_addresses(cc) : null, diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index 7b9ea3f6..f4f622ce 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -17,9 +17,10 @@ * See [[https://tools.ietf.org/html/rfc5322#section-3.4]] */ public class Geary.RFC822.MailboxAddress : + Geary.MessageData.AbstractMessageData, Geary.MessageData.SearchableMessageData, Gee.Hashable, - BaseObject { + DecodedMessageData { private static unichar[] ATEXT = { '!', '#', '$', '%', '&', '\'', '*', '+', '-', @@ -551,7 +552,7 @@ public class Geary.RFC822.MailboxAddress : * * @see to_rfc822_string */ - public string to_string() { + public override string to_string() { return to_rfc822_string(); } diff --git a/src/engine/rfc822/rfc822-mailbox-addresses.vala b/src/engine/rfc822/rfc822-mailbox-addresses.vala index 1e2bdc7c..5890f23f 100644 --- a/src/engine/rfc822/rfc822-mailbox-addresses.vala +++ b/src/engine/rfc822/rfc822-mailbox-addresses.vala @@ -17,7 +17,8 @@ public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageData, Geary.MessageData.SearchableMessageData, - Geary.RFC822.MessageData, Gee.Hashable { + Gee.Hashable, + DecodedMessageData { /** diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala index fdd75ccd..e228b344 100644 --- a/src/engine/rfc822/rfc822-message-data.vala +++ b/src/engine/rfc822/rfc822-message-data.vala @@ -1,46 +1,75 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright © 2016 Software Freedom Conservancy Inc. + * Copyright © 2020 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. */ /** - * RFC822.MessageData represents a base class for all the various elements that may be present in - * an RFC822 message header. Note that some common elements (such as MailAccount) are not - * MessageData because they exist in an RFC822 header in list (i.e. multiple email addresses) form. + * A base interface for objects that represent decoded RFC822 headers. + * + * The value of these objects is the decoded form of the header + * data. Encoded forms can be obtained via {@link to_rfc822_string}. */ +public interface Geary.RFC822.DecodedMessageData : + Geary.MessageData.AbstractMessageData { + + /** Returns an RFC822-safe string representation of the data. */ + public abstract string to_rfc822_string(); -public interface Geary.RFC822.MessageData : Geary.MessageData.AbstractMessageData { } /** - * An RFC822 Message-ID. + * A base interface for objects that represent encoded RFC822 header data. * - * MessageID will normalize all strings so that they begin and end with the proper brackets ("<" and - * ">"). + * The value of these objects is the RFC822 encoded form of the header + * data. Decoded forms can be obtained via means specific to + * implementations of this interface. */ -public class Geary.RFC822.MessageID : Geary.MessageData.StringMessageData, Geary.RFC822.MessageData { +public interface Geary.RFC822.EncodedMessageData : + Geary.MessageData.BlockMessageData { + +} + +/** + * A RFC822 Message-ID. + * + * The decoded form of the id is the `addr-spec` portion, that is, + * without the leading `<` and tailing `>`. + */ +public class Geary.RFC822.MessageID : + Geary.MessageData.StringMessageData, DecodedMessageData { + + private string rfc822; + public MessageID(string value) { - string? normalized = normalize(value); - base (normalized ?? value); + base(value); } - // Adds brackets if required, null if no change required - private static string? normalize(string value) { - bool needs_prefix = !value.has_prefix("<"); - bool needs_suffix = !value.has_suffix(">"); - if (!needs_prefix && !needs_suffix) - return null; - - return "%s%s%s".printf(needs_prefix ? "<" : "", value, needs_suffix ? ">" : ""); + public MessageID.from_rfc822_string(string rfc822) { + base(GMime.utils_decode_message_id(rfc822)); + this.rfc822 = rfc822; } + + /** + * Returns the {@link Date} in RFC 822 format. + */ + public string to_rfc822_string() { + if (this.rfc822 == null) { + this.rfc822 = "<%s>".printf(this.value); + } + return this.rfc822; + } + } /** * A immutable list of RFC822 Message-ID values. */ -public class Geary.RFC822.MessageIDList : Geary.MessageData.AbstractMessageData, Geary.RFC822.MessageData { - public Gee.List list { get; private set; } +public class Geary.RFC822.MessageIDList : + Geary.MessageData.AbstractMessageData, + DecodedMessageData { /** Returns the number of ids in this list. */ @@ -189,71 +218,87 @@ public class Geary.RFC822.MessageIDList : Geary.MessageData.AbstractMessageData, } -public class Geary.RFC822.Date : Geary.RFC822.MessageData, Geary.MessageData.AbstractMessageData, - Gee.Hashable { +public class Geary.RFC822.Date : + Geary.MessageData.AbstractMessageData, + Gee.Hashable, + DecodedMessageData { - public string original { get; private set; } - public DateTime value { get; private set; } - public Date(string rfc822) throws RFC822Error { + public GLib.DateTime value { get; private set; } + + private string? rfc822; + + + public Date(GLib.DateTime datetime) { + this.value = datetime; + this.rfc822 = null; + } + + public Date.from_rfc822_string(string rfc822) throws RFC822Error { var date = GMime.utils_header_decode_date(rfc822); if (date == null) { throw new RFC822Error.INVALID("Not ISO-8601 date: %s", rfc822); } + this.rfc822 = rfc822; this.value = date; - this.original = rfc822; - } - - public Date.from_date_time(DateTime datetime) { - this.original = null; - this.value = datetime; } /** * Returns the {@link Date} in RFC 822 format. */ public string to_rfc822_string() { - return GMime.utils_header_format_date(this.value); - } - - /** - * Returns {@link Date} for transmission. - * - * @see to_rfc822_string - */ - public virtual string serialize() { - return to_rfc822_string(); + if (this.rfc822 == null) { + this.rfc822 = GMime.utils_header_format_date(this.value); + } + return this.rfc822; } public virtual bool equal_to(Geary.RFC822.Date other) { - return (this != other) ? value.equal(other.value) : true; + return this == other || this.value.equal(other.value); } public virtual uint hash() { - return value.hash(); + return this.value.hash(); } public override string to_string() { - return original ?? value.to_string(); + return this.value.to_string(); } } -public class Geary.RFC822.Subject : Geary.MessageData.StringMessageData, - Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData { +public class Geary.RFC822.Subject : + Geary.MessageData.StringMessageData, + Geary.MessageData.SearchableMessageData, + DecodedMessageData { + public const string REPLY_PREFACE = "Re:"; public const string FORWARD_PREFACE = "Fwd:"; - public string original { get; private set; } + + private string rfc822; + public Subject(string value) { - base (value); - original = value; + base(value); + this.rfc822 = null; } - public Subject.decode(string value) { - base (GMime.utils_header_decode_text(Geary.RFC822.get_parser_options(), value)); - original = value; + public Subject.from_rfc822_string(string rfc822) { + base(GMime.utils_header_decode_text(get_parser_options(), rfc822)); + this.rfc822 = rfc822; + } + + /** + * Returns the subject line encoded for an RFC 822 message. + */ + public string to_rfc822_string() { + if (this.rfc822 == null) { + this.rfc822 = GMime.utils_header_encode_text( + get_format_options(), this.value, null + ); + } + return this.rfc822; } public bool is_reply() { @@ -312,9 +357,13 @@ public class Geary.RFC822.Subject : Geary.MessageData.StringMessageData, public string to_searchable_string() { return value; } + } -public class Geary.RFC822.Header : Geary.MessageData.BlockMessageData, Geary.RFC822.MessageData { +public class Geary.RFC822.Header : + Geary.MessageData.BlockMessageData, EncodedMessageData { + + private GMime.Message? message = null; private string[]? names = null; @@ -353,22 +402,30 @@ public class Geary.RFC822.Header : Geary.MessageData.BlockMessageData, Geary.RFC } return this.names; } + } -public class Geary.RFC822.Text : Geary.MessageData.BlockMessageData, Geary.RFC822.MessageData { +public class Geary.RFC822.Text : + Geary.MessageData.BlockMessageData, EncodedMessageData { + public Text(Memory.Buffer buffer) { - base ("RFC822.Text", buffer); + base("RFC822.Text", buffer); } + } -public class Geary.RFC822.Full : Geary.MessageData.BlockMessageData, Geary.RFC822.MessageData { +public class Geary.RFC822.Full : + Geary.MessageData.BlockMessageData, EncodedMessageData { + public Full(Memory.Buffer buffer) { - base ("RFC822.Full", buffer); + base("RFC822.Full", buffer); } + } -// Used for decoding preview text. +/** Represents text providing a preview of an email's body. */ public class Geary.RFC822.PreviewText : Geary.RFC822.Text { + public PreviewText(Memory.Buffer _buffer) { base (_buffer); } @@ -415,4 +472,5 @@ public class Geary.RFC822.PreviewText : Geary.RFC822.Text { public PreviewText.from_string(string preview) { base (new Geary.Memory.StringBuffer(preview)); } + } diff --git a/test/engine/app/app-conversation-monitor-test.vala b/test/engine/app/app-conversation-monitor-test.vala index 0dec7655..3224cf8b 100644 --- a/test/engine/app/app-conversation-monitor-test.vala +++ b/test/engine/app/app-conversation-monitor-test.vala @@ -434,7 +434,7 @@ class Geary.App.ConversationMonitorTest : TestCase { references.message_id ); } - email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + email.set_send_date(new RFC822.Date(now)); email.set_email_properties(new MockEmailProperties(now)); email.set_full_references(mid, null, refs_list); return email; diff --git a/test/engine/app/app-conversation-set-test.vala b/test/engine/app/app-conversation-set-test.vala index 1250ad6a..2966e134 100644 --- a/test/engine/app/app-conversation-set-test.vala +++ b/test/engine/app/app-conversation-set-test.vala @@ -489,7 +489,7 @@ class Geary.App.ConversationSetTest : TestCase { references.message_id ); } - email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + email.set_send_date(new RFC822.Date(now)); email.set_email_properties(new MockEmailProperties(now)); email.set_full_references(mid, null, refs_list); return email; diff --git a/test/engine/app/app-conversation-test.vala b/test/engine/app/app-conversation-test.vala index e6c73143..03ebd8f2 100644 --- a/test/engine/app/app-conversation-test.vala +++ b/test/engine/app/app-conversation-test.vala @@ -289,7 +289,7 @@ class Geary.App.ConversationTest : TestCase { ); email.set_full_references(mid, null, null); email.set_email_properties(new MockEmailProperties(now)); - email.set_send_date(new Geary.RFC822.Date.from_date_time(now)); + email.set_send_date(new RFC822.Date(now)); return email; } diff --git a/test/engine/rfc822/rfc822-message-data-test.vala b/test/engine/rfc822/rfc822-message-data-test.vala index 504efaa0..dd9b2bf3 100644 --- a/test/engine/rfc822/rfc822-message-data-test.vala +++ b/test/engine/rfc822/rfc822-message-data-test.vala @@ -61,7 +61,7 @@ class Geary.RFC822.MessageDataTest : TestCase { public void date_from_rfc822() throws GLib.Error { const string FULL_HOUR_TZ = "Thu, 28 Feb 2019 00:00:00 -0100"; - Date full_hour_tz = new Date(FULL_HOUR_TZ); + Date full_hour_tz = new Date.from_rfc822_string(FULL_HOUR_TZ); assert_int64( ((int64) (-1 * 3600)) * 1000 * 1000, full_hour_tz.value.get_utc_offset(), @@ -81,7 +81,7 @@ class Geary.RFC822.MessageDataTest : TestCase { ); const string HALF_HOUR_TZ = "Thu, 28 Feb 2019 00:00:00 +1030"; - Date half_hour_tz = new Date(HALF_HOUR_TZ); + Date half_hour_tz = new Date.from_rfc822_string(HALF_HOUR_TZ); assert_int64( ((int64) (10.5 * 3600)) * 1000 * 1000, half_hour_tz.value.get_utc_offset() @@ -96,15 +96,15 @@ class Geary.RFC822.MessageDataTest : TestCase { public void date_to_rfc822() throws GLib.Error { const string FULL_HOUR_TZ = "Thu, 28 Feb 2019 00:00:00 -0100"; - Date full_hour_tz = new Date(FULL_HOUR_TZ); + Date full_hour_tz = new Date.from_rfc822_string(FULL_HOUR_TZ); assert_string(FULL_HOUR_TZ, full_hour_tz.to_rfc822_string()); const string HALF_HOUR_TZ = "Thu, 28 Feb 2019 00:00:00 +1030"; - Date half_hour_tz = new Date(HALF_HOUR_TZ); + Date half_hour_tz = new Date.from_rfc822_string(HALF_HOUR_TZ); assert_string(HALF_HOUR_TZ, half_hour_tz.to_rfc822_string()); const string NEG_HALF_HOUR_TZ = "Thu, 28 Feb 2019 00:00:00 -1030"; - Date neg_half_hour_tz = new Date(NEG_HALF_HOUR_TZ); + Date neg_half_hour_tz = new Date.from_rfc822_string(NEG_HALF_HOUR_TZ); assert_string(NEG_HALF_HOUR_TZ, neg_half_hour_tz.to_rfc822_string()); }