Save inline images displayed via MIME Content-ID: Closes #7475
Some reorganization of how data: URIs are assembled and injected into the document. Also, mild improvement to the GMime bindings.
This commit is contained in:
parent
935b90d3a6
commit
9a8f96310c
8 changed files with 124 additions and 87 deletions
|
|
@ -804,19 +804,19 @@ namespace GMime {
|
||||||
[CCode (cname = "g_mime_part_get_best_content_encoding")]
|
[CCode (cname = "g_mime_part_get_best_content_encoding")]
|
||||||
public GMime.ContentEncoding get_best_content_encoding (GMime.EncodingConstraint constraint);
|
public GMime.ContentEncoding get_best_content_encoding (GMime.EncodingConstraint constraint);
|
||||||
[CCode (cname = "g_mime_part_get_content_description")]
|
[CCode (cname = "g_mime_part_get_content_description")]
|
||||||
public unowned string get_content_description ();
|
public unowned string? get_content_description ();
|
||||||
[CCode (cname = "g_mime_part_get_content_encoding")]
|
[CCode (cname = "g_mime_part_get_content_encoding")]
|
||||||
public GMime.ContentEncoding get_content_encoding ();
|
public GMime.ContentEncoding get_content_encoding ();
|
||||||
[CCode (cname = "g_mime_part_get_content_id")]
|
[CCode (cname = "g_mime_part_get_content_id")]
|
||||||
public unowned string get_content_id ();
|
public unowned string? get_content_id ();
|
||||||
[CCode (cname = "g_mime_part_get_content_location")]
|
[CCode (cname = "g_mime_part_get_content_location")]
|
||||||
public unowned string get_content_location ();
|
public unowned string? get_content_location ();
|
||||||
[CCode (cname = "g_mime_part_get_content_md5")]
|
[CCode (cname = "g_mime_part_get_content_md5")]
|
||||||
public unowned string get_content_md5 ();
|
public unowned string? get_content_md5 ();
|
||||||
[CCode (cname = "g_mime_part_get_content_object")]
|
[CCode (cname = "g_mime_part_get_content_object")]
|
||||||
public unowned GMime.DataWrapper get_content_object ();
|
public unowned GMime.DataWrapper? get_content_object ();
|
||||||
[CCode (cname = "g_mime_part_get_filename")]
|
[CCode (cname = "g_mime_part_get_filename")]
|
||||||
public unowned string get_filename ();
|
public unowned string? get_filename ();
|
||||||
[CCode (cname = "g_mime_part_set_content_description")]
|
[CCode (cname = "g_mime_part_set_content_description")]
|
||||||
public void set_content_description (string description);
|
public void set_content_description (string description);
|
||||||
[CCode (cname = "g_mime_part_set_content_encoding")]
|
[CCode (cname = "g_mime_part_set_content_encoding")]
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,13 @@ g_mime_object_get_content_type_parameter nullable="1"
|
||||||
g_mime_object_to_string transfer_ownership="1"
|
g_mime_object_to_string transfer_ownership="1"
|
||||||
g_mime_param_next name="get_next"
|
g_mime_param_next name="get_next"
|
||||||
g_mime_parser_construct_message nullable="1"
|
g_mime_parser_construct_message nullable="1"
|
||||||
|
g_mime_part_get_content_description nullable="1"
|
||||||
|
g_mime_part_get_content_location nullable="1"
|
||||||
|
g_mime_part_get_content_id nullable="1"
|
||||||
|
g_mime_part_get_content_md5 nullable="1"
|
||||||
|
g_mime_part_get_content_object nullable="1"
|
||||||
g_mime_part_get_content_part nullable="1"
|
g_mime_part_get_content_part nullable="1"
|
||||||
|
g_mime_part_get_filename nullable="1"
|
||||||
g_mime_signer_next name="get_next"
|
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.buffer is_array="1" array_length_pos="1.0" type_name="uint8[]"
|
||||||
g_mime_stream_mem_new_with_buffer.len hidden="1"
|
g_mime_stream_mem_new_with_buffer.len hidden="1"
|
||||||
|
|
|
||||||
|
|
@ -1492,12 +1492,13 @@ public class GearyController : Geary.BaseObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_save_buffer_to_file(string filename, Geary.Memory.Buffer buffer) {
|
private void on_save_buffer_to_file(string? filename, Geary.Memory.Buffer buffer) {
|
||||||
Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog(null, main_window, Gtk.FileChooserAction.SAVE,
|
Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog(null, main_window, Gtk.FileChooserAction.SAVE,
|
||||||
Stock._CANCEL, Gtk.ResponseType.CANCEL, Stock._SAVE, Gtk.ResponseType.ACCEPT, null);
|
Stock._CANCEL, Gtk.ResponseType.CANCEL, Stock._SAVE, Gtk.ResponseType.ACCEPT, null);
|
||||||
if (last_save_directory != null)
|
if (last_save_directory != null)
|
||||||
dialog.set_current_folder(last_save_directory.get_path());
|
dialog.set_current_folder(last_save_directory.get_path());
|
||||||
dialog.set_current_name(filename);
|
if (!Geary.String.is_empty(filename))
|
||||||
|
dialog.set_current_name(filename);
|
||||||
dialog.set_do_overwrite_confirmation(true);
|
dialog.set_do_overwrite_confirmation(true);
|
||||||
dialog.confirm_overwrite.connect(on_confirm_overwrite);
|
dialog.confirm_overwrite.connect(on_confirm_overwrite);
|
||||||
dialog.set_create_folders(true);
|
dialog.set_create_folders(true);
|
||||||
|
|
|
||||||
|
|
@ -400,3 +400,50 @@ public string resolve_nesting(string text, string[] values) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a URI suitable for an IMG SRC attribute (or elsewhere, potentially) that is the
|
||||||
|
// memory buffer unpacked into a Base-64 encoded data: URI
|
||||||
|
public string assemble_data_uri(string mimetype, Geary.Memory.Buffer buffer) {
|
||||||
|
// attempt to use UnownedBytesBuffer to avoid memcpying a potentially huge buffer only to
|
||||||
|
// free it when the encoding operation is completed
|
||||||
|
string base64;
|
||||||
|
Geary.Memory.UnownedBytesBuffer? unowned_bytes = buffer as Geary.Memory.UnownedBytesBuffer;
|
||||||
|
if (unowned_bytes != null)
|
||||||
|
base64 = Base64.encode(unowned_bytes.to_unowned_uint8_array());
|
||||||
|
else
|
||||||
|
base64 = Base64.encode(buffer.get_uint8_array());
|
||||||
|
|
||||||
|
return "data:%s;base64,%s".printf(mimetype, base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turns the data: URI created by assemble_data_uri() back into its components. The returned
|
||||||
|
// buffer is decoded.
|
||||||
|
//
|
||||||
|
// TODO: Return mimetype
|
||||||
|
public bool dissasemble_data_uri(string uri, out Geary.Memory.Buffer? buffer) {
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
if (!uri.has_prefix("data:"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// count from semicolon past encoding type specifier
|
||||||
|
int start_index = uri.index_of(";");
|
||||||
|
if (start_index <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// watch for string termination to avoid overflow
|
||||||
|
int base64_len = "base64,".length;
|
||||||
|
for (int ctr = 0; ctr < base64_len; ctr++) {
|
||||||
|
if (uri[start_index++] == Geary.String.EOS)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid a memory copy of the substring by manually calculating the start address
|
||||||
|
uint8[] bytes = Base64.decode((string) (((char *) uri) + start_index));
|
||||||
|
|
||||||
|
// transfer ownership of the byte array directly to the Buffer; this prevents an
|
||||||
|
// unnecessary copy
|
||||||
|
buffer = new Geary.Memory.ByteBuffer.take((owned) bytes, bytes.length);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public class ConversationViewer : Gtk.Box {
|
||||||
private const string MESSAGE_CONTAINER_ID = "message_container";
|
private const string MESSAGE_CONTAINER_ID = "message_container";
|
||||||
private const string SELECTION_COUNTER_ID = "multiple_messages";
|
private const string SELECTION_COUNTER_ID = "multiple_messages";
|
||||||
private const string SPINNER_ID = "spinner";
|
private const string SPINNER_ID = "spinner";
|
||||||
private const string REPLACED_IMAGE_CLASS = "replaced_inline_image";
|
private const string DATA_IMAGE_CLASS = "data_inline_image";
|
||||||
|
|
||||||
private enum SearchState {
|
private enum SearchState {
|
||||||
// Search/find states.
|
// Search/find states.
|
||||||
|
|
@ -94,7 +94,7 @@ public class ConversationViewer : Gtk.Box {
|
||||||
public signal void save_attachments(Gee.List<Geary.Attachment> attachment);
|
public signal void save_attachments(Gee.List<Geary.Attachment> attachment);
|
||||||
|
|
||||||
// Fired when the user wants to save an image buffer to disk
|
// Fired when the user wants to save an image buffer to disk
|
||||||
public signal void save_buffer_to_file(string filename, Geary.Memory.Buffer buffer);
|
public signal void save_buffer_to_file(string? filename, Geary.Memory.Buffer buffer);
|
||||||
|
|
||||||
// Fired when the user clicks the edit draft button.
|
// Fired when the user clicks the edit draft button.
|
||||||
public signal void edit_draft(Geary.Email message);
|
public signal void edit_draft(Geary.Email message);
|
||||||
|
|
@ -563,7 +563,7 @@ public class ConversationViewer : Gtk.Box {
|
||||||
bind_event(web_view, ".email .compressed_note", "click", (Callback) on_body_toggle_clicked, this);
|
bind_event(web_view, ".email .compressed_note", "click", (Callback) on_body_toggle_clicked, this);
|
||||||
bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
|
bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
|
||||||
bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
|
bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
|
||||||
bind_event(web_view, "." + REPLACED_IMAGE_CLASS, "contextmenu", (Callback) on_replaced_image_menu, this);
|
bind_event(web_view, "." + DATA_IMAGE_CLASS, "contextmenu", (Callback) on_data_image_menu, this);
|
||||||
bind_event(web_view, ".remote_images .show_images", "click", (Callback) on_show_images, this);
|
bind_event(web_view, ".remote_images .show_images", "click", (Callback) on_show_images, this);
|
||||||
bind_event(web_view, ".remote_images .show_from", "click", (Callback) on_show_images_from, this);
|
bind_event(web_view, ".remote_images .show_from", "click", (Callback) on_show_images_from, this);
|
||||||
bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, this);
|
bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, this);
|
||||||
|
|
@ -693,46 +693,7 @@ public class ConversationViewer : Gtk.Box {
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return "<img alt=\"%s\" class=\"%s\" src=\"%s\" />".printf(
|
return "<img alt=\"%s\" class=\"%s\" src=\"%s\" />".printf(
|
||||||
filename, REPLACED_IMAGE_CLASS, assemble_replaced_image_uri(mimetype, buffer));
|
filename, DATA_IMAGE_CLASS, assemble_data_uri(mimetype, buffer));
|
||||||
}
|
|
||||||
|
|
||||||
private static string assemble_replaced_image_uri(string mimetype, Geary.Memory.Buffer buffer) {
|
|
||||||
// attempt to use UnownedBytesBuffer to avoid memcpying a potentially huge buffer only to
|
|
||||||
// free it when the encoding operation is completed
|
|
||||||
string base64;
|
|
||||||
Geary.Memory.UnownedBytesBuffer? unowned_bytes = buffer as Geary.Memory.UnownedBytesBuffer;
|
|
||||||
if (unowned_bytes != null)
|
|
||||||
base64 = Base64.encode(unowned_bytes.to_unowned_uint8_array());
|
|
||||||
else
|
|
||||||
base64 = Base64.encode(buffer.get_uint8_array());
|
|
||||||
|
|
||||||
return "data:%s;base64,%s".printf(mimetype, base64);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns the data: URI created by assemble_replaced_image_uri() back into its components. The
|
|
||||||
// returned buffer is decoded.
|
|
||||||
//
|
|
||||||
// TODO: return mimetype
|
|
||||||
private static bool dissasemble_replaced_image_uri(string uri, out Geary.Memory.Buffer? buffer) {
|
|
||||||
buffer = null;
|
|
||||||
|
|
||||||
if (!uri.has_prefix("data:"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// count from semicolon past encoding type specifier
|
|
||||||
int start_index = uri.index_of(";");
|
|
||||||
if (start_index <= 0)
|
|
||||||
return false;
|
|
||||||
start_index += "base64,".length;
|
|
||||||
|
|
||||||
// avoid a memory copy of the substring by manually calculating the start address
|
|
||||||
uint8[] bytes = Base64.decode((string) (((char *) uri) + start_index));
|
|
||||||
|
|
||||||
// transfer ownership of the byte array directly to the Buffer; this prevents an
|
|
||||||
// unnecessary copy
|
|
||||||
buffer = new Geary.Memory.ByteBuffer.take((owned) bytes, bytes.length);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unhide_last_email() {
|
private void unhide_last_email() {
|
||||||
|
|
@ -1328,17 +1289,17 @@ public class ConversationViewer : Gtk.Box {
|
||||||
conversation_viewer.show_attachment_menu(email, attachment);
|
conversation_viewer.show_attachment_menu(email, attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void on_replaced_image_menu(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
private static void on_data_image_menu(WebKit.DOM.Element element, WebKit.DOM.Event event,
|
||||||
ConversationViewer conversation_viewer) {
|
ConversationViewer conversation_viewer) {
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
|
|
||||||
Geary.Memory.Buffer? buffer;
|
Geary.Memory.Buffer? buffer;
|
||||||
if (!dissasemble_replaced_image_uri(element.get_attribute("src"), out buffer))
|
if (!dissasemble_data_uri(element.get_attribute("src"), out buffer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string filename = element.get_attribute("alt");
|
string? filename = element.get_attribute("alt");
|
||||||
|
|
||||||
if (buffer != null && buffer.size > 0 && !Geary.String.is_empty(filename))
|
if (buffer != null && buffer.size > 0)
|
||||||
conversation_viewer.show_replaced_image_menu(filename, buffer);
|
conversation_viewer.show_replaced_image_menu(filename, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1448,7 +1409,7 @@ public class ConversationViewer : Gtk.Box {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show_replaced_image_menu(string filename, Geary.Memory.Buffer buffer) {
|
private void show_replaced_image_menu(string? filename, Geary.Memory.Buffer buffer) {
|
||||||
image_menu = new Gtk.Menu();
|
image_menu = new Gtk.Menu();
|
||||||
image_menu.selection_done.connect(() => {
|
image_menu.selection_done.connect(() => {
|
||||||
image_menu = null;
|
image_menu = null;
|
||||||
|
|
@ -1634,16 +1595,27 @@ public class ConversationViewer : Gtk.Box {
|
||||||
continue;
|
continue;
|
||||||
} else if (src.has_prefix("cid:")) {
|
} else if (src.has_prefix("cid:")) {
|
||||||
string mime_id = src.substring(4);
|
string mime_id = src.substring(4);
|
||||||
|
|
||||||
|
string? filename = message.get_content_filename_by_mime_id(mime_id);
|
||||||
Geary.Memory.Buffer image_content = message.get_content_by_mime_id(mime_id);
|
Geary.Memory.Buffer image_content = message.get_content_by_mime_id(mime_id);
|
||||||
uint8[] image_data = image_content.get_uint8_array();
|
Geary.Memory.UnownedBytesBuffer? unowned_buffer =
|
||||||
|
image_content as Geary.Memory.UnownedBytesBuffer;
|
||||||
|
|
||||||
// Get the content type.
|
// Get the content type.
|
||||||
bool uncertain_content_type;
|
string guess;
|
||||||
string mimetype = ContentType.get_mime_type(ContentType.guess(null, image_data,
|
if (unowned_buffer != null)
|
||||||
out uncertain_content_type));
|
guess = ContentType.guess(null, unowned_buffer.to_unowned_uint8_array(), null);
|
||||||
|
else
|
||||||
// Then set the source to a data url.
|
guess = ContentType.guess(null, image_content.get_uint8_array(), null);
|
||||||
web_view.set_data_url(img, mimetype, image_data);
|
|
||||||
|
string mimetype = ContentType.get_mime_type(guess);
|
||||||
|
|
||||||
|
// Replace the SRC to a data URIm the class to a known label for the popup menu,
|
||||||
|
// and the ALT to its filename, if supplied
|
||||||
|
img.set_attribute("src", assemble_data_uri(mimetype, image_content));
|
||||||
|
img.set_attribute("class", DATA_IMAGE_CLASS);
|
||||||
|
if (!Geary.String.is_empty(filename))
|
||||||
|
img.set_attribute("alt", filename);
|
||||||
} else if (!src.has_prefix("data:")) {
|
} else if (!src.has_prefix("data:")) {
|
||||||
remote_images = true;
|
remote_images = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1838,7 +1810,8 @@ public class ConversationViewer : Gtk.Box {
|
||||||
// Set the image preview and insert it into the container.
|
// Set the image preview and insert it into the container.
|
||||||
WebKit.DOM.HTMLImageElement img =
|
WebKit.DOM.HTMLImageElement img =
|
||||||
Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
|
Util.DOM.select(attachment_table, ".preview img") as WebKit.DOM.HTMLImageElement;
|
||||||
web_view.set_image_src(img, attachment.mime_type, attachment.file.get_path(), ATTACHMENT_PREVIEW_SIZE);
|
web_view.set_attachment_src(img, attachment.mime_type, attachment.file.get_path(),
|
||||||
|
ATTACHMENT_PREVIEW_SIZE);
|
||||||
attachment_container.append_child(attachment_table);
|
attachment_container.append_child(attachment_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -202,21 +202,27 @@ public class ConversationWebView : WebKit.WebView {
|
||||||
private void set_icon_src(string selector, string icon_name) {
|
private void set_icon_src(string selector, string icon_name) {
|
||||||
try {
|
try {
|
||||||
// Load icon.
|
// Load icon.
|
||||||
uint8[] icon_content = null;
|
uint8[]? icon_content = null;
|
||||||
Gdk.Pixbuf? pixbuf = IconFactory.instance.load_symbolic_colored(icon_name, 16);
|
Gdk.Pixbuf? pixbuf = IconFactory.instance.load_symbolic_colored(icon_name, 16);
|
||||||
if (pixbuf != null)
|
if (pixbuf != null)
|
||||||
pixbuf.save_to_buffer(out icon_content, "png"); // Load as PNG.
|
pixbuf.save_to_buffer(out icon_content, "png"); // Load as PNG.
|
||||||
|
|
||||||
|
if (icon_content == null || icon_content.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Geary.Memory.ByteBuffer buffer = new Geary.Memory.ByteBuffer.take((owned) icon_content,
|
||||||
|
icon_content.length);
|
||||||
|
|
||||||
// Then set the source to a data url.
|
// Then set the source to a data url.
|
||||||
WebKit.DOM.HTMLImageElement img = Util.DOM.select(get_dom_document(), selector)
|
WebKit.DOM.HTMLImageElement img = Util.DOM.select(get_dom_document(), selector)
|
||||||
as WebKit.DOM.HTMLImageElement;
|
as WebKit.DOM.HTMLImageElement;
|
||||||
set_data_url(img, "image/png", icon_content);
|
img.set_attribute("src", assemble_data_uri("image/png", buffer));
|
||||||
} catch (Error error) {
|
} catch (Error error) {
|
||||||
warning("Failed to load icon '%s': %s", icon_name, error.message);
|
warning("Failed to load icon '%s': %s", icon_name, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_image_src(WebKit.DOM.HTMLImageElement img, string mime_type, string filename,
|
public void set_attachment_src(WebKit.DOM.HTMLImageElement img, string mime_type, string filename,
|
||||||
int maxwidth, int maxheight = -1) {
|
int maxwidth, int maxheight = -1) {
|
||||||
if( maxheight == -1 ){
|
if( maxheight == -1 ){
|
||||||
maxheight = maxwidth;
|
maxheight = maxwidth;
|
||||||
|
|
@ -248,17 +254,14 @@ public class ConversationWebView : WebKit.WebView {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then set the source to a data url.
|
// Then set the source to a data url.
|
||||||
set_data_url(img, icon_mime_type, content);
|
Geary.Memory.Buffer buffer = new Geary.Memory.ByteBuffer.take((owned) content,
|
||||||
|
content.length);
|
||||||
|
img.set_attribute("src", assemble_data_uri(icon_mime_type, buffer));
|
||||||
} catch (Error error) {
|
} catch (Error error) {
|
||||||
warning("Failed to load image '%s': %s", filename, error.message);
|
warning("Failed to load image '%s': %s", filename, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_data_url(WebKit.DOM.HTMLImageElement img, string mime_type, uint8[] content)
|
|
||||||
throws Error {
|
|
||||||
img.set_attribute("src", "data:%s;base64,%s".printf(mime_type, Base64.encode(content)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool on_navigation_policy_decision_requested(WebKit.WebFrame frame,
|
private bool on_navigation_policy_decision_requested(WebKit.WebFrame frame,
|
||||||
WebKit.NetworkRequest request, WebKit.WebNavigationAction navigation_action,
|
WebKit.NetworkRequest request, WebKit.WebNavigationAction navigation_action,
|
||||||
WebKit.WebPolicyDecision policy_decision) {
|
WebKit.WebPolicyDecision policy_decision) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class Geary.RFC822.Message : BaseObject {
|
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);
|
||||||
|
|
||||||
private const string DEFAULT_ENCODING = "UTF8";
|
private const string DEFAULT_ENCODING = "UTF8";
|
||||||
|
|
||||||
private const string HEADER_IN_REPLY_TO = "In-Reply-To";
|
private const string HEADER_IN_REPLY_TO = "In-Reply-To";
|
||||||
|
|
@ -419,13 +426,6 @@ public class Geary.RFC822.Message : BaseObject {
|
||||||
return message_to_memory_buffer(true, dotstuffed);
|
return message_to_memory_buffer(true, dotstuffed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the main utility method used by the other body constructors. It calls itself
|
* This method is the main utility method used by the other body constructors. It calls itself
|
||||||
* recursively via the last argument ("node").
|
* recursively via the last argument ("node").
|
||||||
|
|
@ -605,13 +605,20 @@ public class Geary.RFC822.Message : BaseObject {
|
||||||
|
|
||||||
public Memory.Buffer get_content_by_mime_id(string mime_id) throws RFC822Error {
|
public Memory.Buffer get_content_by_mime_id(string mime_id) throws RFC822Error {
|
||||||
GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id);
|
GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id);
|
||||||
if (part == null) {
|
if (part == null)
|
||||||
throw new RFC822Error.NOT_FOUND("Could not find a MIME part with content-id %s",
|
throw new RFC822Error.NOT_FOUND("Could not find a MIME part with Content-ID %s", mime_id);
|
||||||
mime_id);
|
|
||||||
}
|
|
||||||
return mime_part_to_memory_buffer(part);
|
return mime_part_to_memory_buffer(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string? get_content_filename_by_mime_id(string mime_id) throws RFC822Error {
|
||||||
|
GMime.Part? part = find_mime_part_by_mime_id(message.get_mime_part(), mime_id);
|
||||||
|
if (part == null)
|
||||||
|
throw new RFC822Error.NOT_FOUND("Could not find a MIME part with Content-ID %s", mime_id);
|
||||||
|
|
||||||
|
return part.get_filename();
|
||||||
|
}
|
||||||
|
|
||||||
private GMime.Part? find_mime_part_by_mime_id(GMime.Object root, string mime_id) {
|
private GMime.Part? find_mime_part_by_mime_id(GMime.Object root, string mime_id) {
|
||||||
// If this is a multipart container, check each of its children.
|
// If this is a multipart container, check each of its children.
|
||||||
if (root is GMime.Multipart) {
|
if (root is GMime.Multipart) {
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ body:not(.nohide) .email.hide .header_container .avatar {
|
||||||
margin-right: -0.67em;
|
margin-right: -0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email .replaced_inline_image {
|
.email .data_inline_image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue