Use GMime to display the text/plain portion of email messages: #3710

This makes available MIME portions of the message, although more work will need to be done to expose all MIME parts.
This commit is contained in:
Jim Nelson 2011-07-06 15:46:24 -07:00
parent 7f293f9a6f
commit 4b03ba004e
12 changed files with 551 additions and 148 deletions

View file

@ -253,9 +253,13 @@ public class MainWindow : Gtk.Window {
return;
}
Geary.Email text = yield current_folder.fetch_email_async(email.location.position,
Geary.Email.Field.BODY);
message_buffer.set_text(text.body.buffer.to_ascii_string());
Geary.Email full = yield current_folder.fetch_email_async(email.location.position,
Geary.Email.Field.HEADER | Geary.Email.Field.BODY);
Geary.Memory.AbstractBuffer buffer = full.get_message().get_first_mime_part_of_content_type(
"text/plain");
message_buffer.set_text(buffer.to_utf8());
}
private void on_select_message_completed(Object? source, AsyncResult result) {

View file

@ -86,6 +86,8 @@ public class Geary.Email : Object {
public Geary.Email.Field fields { get; private set; default = Field.NONE; }
private Geary.RFC822.Message? message = null;
public Email(Geary.EmailLocation location) {
this.location = location;
}
@ -130,12 +132,18 @@ public class Geary.Email : Object {
public void set_message_header(Geary.RFC822.Header header) {
this.header = header;
// reset the message object, which is built from this text
message = null;
fields |= Field.HEADER;
}
public void set_message_body(Geary.RFC822.Text body) {
this.body = body;
// reset the message object, which is built from this text
message = null;
fields |= Field.BODY;
}
@ -145,6 +153,18 @@ public class Geary.Email : Object {
fields |= Field.PROPERTIES;
}
public Geary.RFC822.Message get_message() throws EngineError, RFC822.Error {
if (message != null)
return message;
if (!fields.fulfills(Field.HEADER | Field.BODY))
throw new EngineError.INCOMPLETE_MESSAGE("Parsed email requires HEADER and BODY");
message = new Geary.RFC822.Message.from_parts(header, body);
return message;
}
public string to_string() {
StringBuilder builder = new StringBuilder();

View file

@ -123,7 +123,7 @@ public class Geary.Imap.EnvelopeDecoder : Geary.Imap.FetchDataDecoder {
message_id = null;
return new Envelope(new Geary.RFC822.Date(sent.value),
new Geary.RFC822.Subject(subject.value),
new Geary.RFC822.Subject.from_rfc822(subject.value),
parse_addresses(from), parse_addresses(sender), parse_addresses(reply_to),
(to != null) ? parse_addresses(to) : null,
(cc != null) ? parse_addresses(cc) : null,

View file

@ -0,0 +1,11 @@
/* Copyright 2011 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public errordomain Geary.RFC822.Error {
INVALID,
NOT_FOUND
}

View file

@ -48,6 +48,10 @@ public class Geary.RFC822.Subject : Geary.Common.StringMessageData, Geary.RFC822
public Subject(string value) {
base (value);
}
public Subject.from_rfc822(string value) {
base (GMime.utils_header_decode_text(value));
}
}
public class Geary.RFC822.Header : Geary.Common.BlockMessageData, Geary.RFC822.MessageData {

View file

@ -0,0 +1,78 @@
/* Copyright 2011 Yorba Foundation
*
* 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.Message : Object {
private GMime.Message? message;
public Message(Full full) {
GMime.Parser parser = new GMime.Parser.with_stream(
new GMime.StreamMem.with_buffer(full.buffer.get_buffer()));
message = parser.construct_message();
}
public Message.from_parts(Header header, Text body) {
GMime.StreamCat stream_cat = new GMime.StreamCat();
stream_cat.add_source(new GMime.StreamMem.with_buffer(header.buffer.get_buffer()));
stream_cat.add_source(new GMime.StreamMem.with_buffer(body.buffer.get_buffer()));
GMime.Parser parser = new GMime.Parser.with_stream(stream_cat);
message = parser.construct_message();
}
public bool is_decoded() {
return message != null;
}
public Geary.Memory.AbstractBuffer get_first_mime_part_of_content_type(string content_type)
throws RFC822.Error {
if (!is_decoded())
throw new RFC822.Error.INVALID("Message could not be decoded");
// search for content type starting from the root
GMime.Part? part = find_first_mime_part(message.get_mime_part(), content_type);
if (part == null) {
throw new RFC822.Error.NOT_FOUND("Could not find a MIME part with content-type %s",
content_type);
}
// convert payload to a buffer
GMime.DataWrapper? wrapper = part.get_content_object();
if (wrapper == null) {
throw new RFC822.Error.INVALID("Could not get the content wrapper for content-type %s",
content_type);
}
ByteArray byte_array = new ByteArray();
GMime.StreamMem stream = new GMime.StreamMem.with_byte_array(byte_array);
stream.set_owner(false);
wrapper.write_to_stream(stream);
return new Geary.Memory.Buffer(byte_array.data, byte_array.len);
}
private GMime.Part? find_first_mime_part(GMime.Object current_root, string content_type) {
// descend looking for the content type in a GMime.Part
GMime.Multipart? multipart = current_root as GMime.Multipart;
if (multipart != null) {
int count = multipart.get_count();
for (int ctr = 0; ctr < count; ctr++) {
GMime.Part? child_part = find_first_mime_part(multipart.get_part(ctr), content_type);
if (child_part != null)
return child_part;
}
}
GMime.Part? part = current_root as GMime.Part;
if (part != null && part.get_content_type().to_string() == content_type)
return part;
return null;
}
}

View file

@ -77,6 +77,7 @@ public class Geary.Sqlite.Folder : Geary.AbstractFolder, Geary.LocalFolder {
location.uid.to_string(), to_string());
}
// TODO: The following steps should be atomic
message_id = yield message_table.create_async(
new MessageRow.from_email(message_table, email),
cancellable);

View file

@ -191,13 +191,13 @@ public class Geary.Sqlite.MessageRow : Geary.Sqlite.Row {
}
if ((fields & Geary.Email.Field.HEADER) != 0) {
header = (email.header != null) ? email.header.buffer.to_ascii_string() : null;
header = (email.header != null) ? email.header.buffer.to_utf8() : null;
this.fields = this.fields.set(Geary.Email.Field.HEADER);
}
if ((fields & Geary.Email.Field.BODY) != 0) {
body = (email.body != null) ? email.body.buffer.to_ascii_string() : null;
body = (email.body != null) ? email.body.buffer.to_utf8() : null;
this.fields = this.fields.set(Geary.Email.Field.BODY);
}

View file

@ -20,17 +20,18 @@ public abstract class Geary.Memory.AbstractBuffer {
public abstract InputStream get_input_stream();
/**
* Returns the contents of the buffer as though it was an ASCII (or 7-bit) string. Note that
* Returns the contents of the buffer as though it was an UTF-8 string. Note that
* this involves reading the entire buffer into memory.
*
* If the conversion fails or decodes as invalid UTF-8, an empty string is returned.
*/
public string to_ascii_string() {
public string to_utf8() {
uint8[] buffer = get_buffer();
buffer += (uint8) '\0';
StringBuilder builder = new StringBuilder();
foreach (uint8 byte in buffer)
builder.append_c((char) byte);
string str = (string) buffer;
return builder.str;
return str.validate() ? str : "";
}
}

View file

@ -81,8 +81,10 @@ def build(bld):
'../engine/imap/transport/imap-serializable.vala',
'../engine/imap/transport/imap-serializer.vala',
'../engine/rfc822/rfc822-error.vala',
'../engine/rfc822/rfc822-mailbox-addresses.vala',
'../engine/rfc822/rfc822-mailbox-address.vala',
'../engine/rfc822/rfc822-message.vala',
'../engine/rfc822/rfc822-message-data.vala',
'../engine/sqlite/abstract/sqlite-database.vala',

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,24 @@
GMime cheader_filename="gmime/gmime.h" lower_case_cprefix="g_mime_"
GMime lower_case_cprefix="gmime_" cheader_filename="gmime/gmime.h"
GMimeObject abstract="1"
GMimeStream abstract="1"
g_mime_header_list_get_iter.iter is_out="1"
g_mime_message_get_date.date is_out="1"
g_mime_message_get_date.tz_offset is_out="1"
g_mime_message_get_mime_part is_nullable="1"
g_mime_object_to_string transfer_ownership="1"
g_mime_param_next name="get_next"
g_mime_parser_construct_message nullable="1"
g_mime_part_get_content_part nullable="1"
g_mime_signer_next name="get_next"
g_mime_stream_mem_new_with_buffer.buffer is_array="1" array_length_pos="1.0" type_name="uint8[]"
g_mime_stream_mem_new_with_buffer.len hidden="1"
g_mime_utils_decode_8bit transfer_ownership="1"
g_mime_utils_header_decode_date type_name="time_t"
g_mime_utils_header_decode_date.tz_offset is_out="1" nullable="1"
g_mime_utils_header_decode_phrase transfer_ownership="1"
g_mime_utils_header_decode_text transfer_ownership="1"
InternetAddress hidden="1"
internet_address_* hidden="1"