/* ** Copyright (C) 2008-2020 Dirk-Jan C. Binnema ** ** 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 #include #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; }