diff --git a/src/engine/api/geary-email.vala b/src/engine/api/geary-email.vala index fe0f7d90..e8606a21 100644 --- a/src/engine/api/geary-email.vala +++ b/src/engine/api/geary-email.vala @@ -116,7 +116,7 @@ public class Geary.Email : Object { public Geary.EmailProperties? properties { get; private set; default = null; } // PREVIEW - public RFC822.Text? preview { get; private set; default = null; } + public RFC822.PreviewText? preview { get; private set; default = null; } public Geary.Email.Field fields { get; private set; default = Field.NONE; } @@ -198,7 +198,7 @@ public class Geary.Email : Object { fields |= Field.PROPERTIES; } - public void set_message_preview(Geary.RFC822.Text preview) { + public void set_message_preview(Geary.RFC822.PreviewText preview) { this.preview = preview; fields |= Field.PREVIEW; diff --git a/src/engine/imap/transport/imap-mailbox.vala b/src/engine/imap/transport/imap-mailbox.vala index 9db16706..c5364b47 100644 --- a/src/engine/imap/transport/imap-mailbox.vala +++ b/src/engine/imap/transport/imap-mailbox.vala @@ -90,7 +90,9 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { int plain_id = batch.add(new MailboxOperation(context, fetch_cmd)); int preview_id = NonblockingBatch.INVALID_ID; + int preview_charset_id = NonblockingBatch.INVALID_ID; if (fields.require(Geary.Email.Field.PREVIEW)) { + // Preview text. FetchBodyDataType fetch_preview = new FetchBodyDataType.peek(FetchBodyDataType.SectionPart.NONE, { 1 }, 0, Geary.Email.MAX_PREVIEW_BYTES, null); Gee.List list = new Gee.ArrayList(); @@ -99,6 +101,17 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { FetchCommand preview_cmd = new FetchCommand(msg_set, null, list); preview_id = batch.add(new MailboxOperation(context, preview_cmd)); + + // Preview character set. + FetchBodyDataType fetch_preview_charset = new FetchBodyDataType.peek( + FetchBodyDataType.SectionPart.MIME, + { 1 }, -1, -1, null); + Gee.List list_charset = new Gee.ArrayList(); + list_charset.add(fetch_preview_charset); + + FetchCommand preview_charset_cmd = new FetchCommand(msg_set, null, list_charset); + + preview_charset_id = batch.add(new MailboxOperation(context, preview_charset_cmd)); } yield batch.execute_all_async(cancellable); @@ -129,20 +142,38 @@ public class Geary.Imap.Mailbox : Geary.SmartReference { // process preview FETCH results - if (preview_id != NonblockingBatch.INVALID_ID) { + if (preview_id != NonblockingBatch.INVALID_ID && + preview_charset_id != NonblockingBatch.INVALID_ID) { + MailboxOperation preview_op = (MailboxOperation) batch.get_operation(preview_id); CommandResponse preview_resp = (CommandResponse) batch.get_result(preview_id); + MailboxOperation preview_charset_op = (MailboxOperation) + batch.get_operation(preview_charset_id); + CommandResponse preview_charset_resp = (CommandResponse) + batch.get_result(preview_charset_id); + if (preview_resp.status_response.status != Status.OK) { throw new ImapError.SERVER_ERROR("Server error for %s: %s", preview_op.cmd.to_string(), preview_resp.to_string()); } + if (preview_charset_resp.status_response.status != Status.OK) { + throw new ImapError.SERVER_ERROR("Server error for %s: %s", + preview_charset_op.cmd.to_string(), preview_charset_resp.to_string()); + } + FetchResults[] preview_results = FetchResults.decode(preview_resp); + FetchResults[] preview_header_results = FetchResults.decode(preview_charset_resp); + int i = 0; foreach (FetchResults preview_res in preview_results) { Geary.Email? preview_email = map.get(preview_res.msg_num); - if (preview_email != null) - preview_email.set_message_preview(new RFC822.Text(preview_res.get_body_data()[0])); + if (preview_email == null) + continue; + + preview_email.set_message_preview(new RFC822.PreviewText( + preview_res.get_body_data()[0], preview_header_results[i].get_body_data()[0])); + i++; } } diff --git a/src/engine/rfc822/rfc822-message-data.vala b/src/engine/rfc822/rfc822-message-data.vala index 6ed3988e..93799b57 100644 --- a/src/engine/rfc822/rfc822-message-data.vala +++ b/src/engine/rfc822/rfc822-message-data.vala @@ -149,3 +149,48 @@ public class Geary.RFC822.Full : Geary.Common.BlockMessageData, Geary.RFC822.Mes } } +// Used for decoding preview text. +public class Geary.RFC822.PreviewText : Geary.RFC822.Text { + public PreviewText(Geary.Memory.AbstractBuffer _buffer, Geary.Memory.AbstractBuffer? + preview_header = null) { + + Geary.Memory.AbstractBuffer buffer = _buffer; + + if (preview_header != null) { + string? charset = null; + string? encoding = null; + + // Parse the header. + GMime.Stream header_stream = new GMime.StreamMem.with_buffer( + preview_header.get_array()); + GMime.Parser parser = new GMime.Parser.with_stream(header_stream); + GMime.Part? part = parser.construct_part() as GMime.Part; + if (part != null) { + charset = part.get_content_type_parameter("charset"); + encoding = part.get_header("Content-Transfer-Encoding"); + } + + GMime.StreamMem input_stream = new GMime.StreamMem.with_buffer(buffer.get_array()); + + ByteArray output = new ByteArray(); + GMime.StreamMem output_stream = new GMime.StreamMem.with_byte_array(output); + output_stream.set_owner(false); + + // Convert the encoding and character set. + GMime.StreamFilter filter = new GMime.StreamFilter(output_stream); + if (encoding != null) + filter.add(new GMime.FilterBasic(GMime.content_encoding_from_string(encoding), false)); + + if (charset != null) + filter.add(new GMime.FilterCharset(charset, "UTF8")); + + input_stream.write_to_stream(filter); + uint8[] data = output.data; + data += (uint8) '\0'; + buffer = new Geary.Memory.StringBuffer((string) data); + } + + base (buffer); + } +} + diff --git a/src/engine/rfc822/rfc822-message.vala b/src/engine/rfc822/rfc822-message.vala index a91db65e..9c358f41 100644 --- a/src/engine/rfc822/rfc822-message.vala +++ b/src/engine/rfc822/rfc822-message.vala @@ -183,7 +183,12 @@ public class Geary.RFC822.Message : Object { GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array); stream.set_owner(false); - wrapper.write_to_stream(stream); + // Convert encoding to UTF-8. + GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream); + stream_filter.add(new GMime.FilterCharset(part.get_content_type_parameter("charset"), + "UTF8")); + + wrapper.write_to_stream(stream_filter); return new Geary.Memory.Buffer(byte_array.data, byte_array.len); } diff --git a/src/engine/sqlite/email/sqlite-message-row.vala b/src/engine/sqlite/email/sqlite-message-row.vala index 69d33878..e6507c05 100644 --- a/src/engine/sqlite/email/sqlite-message-row.vala +++ b/src/engine/sqlite/email/sqlite-message-row.vala @@ -120,7 +120,7 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row { email.set_message_body(new RFC822.Text(new Geary.Memory.StringBuffer(body))); if (((fields & Geary.Email.Field.PREVIEW) != 0) && (preview != null)) - email.set_message_preview(new RFC822.Text(new Geary.Memory.StringBuffer(preview))); + email.set_message_preview(new RFC822.PreviewText(new Geary.Memory.StringBuffer(preview))); return email; }