/*
** 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;
}
