Merge branch 'wip/791275-mailsploit-mitigation'. Fixes Bug 791275.
This commit is contained in:
commit
b7eea85725
20 changed files with 1049 additions and 372 deletions
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class AccountDialogEditAlternateEmailsPane : AccountDialogPane {
|
|||
public ListItem(Geary.RFC822.MailboxAddress mailbox) {
|
||||
this.mailbox = mailbox;
|
||||
|
||||
label = "<b>%s</b>".printf(Geary.HTML.escape_markup(mailbox.get_full_address()));
|
||||
label = "<b>%s</b>".printf(Geary.HTML.escape_markup(mailbox.to_full_display()));
|
||||
use_markup = true;
|
||||
ellipsize = Pango.EllipsizeMode.END;
|
||||
set_halign(Gtk.Align.START);
|
||||
|
|
|
|||
|
|
@ -1676,16 +1676,16 @@ public class ComposerWidget : Gtk.EventBox {
|
|||
StringBuilder tooltip = new StringBuilder();
|
||||
if (to_entry.addresses != null)
|
||||
foreach(Geary.RFC822.MailboxAddress addr in this.to_entry.addresses)
|
||||
tooltip.append(_("To: ") + addr.get_full_address() + "\n");
|
||||
tooltip.append(_("To: ") + addr.to_full_display() + "\n");
|
||||
if (cc_entry.addresses != null)
|
||||
foreach(Geary.RFC822.MailboxAddress addr in this.cc_entry.addresses)
|
||||
tooltip.append(_("Cc: ") + addr.get_full_address() + "\n");
|
||||
tooltip.append(_("Cc: ") + addr.to_full_display() + "\n");
|
||||
if (bcc_entry.addresses != null)
|
||||
foreach(Geary.RFC822.MailboxAddress addr in this.bcc_entry.addresses)
|
||||
tooltip.append(_("Bcc: ") + addr.get_full_address() + "\n");
|
||||
tooltip.append(_("Bcc: ") + addr.to_full_display() + "\n");
|
||||
if (reply_to_entry.addresses != null)
|
||||
foreach(Geary.RFC822.MailboxAddress addr in this.reply_to_entry.addresses)
|
||||
tooltip.append(_("Reply-To: ") + addr.get_full_address() + "\n");
|
||||
tooltip.append(_("Reply-To: ") + addr.to_full_display() + "\n");
|
||||
this.header.set_recipients(label, tooltip.str.slice(0, -1)); // Remove trailing \n
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,17 +27,21 @@ public class FormattedConversationData : Geary.BaseObject {
|
|||
this.address = address;
|
||||
this.is_unread = is_unread;
|
||||
}
|
||||
|
||||
|
||||
public string get_full_markup(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes) {
|
||||
return get_as_markup((address in account_mailboxes) ? ME : address.get_short_address());
|
||||
return get_as_markup((address in account_mailboxes) ? ME : address.to_short_display());
|
||||
}
|
||||
|
||||
|
||||
public string get_short_markup(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes) {
|
||||
if (address in account_mailboxes)
|
||||
return get_as_markup(ME);
|
||||
|
||||
string short_address = address.get_short_address().strip();
|
||||
|
||||
|
||||
if (address.is_spoofed()) {
|
||||
return get_full_markup(account_mailboxes);
|
||||
}
|
||||
|
||||
string short_address = Markup.escape_text(address.to_short_display());
|
||||
|
||||
if (", " in short_address) {
|
||||
// assume address is in Last, First format
|
||||
string[] tokens = short_address.split(", ", 2);
|
||||
|
|
@ -57,12 +61,21 @@ public class FormattedConversationData : Geary.BaseObject {
|
|||
|
||||
return get_as_markup(first_name);
|
||||
}
|
||||
|
||||
|
||||
private string get_as_markup(string participant) {
|
||||
return "%s%s%s".printf(
|
||||
is_unread ? "<b>" : "", Geary.HTML.escape_markup(participant), is_unread ? "</b>" : "");
|
||||
string markup = Geary.HTML.escape_markup(participant);
|
||||
|
||||
if (is_unread) {
|
||||
markup = "<b>%s</b>".printf(markup);
|
||||
}
|
||||
|
||||
if (this.address.is_spoofed()) {
|
||||
markup = "<s>%s</s>".printf(markup);
|
||||
}
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
|
||||
public bool equal_to(ParticipantDisplay other) {
|
||||
return address.equal_to(other.address);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* 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.
|
||||
|
|
@ -26,15 +26,6 @@ public class ConversationMessage : Gtk.Grid {
|
|||
private const int MAX_PREVIEW_BYTES = Geary.Email.MAX_PREVIEW_BYTES;
|
||||
|
||||
|
||||
internal static inline bool has_distinct_name(
|
||||
Geary.RFC822.MailboxAddress address) {
|
||||
return (
|
||||
!Geary.String.is_empty(address.name) &&
|
||||
address.name != address.address
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Widget used to display sender/recipient email addresses in
|
||||
// message header Gtk.FlowBox instances.
|
||||
private class AddressFlowBoxChild : Gtk.FlowBoxChild {
|
||||
|
|
@ -50,7 +41,7 @@ public class ConversationMessage : Gtk.Grid {
|
|||
public AddressFlowBoxChild(Geary.RFC822.MailboxAddress address,
|
||||
Type type = Type.OTHER) {
|
||||
this.address = address;
|
||||
this.search_value = address.address.casefold();
|
||||
this.search_value = address.to_searchable_string().casefold();
|
||||
|
||||
// We use two label instances here when address has
|
||||
// distinct parts so we can dim the secondary part, if
|
||||
|
|
@ -60,6 +51,17 @@ public class ConversationMessage : Gtk.Grid {
|
|||
|
||||
Gtk.Grid address_parts = new Gtk.Grid();
|
||||
|
||||
bool is_spoofed = address.is_spoofed();
|
||||
if (is_spoofed) {
|
||||
Gtk.Image spoof_img = new Gtk.Image.from_icon_name(
|
||||
"dialog-warning-symbolic", Gtk.IconSize.SMALL_TOOLBAR
|
||||
);
|
||||
this.set_tooltip_text(
|
||||
_("This email address may have been forged")
|
||||
);
|
||||
address_parts.add(spoof_img);
|
||||
}
|
||||
|
||||
Gtk.Label primary = new Gtk.Label(null);
|
||||
primary.ellipsize = Pango.EllipsizeMode.END;
|
||||
primary.set_halign(Gtk.Align.START);
|
||||
|
|
@ -69,19 +71,21 @@ public class ConversationMessage : Gtk.Grid {
|
|||
}
|
||||
address_parts.add(primary);
|
||||
|
||||
if (has_distinct_name(address)) {
|
||||
primary.set_text(address.name);
|
||||
string display_address = address.to_address_display("", "");
|
||||
|
||||
// Don't display the name if it looks spoofed, to reduce
|
||||
// chance of the user of being tricked by malware.
|
||||
if (address.has_distinct_name() && !is_spoofed) {
|
||||
primary.set_text(address.to_short_display());
|
||||
|
||||
Gtk.Label secondary = new Gtk.Label(null);
|
||||
secondary.ellipsize = Pango.EllipsizeMode.END;
|
||||
secondary.set_halign(Gtk.Align.START);
|
||||
secondary.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
||||
secondary.set_text(address.address);
|
||||
secondary.set_text(display_address);
|
||||
address_parts.add(secondary);
|
||||
|
||||
this.search_value = address.name.casefold() + this.search_value;
|
||||
} else {
|
||||
primary.set_text(address.address);
|
||||
primary.set_text(display_address);
|
||||
}
|
||||
|
||||
// Update prelight state when mouse-overed.
|
||||
|
|
@ -571,7 +575,7 @@ public class ConversationMessage : Gtk.Grid {
|
|||
Gee.List<Geary.RFC822.MailboxAddress> list =
|
||||
this.message.from.get_all();
|
||||
foreach (Geary.RFC822.MailboxAddress addr in list) {
|
||||
text += has_distinct_name(addr) ? addr.name : addr.address;
|
||||
text += addr.to_short_display();
|
||||
|
||||
if (++i < list.size)
|
||||
// Translators: This separates multiple 'from'
|
||||
|
|
@ -765,7 +769,7 @@ public class ConversationMessage : Gtk.Grid {
|
|||
Gee.Map<string,string> values = new Gee.HashMap<string,string>();
|
||||
values[ACTION_OPEN_LINK] =
|
||||
Geary.ComposedEmail.MAILTO_SCHEME + address.address;
|
||||
values[ACTION_COPY_EMAIL] = address.get_full_address();
|
||||
values[ACTION_COPY_EMAIL] = address.to_full_display();
|
||||
values[ACTION_SEARCH_FROM] = address.address;
|
||||
|
||||
Menu model = new Menu();
|
||||
|
|
|
|||
|
|
@ -130,10 +130,10 @@ public class Libnotify : Geary.BaseObject {
|
|||
|
||||
ins = null;
|
||||
}
|
||||
|
||||
issue_current_notification(primary.get_short_address(), body, avatar);
|
||||
|
||||
issue_current_notification(primary.to_short_display(), body, avatar);
|
||||
}
|
||||
|
||||
|
||||
private void issue_current_notification(string summary, string body, Gdk.Pixbuf? icon) {
|
||||
// only one outstanding notification at a time
|
||||
if (current_notification != null) {
|
||||
|
|
|
|||
|
|
@ -1,102 +1,58 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An immutable object containing a representation of an Internet email address.
|
||||
* An immutable representation of an RFC 822 mailbox address.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2822#section-3.4]]
|
||||
* The properties of this class such as {@link name} and {@link
|
||||
* address} are stores decoded UTF-8, thus they must be re-encoded
|
||||
* using methods such as {@link to_rfc822_string} before being re-used
|
||||
* in a message envelope.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc5322#section-3.4]]
|
||||
*/
|
||||
public class Geary.RFC822.MailboxAddress :
|
||||
Geary.MessageData.SearchableMessageData,
|
||||
Gee.Hashable<MailboxAddress>,
|
||||
BaseObject {
|
||||
|
||||
public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageData,
|
||||
Gee.Hashable<MailboxAddress>, BaseObject {
|
||||
internal delegate string ListToStringDelegate(MailboxAddress address);
|
||||
|
||||
/**
|
||||
* The optional user-friendly name associated with the {@link MailboxAddress}.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "Dirk Gently".
|
||||
*/
|
||||
public string? name { get; private set; }
|
||||
|
||||
/**
|
||||
* The routing of the message (optional, obsolete).
|
||||
*/
|
||||
public string? source_route { get; private set; }
|
||||
|
||||
/**
|
||||
* The mailbox (local-part) portion of the {@link MailboxAddress}.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "dirk".
|
||||
*/
|
||||
public string mailbox { get; private set; }
|
||||
|
||||
/**
|
||||
* The domain portion of the {@link MailboxAddress}.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "example.com".
|
||||
*/
|
||||
public string domain { get; private set; }
|
||||
|
||||
/**
|
||||
* The address specification of the {@link MailboxAddress}.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "dirk@example.com".
|
||||
*/
|
||||
public string address { get; private set; }
|
||||
|
||||
public MailboxAddress(string? name, string address) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
|
||||
source_route = null;
|
||||
|
||||
int atsign = address.index_of_char('@');
|
||||
if (atsign > 0) {
|
||||
mailbox = address.slice(0, atsign);
|
||||
domain = address.slice(atsign + 1, address.length);
|
||||
} else {
|
||||
mailbox = "";
|
||||
domain = "";
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
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.domain = domain;
|
||||
|
||||
address = "%s@%s".printf(mailbox, domain);
|
||||
}
|
||||
|
||||
public MailboxAddress.from_rfc822_string(string rfc822) throws RFC822Error {
|
||||
InternetAddressList addrlist = InternetAddressList.parse_string(rfc822);
|
||||
if (addrlist == null)
|
||||
return;
|
||||
|
||||
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) {
|
||||
this(mbox_addr.get_name(), mbox_addr.get_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);
|
||||
return GMime.utils_header_decode_phrase(prepare_header_text_part(name));
|
||||
}
|
||||
|
||||
// unquote the string and decode the text
|
||||
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
|
||||
|
|
@ -118,33 +74,189 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
|
|||
offset = end;
|
||||
}
|
||||
|
||||
return GMime.utils_header_decode_text(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The optional human-readable part of the mailbox address.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "Dirk Gently".
|
||||
*
|
||||
* The returned value has been decoded into UTF-8.
|
||||
*/
|
||||
public string? name { get; private set; }
|
||||
|
||||
/**
|
||||
* The routing of the message (optional, obsolete).
|
||||
*
|
||||
* The returned value has been decoded into UTF-8.
|
||||
*/
|
||||
public string? source_route { get; private set; }
|
||||
|
||||
/**
|
||||
* The mailbox (local-part) portion of the mailbox's address.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "dirk".
|
||||
*
|
||||
* The returned value has been decoded into UTF-8.
|
||||
*/
|
||||
public string mailbox { get; private set; }
|
||||
|
||||
/**
|
||||
* The domain portion of the mailbox's address.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "example.com".
|
||||
*
|
||||
* The returned value has been decoded into UTF-8.
|
||||
*/
|
||||
public string domain { get; private set; }
|
||||
|
||||
/**
|
||||
* The complete address part of the mailbox address.
|
||||
*
|
||||
* For "Dirk Gently <dirk@example.com>", this would be "dirk@example.com".
|
||||
*
|
||||
* The returned value has been decoded into UTF-8.
|
||||
*/
|
||||
public string address { get; private set; }
|
||||
|
||||
|
||||
public MailboxAddress(string? name, string address) {
|
||||
this.name = name;
|
||||
this.source_route = null;
|
||||
this.address = address;
|
||||
|
||||
int atsign = address.last_index_of_char('@');
|
||||
if (atsign > 0) {
|
||||
this.mailbox = address[0:atsign];
|
||||
this.domain = address[atsign + 1:address.length];
|
||||
} else {
|
||||
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 = decode_address_part(mailbox);
|
||||
this.domain = domain;
|
||||
this.address = "%s@%s".printf(mailbox, domain);
|
||||
}
|
||||
|
||||
public MailboxAddress.from_rfc822_string(string rfc822) throws RFC822Error {
|
||||
InternetAddressList addrlist = InternetAddressList.parse_string(rfc822);
|
||||
if (addrlist == null)
|
||||
return;
|
||||
|
||||
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) {
|
||||
this.gmime(mbox_addr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RFC822Error.INVALID("Could not parse RFC822 address: %s", rfc822);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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('@');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable formatted address, showing the name (if available) and the email
|
||||
* address in angled brackets. No RFC822 quoting is performed.
|
||||
* Returns a full human-readable version of the mailbox address.
|
||||
*
|
||||
* @see to_rfc822_string
|
||||
*/
|
||||
public string get_full_address() {
|
||||
return String.is_empty(name) ? address : "%s <%s>".printf(name, address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a simple address, that is, no human-readable name and the email address in angled
|
||||
* This returns a formatted version of the address including
|
||||
* {@link name} (if present, not a spoof, and distinct from the
|
||||
* address) and {@link address} parts, suitable for display to
|
||||
* people. The string will have white space reduced and
|
||||
* non-printable characters removed, and the address will be
|
||||
* surrounded by angle brackets if a name is present.
|
||||
*
|
||||
* If you need a form suitable for sending a message, see {@link
|
||||
* to_rfc822_string} instead.
|
||||
*
|
||||
* @see has_distinct_name
|
||||
* @see is_spoofed
|
||||
* @param open optional string to use as the opening bracket for
|
||||
* the address part, defaults to //<//
|
||||
* @param close optional string to use as the closing bracket for
|
||||
* the address part, defaults to //>//
|
||||
* @return the cleaned //name// part if present, not spoofed and
|
||||
* distinct from //address//, followed by a space then the cleaned
|
||||
* //address// part, cleaned and enclosed within the specified
|
||||
* brackets.
|
||||
*/
|
||||
public string get_simple_address() {
|
||||
return "<%s>".printf(address);
|
||||
public string to_full_display(string open = "<", string close = ">") {
|
||||
string clean_name = Geary.String.reduce_whitespace(this.name);
|
||||
string clean_address = Geary.String.reduce_whitespace(this.address);
|
||||
return (!has_distinct_name() || is_spoofed())
|
||||
? clean_address
|
||||
: "%s %s%s%s".printf(clean_name, open, clean_address, close);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a human-readable pretty address, showing only the name, but if unavailable, the
|
||||
* mailbox name (that is, the account name without the domain).
|
||||
* Returns a short human-readable version of the mailbox address.
|
||||
*
|
||||
* This returns a shortened version of the address suitable for
|
||||
* display to people: Either the {@link name} (if present and not
|
||||
* a spoof) or the {@link address} part otherwise. The string will
|
||||
* have white space reduced and non-printable characters removed.
|
||||
*
|
||||
* @see is_spoofed
|
||||
* @return the cleaned //name// part if present and not spoofed,
|
||||
* or else the cleaned //address// part, cleaned but without
|
||||
* brackets.
|
||||
*/
|
||||
public string get_short_address() {
|
||||
return name ?? mailbox;
|
||||
public string to_short_display() {
|
||||
string clean_name = Geary.String.reduce_whitespace(this.name);
|
||||
string clean_address = Geary.String.reduce_whitespace(this.address);
|
||||
return String.is_empty(clean_name) || is_spoofed()
|
||||
? clean_address
|
||||
: clean_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable version of the address part.
|
||||
*
|
||||
* @param open optional string to use as the opening bracket,
|
||||
* defaults to //<//
|
||||
* @param close optional string to use as the closing bracket,
|
||||
* defaults to //>//
|
||||
* @return the {@link address} part, cleaned and enclosed within the
|
||||
* specified brackets.
|
||||
*/
|
||||
public string to_address_display(string open = "<", string close = ">") {
|
||||
return open + Geary.String.reduce_whitespace(this.address) + close;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -153,47 +265,134 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
|
|||
public bool is_valid() {
|
||||
return is_valid_address(address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the email syntax is valid.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address suitable for insertion into an RFC822 message. RFC822 quoting is
|
||||
* performed if required.
|
||||
* Determines if the mailbox address appears to have been spoofed.
|
||||
*
|
||||
* @see get_full_address
|
||||
* Using recipient and sender mailbox addresses where the name
|
||||
* part is also actually a valid RFC822 address
|
||||
* (e.g. "you@example.com <jerk@spammer.com>") is a common tactic
|
||||
* used by spammers and malware authors to exploit MUAs that will
|
||||
* display the name part only if present. It also enables more
|
||||
* sophisticated attacks such as
|
||||
* [[https://www.mailsploit.com/|Mailsploit]], which uses
|
||||
* Quoted-Printable or Base64 encoded nulls, new lines, @'s and
|
||||
* other characters to further trick MUAs into displaying a bogus
|
||||
* address.
|
||||
*
|
||||
* This method attempts to detect such attacks by examining the
|
||||
* {@link name} for non-printing characters and determining if it
|
||||
* is by itself also a valid RFC822 address.
|
||||
*
|
||||
* @return //true// if the complete decoded address contains any
|
||||
* non-printing characters, if the name part is also a valid
|
||||
* RFC822 address, or if the address part is not a valid RFC822
|
||||
* address.
|
||||
*/
|
||||
public bool is_spoofed() {
|
||||
// Empty test and regexes must apply to the raw values, not
|
||||
// clean ones, otherwise any control chars present will have
|
||||
// been lost
|
||||
const string CONTROLS = "[[:cntrl:]]+";
|
||||
|
||||
bool is_spoof = false;
|
||||
|
||||
// 1. Check the name part contains no controls and doesn't
|
||||
// look like an email address
|
||||
if (!Geary.String.is_empty(this.name)) {
|
||||
if (Regex.match_simple(CONTROLS, this.name)) {
|
||||
is_spoof = true;
|
||||
} else {
|
||||
// Clean up the name as usual, but remove all
|
||||
// whitespace so an attack can't get away with a name
|
||||
// like "potus @ whitehouse . gov"
|
||||
string clean_name = Geary.String.reduce_whitespace(this.name);
|
||||
clean_name = clean_name.replace(" ", "");
|
||||
if (is_valid_address(clean_name)) {
|
||||
is_spoof = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check the mailbox part of the address doesn't contain an
|
||||
// @. Is actually legal if quoted, but rarely (never?) found
|
||||
// in the wild and better be safe than sorry.
|
||||
if (!is_spoof && this.mailbox.contains("@")) {
|
||||
is_spoof = true;
|
||||
}
|
||||
|
||||
// 3. Check the address doesn't contain any spaces or
|
||||
// controls. Again, space in the mailbox is allowed if quoted,
|
||||
// but in practice should rarely be used.
|
||||
if (!is_spoof && Regex.match_simple(Geary.String.WS_OR_NP, this.address)) {
|
||||
is_spoof = true;
|
||||
}
|
||||
|
||||
return is_spoof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the name part is different to the address part.
|
||||
*
|
||||
* @return //true// if {@link name} is not empty, and the cleaned
|
||||
* versions of the name part and {@link address} are not equal.
|
||||
*/
|
||||
public bool has_distinct_name() {
|
||||
string clean_name = Geary.String.reduce_whitespace(this.name);
|
||||
return (
|
||||
!Geary.String.is_empty(clean_name) &&
|
||||
clean_name != Geary.String.reduce_whitespace(this.address)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete mailbox address, armoured for RFC 822 use.
|
||||
*
|
||||
* This method is similar to {@link to_full_display}, but only
|
||||
* checks for a distinct address (per Postel's Law) and not for
|
||||
* any spoofing, and does not strip extra white space or
|
||||
* non-printing characters.
|
||||
*
|
||||
* @return the RFC822 encoded form of the full address.
|
||||
*/
|
||||
public string to_rfc822_string() {
|
||||
return String.is_empty(name)
|
||||
? address
|
||||
: "%s <%s>".printf(GMime.utils_quote_string(name), address);
|
||||
return has_distinct_name()
|
||||
? "%s <%s>".printf(
|
||||
GMime.utils_header_encode_phrase(this.name),
|
||||
to_rfc822_address()
|
||||
)
|
||||
: to_rfc822_address();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the address part only, armoured for RFC 822 use.
|
||||
*
|
||||
* @return the RFC822 encoded form of the address, without angle
|
||||
* brackets.
|
||||
*/
|
||||
public string to_rfc822_address() {
|
||||
return "%s@%s".printf(
|
||||
// XXX utils_quote_string won't quote if spaces or quotes
|
||||
// present, so need to do that manually
|
||||
GMime.utils_quote_string(GMime.utils_header_encode_text(this.mailbox)),
|
||||
// XXX Need to punycode international domains.
|
||||
this.domain
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* See Geary.MessageData.SearchableMessageData.
|
||||
*/
|
||||
public string to_searchable_string() {
|
||||
return get_full_address();
|
||||
return has_distinct_name()
|
||||
? "%s <%s>".printf(this.name, this.address)
|
||||
: this.address;
|
||||
}
|
||||
|
||||
|
||||
public uint hash() {
|
||||
return String.stri_hash(address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Equality is defined as a case-insensitive comparison of the {@link address}.
|
||||
*/
|
||||
|
|
@ -205,30 +404,13 @@ public class Geary.RFC822.MailboxAddress : Geary.MessageData.SearchableMessageDa
|
|||
return this.address.normalize().casefold() == address.normalize().casefold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RFC822 formatted version of the address.
|
||||
*
|
||||
* @see to_rfc822_string
|
||||
*/
|
||||
public string to_string() {
|
||||
return get_full_address();
|
||||
return to_rfc822_string();
|
||||
}
|
||||
|
||||
internal static string list_to_string(Gee.List<MailboxAddress> addrs,
|
||||
string empty, ListToStringDelegate to_s) {
|
||||
switch (addrs.size) {
|
||||
case 0:
|
||||
return empty;
|
||||
|
||||
case 1:
|
||||
return to_s(addrs[0]);
|
||||
|
||||
default:
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (MailboxAddress addr in addrs) {
|
||||
if (!String.is_empty(builder.str))
|
||||
builder.append(", ");
|
||||
|
||||
builder.append(to_s(addr));
|
||||
}
|
||||
|
||||
return builder.str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,74 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageData,
|
||||
Geary.MessageData.SearchableMessageData, Geary.RFC822.MessageData, Gee.Hashable<MailboxAddresses> {
|
||||
|
||||
public int size { get { return addrs.size; } }
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
|
||||
/**
|
||||
* Converts a list of mailbox addresses to a string.
|
||||
*
|
||||
* The delegate //to_s// is used for converting addresses in the
|
||||
* given list. If the list is empty, the given empty string is
|
||||
* returned.
|
||||
*/
|
||||
private static string list_to_string(Gee.List<MailboxAddress> addrs,
|
||||
string empty,
|
||||
ListToStringDelegate to_s) {
|
||||
switch (addrs.size) {
|
||||
case 0:
|
||||
return empty;
|
||||
|
||||
case 1:
|
||||
return to_s(addrs[0]);
|
||||
|
||||
default:
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (MailboxAddress addr in addrs) {
|
||||
if (!String.is_empty(builder.str))
|
||||
builder.append(", ");
|
||||
|
||||
builder.append(to_s(addr));
|
||||
}
|
||||
|
||||
return builder.str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Signature for "to_string" implementation for {@link list_to_string}. */
|
||||
private delegate string ListToStringDelegate(MailboxAddress address);
|
||||
|
||||
/** Returns the number of addresses in this list. */
|
||||
public int size {
|
||||
get { return this.addrs.size; }
|
||||
}
|
||||
|
||||
private Gee.List<MailboxAddress> addrs = new Gee.ArrayList<MailboxAddress>();
|
||||
|
||||
|
||||
|
||||
public MailboxAddresses(Gee.Collection<MailboxAddress> addrs) {
|
||||
this.addrs.add_all(addrs);
|
||||
}
|
||||
|
||||
|
||||
public MailboxAddresses.single(MailboxAddress addr) {
|
||||
addrs.add(addr);
|
||||
this.addrs.add(addr);
|
||||
}
|
||||
|
||||
|
||||
public MailboxAddresses.from_rfc822_string(string rfc822) {
|
||||
InternetAddressList addrlist = InternetAddressList.parse_string(rfc822);
|
||||
if (addrlist == null)
|
||||
|
|
@ -27,102 +77,125 @@ 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public new MailboxAddress? get(int index) {
|
||||
return addrs.get(index);
|
||||
}
|
||||
|
||||
|
||||
public Gee.Iterator<MailboxAddress> iterator() {
|
||||
return addrs.iterator();
|
||||
}
|
||||
|
||||
|
||||
public Gee.List<MailboxAddress> get_all() {
|
||||
return addrs.read_only_view;
|
||||
}
|
||||
|
||||
|
||||
public bool contains_normalized(string address) {
|
||||
if (addrs.size < 1)
|
||||
return false;
|
||||
|
||||
|
||||
string normalized_address = address.normalize().casefold();
|
||||
|
||||
|
||||
foreach (MailboxAddress mailbox_address in addrs) {
|
||||
if (mailbox_address.address.normalize().casefold() == normalized_address)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool contains(string address) {
|
||||
if (addrs.size < 1)
|
||||
return false;
|
||||
|
||||
|
||||
foreach (MailboxAddress a in addrs)
|
||||
if (a.address == address)
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the addresses suitable for insertion into an RFC822 message. RFC822 quoting is
|
||||
* performed if required.
|
||||
* 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.
|
||||
*
|
||||
* @see MailboxAddress.to_rfc822_string
|
||||
*/
|
||||
public string to_rfc822_string() {
|
||||
return MailboxAddress.list_to_string(addrs, "", (a) => a.to_rfc822_string());
|
||||
return list_to_string(addrs, ", ", (a) => a.to_rfc822_string());
|
||||
}
|
||||
|
||||
|
||||
public uint hash() {
|
||||
// create sorted set to ensure ordering no matter the list's order
|
||||
Gee.TreeSet<string> sorted_addresses = traverse<RFC822.MailboxAddress>(addrs)
|
||||
.map<string>(m => m.address)
|
||||
.to_tree_set(String.stri_cmp);
|
||||
|
||||
|
||||
// xor all strings in sorted order
|
||||
uint xor = 0;
|
||||
foreach (string address in sorted_addresses)
|
||||
xor ^= address.hash();
|
||||
|
||||
|
||||
return xor;
|
||||
}
|
||||
|
||||
|
||||
public bool equal_to(MailboxAddresses other) {
|
||||
if (this == other)
|
||||
return true;
|
||||
|
||||
|
||||
if (addrs.size != other.addrs.size)
|
||||
return false;
|
||||
|
||||
|
||||
Gee.HashSet<RFC822.MailboxAddress> first = new Gee.HashSet<RFC822.MailboxAddress>();
|
||||
first.add_all(addrs);
|
||||
|
||||
|
||||
Gee.HashSet<RFC822.MailboxAddress> second = new Gee.HashSet<RFC822.MailboxAddress>();
|
||||
second.add_all(other.addrs);
|
||||
|
||||
|
||||
return Collection.are_sets_equal<RFC822.MailboxAddress>(first, second);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See Geary.MessageData.SearchableMessageData.
|
||||
*/
|
||||
public string to_searchable_string() {
|
||||
return MailboxAddress.list_to_string(addrs, "", (a) => a.to_searchable_string());
|
||||
return list_to_string(addrs, " ", (a) => a.to_searchable_string());
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
return MailboxAddress.list_to_string(addrs, "(no addresses)", (a) => a.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
public override string to_string() {
|
||||
return list_to_string(addrs, "(no addresses)", (a) => a.to_string());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
* Copyright 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.
|
||||
|
|
@ -416,106 +418,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>();
|
||||
|
||||
|
|
@ -816,19 +718,21 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the full list of recipients (to, cc, and bcc) as a searchable
|
||||
* string. Note that values that come out of this function are persisted.
|
||||
*/
|
||||
public string? get_searchable_recipients() {
|
||||
Gee.List<RFC822.MailboxAddress>? recipients = get_recipients();
|
||||
if (recipients == null)
|
||||
return null;
|
||||
|
||||
return RFC822.MailboxAddress.list_to_string(recipients, "", (a) => a.to_searchable_string());
|
||||
string searchable = null;
|
||||
Gee.List<RFC822.MailboxAddress>? recipient_list = get_recipients();
|
||||
if (recipient_list != null) {
|
||||
MailboxAddresses recipients = new MailboxAddresses(recipient_list);
|
||||
searchable = recipients.to_searchable_string();
|
||||
}
|
||||
return searchable;
|
||||
}
|
||||
|
||||
|
||||
public Memory.Buffer get_content_by_mime_id(string mime_id) throws RFC822Error {
|
||||
GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id);
|
||||
if (part == null)
|
||||
|
|
@ -873,7 +777,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.
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ public class Geary.Smtp.EhloRequest : Geary.Smtp.Request {
|
|||
|
||||
public class Geary.Smtp.MailRequest : Geary.Smtp.Request {
|
||||
public MailRequest(Geary.RFC822.MailboxAddress from) {
|
||||
base (Command.MAIL, { "from:%s".printf(from.get_simple_address()) });
|
||||
base (Command.MAIL, { "from:<%s>".printf(from.to_rfc822_address()) });
|
||||
}
|
||||
|
||||
|
||||
public MailRequest.plain(string addr) {
|
||||
base (Command.MAIL, { "from:<%s>".printf(addr) });
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class Geary.Smtp.MailRequest : Geary.Smtp.Request {
|
|||
|
||||
public class Geary.Smtp.RcptRequest : Geary.Smtp.Request {
|
||||
public RcptRequest(Geary.RFC822.MailboxAddress to) {
|
||||
base (Command.RCPT, { "to:%s".printf(to.get_simple_address()) });
|
||||
base (Command.RCPT, { "to:%s".printf(to.to_address_display("<", ">")) });
|
||||
}
|
||||
|
||||
public RcptRequest.plain(string addr) {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ extern string glib_substring(string str, long start_pos, long end_pos);
|
|||
|
||||
namespace Geary.String {
|
||||
|
||||
/** The end-of-string character, NUL. */
|
||||
public const char EOS = '\0';
|
||||
|
||||
/** A regex that matches one or more whitespace or non-printing chars. */
|
||||
public const string WS_OR_NP = "[[:space:][:cntrl:]]+";
|
||||
|
||||
|
||||
public bool is_empty_or_whitespace(string? str) {
|
||||
return (str == null || str[0] == EOS || str.strip()[0] == EOS);
|
||||
}
|
||||
|
|
@ -50,23 +55,23 @@ public int stri_cmp(string a, string b) {
|
|||
return strcmp(a.down(), b.down());
|
||||
}
|
||||
|
||||
// Removes redundant spaces, tabs, and newlines.
|
||||
public string reduce_whitespace(string _s) {
|
||||
string s = _s;
|
||||
s = s.replace("\n", " ");
|
||||
s = s.replace("\r", " ");
|
||||
s = s.replace("\t", " ");
|
||||
s = s.strip();
|
||||
|
||||
// Condense multiple spaces to one.
|
||||
for (int i = 1; i < s.length; i++) {
|
||||
if (s.get_char(i) == ' ' && s.get_char(i - 1) == ' ') {
|
||||
s = s.slice(0, i - 1) + s.slice(i, s.length);
|
||||
i--;
|
||||
}
|
||||
/**
|
||||
* Removes redundant white space and non-printing characters.
|
||||
*
|
||||
* @return the input string /str/, modified so that any non-printing
|
||||
* characters are converted to spaces, all consecutive spaces are
|
||||
* coalesced into a single space, and stripped of leading and trailing
|
||||
* white space. If //null// is passed in, the empty string is
|
||||
* returned.
|
||||
*/
|
||||
public string reduce_whitespace(string? str) {
|
||||
string s = str ?? "";
|
||||
try {
|
||||
s = new Regex(WS_OR_NP).replace(s, -1, 0, " ");
|
||||
} catch (Error err) {
|
||||
// Oh well
|
||||
}
|
||||
|
||||
return s;
|
||||
return s.strip();
|
||||
}
|
||||
|
||||
// Slices a string to, at most, max_length number of bytes (NOT including the null.)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ set(TEST_ENGINE_SRC
|
|||
engine/imap-engine/account-processor-test.vala
|
||||
engine/mime-content-type-test.vala
|
||||
engine/rfc822-mailbox-address-test.vala
|
||||
engine/rfc822-mailbox-addresses-test.vala
|
||||
engine/rfc822-message-test.vala
|
||||
engine/rfc822-message-data-test.vala
|
||||
engine/rfc822-utils-test.vala
|
||||
|
|
@ -27,6 +28,7 @@ set(TEST_ENGINE_SRC
|
|||
engine/util-idle-manager-test.vala
|
||||
engine/util-inet-test.vala
|
||||
engine/util-js-test.vala
|
||||
engine/util-string-test.vala
|
||||
engine/util-timeout-manager-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.
|
||||
|
|
@ -10,6 +10,15 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
|
|||
public MailboxAddressTest() {
|
||||
base("Geary.RFC822.MailboxAddressTest");
|
||||
add_test("is_valid_address", is_valid_address);
|
||||
add_test("unescaped_constructor", unescaped_constructor);
|
||||
add_test("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);
|
||||
add_test("to_short_display", to_short_display);
|
||||
// latter depends on the former, so test that first
|
||||
add_test("to_rfc822_address", to_rfc822_address);
|
||||
add_test("to_rfc822_string", to_rfc822_string);
|
||||
}
|
||||
|
||||
public void is_valid_address() {
|
||||
|
|
@ -30,4 +39,209 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
|
|||
assert(Geary.RFC822.MailboxAddress.is_valid_address("") == false);
|
||||
}
|
||||
|
||||
public void unescaped_constructor() {
|
||||
MailboxAddress addr1 = new MailboxAddress("test1", "test2@example.com");
|
||||
assert(addr1.name == "test1");
|
||||
assert(addr1.address == "test2@example.com");
|
||||
assert(addr1.mailbox == "test2");
|
||||
assert(addr1.domain == "example.com");
|
||||
|
||||
MailboxAddress addr2 = new MailboxAddress(null, "test1@test2@example.com");
|
||||
assert(addr2.address == "test1@test2@example.com");
|
||||
assert(addr2.mailbox == "test1@test2");
|
||||
assert(addr2.domain == "example.com");
|
||||
|
||||
MailboxAddress addr3 = new MailboxAddress(null, "©@example.com");
|
||||
assert(addr3.address == "©@example.com");
|
||||
assert(addr3.mailbox == "©");
|
||||
assert(addr3.domain == "example.com");
|
||||
|
||||
MailboxAddress addr4 = new MailboxAddress(null, "😸@example.com");
|
||||
assert(addr4.address == "😸@example.com");
|
||||
assert(addr4.mailbox == "😸");
|
||||
assert(addr4.domain == "example.com");
|
||||
|
||||
MailboxAddress addr5 = new MailboxAddress(null, "example.com");
|
||||
assert(addr5.address == "example.com");
|
||||
assert(addr5.mailbox == "");
|
||||
assert(addr5.domain == "");
|
||||
}
|
||||
|
||||
public void 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);
|
||||
assert(new MailboxAddress("", "example@example.com").is_spoofed() == false);
|
||||
assert(new MailboxAddress("test", "example@example.com").is_spoofed() == false);
|
||||
assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false);
|
||||
assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false);
|
||||
assert(new MailboxAddress("test?", "example@example.com").is_spoofed() == false);
|
||||
|
||||
assert(new MailboxAddress("test@example.com", "example@example.com").is_spoofed() == true);
|
||||
assert(new MailboxAddress("test @ example . com", "example@example.com").is_spoofed() == true);
|
||||
assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true);
|
||||
assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true);
|
||||
assert(new MailboxAddress("test", "example@\nexample@example.com").is_spoofed() == true);
|
||||
assert(new MailboxAddress("test", "example@example@example.com").is_spoofed() == true);
|
||||
|
||||
try {
|
||||
assert(new MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" <demo@mailsploit.com>")
|
||||
.is_spoofed() == true);
|
||||
} catch (Error err) {
|
||||
assert_no_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
public void has_distinct_name() {
|
||||
assert(new MailboxAddress("example", "example@example.com").has_distinct_name() == true);
|
||||
|
||||
assert(new MailboxAddress("", "example@example.com").has_distinct_name() == false);
|
||||
assert(new MailboxAddress(" ", "example@example.com").has_distinct_name() == false);
|
||||
assert(new MailboxAddress("example@example.com", "example@example.com").has_distinct_name() == false);
|
||||
assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false);
|
||||
assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false);
|
||||
}
|
||||
|
||||
public void to_full_display() {
|
||||
assert(new MailboxAddress("", "example@example.com").to_full_display() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("Test", "example@example.com").to_full_display() ==
|
||||
"Test <example@example.com>");
|
||||
assert(new MailboxAddress("example@example.com", "example@example.com").to_full_display() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("Test", "example@example@example.com").to_full_display() ==
|
||||
"example@example@example.com");
|
||||
}
|
||||
|
||||
public void to_short_display() {
|
||||
assert(new MailboxAddress("", "example@example.com").to_short_display() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("Test", "example@example.com").to_short_display() ==
|
||||
"Test");
|
||||
assert(new MailboxAddress("example@example.com", "example@example.com").to_short_display() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("Test", "example@example@example.com").to_short_display() ==
|
||||
"example@example@example.com");
|
||||
}
|
||||
|
||||
public void to_rfc822_address() {
|
||||
assert(new MailboxAddress(null, "example@example.com").to_rfc822_address() ==
|
||||
"example@example.com");
|
||||
//assert(new MailboxAddress(null, "test test@example.com").to_rfc822_address() ==
|
||||
// "\"test test\"@example.com");
|
||||
//assert(new MailboxAddress(null, "test\" test@example.com").to_rfc822_address() ==
|
||||
// "\"test\" test\"@example.com");
|
||||
//assert(new MailboxAddress(null, "test\"test@example.com").to_rfc822_address() ==
|
||||
// "\"test\"test\"@example.com");
|
||||
assert(new MailboxAddress(null, "test@test@example.com").to_rfc822_address() ==
|
||||
"\"test@test\"@example.com");
|
||||
assert(new MailboxAddress(null, "©@example.com").to_rfc822_address() ==
|
||||
"\"=?iso-8859-1?b?qQ==?=\"@example.com");
|
||||
assert(new MailboxAddress(null, "😸@example.com").to_rfc822_address() ==
|
||||
"\"=?UTF-8?b?8J+YuA==?=\"@example.com");
|
||||
}
|
||||
|
||||
public void to_rfc822_string() {
|
||||
assert(new MailboxAddress("", "example@example.com").to_rfc822_string() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress(" ", "example@example.com").to_rfc822_string() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("test", "example@example.com").to_rfc822_string() ==
|
||||
"test <example@example.com>");
|
||||
assert(new MailboxAddress("test test", "example@example.com").to_rfc822_string() ==
|
||||
"test test <example@example.com>");
|
||||
assert(new MailboxAddress("example@example.com", "example@example.com").to_rfc822_string() ==
|
||||
"example@example.com");
|
||||
assert(new MailboxAddress("test?", "example@example.com").to_rfc822_string() ==
|
||||
"test? <example@example.com>");
|
||||
assert(new MailboxAddress("test@test", "example@example.com").to_rfc822_string() ==
|
||||
"\"test@test\" <example@example.com>");
|
||||
assert(new MailboxAddress(";", "example@example.com").to_rfc822_string() ==
|
||||
"\";\" <example@example.com>");
|
||||
assert(new MailboxAddress("©", "example@example.com").to_rfc822_string() ==
|
||||
"=?iso-8859-1?b?qQ==?= <example@example.com>");
|
||||
assert(new MailboxAddress("😸", "example@example.com").to_rfc822_string() ==
|
||||
"=?UTF-8?b?8J+YuA==?= <example@example.com>");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
46
test/engine/rfc822-mailbox-addresses-test.vala
Normal file
46
test/engine/rfc822-mailbox-addresses-test.vala
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
class Geary.RFC822.MailboxAddressesTest : Gee.TestCase {
|
||||
|
||||
public MailboxAddressesTest() {
|
||||
base("Geary.RFC822.MailboxAddressesTest");
|
||||
add_test("from_rfc822_string_encoded", from_rfc822_string_encoded);
|
||||
add_test("to_rfc822_string", to_rfc822_string);
|
||||
}
|
||||
|
||||
public void from_rfc822_string_encoded() {
|
||||
MailboxAddresses addrs = new MailboxAddresses.from_rfc822_string("test@example.com");
|
||||
assert(addrs.size == 1);
|
||||
|
||||
addrs = new MailboxAddresses.from_rfc822_string("test1@example.com, test2@example.com");
|
||||
assert(addrs.size == 2);
|
||||
|
||||
// Courtesy Mailsploit https://www.mailsploit.com
|
||||
addrs = new MailboxAddresses.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" <demo@mailsploit.com>");
|
||||
assert(addrs.size == 1);
|
||||
|
||||
// Courtesy Mailsploit https://www.mailsploit.com
|
||||
addrs = new MailboxAddresses.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=?=\", <demo@mailsploit.com>");
|
||||
assert(addrs.size == 2);
|
||||
}
|
||||
|
||||
public void to_rfc822_string() {
|
||||
assert(new_addreses({ "test1@example.com" }).to_rfc822_string() ==
|
||||
"test1@example.com");
|
||||
assert(new_addreses({ "test1@example.com", "test2@example.com" }).to_rfc822_string() ==
|
||||
"test1@example.com, test2@example.com");
|
||||
}
|
||||
|
||||
private MailboxAddresses new_addreses(string[] address_strings) {
|
||||
Gee.List<MailboxAddress> addresses = new Gee.LinkedList<MailboxAddress>();
|
||||
foreach (string address in address_strings) {
|
||||
addresses.add(new MailboxAddress(null, address));
|
||||
}
|
||||
return new MailboxAddresses(addresses);
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ https://app.foobar.com/xxxxxxxxxxxxx">https://app.foobar.com/xxxxxxxxxxx</a=
|
|||
></p></td></tr>
|
||||
</table></body></html>""";
|
||||
|
||||
public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx. Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx";
|
||||
public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx. Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx";
|
||||
|
||||
public static string HTML_BODY2_ENCODED = """<!DOCTYPE html>
|
||||
<!--2c2a1c66-0638-7c87-5057-bff8be4291eb_v180-->
|
||||
|
|
@ -618,5 +618,5 @@ x 133, 3000 Bern 6, Switzerland
|
|||
|
||||
""";
|
||||
|
||||
public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland";
|
||||
public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland";
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
47
test/engine/util-string-test.vala
Normal file
47
test/engine/util-string-test.vala
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
class Geary.String.Test : Gee.TestCase {
|
||||
|
||||
public Test() {
|
||||
base("Geary.String.Test");
|
||||
add_test("test_whitespace", test_whitespace);
|
||||
add_test("test_nonprinting", test_nonprinting);
|
||||
}
|
||||
|
||||
public void test_whitespace() {
|
||||
assert(reduce_whitespace("") == "");
|
||||
assert(reduce_whitespace(" ") == "");
|
||||
assert(reduce_whitespace(" ") == "");
|
||||
assert(reduce_whitespace(" ") == "");
|
||||
assert(reduce_whitespace("test") == "test");
|
||||
assert(reduce_whitespace("test ") == "test");
|
||||
assert(reduce_whitespace("test ") == "test");
|
||||
assert(reduce_whitespace("test\n") == "test");
|
||||
assert(reduce_whitespace("test\r") == "test");
|
||||
assert(reduce_whitespace("test\t") == "test");
|
||||
assert(reduce_whitespace(" test") == "test");
|
||||
assert(reduce_whitespace(" test") == "test");
|
||||
assert(reduce_whitespace("test test") == "test test");
|
||||
assert(reduce_whitespace("test test") == "test test");
|
||||
assert(reduce_whitespace("test\ntest") == "test test");
|
||||
assert(reduce_whitespace("test\n test") == "test test");
|
||||
assert(reduce_whitespace("test \ntest") == "test test");
|
||||
assert(reduce_whitespace("test \n test") == "test test");
|
||||
assert(reduce_whitespace("test\rtest") == "test test");
|
||||
assert(reduce_whitespace("test\ttest") == "test test");
|
||||
}
|
||||
|
||||
public void test_nonprinting() {
|
||||
assert(reduce_whitespace("\0") == ""); // NUL
|
||||
assert(reduce_whitespace("\u00A0") == ""); // ENQUIRY
|
||||
assert(reduce_whitespace("\u00A0") == ""); // NO-BREAK SPACE
|
||||
assert(reduce_whitespace("\u2003") == ""); // EM SPACE
|
||||
assert(reduce_whitespace("test\n") == "test");
|
||||
assert(reduce_whitespace("test\ntest") == "test test");
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ geary_test_engine_sources = [
|
|||
'engine/imap-engine/account-processor-test.vala',
|
||||
'engine/mime-content-type-test.vala',
|
||||
'engine/rfc822-mailbox-address-test.vala',
|
||||
'engine/rfc822-mailbox-addresses-test.vala',
|
||||
'engine/rfc822-message-test.vala',
|
||||
'engine/rfc822-message-data-test.vala',
|
||||
'engine/rfc822-utils-test.vala',
|
||||
|
|
@ -23,6 +24,7 @@ geary_test_engine_sources = [
|
|||
'engine/util-idle-manager-test.vala',
|
||||
'engine/util-inet-test.vala',
|
||||
'engine/util-js-test.vala',
|
||||
'engine/util-string-test.vala',
|
||||
'engine/util-timeout-manager-test.vala'
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,11 @@ int main(string[] args) {
|
|||
engine.add_suite(new Geary.JS.Test().get_suite());
|
||||
engine.add_suite(new Geary.Mime.ContentTypeTest().get_suite());
|
||||
engine.add_suite(new Geary.RFC822.MailboxAddressTest().get_suite());
|
||||
engine.add_suite(new Geary.RFC822.MailboxAddressesTest().get_suite());
|
||||
engine.add_suite(new Geary.RFC822.MessageTest().get_suite());
|
||||
engine.add_suite(new Geary.RFC822.MessageDataTest().get_suite());
|
||||
engine.add_suite(new Geary.RFC822.Utils.Test().get_suite());
|
||||
engine.add_suite(new Geary.String.Test().get_suite());
|
||||
|
||||
/*
|
||||
* Run the tests
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue