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.
This commit is contained in:
Michael Gratton 2020-05-06 11:27:00 +10:00 committed by Michael James Gratton
parent 5b253cbee6
commit 6e7631b8d3
10 changed files with 138 additions and 78 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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,

View file

@ -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<MailboxAddress>,
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();
}

View file

@ -17,7 +17,8 @@
public class Geary.RFC822.MailboxAddresses :
Geary.MessageData.AbstractMessageData,
Geary.MessageData.SearchableMessageData,
Geary.RFC822.MessageData, Gee.Hashable<MailboxAddresses> {
Gee.Hashable<MailboxAddresses>,
DecodedMessageData {
/**

View file

@ -1,46 +1,75 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
/*
* Copyright © 2016 Software Freedom Conservancy Inc.
* Copyright © 2020 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.
*/
/**
* 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<MessageID> 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<Geary.RFC822.Date> {
public class Geary.RFC822.Date :
Geary.MessageData.AbstractMessageData,
Gee.Hashable<Geary.RFC822.Date>,
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));
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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());
}