Convert all MIME handling to Engine classes: Closes #6530
We've had numerous bugs due to improper MIME comparisons and dealing with Content-Type and Content-Disposition (or their lack of presence in a message). Now the Engine offers MIME classes that better deal with these issues without exporting the GMime structures, which are not as easy to manage and don't offer some of the things that have bitten us in the past (such as case-insensitive comparisons).
This commit is contained in:
parent
eed221bf3a
commit
e29a9c801a
17 changed files with 702 additions and 122 deletions
|
|
@ -682,13 +682,13 @@ namespace GMime {
|
|||
[CCode (cname = "g_mime_object_encode")]
|
||||
public virtual void encode (GMime.EncodingConstraint constraint);
|
||||
[CCode (cname = "g_mime_object_get_content_disposition")]
|
||||
public unowned GMime.ContentDisposition get_content_disposition ();
|
||||
public unowned GMime.ContentDisposition? get_content_disposition ();
|
||||
[CCode (cname = "g_mime_object_get_content_disposition_parameter")]
|
||||
public unowned string get_content_disposition_parameter (string attribute);
|
||||
[CCode (cname = "g_mime_object_get_content_id")]
|
||||
public unowned string get_content_id ();
|
||||
[CCode (cname = "g_mime_object_get_content_type")]
|
||||
public unowned GMime.ContentType get_content_type ();
|
||||
public unowned GMime.ContentType? get_content_type ();
|
||||
[CCode (cname = "g_mime_object_get_content_type_parameter")]
|
||||
public unowned string? get_content_type_parameter (string name);
|
||||
[CCode (cname = "g_mime_object_get_disposition")]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ 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_get_content_disposition nullable="1"
|
||||
g_mime_object_get_content_type nullable="1"
|
||||
g_mime_object_get_content_type_parameter nullable="1"
|
||||
g_mime_object_to_string transfer_ownership="1"
|
||||
g_mime_param_next name="get_next"
|
||||
|
|
|
|||
|
|
@ -229,6 +229,13 @@ engine/memory/memory-unowned-byte-array-buffer.vala
|
|||
engine/memory/memory-unowned-bytes-buffer.vala
|
||||
engine/memory/memory-unowned-string-buffer.vala
|
||||
|
||||
engine/mime/mime-content-disposition.vala
|
||||
engine/mime/mime-content-parameters.vala
|
||||
engine/mime/mime-content-type.vala
|
||||
engine/mime/mime-data-format.vala
|
||||
engine/mime/mime-disposition-type.vala
|
||||
engine/mime/mime-error.vala
|
||||
|
||||
engine/nonblocking/nonblocking-abstract-semaphore.vala
|
||||
engine/nonblocking/nonblocking-batch.vala
|
||||
engine/nonblocking/nonblocking-concurrent.vala
|
||||
|
|
|
|||
|
|
@ -15,8 +15,16 @@ public class ConversationViewer : Gtk.Box {
|
|||
| Geary.Email.Field.FLAGS
|
||||
| Geary.Email.Field.PREVIEW;
|
||||
|
||||
public const string INLINE_MIME_TYPES =
|
||||
"image/png image/gif image/jpeg image/pjpeg image/bmp image/x-icon image/x-xbitmap image/x-xbm";
|
||||
private const string[] INLINE_MIME_TYPES = {
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/pjpeg",
|
||||
"image/bmp",
|
||||
"image/x-icon",
|
||||
"image/x-xbitmap",
|
||||
"image/x-xbm"
|
||||
};
|
||||
|
||||
private const int ATTACHMENT_PREVIEW_SIZE = 50;
|
||||
private const int SELECT_CONVERSATION_TIMEOUT_MSEC = 100;
|
||||
|
|
@ -691,9 +699,26 @@ public class ConversationViewer : Gtk.Box {
|
|||
}
|
||||
}
|
||||
|
||||
private static string? inline_image_replacer(string filename, string mimetype, Geary.Memory.Buffer buffer) {
|
||||
if (!(mimetype in INLINE_MIME_TYPES))
|
||||
private static bool is_content_type_supported_inline(Geary.Mime.ContentType content_type) {
|
||||
foreach (string mime_type in INLINE_MIME_TYPES) {
|
||||
try {
|
||||
if (content_type.is_mime_type(mime_type))
|
||||
return true;
|
||||
} catch (Error err) {
|
||||
debug("Unable to compare MIME type %s: %s", mime_type, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? inline_image_replacer(string filename, Geary.Mime.ContentType? content_type,
|
||||
Geary.Mime.ContentDisposition? disposition, Geary.Memory.Buffer buffer) {
|
||||
if (content_type == null || !is_content_type_supported_inline(content_type)) {
|
||||
debug("Not displaying %s inline: unsupported Content-Type", content_type.to_string());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Even if the image doesn't need to be rotated, there's a win here: by reducing the size
|
||||
// of the image at load time, it reduces the amount of work that has to be done to insert
|
||||
|
|
@ -729,7 +754,7 @@ public class ConversationViewer : Gtk.Box {
|
|||
}
|
||||
|
||||
return "<img alt=\"%s\" class=\"%s\" src=\"%s\" />".printf(
|
||||
filename, DATA_IMAGE_CLASS, assemble_data_uri(mimetype, rotated_image));
|
||||
filename, DATA_IMAGE_CLASS, assemble_data_uri(content_type.get_mime_type(), rotated_image));
|
||||
}
|
||||
|
||||
// Called by Gdk.PixbufLoader when the image's size has been determined but not loaded yet ...
|
||||
|
|
@ -1809,12 +1834,12 @@ public class ConversationViewer : Gtk.Box {
|
|||
}
|
||||
|
||||
private static bool should_show_attachment(Geary.Attachment attachment) {
|
||||
switch (attachment.disposition) {
|
||||
case Geary.Attachment.Disposition.ATTACHMENT:
|
||||
switch (attachment.content_disposition.disposition_type) {
|
||||
case Geary.Mime.DispositionType.ATTACHMENT:
|
||||
return true;
|
||||
|
||||
case Geary.Attachment.Disposition.INLINE:
|
||||
return !(attachment.mime_type in INLINE_MIME_TYPES);
|
||||
case Geary.Mime.DispositionType.INLINE:
|
||||
return !is_content_type_supported_inline(attachment.content_type);
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
|
|
@ -1876,7 +1901,7 @@ public class ConversationViewer : Gtk.Box {
|
|||
// Set the image preview and insert it into the container.
|
||||
WebKit.DOM.HTMLImageElement img =
|
||||
Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
|
||||
web_view.set_attachment_src(img, attachment.mime_type, attachment.file.get_path(),
|
||||
web_view.set_attachment_src(img, attachment.content_type, attachment.file.get_path(),
|
||||
ATTACHMENT_PREVIEW_SIZE);
|
||||
attachment_container.append_child(attachment_table);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ public class ConversationWebView : WebKit.WebView {
|
|||
}
|
||||
}
|
||||
|
||||
public void set_attachment_src(WebKit.DOM.HTMLImageElement img, string mime_type, string filename,
|
||||
int maxwidth, int maxheight = -1) {
|
||||
public void set_attachment_src(WebKit.DOM.HTMLImageElement img, Geary.Mime.ContentType content_type,
|
||||
string filename, int maxwidth, int maxheight = -1) {
|
||||
if( maxheight == -1 ){
|
||||
maxheight = maxwidth;
|
||||
}
|
||||
|
|
@ -231,9 +231,9 @@ public class ConversationWebView : WebKit.WebView {
|
|||
try {
|
||||
// If the file is an image, use it. Otherwise get the icon for this mime_type.
|
||||
uint8[] content;
|
||||
string content_type = ContentType.from_mime_type(mime_type);
|
||||
string icon_mime_type = mime_type;
|
||||
if (mime_type.has_prefix("image/")) {
|
||||
string gio_content_type = ContentType.from_mime_type(content_type.get_mime_type());
|
||||
string icon_mime_type = content_type.get_mime_type();
|
||||
if (content_type.has_media_type("image")) {
|
||||
// Get a thumbnail for the image.
|
||||
// TODO Generate and save the thumbnail when extracting the attachments rather than
|
||||
// when showing them in the viewer.
|
||||
|
|
@ -245,7 +245,7 @@ public class ConversationWebView : WebKit.WebView {
|
|||
icon_mime_type = "image/png";
|
||||
} else {
|
||||
// Load the icon for this mime type.
|
||||
ThemedIcon icon = ContentType.get_icon(content_type) as ThemedIcon;
|
||||
ThemedIcon icon = ContentType.get_icon(gio_content_type) as ThemedIcon;
|
||||
string icon_filename = IconFactory.instance.lookup_icon(icon.names[0], maxwidth)
|
||||
.get_filename();
|
||||
FileUtils.get_data(icon_filename, out content);
|
||||
|
|
|
|||
|
|
@ -11,42 +11,6 @@
|
|||
*/
|
||||
|
||||
public abstract class Geary.Attachment : BaseObject {
|
||||
// NOTE: These values are persisted on disk and should not be modified unless you know what
|
||||
// you're doing.
|
||||
public enum Disposition {
|
||||
ATTACHMENT = 0,
|
||||
INLINE = 1;
|
||||
|
||||
public static Disposition? from_string(string? str) {
|
||||
// Returns null to indicate an unknown disposition
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (str.down()) {
|
||||
case "attachment":
|
||||
return ATTACHMENT;
|
||||
|
||||
case "inline":
|
||||
return INLINE;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Disposition from_int(int i) {
|
||||
switch (i) {
|
||||
case INLINE:
|
||||
return INLINE;
|
||||
|
||||
case ATTACHMENT:
|
||||
default:
|
||||
return ATTACHMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier that can be used to locate the {@link Attachment} in an {@link Email}.
|
||||
*
|
||||
|
|
@ -69,9 +33,9 @@ public abstract class Geary.Attachment : BaseObject {
|
|||
public File file { get; private set; }
|
||||
|
||||
/**
|
||||
* The MIME type of the {@link Attachment}.
|
||||
* The {@link Mime.ContentType} of the {@link Attachment}.
|
||||
*/
|
||||
public string mime_type { get; private set; }
|
||||
public Mime.ContentType content_type { get; private set; }
|
||||
|
||||
/**
|
||||
* The file size (in bytes) if the {@link file}.
|
||||
|
|
@ -83,16 +47,16 @@ public abstract class Geary.Attachment : BaseObject {
|
|||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183]]
|
||||
*/
|
||||
public Disposition disposition { get; private set; }
|
||||
public Mime.ContentDisposition content_disposition { get; private set; }
|
||||
|
||||
protected Attachment(string id, File file, bool has_supplied_filename, string mime_type, int64 filesize,
|
||||
Disposition disposition) {
|
||||
protected Attachment(string id, File file, bool has_supplied_filename, Mime.ContentType content_type,
|
||||
int64 filesize, Mime.ContentDisposition content_disposition) {
|
||||
this.id = id;
|
||||
this.file = file;
|
||||
this.has_supplied_filename = has_supplied_filename;
|
||||
this.mime_type = mime_type;
|
||||
this.content_type = content_type;
|
||||
this.filesize = filesize;
|
||||
this.disposition = disposition;
|
||||
this.content_disposition = content_disposition;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ private class Geary.ImapDB.Attachment : Geary.Attachment {
|
|||
|
||||
private const string ATTACHMENTS_DIR = "attachments";
|
||||
|
||||
protected Attachment(File data_dir, string? filename, string mime_type, int64 filesize,
|
||||
int64 message_id, int64 attachment_id, Geary.Attachment.Disposition disposition) {
|
||||
protected Attachment(File data_dir, string? filename, Mime.ContentType content_type, int64 filesize,
|
||||
int64 message_id, int64 attachment_id, Mime.ContentDisposition content_disposition) {
|
||||
base (generate_id(attachment_id),generate_file(data_dir, message_id, attachment_id, filename),
|
||||
!String.is_empty(filename), mime_type, filesize, disposition);
|
||||
!String.is_empty(filename), content_type, filesize, content_disposition);
|
||||
}
|
||||
|
||||
private static string generate_id(int64 attachment_id) {
|
||||
|
|
|
|||
|
|
@ -256,9 +256,9 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
|
|||
try {
|
||||
Geary.RFC822.Message message = new Geary.RFC822.Message.from_parts(
|
||||
new RFC822.Header(header), new RFC822.Text(body));
|
||||
Geary.Attachment.Disposition? target_disposition = null;
|
||||
Mime.DispositionType target_disposition = Mime.DispositionType.UNSPECIFIED;
|
||||
if (message.get_sub_messages().is_empty)
|
||||
target_disposition = Geary.Attachment.Disposition.INLINE;
|
||||
target_disposition = Mime.DispositionType.INLINE;
|
||||
Geary.ImapDB.Folder.do_save_attachments_db(cx, id,
|
||||
message.get_attachments(target_disposition), this, null);
|
||||
} catch (Error e) {
|
||||
|
|
|
|||
|
|
@ -1874,9 +1874,11 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
|
||||
Gee.List<Geary.Attachment> list = new Gee.ArrayList<Geary.Attachment>();
|
||||
do {
|
||||
Mime.ContentDisposition disposition = new Mime.ContentDisposition.simple(
|
||||
Mime.DispositionType.from_int(results.int_at(4)));
|
||||
list.add(new ImapDB.Attachment(cx.database.db_file.get_parent(), results.string_at(1),
|
||||
results.nonnull_string_at(2), results.int64_at(3), message_id, results.rowid_at(0),
|
||||
Geary.Attachment.Disposition.from_int(results.int_at(4))));
|
||||
Mime.ContentType.deserialize(results.nonnull_string_at(2)), results.int64_at(3),
|
||||
message_id, results.rowid_at(0), disposition));
|
||||
} while (results.next(cancellable));
|
||||
|
||||
return list;
|
||||
|
|
@ -1907,6 +1909,14 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
attachment_data.write_to_stream(stream); // data is null if it's 0 bytes
|
||||
uint filesize = byte_array.len;
|
||||
|
||||
// convert into DispositionType enum, which is stored as int
|
||||
// (legacy code stored UNSPECIFIED as NULL, which is zero, which is ATTACHMENT, so preserve
|
||||
// this behavior)
|
||||
Mime.DispositionType disposition_type = Mime.DispositionType.deserialize(disposition,
|
||||
null);
|
||||
if (disposition_type == Mime.DispositionType.UNSPECIFIED)
|
||||
disposition_type = Mime.DispositionType.ATTACHMENT;
|
||||
|
||||
// Insert it into the database.
|
||||
Db.Statement stmt = cx.prepare("""
|
||||
INSERT INTO MessageAttachmentTable (message_id, filename, mime_type, filesize, disposition)
|
||||
|
|
@ -1916,7 +1926,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
|
|||
stmt.bind_string(1, filename);
|
||||
stmt.bind_string(2, mime_type);
|
||||
stmt.bind_uint(3, filesize);
|
||||
stmt.bind_int(4, Geary.Attachment.Disposition.from_string(disposition));
|
||||
stmt.bind_int(4, disposition_type);
|
||||
|
||||
int64 attachment_id = stmt.exec_insert(cancellable);
|
||||
|
||||
|
|
|
|||
108
src/engine/mime/mime-content-disposition.vala
Normal file
108
src/engine/mime/mime-content-disposition.vala
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A representation of the RFC 2183 Content-Disposition field.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183]]
|
||||
*/
|
||||
|
||||
public class Geary.Mime.ContentDisposition : Geary.BaseObject {
|
||||
/**
|
||||
* Filename parameter name.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2.3]]
|
||||
*/
|
||||
public const string FILENAME = "filename";
|
||||
|
||||
/**
|
||||
* Creation-Date parameter name.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2.4]]
|
||||
*/
|
||||
public const string CREATION_DATE = "creation-date";
|
||||
|
||||
/**
|
||||
* Modification-Date parameter name.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2.5]]
|
||||
*/
|
||||
public const string MODIFICATION_DATE = "modification-date";
|
||||
|
||||
/**
|
||||
* Read-Date parameter name.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2.6]]
|
||||
*/
|
||||
public const string READ_DATE = "read-date";
|
||||
|
||||
/**
|
||||
* Size parameter name.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2.7]]
|
||||
*/
|
||||
public const string SIZE = "size";
|
||||
|
||||
/**
|
||||
* The {@link DispositionType}, which is {@link DispositionType.NONE} if not specified.
|
||||
*/
|
||||
public DispositionType disposition_type { get; private set; }
|
||||
|
||||
/**
|
||||
* True if the original DispositionType was unknown.
|
||||
*/
|
||||
public bool is_unknown_disposition_type { get; private set; }
|
||||
|
||||
/**
|
||||
* The original disposition type string.
|
||||
*/
|
||||
public string? original_disposition_type_string { get; private set; }
|
||||
|
||||
/**
|
||||
* Various parameters associated with the content's disposition.
|
||||
*
|
||||
* This is never null. Rather, an empty ContentParameters is held if the Content-Type has
|
||||
* no parameters.
|
||||
*
|
||||
* @see FILENAME
|
||||
* @see CREATION_DATE
|
||||
* @see MODIFICATION_DATE
|
||||
* @see READ_DATE
|
||||
* @see SIZE
|
||||
*/
|
||||
public ContentParameters params { get; private set; }
|
||||
|
||||
/**
|
||||
* Create a Content-Disposition representation
|
||||
*/
|
||||
public ContentDisposition(string? disposition, ContentParameters? params) {
|
||||
bool is_unknown;
|
||||
disposition_type = DispositionType.deserialize(disposition, out is_unknown);
|
||||
is_unknown_disposition_type = is_unknown;
|
||||
original_disposition_type_string = disposition;
|
||||
this.params = params ?? new ContentParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simplified Content-Disposition representation.
|
||||
*/
|
||||
public ContentDisposition.simple(DispositionType disposition_type) {
|
||||
this.disposition_type = disposition_type;
|
||||
is_unknown_disposition_type = false;
|
||||
original_disposition_type_string = null;
|
||||
this.params = new ContentParameters();
|
||||
}
|
||||
|
||||
internal ContentDisposition.from_gmime(GMime.ContentDisposition content_disposition) {
|
||||
bool is_unknown;
|
||||
disposition_type = DispositionType.deserialize(content_disposition.get_disposition(),
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
114
src/engine/mime/mime-content-parameters.vala
Normal file
114
src/engine/mime/mime-content-parameters.vala
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Content parameters (for {@link ContentType} and {@link ContentDisposition}).
|
||||
*/
|
||||
|
||||
public class Geary.Mime.ContentParameters : BaseObject {
|
||||
public int size {
|
||||
get {
|
||||
return params.size;
|
||||
}
|
||||
}
|
||||
|
||||
public Gee.Collection<string> attributes {
|
||||
owned get {
|
||||
return params.keys;
|
||||
}
|
||||
}
|
||||
|
||||
// See get_parameters() for why the keys but not the values are stored case-insensitive
|
||||
private Gee.HashMap<string, string> params = new Gee.HashMap<string, string>(
|
||||
String.stri_hash, String.stri_equal);
|
||||
|
||||
/**
|
||||
* Create a mapping of content parameters.
|
||||
*
|
||||
* A Gee.Map may be supplied to initialize the parameter attributes (names) and values.
|
||||
*
|
||||
* Note that params may be any kind of Map, but they will be stored internally in a Map that
|
||||
* uses case-insensitive keys. See {@link get_parameters} for more details.
|
||||
*/
|
||||
public ContentParameters(Gee.Map<string, string>? params = null) {
|
||||
if (params != null && params.size > 0)
|
||||
Collection.map_set_all<string, string>(this.params, params);
|
||||
}
|
||||
|
||||
internal ContentParameters.from_gmime(GMime.Param? gmime_param) {
|
||||
while (gmime_param != null) {
|
||||
set_parameter(gmime_param.get_name(), gmime_param.get_value());
|
||||
gmime_param = gmime_param.get_next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A read-only mapping of parameter attributes (names) and values.
|
||||
*
|
||||
* Note that names are stored as case-insensitive tokens. The MIME specification does allow
|
||||
* for some parameter values to be case-sensitive and so they are stored as such. It is up
|
||||
* to the caller to use the right comparison method.
|
||||
*
|
||||
* @see is_parameter_ci
|
||||
* @see is_parameter_cs
|
||||
*/
|
||||
public Gee.Map<string, string> get_parameters() {
|
||||
return params.read_only_view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter value for the attribute name.
|
||||
*
|
||||
* Returns null if not present.
|
||||
*/
|
||||
public string? get_value(string attribute) {
|
||||
return params.get(attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the attribute has the supplied value (case-insensitive comparison).
|
||||
*
|
||||
* @see has_value_ci
|
||||
*/
|
||||
public bool has_value_ci(string attribute, string value) {
|
||||
string? stored = params.get(attribute);
|
||||
|
||||
return (stored != null) ? String.stri_equal(stored, value) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the attribute has the supplied value (case-sensitive comparison).
|
||||
*
|
||||
* @see has_value_cs
|
||||
*/
|
||||
public bool has_value_cs(string attribute, string value) {
|
||||
string? stored = params.get(attribute);
|
||||
|
||||
return (stored != null) ? (stored == value) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or replace the parameter.
|
||||
*
|
||||
* Returns true if the parameter was added, false, otherwise.
|
||||
*/
|
||||
public bool set_parameter(string attribute, string value) {
|
||||
bool added = !params.has_key(attribute);
|
||||
params.set(attribute, value);
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the parameter.
|
||||
*
|
||||
* Returns true if the parameter was present.
|
||||
*/
|
||||
public bool remove_parameter(string attribute) {
|
||||
return params.unset(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
199
src/engine/mime/mime-content-type.vala
Normal file
199
src/engine/mime/mime-content-type.vala
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A representation of an RFC 2045 MIME Content-Type field.
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2045#section-5]]
|
||||
*/
|
||||
|
||||
public class Geary.Mime.ContentType : Geary.BaseObject {
|
||||
/*
|
||||
* MIME wildcard for comparing {@link media_type} and {@link media_subtype}.
|
||||
*
|
||||
* @see is_type
|
||||
*/
|
||||
public const string WILDCARD = "*";
|
||||
|
||||
/**
|
||||
* The type (discrete or concrete) portion of the Content-Type field.
|
||||
*
|
||||
* It's highly recommended the caller use the various ''has'' and ''is'' methods when performing
|
||||
* comparisons rather than direct string operations.
|
||||
*
|
||||
* media_type may be {@link WILDCARD}, in which case it matches with any other media_type.
|
||||
*
|
||||
* @see has_media_type
|
||||
*/
|
||||
public string media_type { get; private set; }
|
||||
|
||||
/**
|
||||
* The subtype (extension-token or iana-token) portion of the Content-Type field.
|
||||
*
|
||||
* It's highly recommended the caller use the various ''has'' and ''is'' methods when performing
|
||||
* comparisons rather than direct string operations.
|
||||
*
|
||||
* media_subtype may be {@link WILDCARD}, in which case it matches with any other media_subtype.
|
||||
*
|
||||
* @see has_media_subtype
|
||||
*/
|
||||
public string media_subtype { get; private set; }
|
||||
|
||||
/**
|
||||
* Content parameters, if any, in the Content-Type field.
|
||||
*
|
||||
* This is never null. Rather, an empty ContentParameters is held if the Content-Type has
|
||||
* no parameters.
|
||||
*/
|
||||
public ContentParameters params { get; private set; }
|
||||
|
||||
/**
|
||||
* Create a MIME Content-Type representative object.
|
||||
*/
|
||||
public ContentType(string media_type, string media_subtype, ContentParameters? params) {
|
||||
this.media_type = media_type.strip();
|
||||
this.media_subtype = media_subtype.strip();
|
||||
this.params = params ?? new ContentParameters();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public static ContentType deserialize(string str) throws MimeError {
|
||||
// perform a little sanity checking here, as it doesn't appear the GMime constructor has
|
||||
// any error-reporting at all
|
||||
if (String.is_empty(str))
|
||||
throw new MimeError.PARSE("Empty MIME Content-Type");
|
||||
|
||||
if (!str.contains("/"))
|
||||
throw new MimeError.PARSE("Invalid MIME Content-Type: %s", str);
|
||||
|
||||
return new ContentType.from_gmime(new GMime.ContentType.from_string(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the {@link media_type} with the supplied type.
|
||||
*
|
||||
* An asterisk ("*") or {@link WILDCARD) are accepted, which will always return true.
|
||||
*
|
||||
* @see is_type
|
||||
*/
|
||||
public bool has_media_type(string media_type) {
|
||||
return (media_type != WILDCARD) ? String.stri_equal(this.media_type, media_type) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the {@link media_subtype} with the supplied subtype.
|
||||
*
|
||||
* An asterisk ("*") or {@link WILDCARD) are accepted, which will always return true.
|
||||
*
|
||||
* @see is_type
|
||||
*/
|
||||
public bool has_media_subtype(string media_subtype) {
|
||||
return (media_subtype != WILDCARD) ? String.stri_equal(this.media_subtype, media_subtype) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ContentType}'s media content type (its "MIME type").
|
||||
*
|
||||
* This returns the bare MIME content type description lacking all parameters. For example,
|
||||
* "image/jpeg; name='photo.JPG'" will be returned as "image/jpeg".
|
||||
*
|
||||
* @see serialize
|
||||
*/
|
||||
public string get_mime_type() {
|
||||
return "%s/%s".printf(media_type, media_subtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the supplied type and subtype with this instance's.
|
||||
*
|
||||
* Asterisks (or {@link WILDCARD}) may be supplied for either field.
|
||||
*
|
||||
* @see is_same
|
||||
*/
|
||||
public bool is_type(string media_type, string media_subtype) {
|
||||
return has_media_type(media_type) && has_media_subtype(media_subtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@link ContentType} with another instance.
|
||||
*
|
||||
* This is slightly different than the notion of "equal to", as it's possible for
|
||||
* {@link ContentType} to hold {@link WILDCARD}s, which don't imply equality.
|
||||
*
|
||||
* @see is_type
|
||||
*/
|
||||
public bool is_same(ContentType other) {
|
||||
return is_type(other.media_type, other.media_subtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the supplied MIME type (i.e. "image/jpeg") with this instance.
|
||||
*
|
||||
* As in {@link get_mime_type}, this method is only worried about the media type and subtype
|
||||
* in the supplied string. Parameters are ignored.
|
||||
*
|
||||
* Throws {@link MimeError} if the supplied string doesn't look like a MIME type.
|
||||
*/
|
||||
public bool is_mime_type(string mime_type) throws MimeError {
|
||||
int index = mime_type.index_of_char('/');
|
||||
if (index < 0)
|
||||
throw new MimeError.PARSE("Invalid MIME type: %s", mime_type);
|
||||
|
||||
string mime_media_type = mime_type.substring(0, index).strip();
|
||||
|
||||
string mime_media_subtype = mime_type.substring(index + 1);
|
||||
index = mime_media_subtype.index_of_char(';');
|
||||
if (index >= 0)
|
||||
mime_media_subtype = mime_media_subtype.substring(0, index);
|
||||
mime_media_subtype = mime_media_subtype.strip();
|
||||
|
||||
if (String.is_empty(mime_media_type) || String.is_empty(mime_media_subtype))
|
||||
throw new MimeError.PARSE("Invalid MIME type: %s", mime_type);
|
||||
|
||||
return is_type(mime_media_type, mime_media_subtype);
|
||||
}
|
||||
|
||||
public string serialize() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append_printf("%s/%s", media_type, media_subtype);
|
||||
|
||||
if (params != null && params.size > 0) {
|
||||
foreach (string attribute in params.attributes) {
|
||||
string value = params.get_value(attribute);
|
||||
|
||||
switch (DataFormat.get_encoding_requirement(value)) {
|
||||
case DataFormat.Encoding.QUOTING_OPTIONAL:
|
||||
builder.append_printf("; %s=%s", attribute, value);
|
||||
break;
|
||||
|
||||
case DataFormat.Encoding.QUOTING_REQUIRED:
|
||||
builder.append_printf("; %s=\"%s\"", attribute, value);
|
||||
break;
|
||||
|
||||
case DataFormat.Encoding.UNALLOWED:
|
||||
message("Cannot encode ContentType param value %s=\"%s\": unallowed",
|
||||
attribute, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.str;
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return serialize();
|
||||
}
|
||||
}
|
||||
|
||||
45
src/engine/mime/mime-data-format.vala
Normal file
45
src/engine/mime/mime-data-format.vala
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility methods for manipulating and examining data particular to MIME.
|
||||
*/
|
||||
|
||||
namespace Geary.Mime.DataFormat {
|
||||
|
||||
private const char[] CONTENT_TYPE_TOKEN_SPECIALS = {
|
||||
'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '='
|
||||
};
|
||||
|
||||
public enum Encoding {
|
||||
QUOTING_REQUIRED,
|
||||
QUOTING_OPTIONAL,
|
||||
UNALLOWED
|
||||
}
|
||||
|
||||
public Encoding get_encoding_requirement(string str) {
|
||||
if (String.is_empty(str))
|
||||
return Encoding.QUOTING_REQUIRED;
|
||||
|
||||
Encoding encoding = Encoding.QUOTING_OPTIONAL;
|
||||
int index = 0;
|
||||
for (;;) {
|
||||
char ch = str[index++];
|
||||
if (ch == String.EOS)
|
||||
break;
|
||||
|
||||
if (ch.iscntrl())
|
||||
return Encoding.UNALLOWED;
|
||||
|
||||
// don't return immediately, it's possible unallowed characters may still be ahead
|
||||
if (ch.isspace() || ch in CONTENT_TYPE_TOKEN_SPECIALS)
|
||||
encoding = Encoding.QUOTING_REQUIRED;
|
||||
}
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
}
|
||||
87
src/engine/mime/mime-disposition-type.vala
Normal file
87
src/engine/mime/mime-disposition-type.vala
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A representation of a MIME Content-Disposition type field.
|
||||
*
|
||||
* Note that NONE only indicates that the Content-Disposition type field was not present.
|
||||
* RFC 2183 Section 2.8 specifies that unknown type fields should be treated as attachments,
|
||||
* which is true in this code as well.
|
||||
*
|
||||
* These values may be persisted on disk and should not be modified unless you know what
|
||||
* you're doing. (Legacy code requires that NONE be -1.)
|
||||
*
|
||||
* See [[https://tools.ietf.org/html/rfc2183#section-2]]
|
||||
*/
|
||||
|
||||
public enum Geary.Mime.DispositionType {
|
||||
UNSPECIFIED = -1,
|
||||
ATTACHMENT = 0,
|
||||
INLINE = 1;
|
||||
|
||||
/**
|
||||
* Convert the disposition-type field into an internal representation.
|
||||
*
|
||||
* Empty or blank fields result in {@link UNSPECIFIED}. Unknown fields are converted to
|
||||
* {@link ATTACHMENT} as per RFC 2183 Section 2.8. However, since the caller may want to
|
||||
* make a decision about unknown vs. unspecified type fields, is_unknown is returned as well.
|
||||
*/
|
||||
public static DispositionType deserialize(string? str, out bool is_unknown) {
|
||||
is_unknown = false;
|
||||
|
||||
if (String.is_empty_or_whitespace(str))
|
||||
return UNSPECIFIED;
|
||||
|
||||
switch (str.down()) {
|
||||
case "inline":
|
||||
return INLINE;
|
||||
|
||||
case "attachment":
|
||||
return ATTACHMENT;
|
||||
|
||||
default:
|
||||
is_unknown = true;
|
||||
|
||||
return ATTACHMENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if value is {@link UNSPECIFIED}
|
||||
*/
|
||||
public string? serialize() {
|
||||
switch (this) {
|
||||
case UNSPECIFIED:
|
||||
return null;
|
||||
|
||||
case ATTACHMENT:
|
||||
return "attachment";
|
||||
|
||||
case INLINE:
|
||||
return "inline";
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
internal static DispositionType from_int(int i) {
|
||||
switch (i) {
|
||||
case INLINE:
|
||||
return INLINE;
|
||||
|
||||
case UNSPECIFIED:
|
||||
return UNSPECIFIED;
|
||||
|
||||
// see note in class description for why unknown content-dispositions are treated as
|
||||
// attachments
|
||||
case ATTACHMENT:
|
||||
default:
|
||||
return ATTACHMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/engine/mime/mime-error.vala
Normal file
13
src/engine/mime/mime-error.vala
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Errors related to {@link Geary.Mime}.
|
||||
*/
|
||||
|
||||
public errordomain MimeError {
|
||||
PARSE
|
||||
}
|
||||
|
|
@ -330,9 +330,13 @@ public class Geary.RFC822.PreviewText : Geary.RFC822.Text {
|
|||
GMime.Parser parser = new GMime.Parser.with_stream(header_stream);
|
||||
GMime.Part? part = parser.construct_part() as GMime.Part;
|
||||
if (part != null) {
|
||||
is_html = (part.get_content_type().to_string() == "text/html");
|
||||
Mime.ContentType? content_type = null;
|
||||
if (part.get_content_type() != null) {
|
||||
content_type = new Mime.ContentType.from_gmime(part.get_content_type());
|
||||
is_html = content_type.is_type("text", "html");
|
||||
charset = content_type.params.get_value("charset");
|
||||
}
|
||||
|
||||
charset = part.get_content_type_parameter("charset");
|
||||
encoding = part.get_header("Content-Transfer-Encoding");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
* This delegate is an optional parameter to the body constructers that allows callers
|
||||
* to process arbitrary non-text, inline MIME parts.
|
||||
*/
|
||||
public delegate string? InlinePartReplacer(string filename, string mimetype,
|
||||
Geary.Memory.Buffer buffer);
|
||||
public delegate string? InlinePartReplacer(string filename, Mime.ContentType? content_type,
|
||||
Mime.ContentDisposition? disposition, Geary.Memory.Buffer buffer);
|
||||
|
||||
private const string DEFAULT_ENCODING = "UTF8";
|
||||
|
||||
|
|
@ -470,35 +470,41 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
if (part == null)
|
||||
return false;
|
||||
|
||||
Mime.ContentDisposition? disposition = null;
|
||||
if (part.get_content_disposition() != null)
|
||||
disposition = new Mime.ContentDisposition.from_gmime(part.get_content_disposition());
|
||||
|
||||
// Stop processing if the part is an attachment
|
||||
string? disposition = part.get_disposition();
|
||||
if (disposition != null && disposition.down() == "attachment")
|
||||
if (disposition != null && disposition.disposition_type == Mime.DispositionType.ATTACHMENT)
|
||||
return false;
|
||||
|
||||
/* Handle text parts that are not attachments
|
||||
* They may have inline disposition, or they may have no disposition specified
|
||||
*/
|
||||
GMime.ContentType content_type = part.get_content_type();
|
||||
if (String.stri_equal(content_type.get_media_type(), "text")) {
|
||||
if (String.stri_equal(content_type.get_media_subtype(), text_subtype)) {
|
||||
body = mime_part_to_memory_buffer(part, true, to_html).to_string();
|
||||
return true;
|
||||
Mime.ContentType? content_type = null;
|
||||
if (part.get_content_type() != null) {
|
||||
content_type = new Mime.ContentType.from_gmime(part.get_content_type());
|
||||
if (content_type.has_media_type("text")) {
|
||||
if (content_type.has_media_subtype(text_subtype)) {
|
||||
body = mime_part_to_memory_buffer(part, true, to_html).to_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
// We were the wrong kind of text part
|
||||
return false;
|
||||
}
|
||||
|
||||
// We were the wrong kind of text part
|
||||
return false;
|
||||
}
|
||||
|
||||
// If images have no disposition, they are handled elsewhere; see #7299
|
||||
if (disposition == null)
|
||||
if (disposition == null || disposition.disposition_type == Mime.DispositionType.UNSPECIFIED)
|
||||
return false;
|
||||
|
||||
// Hand off to the replacer for processing
|
||||
if (replacer == null)
|
||||
return false;
|
||||
|
||||
string? replaced_part = replacer(RFC822.Utils.get_attachment_filename(part), content_type.to_string(),
|
||||
mime_part_to_memory_buffer(part));
|
||||
// Hand off to the replacer for processing
|
||||
string? replaced_part = replacer(RFC822.Utils.get_attachment_filename(part), content_type,
|
||||
disposition, mime_part_to_memory_buffer(part));
|
||||
if (replaced_part != null)
|
||||
body = replaced_part;
|
||||
|
||||
|
|
@ -642,16 +648,16 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
internal Gee.List<GMime.Part> get_attachments(Geary.Attachment.Disposition? disposition = null)
|
||||
throws RFC822Error {
|
||||
// A null disposition means "return all Mime parts recognized by Geary.Attachment.Disposition"
|
||||
// UNSPECIFIED disposition means "return all Mime parts"
|
||||
internal Gee.List<GMime.Part> get_attachments(
|
||||
Mime.DispositionType disposition = Mime.DispositionType.UNSPECIFIED) throws RFC822Error {
|
||||
Gee.List<GMime.Part> attachments = new Gee.ArrayList<GMime.Part>();
|
||||
get_attachments_recursively(attachments, message.get_mime_part(), disposition);
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private void get_attachments_recursively(Gee.List<GMime.Part> attachments, GMime.Object root,
|
||||
Geary.Attachment.Disposition? requested_disposition) throws RFC822Error {
|
||||
Mime.DispositionType requested_disposition) throws RFC822Error {
|
||||
// If this is a multipart container, dive into each of its children.
|
||||
GMime.Multipart? multipart = root as GMime.Multipart;
|
||||
if (multipart != null) {
|
||||
|
|
@ -666,14 +672,15 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
GMime.MessagePart? messagepart = root as GMime.MessagePart;
|
||||
if (messagepart != null) {
|
||||
GMime.Message message = messagepart.get_message();
|
||||
Geary.Attachment.Disposition? disposition = Geary.Attachment.Disposition.from_string(
|
||||
root.get_disposition());
|
||||
if (disposition == null) {
|
||||
bool is_unknown;
|
||||
Mime.DispositionType disposition = Mime.DispositionType.deserialize(root.get_disposition(),
|
||||
out is_unknown);
|
||||
if (disposition == Mime.DispositionType.UNSPECIFIED || is_unknown) {
|
||||
// This is often the case, and we'll treat these as attached
|
||||
disposition = Geary.Attachment.Disposition.ATTACHMENT;
|
||||
disposition = Mime.DispositionType.ATTACHMENT;
|
||||
}
|
||||
|
||||
if (requested_disposition == null || disposition == requested_disposition) {
|
||||
if (requested_disposition == Mime.DispositionType.UNSPECIFIED || disposition == requested_disposition) {
|
||||
GMime.Stream stream = new GMime.StreamMem();
|
||||
message.write_to_stream(stream);
|
||||
GMime.DataWrapper data = new GMime.DataWrapper.with_stream(stream,
|
||||
|
|
@ -695,32 +702,24 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
return;
|
||||
}
|
||||
|
||||
Geary.Attachment.Disposition? part_disposition = Geary.Attachment.Disposition.from_string(
|
||||
part.get_disposition());
|
||||
if (part_disposition == null) {
|
||||
// The part disposition was unknown to Geary.Attachment.Disposition
|
||||
Mime.DispositionType part_disposition = Mime.DispositionType.deserialize(part.get_disposition(),
|
||||
null);
|
||||
if (part_disposition == Mime.DispositionType.UNSPECIFIED)
|
||||
return;
|
||||
}
|
||||
|
||||
GMime.ContentType content_type = part.get_content_type();
|
||||
if (part_disposition == Geary.Attachment.Disposition.INLINE &&
|
||||
content_type.get_media_type().down() == "text") {
|
||||
string subtype = content_type.get_media_subtype().down();
|
||||
if (subtype == "html" || subtype == "plain") {
|
||||
// These are part of the body.
|
||||
if (part.get_content_type() != null) {
|
||||
Mime.ContentType content_type = new Mime.ContentType.from_gmime(part.get_content_type());
|
||||
if (part_disposition == Mime.DispositionType.INLINE
|
||||
&& content_type.has_media_type("text")
|
||||
&& (content_type.has_media_subtype("html") || content_type.has_media_subtype("plain"))) {
|
||||
// these are part of the body
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (requested_disposition == null) {
|
||||
// Return any attachment whose disposition is recognized by Geary.Attachment.Disposition
|
||||
// Catch remaining disposition-type matches
|
||||
if (requested_disposition == Mime.DispositionType.UNSPECIFIED || part_disposition == requested_disposition)
|
||||
attachments.add(part);
|
||||
return;
|
||||
}
|
||||
|
||||
if (part_disposition == requested_disposition) {
|
||||
attachments.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
public Gee.List<Geary.RFC822.Message> get_sub_messages() {
|
||||
|
|
@ -766,11 +765,14 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
|
||||
private Memory.Buffer mime_part_to_memory_buffer(GMime.Part part,
|
||||
bool to_utf8 = false, bool to_html = false) throws RFC822Error {
|
||||
|
||||
Mime.ContentType? content_type = null;
|
||||
if (part.get_content_type() != null)
|
||||
content_type = new Mime.ContentType.from_gmime(part.get_content_type());
|
||||
|
||||
GMime.DataWrapper? wrapper = part.get_content_object();
|
||||
if (wrapper == null) {
|
||||
throw new RFC822Error.INVALID("Could not get the content wrapper for content-type %s",
|
||||
part.get_content_type().to_string());
|
||||
content_type.to_string());
|
||||
}
|
||||
|
||||
ByteArray byte_array = new ByteArray();
|
||||
|
|
@ -780,17 +782,17 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
// Convert encoding to UTF-8.
|
||||
GMime.StreamFilter stream_filter = new GMime.StreamFilter(stream);
|
||||
if (to_utf8) {
|
||||
string? charset = part.get_content_type_parameter("charset");
|
||||
string? charset = (content_type != null) ? content_type.params.get_value("charset") : null;
|
||||
if (String.is_empty(charset))
|
||||
charset = DEFAULT_ENCODING;
|
||||
stream_filter.add(Geary.RFC822.Utils.create_utf8_filter_charset(charset));
|
||||
}
|
||||
string format = part.get_content_type_parameter("format") ?? "";
|
||||
bool flowed = (format.down() == "flowed");
|
||||
string delsp_par = part.get_content_type_parameter("DelSp") ?? "no";
|
||||
bool delsp = (delsp_par.down() == "yes");
|
||||
|
||||
bool flowed = (content_type != null) ? content_type.params.has_value_ci("format", "flowed") : false;
|
||||
bool delsp = (content_type != null) ? content_type.params.has_value_ci("DelSp", "yes") : false;
|
||||
if (flowed)
|
||||
stream_filter.add(new Geary.RFC822.FilterFlowed(to_html, delsp));
|
||||
|
||||
if (to_html) {
|
||||
if (!flowed)
|
||||
stream_filter.add(new Geary.RFC822.FilterPlain());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue