mu/lib/mu-msg-part.c

984 lines
24 KiB
C

/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
/*
** Copyright (C) 2008-2014 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif /*HAVE_CONFIG_H*/
#include <string.h>
#include <unistd.h>
#include "utils/mu-util.h"
#include "utils/mu-str.h"
#include "mu-msg-priv.h"
#include "mu-msg-part.h"
struct _DoData {
GMimeObject *mime_obj;
unsigned index;
};
typedef struct _DoData DoData;
static void
do_it_with_index (MuMsg *msg, MuMsgPart *part, DoData *ddata)
{
if (ddata->mime_obj)
return;
if (part->index == ddata->index) {
/* Add a reference to this object, this way if it is
* encrypted it will not be garbage collected before
* we are done with it. */
g_object_ref (part->data);
ddata->mime_obj = (GMimeObject*)part->data;
}
}
static GMimeObject*
get_mime_object_at_index (MuMsg *msg, MuMsgOptions opts, unsigned index)
{
DoData ddata;
ddata.mime_obj = NULL;
ddata.index = index;
mu_msg_part_foreach (msg, opts,
(MuMsgPartForeachFunc)do_it_with_index,
&ddata);
return ddata.mime_obj;
}
typedef gboolean (*MuMsgPartMatchFunc) (MuMsgPart *, gpointer);
struct _MatchData {
MuMsgPartMatchFunc match_func;
gpointer user_data;
int index;
};
typedef struct _MatchData MatchData;
static void
check_match (MuMsg *msg, MuMsgPart *part, MatchData *mdata)
{
if (mdata->index != -1)
return;
if (mdata->match_func (part, mdata->user_data))
mdata->index = part->index;
}
static int
get_matching_part_index (MuMsg *msg, MuMsgOptions opts,
MuMsgPartMatchFunc func, gpointer user_data)
{
MatchData mdata;
mdata.match_func = func;
mdata.user_data = user_data;
mdata.index = -1;
mu_msg_part_foreach (msg, opts,
(MuMsgPartForeachFunc)check_match,
&mdata);
return mdata.index;
}
static void
accumulate_text_message (MuMsg *msg, MuMsgPart *part, GString **gstrp)
{
const gchar *str;
char *adrs;
GMimeMessage *mimemsg;
InternetAddressList *addresses;
/* put sender, recipients and subject in the string, so they
* can be indexed as well */
mimemsg = GMIME_MESSAGE (part->data);
addresses = g_mime_message_get_addresses (mimemsg, GMIME_ADDRESS_TYPE_FROM);
adrs = internet_address_list_to_string (addresses, NULL, FALSE);
g_string_append_printf
(*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : "");
g_free (adrs);
str = g_mime_message_get_subject (mimemsg);
g_string_append_printf
(*gstrp, "%s%s", str ? str : "", str ? "\n" : "");
addresses = g_mime_message_get_all_recipients (mimemsg);
adrs = internet_address_list_to_string (addresses, NULL, FALSE);
g_object_unref (addresses);
g_string_append_printf
(*gstrp, "%s%s", adrs ? adrs : "", adrs ? "\n" : "");
g_free (adrs);
}
static void
accumulate_text_part (MuMsg *msg, MuMsgPart *part, GString **gstrp)
{
GMimeContentType *ctype;
gboolean err;
char *txt;
ctype = g_mime_object_get_content_type ((GMimeObject*)part->data);
if (!g_mime_content_type_is_type (ctype, "text", "plain"))
return; /* not plain text */
txt = mu_msg_mime_part_to_string
((GMimePart*)part->data, &err);
if (txt)
g_string_append (*gstrp, txt);
g_free (txt);
}
static void
accumulate_text (MuMsg *msg, MuMsgPart *part, GString **gstrp)
{
if (GMIME_IS_MESSAGE(part->data))
accumulate_text_message (msg, part, gstrp);
else if (GMIME_IS_PART (part->data))
accumulate_text_part (msg, part, gstrp);
}
/* declaration, so we can use it earlier */
static gboolean
handle_mime_object (MuMsg *msg, GMimeObject *mobj, GMimeObject *parent,
MuMsgOptions opts, unsigned *index, gboolean decrypted,
MuMsgPartForeachFunc func, gpointer user_data);
static char*
get_text_from_mime_msg (MuMsg *msg, GMimeMessage *mmsg, MuMsgOptions opts)
{
GString *gstr;
unsigned index;
index = 1;
gstr = g_string_sized_new (4096);
handle_mime_object (msg,
mmsg->mime_part,
(GMimeObject *) mmsg,
opts,
&index,
FALSE,
(MuMsgPartForeachFunc)accumulate_text,
&gstr);
return g_string_free (gstr, FALSE);
}
char*
mu_msg_part_get_text (MuMsg *msg, MuMsgPart *self, MuMsgOptions opts)
{
GMimeObject *mobj;
GMimeMessage *mime_msg;
gboolean err;
g_return_val_if_fail (msg, NULL);
g_return_val_if_fail (self && GMIME_IS_OBJECT(self->data),
NULL);
mobj = (GMimeObject*)self->data;
err = FALSE;
if (GMIME_IS_PART (mobj)) {
if (self->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN)
return mu_msg_mime_part_to_string ((GMimePart*)mobj,
&err);
else
return NULL; /* non-text MimePart */
}
mime_msg = NULL;
if (GMIME_IS_MESSAGE_PART (mobj))
mime_msg = g_mime_message_part_get_message
((GMimeMessagePart*)mobj);
else if (GMIME_IS_MESSAGE (mobj))
mime_msg = (GMimeMessage*)mobj;
/* apparently, g_mime_message_part_get_message may still
* return NULL */
if (mime_msg)
return get_text_from_mime_msg (msg, mime_msg, opts);
return NULL;
}
/* note: this will return -1 in case of error or if the size is
* unknown */
static ssize_t
get_part_size (GMimePart *part)
{
GMimeDataWrapper *wrapper;
GMimeStream *stream;
wrapper = g_mime_part_get_content (part);
if (!GMIME_IS_DATA_WRAPPER(wrapper))
return -1;
stream = g_mime_data_wrapper_get_stream (wrapper);
if (!stream)
return -1; /* no stream -> size is 0 */
else
return g_mime_stream_length (stream);
/* NOTE: stream/wrapper are owned by gmime, no unreffing */
}
static char*
cleanup_filename (char *fname)
{
GString *gstr;
gchar *cur;
gunichar uc;
gstr = g_string_sized_new (strlen (fname));
/* replace control characters, slashes, and colons by '-' */
for (cur = fname; cur && *cur; cur = g_utf8_next_char (cur)) {
uc = g_utf8_get_char (cur);
if (g_unichar_iscntrl (uc) || uc == '/' || uc == ':')
g_string_append_unichar (gstr, '-');
else
g_string_append_unichar (gstr, uc);
}
g_free (fname);
return g_string_free (gstr, FALSE);
}
/*
* when a part doesn't have a filename, it can be useful to 'guess' one based on
* its mime-type, which allows other tools to handle them correctly, e.g. from
* mu4e.
*
* For now, we only handle calendar invitations in that way, but others may
* follow.
*/
static char*
guess_file_name (GMimeObject *mobj, unsigned index)
{
GMimeContentType *ctype;
ctype = g_mime_object_get_content_type (mobj);
/* special case for calendars; map to '.vcs' */
if (g_mime_content_type_is_type (ctype, "text", "calendar"))
return g_strdup_printf ("vcal-%u.vcs", index);
/* fallback */
return g_strdup_printf ("%u.msgpart", index);
}
static char*
mime_part_get_filename (GMimeObject *mobj, unsigned index,
gboolean construct_if_needed)
{
gchar *fname;
fname = NULL;
if (GMIME_IS_PART (mobj)) {
/* the easy case: the part has a filename */
fname = (gchar*)g_mime_part_get_filename (GMIME_PART(mobj));
if (fname) /* don't include directory components */
fname = g_path_get_basename (fname);
}
if (!fname && !construct_if_needed)
return NULL;
if (GMIME_IS_MESSAGE_PART(mobj)) {
GMimeMessage *msg;
const char *subj;
msg = g_mime_message_part_get_message
(GMIME_MESSAGE_PART(mobj));
subj = g_mime_message_get_subject (msg);
fname = g_strdup_printf ("%s.eml", subj ? subj : "message");
}
if (!fname)
fname = guess_file_name (mobj, index);
/* replace control characters, slashes, and colons */
fname = cleanup_filename (fname);
return fname;
}
char*
mu_msg_part_get_filename (MuMsgPart *mpart, gboolean construct_if_needed)
{
g_return_val_if_fail (mpart, NULL);
g_return_val_if_fail (GMIME_IS_OBJECT(mpart->data), NULL);
return mime_part_get_filename ((GMimeObject*)mpart->data,
mpart->index, construct_if_needed);
}
const gchar*
mu_msg_part_get_content_id (MuMsgPart *mpart)
{
g_return_val_if_fail (mpart, NULL);
g_return_val_if_fail (GMIME_IS_OBJECT(mpart->data), NULL);
return g_mime_object_get_content_id((GMimeObject*)mpart->data);
}
static MuMsgPartType
get_disposition (GMimeObject *mobj)
{
const char *disp;
disp = g_mime_object_get_disposition (mobj);
if (!disp)
return MU_MSG_PART_TYPE_NONE;
if (strcasecmp (disp, GMIME_DISPOSITION_ATTACHMENT) == 0)
return MU_MSG_PART_TYPE_ATTACHMENT;
if (strcasecmp (disp, GMIME_DISPOSITION_INLINE) == 0)
return MU_MSG_PART_TYPE_INLINE;
return MU_MSG_PART_TYPE_NONE;
}
/* call 'func' with information about this MIME-part */
static inline void
check_signature (MuMsg *msg, GMimeMultipartSigned *part, MuMsgOptions opts)
{
GError *err;
err = NULL;
mu_msg_crypto_verify_part (part, opts, &err);
if (err) {
g_warning ("error verifying signature: %s", err->message);
g_clear_error (&err);
}
}
/* Note: this is function will be called by GMime when it needs a
* password. However, GMime <= 2.6.10 does not handle
* getting passwords correctly, so this might fail. see:
* password_requester in mu-msg-crypto.c */
static gchar*
get_console_pw (const char* user_id, const char *prompt_ctx,
gboolean reprompt, gpointer user_data)
{
char *prompt, *pass;
if (!g_mime_check_version(2,6,11))
g_printerr (
"*** the gmime library you are using has version "
"%u.%u.%u (<= 2.6.10)\n"
"*** this version has a bug in its password "
"retrieval routine, and probably won't work.\n",
gmime_major_version, gmime_minor_version,
gmime_micro_version);
if (reprompt)
g_print ("Authentication failed. Please try again\n");
prompt = g_strdup_printf ("Password for %s: ", user_id);
pass = mu_util_read_password (prompt);
g_free (prompt);
return pass;
}
static gboolean
handle_encrypted_part (MuMsg *msg, GMimeMultipartEncrypted *part,
MuMsgOptions opts, unsigned *index,
MuMsgPartForeachFunc func, gpointer user_data)
{
GError *err;
gboolean rv;
GMimeObject *dec;
MuMsgPartPasswordFunc pw_func;
if (opts & MU_MSG_OPTION_CONSOLE_PASSWORD)
pw_func = (MuMsgPartPasswordFunc)get_console_pw;
else
pw_func = NULL;
err = NULL;
dec = mu_msg_crypto_decrypt_part (part, opts, pw_func, NULL, &err);
if (err) {
g_warning ("error decrypting part: %s", err->message);
g_clear_error (&err);
}
if (dec) {
rv = handle_mime_object (msg, dec, (GMimeObject *) part,
opts, index, TRUE, func, user_data);
g_object_unref (dec);
} else {
/* On failure to decrypt, list the encrypted part as
* an attachment
*/
GMimeObject *encrypted;
encrypted = g_mime_multipart_get_part (
GMIME_MULTIPART (part), 1);
g_return_val_if_fail (GMIME_IS_PART(encrypted), FALSE);
rv = handle_mime_object (msg, encrypted, (GMimeObject *) part,
opts, index, FALSE, func, user_data);
}
return rv;
}
static gboolean
looks_like_text_body_part (GMimeContentType *ctype)
{
unsigned u;
static struct {
const char *type;
const char *subtype;
} types[] = {
{ "text", "plain" },
{ "text", "x-markdown" },
{ "text", "x-diff" },
{ "text", "x-patch" },
{ "application", "x-patch"}
/* possible other types */
};
for (u = 0; u != G_N_ELEMENTS(types); ++u)
if (g_mime_content_type_is_type (
ctype, types[u].type, types[u].subtype))
return TRUE;
return FALSE;
}
/* call 'func' with information about this MIME-part */
static gboolean
handle_part (MuMsg *msg, GMimePart *part, GMimeObject *parent,
MuMsgOptions opts, unsigned *index, gboolean decrypted,
MuMsgPartForeachFunc func, gpointer user_data)
{
GMimeContentType *ct;
MuMsgPart msgpart;
memset (&msgpart, 0, sizeof(MuMsgPart));
msgpart.size = get_part_size (part);
msgpart.part_type = MU_MSG_PART_TYPE_LEAF;
msgpart.part_type |= get_disposition ((GMimeObject*)part);
if (decrypted)
msgpart.part_type |= MU_MSG_PART_TYPE_DECRYPTED;
else if ((opts & MU_MSG_OPTION_DECRYPT) &&
GMIME_IS_MULTIPART_ENCRYPTED (parent))
msgpart.part_type |= MU_MSG_PART_TYPE_ENCRYPTED;
ct = g_mime_object_get_content_type ((GMimeObject*)part);
if (GMIME_IS_CONTENT_TYPE(ct)) {
msgpart.type = g_mime_content_type_get_media_type (ct);
msgpart.subtype = g_mime_content_type_get_media_subtype (ct);
/* store in the part_type as well, for quick checking */
if (looks_like_text_body_part (ct))
msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_PLAIN;
else if (g_mime_content_type_is_type (ct, "text", "html"))
msgpart.part_type |= MU_MSG_PART_TYPE_TEXT_HTML;
}
/* put the verification info in the pgp-signature and every
* descendent of a pgp-encrypted part */
msgpart.sig_status_report = NULL;
if (g_ascii_strcasecmp (msgpart.subtype, "pgp-signature") == 0 ||
decrypted) {
msgpart.sig_status_report =
(MuMsgPartSigStatusReport*)
g_object_get_data (G_OBJECT(parent),
SIG_STATUS_REPORT);
if (msgpart.sig_status_report)
msgpart.part_type |= MU_MSG_PART_TYPE_SIGNED;
}
msgpart.data = (gpointer)part;
msgpart.index = (*index)++;
func (msg, &msgpart, user_data);
return TRUE;
}
/* call 'func' with information about this MIME-part */
static gboolean
handle_message_part (MuMsg *msg, GMimeMessagePart *mimemsgpart,
GMimeObject *parent, MuMsgOptions opts, unsigned *index,
gboolean decrypted,
MuMsgPartForeachFunc func, gpointer user_data)
{
MuMsgPart msgpart;
memset (&msgpart, 0, sizeof(MuMsgPart));
msgpart.type = "message";
msgpart.subtype = "rfc822";
msgpart.index = (*index)++;
/* msgpart.size = 0; /\* maybe calculate this? *\/ */
msgpart.part_type = MU_MSG_PART_TYPE_MESSAGE;
msgpart.part_type |= get_disposition ((GMimeObject*)mimemsgpart);
msgpart.data = (gpointer)mimemsgpart;
func (msg, &msgpart, user_data);
if (opts & MU_MSG_OPTION_RECURSE_RFC822) {
GMimeMessage *mmsg; /* may return NULL for some
* messages */
mmsg = g_mime_message_part_get_message (mimemsgpart);
if (mmsg)
return handle_mime_object (msg,
mmsg->mime_part,
parent,
opts,
index,
decrypted,
func,
user_data);
}
return TRUE;
}
static gboolean
handle_multipart (MuMsg *msg, GMimeMultipart *mpart, GMimeObject *parent,
MuMsgOptions opts, unsigned *index, gboolean decrypted,
MuMsgPartForeachFunc func, gpointer user_data)
{
gboolean res;
GMimeObject *part;
guint i;
res = TRUE;
for (i = 0; i < mpart->children->len; i++) {
part = (GMimeObject *) mpart->children->pdata[i];
res &= handle_mime_object (msg, part, parent,
opts, index, decrypted,
func, user_data);
}
return res;
}
static gboolean
handle_mime_object (MuMsg *msg, GMimeObject *mobj, GMimeObject *parent,
MuMsgOptions opts, unsigned *index, gboolean decrypted,
MuMsgPartForeachFunc func, gpointer user_data)
{
if (GMIME_IS_PART (mobj))
return handle_part
(msg, GMIME_PART(mobj), parent,
opts, index, decrypted, func, user_data);
else if (GMIME_IS_MESSAGE_PART (mobj))
return handle_message_part
(msg, GMIME_MESSAGE_PART(mobj),
parent, opts, index, decrypted, func, user_data);
else if ((opts & MU_MSG_OPTION_VERIFY) &&
GMIME_IS_MULTIPART_SIGNED (mobj)) {
check_signature
(msg, GMIME_MULTIPART_SIGNED (mobj), opts);
return handle_multipart
(msg, GMIME_MULTIPART (mobj), mobj, opts,
index, decrypted, func, user_data);
} else if ((opts & MU_MSG_OPTION_DECRYPT) &&
GMIME_IS_MULTIPART_ENCRYPTED (mobj))
return handle_encrypted_part
(msg, GMIME_MULTIPART_ENCRYPTED (mobj),
opts, index, func, user_data);
else if (GMIME_IS_MULTIPART (mobj))
return handle_multipart
(msg, GMIME_MULTIPART (mobj), parent, opts,
index, decrypted, func, user_data);
return TRUE;
}
gboolean
mu_msg_part_foreach (MuMsg *msg, MuMsgOptions opts,
MuMsgPartForeachFunc func, gpointer user_data)
{
unsigned index;
index = 1;
g_return_val_if_fail (msg, FALSE);
if (!mu_msg_load_msg_file (msg, NULL))
return FALSE;
return handle_mime_object (msg,
msg->_file->_mime_msg->mime_part,
(GMimeObject *) msg->_file->_mime_msg,
opts,
&index,
FALSE,
func,
user_data);
}
static gboolean
write_part_to_fd (GMimePart *part, int fd, GError **err)
{
GMimeStream *stream;
GMimeDataWrapper *wrapper;
gboolean rv;
stream = g_mime_stream_fs_new (fd);
if (!GMIME_IS_STREAM(stream)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"failed to create stream");
return FALSE;
}
g_mime_stream_fs_set_owner (GMIME_STREAM_FS(stream), FALSE);
wrapper = g_mime_part_get_content (part);
if (!GMIME_IS_DATA_WRAPPER(wrapper)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"failed to create wrapper");
g_object_unref (stream);
return FALSE;
}
g_object_ref (part); /* FIXME: otherwise, the unrefs below
* give errors...*/
if (g_mime_data_wrapper_write_to_stream (wrapper, stream) == -1) {
rv = FALSE;
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"failed to write to stream");
} else
rv = TRUE;
/* g_object_unref (wrapper); we don't own it */
g_object_unref (stream);
return rv;
}
static gboolean
write_object_to_fd (GMimeObject *obj, int fd, GError **err)
{
gchar *str;
str = g_mime_object_to_string (obj, NULL);
if (!str) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"could not get string from object");
return FALSE;
}
if (write (fd, str, strlen(str)) == -1) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"failed to write object: %s",
strerror(errno));
return FALSE;
}
return TRUE;
}
static gboolean
save_object (GMimeObject *obj, MuMsgOptions opts, const char *fullpath,
GError **err)
{
int fd;
gboolean rv;
gboolean use_existing, overwrite;
use_existing = opts & MU_MSG_OPTION_USE_EXISTING;
overwrite = opts & MU_MSG_OPTION_OVERWRITE;
/* don't try to overwrite when we already have it; useful when
* you're sure it's not a different file with the same name */
if (use_existing && access (fullpath, F_OK) == 0)
return TRUE;
/* ok, try to create the file */
fd = mu_util_create_writeable_fd (fullpath, 0600, overwrite);
if (fd == -1) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
"could not open '%s' for writing: %s",
fullpath, errno ? strerror(errno) : "error");
return FALSE;
}
if (GMIME_IS_PART (obj))
rv = write_part_to_fd ((GMimePart*)obj, fd, err);
else
rv = write_object_to_fd (obj, fd, err);
/* Unref it since it was referenced earlier by
* get_mime_object_at_index */
g_object_unref (obj);
if (close (fd) != 0 && !err) { /* don't write on top of old err */
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
"could not close '%s': %s",
fullpath, errno ? strerror(errno) : "error");
return FALSE;
}
return rv;
}
gchar*
mu_msg_part_get_path (MuMsg *msg, MuMsgOptions opts,
const char* targetdir, unsigned index, GError **err)
{
char *fname, *filepath;
GMimeObject* mobj;
g_return_val_if_fail (msg, NULL);
if (!mu_msg_load_msg_file (msg, NULL))
return NULL;
mobj = get_mime_object_at_index (msg, opts, index);
if (!mobj){
mu_util_g_set_error (err, MU_ERROR_GMIME,
"cannot find part %u", index);
return NULL;
}
fname = mime_part_get_filename (mobj, index, TRUE);
filepath = g_build_path (G_DIR_SEPARATOR_S, targetdir ? targetdir : "",
fname, NULL);
/* Unref it since it was referenced earlier by
* get_mime_object_at_index */
g_object_unref (mobj);
g_free (fname);
return filepath;
}
gchar*
mu_msg_part_get_cache_path (MuMsg *msg, MuMsgOptions opts, guint partid,
GError **err)
{
char *dirname, *filepath;
const char* path;
g_return_val_if_fail (msg, NULL);
if (!mu_msg_load_msg_file (msg, NULL))
return NULL;
path = mu_msg_get_path (msg);
/* g_compute_checksum_for_string may be better, but requires
* rel. new glib (2.16) */
dirname = g_strdup_printf ("%s%c%x%c%u",
mu_util_cache_dir(), G_DIR_SEPARATOR,
g_str_hash (path), G_DIR_SEPARATOR,
partid);
if (!mu_util_create_dir_maybe (dirname, 0700, FALSE)) {
mu_util_g_set_error (err, MU_ERROR_FILE,
"failed to create dir %s", dirname);
g_free (dirname);
return NULL;
}
filepath = mu_msg_part_get_path (msg, opts, dirname, partid, err);
g_free (dirname);
return filepath;
}
gboolean
mu_msg_part_save (MuMsg *msg, MuMsgOptions opts,
const char *fullpath, guint partidx, GError **err)
{
GMimeObject *part;
g_return_val_if_fail (msg, FALSE);
g_return_val_if_fail (fullpath, FALSE);
g_return_val_if_fail (!((opts & MU_MSG_OPTION_OVERWRITE) &&
(opts & MU_MSG_OPTION_USE_EXISTING)), FALSE);
if (!mu_msg_load_msg_file (msg, err))
return FALSE;
part = get_mime_object_at_index (msg, opts, partidx);
/* special case: convert a message-part into a message */
if (GMIME_IS_MESSAGE_PART (part))
part = (GMimeObject*)g_mime_message_part_get_message
(GMIME_MESSAGE_PART (part));
if (!part) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"part %u does not exist", partidx);
return FALSE;
}
if (!GMIME_IS_PART(part) && !GMIME_IS_MESSAGE(part)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
"unexpected type %s for part %u",
G_OBJECT_TYPE_NAME((GObject*)part),
partidx);
return FALSE;
}
return save_object (part, opts, fullpath, err);
}
gchar*
mu_msg_part_save_temp (MuMsg *msg, MuMsgOptions opts, guint partidx,
GError **err)
{
gchar *filepath;
filepath = mu_msg_part_get_cache_path (msg, opts, partidx, err);
if (!filepath)
return NULL;
if (!mu_msg_part_save (msg, opts, filepath, partidx, err)) {
g_free (filepath);
return NULL;
}
return filepath;
}
static gboolean
match_cid (MuMsgPart *mpart, const char *cid)
{
const char *this_cid;
this_cid = g_mime_object_get_content_id ((GMimeObject*)mpart->data);
return g_strcmp0 (this_cid, cid) ? TRUE : FALSE;
}
int
mu_msg_find_index_for_cid (MuMsg *msg, MuMsgOptions opts,
const char *sought_cid)
{
const char* cid;
g_return_val_if_fail (msg, -1);
g_return_val_if_fail (sought_cid, -1);
if (!mu_msg_load_msg_file (msg, NULL))
return -1;
cid = g_str_has_prefix (sought_cid, "cid:") ?
sought_cid + 4 : sought_cid;
return get_matching_part_index (msg, opts,
(MuMsgPartMatchFunc)match_cid,
(gpointer)cid);
}
struct _RxMatchData {
GSList *_lst;
const GRegex *_rx;
guint _idx;
};
typedef struct _RxMatchData RxMatchData;
static void
match_filename_rx (MuMsg *msg, MuMsgPart *mpart, RxMatchData *mdata)
{
char *fname;
fname = mu_msg_part_get_filename (mpart, FALSE);
if (!fname)
return;
if (g_regex_match (mdata->_rx, fname, 0, NULL))
mdata->_lst = g_slist_prepend (mdata->_lst,
GUINT_TO_POINTER(mpart->index));
g_free (fname);
}
GSList*
mu_msg_find_files (MuMsg *msg, MuMsgOptions opts, const GRegex *pattern)
{
RxMatchData mdata;
g_return_val_if_fail (msg, NULL);
g_return_val_if_fail (pattern, NULL);
if (!mu_msg_load_msg_file (msg, NULL))
return NULL;
mdata._lst = NULL;
mdata._rx = pattern;
mdata._idx = 0;
mu_msg_part_foreach (msg, opts,
(MuMsgPartForeachFunc)match_filename_rx,
&mdata);
return mdata._lst;
}
gboolean
mu_msg_part_maybe_attachment (MuMsgPart *part)
{
g_return_val_if_fail (part, FALSE);
/* attachments must be leaf parts */
if (!(part->part_type & MU_MSG_PART_TYPE_LEAF))
return FALSE;
/* parts other than text/plain, text/html are considered
* attachments as well */
if (!(part->part_type & MU_MSG_PART_TYPE_TEXT_PLAIN) &&
!(part->part_type & MU_MSG_PART_TYPE_TEXT_HTML))
return TRUE;
return part->part_type & MU_MSG_PART_TYPE_ATTACHMENT ? TRUE : FALSE;
}