Merge branch 'mjog/dovecot-envelope-mailbox-quirk' into 'mainline'

Dovecot envelope mailbox quirk

See merge request GNOME/geary!546
This commit is contained in:
Michael Gratton 2020-08-18 07:07:35 +00:00
commit 67a2d32a6c
21 changed files with 490 additions and 205 deletions

View file

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

View file

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

View file

@ -38,5 +38,83 @@ 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) {
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();
} else if (greeting.has_prefix("Dovecot")) {
update_for_dovecot();
}
}
}
/**
* 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;
}
/**
* 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";
}
}

View file

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

View file

@ -29,6 +29,17 @@ public class Geary.Imap.ListParameter : Geary.Imap.Parameter {
private Gee.List<Parameter> list = new Gee.ArrayList<Parameter>();
/** 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.
*

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,6 +22,8 @@ public class Geary.RFC822.MailboxAddress :
Gee.Hashable<MailboxAddress>,
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) {
@ -206,7 +211,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;
@ -228,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 {
@ -261,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, '@');
@ -281,7 +299,7 @@ public class Geary.RFC822.MailboxAddress :
} else {
this.mailbox = "";
this.domain = "";
this.address = address;
this.address = decode_address_part(address);
}
}
@ -497,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;
}
/**

View file

@ -0,0 +1,135 @@
/*
* Copyright 2019 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.
*/
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 <from@example.com>");
assert_equal(decoded.sender.to_rfc822_string(), "From <from@example.com>");
assert_equal(decoded.reply_to.to_rfc822_string(), "From <from@example.com>");
assert_non_null(decoded.to, "to");
assert_equal(decoded.to.to_rfc822_string(), "To <to@example.com>");
assert_non_null(decoded.cc, "cc");
assert_equal(decoded.cc.to_rfc822_string(), "Cc <cc@example.com>");
assert_non_null(decoded.bcc, "bcc");
assert_equal(decoded.bcc.to_rfc822_string(), "Bcc <bcc@example.com>");
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 <cc@example.com>");
}
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 <to>");
assert_non_null(decoded.cc, "cc");
assert_equal(decoded.cc.to_rfc822_string(), "Cc <cc@example.com>");
}
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;
}
}

View file

@ -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) {

View file

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

View file

@ -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("<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");
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 <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("\"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("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?= <test2@example.com>");
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?= <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?=\" <test2@example.com>");
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?=\" <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);
// 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>?");
assert(addr.address == "demo@mailsploit.com");
// Courtesy Mailsploit https://www.mailsploit.com
encoded = "\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" <demo@mailsploit.com>";
addr = new MailboxAddress.from_rfc822_string(encoded);
assert_equal(addr.name, "test <potus@whitehouse.gov>?", 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 {

View file

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

View file

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

View file

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