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