mirror of
https://github.com/djcb/mu.git
synced 2024-06-28 07:41:04 +02:00
3dd721d5a3
Update all cc code using .clang-format; please do so as well for future PRs etc.; emacs has a handy 'clang-format' mode to make this automatic. For comparing old changes with git blame, we can disregard this one using --ignore-rev (see https://www.moxio.com/blog/43/ignoring-bulk-change-commits-with-git-blame )
1024 lines
27 KiB
C++
1024 lines
27 KiB
C++
/*
|
|
** Copyright (C) 2008-2020 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.
|
|
**
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "mu-msg.hh"
|
|
#include "utils/mu-util.h"
|
|
#include "utils/mu-str.h"
|
|
#include "mu-msg-priv.hh"
|
|
#include "mu-msg-part.hh"
|
|
|
|
using namespace Mu;
|
|
|
|
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;
|
|
|
|
/* wipe out some irrelevant options */
|
|
opts &= (MuMsgOptions)~MU_MSG_OPTION_VERIFY;
|
|
opts &= ~MU_MSG_OPTION_EXTRACT_IMAGES;
|
|
|
|
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::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 == ':' || 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::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::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;
|
|
}
|
|
|
|
static MuMsgPartSigStatusReport*
|
|
copy_status_report_maybe(GObject* obj)
|
|
{
|
|
MuMsgPartSigStatusReport *report, *copy;
|
|
|
|
report = (MuMsgPartSigStatusReport*)g_object_get_data(obj, SIG_STATUS_REPORT);
|
|
if (!report)
|
|
return NULL; /* nothing to copy */
|
|
|
|
copy = g_slice_new0(MuMsgPartSigStatusReport);
|
|
copy->verdict = report->verdict;
|
|
|
|
if (report->report)
|
|
copy->report = g_strdup(report->report);
|
|
if (report->signers)
|
|
copy->signers = g_strdup(report->signers);
|
|
|
|
return copy;
|
|
}
|
|
|
|
/* 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 = copy_status_report_maybe(G_OBJECT(parent));
|
|
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);
|
|
|
|
mu_msg_part_sig_status_report_destroy(msgpart.sig_status_report);
|
|
|
|
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::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",
|
|
g_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 ? g_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);
|
|
|
|
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 ? g_strerror(errno) : "error");
|
|
return FALSE;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
gchar*
|
|
Mu::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::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::mu_msg_part_save(MuMsg* msg,
|
|
MuMsgOptions opts,
|
|
const char* fullpath,
|
|
guint partidx,
|
|
GError** err)
|
|
{
|
|
gboolean rv;
|
|
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);
|
|
|
|
rv = FALSE;
|
|
|
|
if (!mu_msg_load_msg_file(msg, err))
|
|
return rv;
|
|
|
|
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);
|
|
else 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);
|
|
else
|
|
rv = save_object(part, opts, fullpath, err);
|
|
|
|
g_clear_object(&part);
|
|
|
|
return rv;
|
|
}
|
|
|
|
gchar*
|
|
Mu::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::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, (GRegexMatchFlags)0, NULL))
|
|
mdata->_lst = g_slist_prepend(mdata->_lst, GUINT_TO_POINTER(mpart->index));
|
|
g_free(fname);
|
|
}
|
|
|
|
GSList*
|
|
Mu::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::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;
|
|
}
|