From 1aac6f22847f2602b10b9f3da91d8d38a949ec0f Mon Sep 17 00:00:00 2001 From: Torben Date: Sat, 30 Nov 2019 20:09:28 +0100 Subject: [PATCH] Change GMime dependency from 2.6.17 to 3.2.4 This commit squashes several non-compiling commits: 66dd6500 Change required GMime version to 3.2.4 or higher 4b9c8a38 Fix some compilations errors in test code 98aa5a2e Fix some (hopefully) last compilation errors 558360c6 Fix parser format when getting message headers cc248ffc Fix name of stream-buffer mode b293c66b Fix another iteration over a header-list 52fa183f Fix parsing of Gmime parameters e078ee62 Use Unix2Dos-, Dos2Unix- and/or SmtpData-filters instead of 'FilterCRLF' ff31b8e5 Fix setting of email headers eb676482 Fix compilation errros due to string-uint8-char conversion problems 8558769f Fix datetime conversion d44a28cd Remove some obsolete arguments 1ce81662 Pass charset where it's required 6013806f Pass GMime.ParserOptions to header-decode methods 986d05a0 Pass GMime.FormatOptions where it's required e9b93187 Pass GMime.ParserOptions where it's required 640ce667 Fix compilation errors in GMime initialization 312f80bf Remove use of GMime.HeaderIter acc73d14 Change GMime dependency from 2.6 to 3.0 54fc250a Adapt names to 'offical' gmime-2.6 bindings The commits are part of the branch 'letorbi/gmime-3-spread', which can be found at: https://gitlab.gnome.org/letorbi/geary/tree/letorbi/gmime-3-spread --- meson.build | 2 +- src/engine/imap-db/imap-db-attachment.vala | 2 +- src/engine/mime/mime-content-disposition.vala | 2 +- src/engine/mime/mime-content-parameters.vala | 10 +- src/engine/mime/mime-content-type.vala | 7 +- .../rfc822-gmime-filter-blockquotes.vala | 8 +- .../rfc822/rfc822-gmime-filter-flowed.vala | 6 +- .../rfc822/rfc822-gmime-filter-plain.vala | 6 +- src/engine/rfc822/rfc822-mailbox-address.vala | 32 ++- .../rfc822/rfc822-mailbox-addresses.vala | 17 +- src/engine/rfc822/rfc822-message-data.vala | 50 +--- src/engine/rfc822/rfc822-message.vala | 233 ++++++++++-------- src/engine/rfc822/rfc822-part.vala | 4 +- src/engine/rfc822/rfc822-utils.vala | 4 +- src/engine/rfc822/rfc822.vala | 24 +- .../imap-db/imap-db-attachment-test.vala | 20 +- test/engine/rfc822-part-test.vala | 12 +- 17 files changed, 236 insertions(+), 203 deletions(-) diff --git a/meson.build b/meson.build index 62f092a6..c8e0c277 100644 --- a/meson.build +++ b/meson.build @@ -57,7 +57,7 @@ target_webkit = '2.24' # Primary deps glib = dependency('glib-2.0', version: '>=' + target_glib) -gmime = dependency('gmime-2.6', version: '>= 2.6.17') +gmime = dependency('gmime-3.0', version: '>= 3.2.4') gtk = dependency('gtk+-3.0', version: '>=' + target_gtk) sqlite = dependency('sqlite3', version: '>= 3.24') webkit2gtk = dependency('webkit2gtk-4.0', version: '>=' + target_webkit) diff --git a/src/engine/imap-db/imap-db-attachment.vala b/src/engine/imap-db/imap-db-attachment.vala index f2aacc25..4db81717 100644 --- a/src/engine/imap-db/imap-db-attachment.vala +++ b/src/engine/imap-db/imap-db-attachment.vala @@ -180,7 +180,7 @@ private class Geary.ImapDB.Attachment : Geary.Attachment { target_stream ); stream = new GMime.StreamBuffer( - stream, GMime.StreamBufferMode.BLOCK_WRITE + stream, GMime.StreamBufferMode.WRITE ); part.write_to_stream(stream, RFC822.Part.EncodingConversion.NONE); diff --git a/src/engine/mime/mime-content-disposition.vala b/src/engine/mime/mime-content-disposition.vala index 9b34849f..675aac75 100644 --- a/src/engine/mime/mime-content-disposition.vala +++ b/src/engine/mime/mime-content-disposition.vala @@ -102,7 +102,7 @@ public class Geary.Mime.ContentDisposition : Geary.BaseObject { out is_unknown); is_unknown_disposition_type = is_unknown; original_disposition_type_string = content_disposition.get_disposition(); - params = new ContentParameters.from_gmime(content_disposition.get_params()); + params = new ContentParameters.from_gmime(content_disposition.get_parameters()); } } diff --git a/src/engine/mime/mime-content-parameters.vala b/src/engine/mime/mime-content-parameters.vala index 0267a9d5..cc201053 100644 --- a/src/engine/mime/mime-content-parameters.vala +++ b/src/engine/mime/mime-content-parameters.vala @@ -51,11 +51,13 @@ public class Geary.Mime.ContentParameters : BaseObject { } } - internal ContentParameters.from_gmime(GMime.Param? gmime_param) { + internal ContentParameters.from_gmime(GMime.ParamList? gmime_params) { Gee.Map params = new Gee.HashMap(); - while (gmime_param != null) { - params.set(gmime_param.get_name(), gmime_param.get_value()); - gmime_param = gmime_param.get_next(); + if (gmime_params != null) { + for (int i=0; i < gmime_params.length(); i++) { + GMime.Param gmime_param = gmime_params.get_parameter_at(i); + params.set(gmime_param.get_name(), gmime_param.get_value()); + } } this(params); } diff --git a/src/engine/mime/mime-content-type.vala b/src/engine/mime/mime-content-type.vala index ed59a491..9fe333d8 100644 --- a/src/engine/mime/mime-content-type.vala +++ b/src/engine/mime/mime-content-type.vala @@ -74,7 +74,10 @@ public class Geary.Mime.ContentType : Geary.BaseObject { if (!str.contains("/")) throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str); - return new ContentType.from_gmime(new GMime.ContentType.from_string(str)); + return new ContentType.from_gmime(GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), + str + )); } /** @@ -158,7 +161,7 @@ public class Geary.Mime.ContentType : Geary.BaseObject { internal ContentType.from_gmime(GMime.ContentType content_type) { media_type = content_type.get_media_type().strip(); media_subtype = content_type.get_media_subtype().strip(); - params = new ContentParameters.from_gmime(content_type.get_params()); + params = new ContentParameters.from_gmime(content_type.get_parameters()); } /** diff --git a/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala b/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala index 7d18342d..6f8c118f 100644 --- a/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala +++ b/src/engine/rfc822/rfc822-gmime-filter-blockquotes.vala @@ -49,7 +49,7 @@ private class Geary.RFC822.FilterBlockquotes : GMime.Filter { return new_filter; } - private void do_filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + private void do_filter(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace, bool flush) { // This may not be strictly necessary. @@ -64,7 +64,7 @@ private class Geary.RFC822.FilterBlockquotes : GMime.Filter { } for (uint i = 0; i < inbuf.length; i++) { - char c = inbuf[i]; + uint8 c = inbuf[i]; if (in_prefix && !in_tag) { if (c == Geary.RFC822.Utils.QUOTE_MARKER) { @@ -122,12 +122,12 @@ private class Geary.RFC822.FilterBlockquotes : GMime.Filter { outprespace = this.outpre; } - public override void filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void filter(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { do_filter(inbuf, prespace, out processed_buffer, out outprespace, false); } - public override void complete(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void complete(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { do_filter(inbuf, prespace, out processed_buffer, out outprespace, true); } diff --git a/src/engine/rfc822/rfc822-gmime-filter-flowed.vala b/src/engine/rfc822/rfc822-gmime-filter-flowed.vala index 3f5a28a9..d018c537 100644 --- a/src/engine/rfc822/rfc822-gmime-filter-flowed.vala +++ b/src/engine/rfc822/rfc822-gmime-filter-flowed.vala @@ -57,7 +57,7 @@ private class Geary.RFC822.FilterFlowed : GMime.Filter { return new_filter; } - public override void filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void filter(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { // Worst-case scenario: We are about to leave the prefix, @@ -67,7 +67,7 @@ private class Geary.RFC822.FilterFlowed : GMime.Filter { uint out_index = 0; for (uint i = 0; i < inbuf.length; i++) { - char c = inbuf[i]; + uint8 c = inbuf[i]; if (this.in_prefix) { if (c == '>') { @@ -147,7 +147,7 @@ private class Geary.RFC822.FilterFlowed : GMime.Filter { outprespace = this.outpre; } - public override void complete(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void complete(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { filter(inbuf, prespace, out processed_buffer, out outprespace); } diff --git a/src/engine/rfc822/rfc822-gmime-filter-plain.vala b/src/engine/rfc822/rfc822-gmime-filter-plain.vala index d66e3284..b38baac0 100644 --- a/src/engine/rfc822/rfc822-gmime-filter-plain.vala +++ b/src/engine/rfc822/rfc822-gmime-filter-plain.vala @@ -26,7 +26,7 @@ private class Geary.RFC822.FilterPlain : GMime.Filter { return new_filter; } - public override void filter(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void filter(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { // This may not be strictly necessary. @@ -34,7 +34,7 @@ private class Geary.RFC822.FilterPlain : GMime.Filter { uint out_index = 0; for (uint i = 0; i < inbuf.length; i++) { - char c = inbuf[i]; + uint8 c = inbuf[i]; if (in_prefix) { if (c == '>') { @@ -56,7 +56,7 @@ private class Geary.RFC822.FilterPlain : GMime.Filter { outprespace = this.outpre; } - public override void complete(char[] inbuf, size_t prespace, out unowned char[] processed_buffer, + public override void complete(uint8[] inbuf, size_t prespace, out unowned uint8[] processed_buffer, out size_t outprespace) { filter(inbuf, prespace, out processed_buffer, out outprespace); } diff --git a/src/engine/rfc822/rfc822-mailbox-address.vala b/src/engine/rfc822/rfc822-mailbox-address.vala index 73803f19..1153cdd1 100644 --- a/src/engine/rfc822/rfc822-mailbox-address.vala +++ b/src/engine/rfc822/rfc822-mailbox-address.vala @@ -42,11 +42,17 @@ public class Geary.RFC822.MailboxAddress : } private static string decode_name(string name) { - return GMime.utils_header_decode_phrase(prepare_header_text_part(name)); + return GMime.utils_header_decode_phrase( + Geary.RFC822.get_parser_options(), + prepare_header_text_part(name) + ); } private static string decode_address_part(string mailbox) { - return GMime.utils_header_decode_text(prepare_header_text_part(mailbox)); + return GMime.utils_header_decode_text( + Geary.RFC822.get_parser_options(), + prepare_header_text_part(mailbox) + ); } private static bool display_name_needs_quoting(string name) { @@ -118,8 +124,9 @@ public class Geary.RFC822.MailboxAddress : // _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); + string text = GMime.utils_text_is_8bit(part.data) + ? part : GMime.utils_decode_8bit(Geary.RFC822.get_parser_options(), + part.data); // unquote the string then decode the text GMime.utils_unquote_string(text); @@ -222,16 +229,19 @@ public class Geary.RFC822.MailboxAddress : } public MailboxAddress.from_rfc822_string(string rfc822) throws RFC822Error { - InternetAddressList addrlist = InternetAddressList.parse_string(rfc822); + GMime.InternetAddressList addrlist = GMime.InternetAddressList.parse( + Geary.RFC822.get_parser_options(), + rfc822 + ); if (addrlist == null) return; int length = addrlist.length(); for (int ctr = 0; ctr < length; ctr++) { - InternetAddress? addr = addrlist.get_address(ctr); + GMime.InternetAddress? addr = addrlist.get_address(ctr); // TODO: Handle group lists - InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox; + GMime.InternetAddressMailbox? mbox_addr = addr as GMime.InternetAddressMailbox; if (mbox_addr != null) { this.gmime(mbox_addr); return; @@ -240,7 +250,7 @@ public class Geary.RFC822.MailboxAddress : throw new RFC822Error.INVALID("Could not parse RFC822 address: %s", rfc822); } - public MailboxAddress.gmime(InternetAddressMailbox mailbox) { + public MailboxAddress.gmime(GMime.InternetAddressMailbox mailbox) { // GMime strips source route for us, so the address part // should only ever contain a single '@' string? name = mailbox.get_name(); @@ -456,7 +466,11 @@ public class Geary.RFC822.MailboxAddress : public string to_rfc822_string() { return has_distinct_name() ? "%s <%s>".printf( - GMime.utils_header_encode_phrase(this.name), + GMime.utils_header_encode_phrase( + Geary.RFC822.get_format_options(), + this.name, + Geary.RFC822.get_charset() + ), to_rfc822_address() ) : to_rfc822_address(); diff --git a/src/engine/rfc822/rfc822-mailbox-addresses.vala b/src/engine/rfc822/rfc822-mailbox-addresses.vala index 8ebf79de..5d647fa7 100644 --- a/src/engine/rfc822/rfc822-mailbox-addresses.vala +++ b/src/engine/rfc822/rfc822-mailbox-addresses.vala @@ -74,27 +74,30 @@ public class Geary.RFC822.MailboxAddresses : } public MailboxAddresses.from_rfc822_string(string rfc822) { - InternetAddressList addrlist = InternetAddressList.parse_string(rfc822); + GMime.InternetAddressList addrlist = GMime.InternetAddressList.parse( + Geary.RFC822.get_parser_options(), + rfc822 + ); if (addrlist == null) return; int length = addrlist.length(); for (int ctr = 0; ctr < length; ctr++) { - InternetAddress? addr = addrlist.get_address(ctr); + GMime.InternetAddress? addr = addrlist.get_address(ctr); - InternetAddressMailbox? mbox_addr = addr as InternetAddressMailbox; + GMime.InternetAddressMailbox? mbox_addr = addr as GMime.InternetAddressMailbox; 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; + GMime.InternetAddressGroup? mbox_group = addr as GMime.InternetAddressGroup; if (mbox_group != null) { - InternetAddressList group_list = mbox_group.get_members(); + GMime.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; + GMime.InternetAddressMailbox? group_addr = + addrlist.get_address(i) as GMime.InternetAddressMailbox; if (group_addr != null) { this.addrs.add(new MailboxAddress.gmime(group_addr)); } diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala index 2d399986..b5d62ad5 100644 --- a/src/engine/rfc822/rfc822-message-data.vala +++ b/src/engine/rfc822/rfc822-message-data.vala @@ -173,27 +173,13 @@ public class Geary.RFC822.Date : Geary.RFC822.MessageData, Geary.MessageData.Abs public DateTime value { get; private set; } public Date(string rfc822) throws ImapError { - int offset = 0; - int64 time_t_utc = GMime.utils_header_decode_date(rfc822, out offset); - if (time_t_utc == 0) - throw new ImapError.PARSE_ERROR( - "Unable to parse \"%s\": Not ISO-8601 date", rfc822 - ); - - DateTime? value = new DateTime.from_unix_utc(time_t_utc); + DateTime? value = GMime.utils_header_decode_date(rfc822); if (value == null) { throw new ImapError.PARSE_ERROR( "Unable to parse \"%s\": Outside supported range", rfc822 ); } this.value = value; - - if (offset != 0) { - this.value = value.to_timezone( - new GLib.TimeZone("%+05d".printf(offset)) - ); - } - this.original = rfc822; } @@ -206,18 +192,7 @@ public class Geary.RFC822.Date : Geary.RFC822.MessageData, Geary.MessageData.Abs * Returns the {@link Date} in RFC 822 format. */ public string to_rfc822_string() { - // Although GMime documents its conversion methods as - // requiring the tz offset in hours, it appears the number is - // handed directly to the string (i.e. an offset of -7:30 becomes - // "-0007", whereas we want "-0730"). - int hours = (int) GLib.Math.floor(value.get_utc_offset() / TimeSpan.HOUR); - int minutes = (int) ( - (value.get_utc_offset() % TimeSpan.HOUR) / (double) TimeSpan.HOUR * 60 - ); - return GMime.utils_header_format_date( - (time_t) this.value.to_utc().to_unix(), - (hours * 100) + minutes - ); + return GMime.utils_header_format_date(this.value); } /** @@ -261,7 +236,7 @@ public class Geary.RFC822.Subject : Geary.MessageData.StringMessageData, } public Subject.decode(string value) { - base (GMime.utils_header_decode_text(value)); + base (GMime.utils_header_decode_text(Geary.RFC822.get_parser_options(), value)); original = value; } @@ -337,9 +312,10 @@ public class Geary.RFC822.Header : Geary.MessageData.BlockMessageData, Geary.RFC GMime.Parser parser = new GMime.Parser.with_stream(Utils.create_stream_mem(buffer)); parser.set_respect_content_length(false); - parser.set_scan_from(false); + // TODO Could this be omitted? + parser.set_format(GMime.Format.MESSAGE); - message = parser.construct_message(); + message = parser.construct_message(Geary.RFC822.get_parser_options()); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 headers"); @@ -347,17 +323,15 @@ public class Geary.RFC822.Header : Geary.MessageData.BlockMessageData, Geary.RFC } public string? get_header(string name) throws RFC822Error { - return get_headers().get(name); + return get_headers().get_header(name).get_value(); } public string[] get_header_names() throws RFC822Error { if (this.names == null) { this.names = new string[0]; - GMime.HeaderIter iter = new GMime.HeaderIter(); - if (get_headers().get_iter(iter) && iter.first()) { - do { - names += iter.get_name(); - } while (iter.next()); + GMime.HeaderList headers = get_headers(); + for (int i = 0; i < headers.get_count(); i++) { + names += headers.get_header_at(i).get_name(); } } return this.names; @@ -388,7 +362,7 @@ public class Geary.RFC822.PreviewText : Geary.RFC822.Text { // Parse the header. GMime.Stream header_stream = Utils.create_stream_mem(preview_header); GMime.Parser parser = new GMime.Parser.with_stream(header_stream); - GMime.Part? gpart = parser.construct_part() as GMime.Part; + GMime.Part? gpart = parser.construct_part(Geary.RFC822.get_parser_options()) as GMime.Part; if (gpart != null) { Part part = new Part(gpart); @@ -402,7 +376,7 @@ public class Geary.RFC822.PreviewText : Geary.RFC822.Text { new GMime.StreamMem.with_buffer(preview.get_uint8_array()), gpart.get_content_encoding() ); - gpart.set_content_object(body); + gpart.set_content(body); try { Memory.Buffer preview_buffer = part.write_to_buffer( diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala index 02d4b397..8a94cf2c 100644 --- a/src/engine/rfc822/rfc822-message.vala +++ b/src/engine/rfc822/rfc822-message.vala @@ -29,6 +29,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { */ public delegate string? InlinePartReplacer(Part part); + private const string HEADER_DATE = "Date"; private const string HEADER_SENDER = "Sender"; private const string HEADER_IN_REPLY_TO = "In-Reply-To"; private const string HEADER_REFERENCES = "References"; @@ -89,7 +90,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { public Message(Full full) throws RFC822Error { GMime.Parser parser = new GMime.Parser.with_stream(Utils.create_stream_mem(full.buffer)); - message = parser.construct_message(); + message = parser.construct_message(Geary.RFC822.get_parser_options()); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 message"); @@ -115,7 +116,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { stream_cat.add_source(new GMime.StreamMem.with_buffer(body.buffer.get_bytes().get_data())); GMime.Parser parser = new GMime.Parser.with_stream(stream_cat); - message = parser.construct_message(); + message = parser.construct_message(Geary.RFC822.get_parser_options()); if (message == null) throw new RFC822Error.INVALID("Unable to parse RFC 822 message"); @@ -136,67 +137,71 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { this.from = email.from; this.date = email.date; - // GMimeMessage.set_sender actually sets the From header - and - // although the API docs make it sound otherwise, it also - // supports a list of addresses - message.set_sender(this.from.to_rfc822_string()); - message.set_date_as_string(this.date.serialize()); - if (message_id != null) { - this.message_id = new MessageID(message_id); - message.set_message_id(message_id); + //message.set_date_as_string(this.date.serialize()); + this.message.set_header(HEADER_DATE, + this.date.serialize(), + Geary.RFC822.get_charset()); + + if (email.from != null) { + foreach (RFC822.MailboxAddress mailbox in email.from) + this.message.add_mailbox(GMime.AddressType.FROM, mailbox.name, mailbox.address); + } + + if (email.sender != null) { + this.message.add_mailbox(GMime.AddressType.SENDER, this.sender.name, this.sender.address); + // TODO Is setting the header still required? + this.message.set_header(HEADER_SENDER, + this.sender.to_rfc822_string(), + Geary.RFC822.get_charset()); } // Optional headers if (email.to != null) { this.to = email.to; foreach (RFC822.MailboxAddress mailbox in email.to) - this.message.add_recipient(GMime.RecipientType.TO, mailbox.name, mailbox.address); + this.message.add_mailbox(GMime.AddressType.TO, mailbox.name, mailbox.address); } if (email.cc != null) { this.cc = email.cc; foreach (RFC822.MailboxAddress mailbox in email.cc) - this.message.add_recipient(GMime.RecipientType.CC, mailbox.name, mailbox.address); + this.message.add_mailbox(GMime.AddressType.CC, mailbox.name, mailbox.address); } if (email.bcc != null) { this.bcc = email.bcc; foreach (RFC822.MailboxAddress mailbox in email.bcc) - this.message.add_recipient(GMime.RecipientType.BCC, mailbox.name, mailbox.address); - } - - if (email.sender != null) { - this.sender = email.sender; - this.message.set_header(HEADER_SENDER, - email.sender.to_rfc822_string()); - } - - if (email.reply_to != null) { - this.reply_to = email.reply_to; - this.message.set_reply_to(email.reply_to.to_rfc822_string()); + this.message.add_mailbox(GMime.AddressType.BCC, mailbox.name, mailbox.address); } if (email.in_reply_to != null) { this.in_reply_to = email.in_reply_to; + foreach (RFC822.MailboxAddress mailbox in email.reply_to) + this.message.add_mailbox(GMime.AddressType.BCC, mailbox.name, mailbox.address); + // TODO Is setting the header still required? this.message.set_header(HEADER_IN_REPLY_TO, - email.in_reply_to.to_rfc822_string()); + email.in_reply_to.to_rfc822_string(), + Geary.RFC822.get_charset()); } if (email.references != null) { this.references = email.references; this.message.set_header(HEADER_REFERENCES, - email.references.to_rfc822_string()); + email.references.to_rfc822_string(), + Geary.RFC822.get_charset()); } if (email.subject != null) { this.subject = email.subject; - this.message.set_subject(email.subject.value); + this.message.set_subject(email.subject.value, + Geary.RFC822.get_charset()); } // User-Agent if (!Geary.String.is_empty(email.mailer)) { this.mailer = email.mailer; - this.message.set_header(HEADER_MAILER, email.mailer); + this.message.set_header(HEADER_MAILER, email.mailer, + Geary.RFC822.get_charset()); } // Build the message's body mime parts @@ -406,7 +411,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { string type) { GMime.Object? part = coalesce_parts(parts, "related"); if (parts.size > 1) { - part.set_header("Type", type); + part.set_header("Type", type, Geary.RFC822.get_charset()); } return part; } @@ -437,7 +442,8 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { part.set_disposition(disposition.serialize()); part.set_filename(file.get_basename()); - GMime.ContentType content_type = new GMime.ContentType.from_string( + GMime.ContentType content_type = GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), file_info.get_content_type() ); part.set_content_type(content_type); @@ -468,7 +474,10 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { ); } - GMime.ContentType? content_type = new GMime.ContentType.from_string(mime_type.get_mime_type()); + GMime.ContentType? content_type = GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), + mime_type.get_mime_type() + ); if (content_type == null) { throw new RFC822Error.INVALID( @@ -515,7 +524,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { } part.set_content_encoding(encoding); - part.set_content_object( + part.set_content( new GMime.DataWrapper.with_stream( stream, GMime.ContentEncoding.BINARY ) @@ -536,7 +545,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { Geary.Email email = new Geary.Email(id); email.set_message_header(new Geary.RFC822.Header(new Geary.Memory.StringBuffer( - message.get_headers()))); + message.get_headers(Geary.RFC822.get_format_options())))); email.set_send_date(date); email.set_originators(from, sender, reply_to); email.set_receivers(to, cc, bcc); @@ -884,68 +893,72 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { } 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; + GMime.HeaderList headers = this.message.get_header_list(); + for (int i=0; i < headers.get_count(); i++) { + GMime.Header header = headers.get_header_at(i); + string name = header.get_name(); + string value = header.get_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 "message-id": - this.message_id = new MessageID(value); - 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; + 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 "message-id": + this.message_id = new MessageID(value); + 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(Geary.RFC822.get_parser_options(), value); + break; + + default: + break; + } + }; } private MailboxAddresses append_address(MailboxAddresses? existing, @@ -990,11 +1003,11 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { if (requested_disposition == Mime.DispositionType.UNSPECIFIED || disposition == requested_disposition) { GMime.Stream stream = new GMime.StreamMem(); - message.write_to_stream(stream); + message.write_to_stream(Geary.RFC822.get_format_options(), stream); GMime.DataWrapper data = new GMime.DataWrapper.with_stream(stream, GMime.ContentEncoding.BINARY); // Equivalent to no encoding GMime.Part part = new GMime.Part.with_type("message", "rfc822"); - part.set_content_object(data); + part.set_content(data); part.set_filename((message.get_subject() ?? _("(no subject)")) + ".eml"); attachments.add(new Part(part)); } @@ -1017,7 +1030,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { #if WITH_TNEF_SUPPORT if (content_type.is_type("application", "vnd.ms-tnef")) { GMime.StreamMem stream = new GMime.StreamMem(); - ((GMime.Part) root).get_content_object().write_to_stream(stream); + ((GMime.Part) root).get_content().write_to_stream(stream); ByteArray tnef_data = stream.get_byte_array(); Ytnef.TNEFStruct tn; if (Ytnef.ParseMemory(tnef_data.data, out tn) == 0) { @@ -1052,8 +1065,8 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { GMime.Part part = new GMime.Part(); part.set_filename(filename); - part.set_content_type(new GMime.ContentType.from_string(GLib.ContentType.guess(filename, data, null))); - part.set_content_object(new GMime.DataWrapper.with_stream(new GMime.StreamMem.with_buffer(data), GMime.ContentEncoding.BINARY)); + part.set_content_type(GMime.ContentType.parse(Geary.RFC822.get_parser_options(), GLib.ContentType.guess(filename, data, null))); + part.set_content(new GMime.DataWrapper.with_stream(new GMime.StreamMem.with_buffer(data), GMime.ContentEncoding.BINARY)); return part; } #endif @@ -1092,9 +1105,17 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { stream.set_owner(false); GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream); - stream_filter.add(new GMime.FilterCRLF(encoded, dotstuffed)); + if (encoded) { + stream_filter.add(new GMime.FilterUnix2Dos(false)); + } + else { + stream_filter.add(new GMime.FilterDos2Unix(false)); + } + if (dotstuffed) { + stream_filter.add(new GMime.FilterSmtpData()); + } - if (message.write_to_stream(stream_filter) < 0) + if (message.write_to_stream(Geary.RFC822.get_format_options(), stream_filter) < 0) throw new RFC822Error.FAILED("Unable to write RFC822 message to memory buffer"); if (stream_filter.flush() != 0) @@ -1104,7 +1125,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { } public string to_string() { - return message.to_string(); + return message.to_string(Geary.RFC822.get_format_options()); } /** @@ -1152,11 +1173,13 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { // Base64-encoded text needs to have CR's added after LF's // before encoding, otherwise it breaks format=flowed. See // Bug 753528. - filter_stream.add(new GMime.FilterCRLF(true, false)); + filter_stream.add(new GMime.FilterUnix2Dos(false)); } - GMime.ContentType complete_type = - new GMime.ContentType.from_string(content_type); + GMime.ContentType complete_type = GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), + content_type + ); complete_type.set_parameter("charset", charset); if (is_flowed) { complete_type.set_parameter("format", "flowed"); @@ -1168,7 +1191,7 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet { GMime.Part body_part = new GMime.Part(); body_part.set_content_type(complete_type); - body_part.set_content_object(body); + body_part.set_content(body); body_part.set_content_encoding(encoding); return body_part; } diff --git a/src/engine/rfc822/rfc822-part.vala b/src/engine/rfc822/rfc822-part.vala index b8a3b2a8..6d0521d1 100644 --- a/src/engine/rfc822/rfc822-part.vala +++ b/src/engine/rfc822/rfc822-part.vala @@ -160,7 +160,7 @@ public class Geary.RFC822.Part : Object { BodyFormatting format = BodyFormatting.NONE) throws RFC822Error { GMime.DataWrapper? wrapper = (this.source_part != null) - ? this.source_part.get_content_object() : null; + ? this.source_part.get_content() : null; if (wrapper == null) { throw new RFC822Error.INVALID( "Could not get the content wrapper for content-type %s", @@ -201,7 +201,7 @@ public class Geary.RFC822.Part : Object { if ((this.source_part == null || this.source_part.encoding != BASE64) && !(content_type.media_subtype in CR_PRESERVING_TEXT_TYPES)) { - filter.add(new GMime.FilterCRLF(false, false)); + filter.add(new GMime.FilterDos2Unix(false)); } if (flowed) { diff --git a/src/engine/rfc822/rfc822-utils.vala b/src/engine/rfc822/rfc822-utils.vala index 37a967fb..d9278f64 100644 --- a/src/engine/rfc822/rfc822-utils.vala +++ b/src/engine/rfc822/rfc822-utils.vala @@ -186,7 +186,7 @@ public string email_addresses_for_reply(Geary.RFC822.MailboxAddresses? addresses } -public bool comp_char_arr_slice(char[] array, uint start, string comp) { +public bool comp_char_arr_slice(uint8[] array, uint start, string comp) { for (int i = 0; i < comp.length; i++) { if (array[start + i] != comp[i]) return false; @@ -277,7 +277,7 @@ public async string get_best_charset(GMime.Stream in_stream, }, cancellable ); - return filter.charset(); + return filter.get_charset(); } /** diff --git a/src/engine/rfc822/rfc822.vala b/src/engine/rfc822/rfc822.vala index 9195eac1..39a400e5 100644 --- a/src/engine/rfc822/rfc822.vala +++ b/src/engine/rfc822/rfc822.vala @@ -32,18 +32,7 @@ public void init() { if (init_count++ != 0) return; - GMime.init(GMime.ENABLE_RFC2047_WORKAROUNDS); - - // This has the effect of ensuring all non US-ASCII and non-ISO-8859-1 - // headers are always encoded as UTF-8. This should be fine because - // message bodies are also always sent as UTF-8. - const string?[] USER_CHARSETS = { - UTF8_CHARSET, - // GMime.set_user_charsets calls g_strdupv under the hood, so - // the array needs to be null-terminated - null - }; - GMime.set_user_charsets(USER_CHARSETS); + GMime.init(); try { invalid_filename_character_re = new Regex("[/\\0]"); @@ -52,6 +41,17 @@ public void init() { } } +public GMime.FormatOptions get_format_options() { + return new GMime.FormatOptions(); +} + +public GMime.ParserOptions get_parser_options() { + return new GMime.ParserOptions(); +} + +public string? get_charset() { + return null; +} internal bool is_utf_8(string charset) { string up = charset.up(); diff --git a/test/engine/imap-db/imap-db-attachment-test.vala b/test/engine/imap-db/imap-db-attachment-test.vala index d24f2b8d..9366802e 100644 --- a/test/engine/imap-db/imap-db-attachment-test.vala +++ b/test/engine/imap-db/imap-db-attachment-test.vala @@ -21,7 +21,7 @@ class Geary.ImapDB.AttachmentTest : TestCase { public void new_from_minimal_mime_part() throws Error { GMime.Part part = new_part(null, ATTACHMENT_BODY.data); - part.set_header("Content-Type", ""); + part.set_header("Content-Type", "", Geary.RFC822.get_charset()); Attachment test = new Attachment.from_part( 1, new Geary.RFC822.Part(part) @@ -51,7 +51,8 @@ class Geary.ImapDB.AttachmentTest : TestCase { part.set_content_id(ID); part.set_content_description(DESC); part.set_content_disposition( - new GMime.ContentDisposition.from_string( + GMime.ContentDisposition.parse( + Geary.RFC822.get_parser_options(), "attachment; filename=%s".printf(NAME) ) ); @@ -74,7 +75,10 @@ class Geary.ImapDB.AttachmentTest : TestCase { public void new_from_inline_mime_part() throws Error { GMime.Part part = new_part(null, ATTACHMENT_BODY.data); part.set_content_disposition( - new GMime.ContentDisposition.from_string("inline") + GMime.ContentDisposition.parse( + Geary.RFC822.get_parser_options(), + "inline" + ) ); Attachment test = new Attachment.from_part( @@ -205,7 +209,8 @@ CREATE TABLE MessageAttachmentTable ( part.set_content_id(ID); part.set_content_description(DESCRIPTION); part.set_content_disposition( - new GMime.ContentDisposition.from_string( + GMime.ContentDisposition.parse( + Geary.RFC822.get_parser_options(), "inline; filename=%s;".printf(FILENAME) )); @@ -352,12 +357,15 @@ private GMime.Part new_part(string? mime_type, GMime.ContentEncoding encoding = GMime.ContentEncoding.DEFAULT) { GMime.Part part = new GMime.Part(); if (mime_type != null) { - part.set_content_type(new GMime.ContentType.from_string(mime_type)); + part.set_content_type(GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), + mime_type + )); } GMime.DataWrapper body_wrapper = new GMime.DataWrapper.with_stream( new GMime.StreamMem.with_buffer(body), encoding ); - part.set_content_object(body_wrapper); + part.set_content(body_wrapper); return part; } diff --git a/test/engine/rfc822-part-test.vala b/test/engine/rfc822-part-test.vala index b6e82e60..4fbe5d87 100644 --- a/test/engine/rfc822-part-test.vala +++ b/test/engine/rfc822-part-test.vala @@ -40,7 +40,10 @@ class Geary.RFC822.PartTest : TestCase { part.set_content_id(ID); part.set_content_description(DESC); part.set_content_disposition( - new GMime.ContentDisposition.from_string("inline") + GMime.ContentDisposition.parse( + Geary.RFC822.get_parser_options(), + "inline" + ) ); Part test = new Part(part); @@ -93,13 +96,16 @@ class Geary.RFC822.PartTest : TestCase { uint8[] body) { GMime.Part part = new GMime.Part(); if (mime_type != null) { - part.set_content_type(new GMime.ContentType.from_string(mime_type)); + part.set_content_type(GMime.ContentType.parse( + Geary.RFC822.get_parser_options(), + mime_type + )); } GMime.DataWrapper body_wrapper = new GMime.DataWrapper.with_stream( new GMime.StreamMem.with_buffer(body), GMime.ContentEncoding.BINARY ); - part.set_content_object(body_wrapper); + part.set_content(body_wrapper); part.encode(GMime.EncodingConstraint.7BIT); return part; }