/* ** Copyright (C) 2010-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 "mu-msg-part.hh" #include "mu-cmd.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" using namespace Mu; static gboolean save_part(MuMsg* msg, const char* targetdir, guint partidx, const MuConfig* opts) { GError* err; gchar* filepath; gboolean rv; MuMsgOptions msgopts; err = NULL; rv = FALSE; msgopts = mu_config_get_msg_options(opts); filepath = mu_msg_part_get_path(msg, msgopts, targetdir, partidx, &err); if (!filepath) goto exit; if (!mu_msg_part_save(msg, msgopts, filepath, partidx, &err)) goto exit; if (opts->play) rv = mu_util_play(filepath, TRUE, FALSE, &err); else rv = TRUE; exit: if (err) { g_printerr("error with MIME-part: %s\n", err->message); g_clear_error(&err); } g_free(filepath); return rv; } static gboolean save_numbered_parts(MuMsg* msg, const MuConfig* opts) { gboolean rv; char ** parts, **cur; parts = g_strsplit(opts->parts, ",", 0); for (rv = TRUE, cur = parts; cur && *cur; ++cur) { unsigned idx; int i; char* endptr; idx = (unsigned)(i = strtol(*cur, &endptr, 10)); if (i < 0 || *cur == endptr) { g_printerr("invalid MIME-part index '%s'\n", *cur); rv = FALSE; break; } if (!save_part(msg, opts->targetdir, idx, opts)) { g_printerr("failed to save MIME-part %d\n", idx); rv = FALSE; break; } } g_strfreev(parts); return rv; } static GRegex* anchored_regex(const char* pattern) { GRegex* rx; GError* err; gchar* anchored; anchored = g_strdup_printf("%s%s%s", pattern[0] == '^' ? "" : "^", pattern, pattern[strlen(pattern) - 1] == '$' ? "" : "$"); err = NULL; rx = g_regex_new(anchored, (GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE), (GRegexMatchFlags)0, &err); g_free(anchored); if (!rx) { g_printerr("error in regular expression '%s': %s\n", pattern, err->message ? err->message : "error"); g_error_free(err); return NULL; } return rx; } static gboolean save_part_with_filename(MuMsg* msg, const char* pattern, const MuConfig* opts) { GSList * lst, *cur; GRegex* rx; gboolean rv; MuMsgOptions msgopts; msgopts = mu_config_get_msg_options(opts); /* 'anchor' the pattern with '^...$' if not already */ rx = anchored_regex(pattern); if (!rx) return FALSE; lst = mu_msg_find_files(msg, msgopts, rx); g_regex_unref(rx); if (!lst) { g_printerr("no matching attachments found"); return FALSE; } for (cur = lst, rv = TRUE; cur; cur = g_slist_next(cur)) rv = rv && save_part(msg, opts->targetdir, GPOINTER_TO_UINT(cur->data), opts); g_slist_free(lst); return rv; } struct _SaveData { gboolean result; guint saved_num; const MuConfig* opts; }; typedef struct _SaveData SaveData; static gboolean ignore_part(MuMsg* msg, MuMsgPart* part, SaveData* sd) { /* something went wrong somewhere; stop */ if (!sd->result) return TRUE; /* only consider leaf parts */ if (!(part->part_type & MU_MSG_PART_TYPE_LEAF)) return TRUE; /* filter out non-attachments? */ if (!sd->opts->save_all && !(mu_msg_part_maybe_attachment(part))) return TRUE; return FALSE; } static void save_part_if(MuMsg* msg, MuMsgPart* part, SaveData* sd) { gchar* filepath; gboolean rv; GError* err; MuMsgOptions msgopts; if (ignore_part(msg, part, sd)) return; rv = FALSE; filepath = NULL; err = NULL; msgopts = mu_config_get_msg_options(sd->opts); filepath = mu_msg_part_get_path(msg, msgopts, sd->opts->targetdir, part->index, &err); if (!filepath) goto exit; if (!mu_msg_part_save(msg, msgopts, filepath, part->index, &err)) goto exit; if (sd->opts->play) rv = mu_util_play(filepath, TRUE, FALSE, &err); else rv = TRUE; ++sd->saved_num; exit: if (err) g_printerr("error saving MIME part: %s", err->message); g_free(filepath); g_clear_error(&err); sd->result = rv; } static gboolean save_certain_parts(MuMsg* msg, const MuConfig* opts) { SaveData sd; MuMsgOptions msgopts; sd.result = TRUE; sd.saved_num = 0; sd.opts = opts; msgopts = mu_config_get_msg_options(opts); mu_msg_part_foreach(msg, msgopts, (MuMsgPartForeachFunc)save_part_if, &sd); if (sd.saved_num == 0) { g_printerr("no %s extracted from this message", opts->save_attachments ? "attachments" : "parts"); sd.result = FALSE; } return sd.result; } static gboolean save_parts(const char* path, const char* filename, const MuConfig* opts) { MuMsg* msg; gboolean rv; GError* err; err = NULL; msg = mu_msg_new_from_file(path, NULL, &err); if (!msg) { if (err) { g_printerr("error: %s", err->message); g_error_free(err); } return FALSE; } /* note, mu_cmd_extract already checks whether what's in opts * is somewhat, so no need for extensive checking here */ /* should we save some explicit parts? */ if (opts->parts) rv = save_numbered_parts(msg, opts); else if (filename) rv = save_part_with_filename(msg, filename, opts); else rv = save_certain_parts(msg, opts); mu_msg_unref(msg); return rv; } #define color_maybe(C) \ do { \ if (color) \ fputs((C), stdout); \ } while (0) static const char* disp_str(MuMsgPartType ptype) { if (ptype & MU_MSG_PART_TYPE_ATTACHMENT) return "attach"; if (ptype & MU_MSG_PART_TYPE_INLINE) return "inline"; return ""; } static void each_part_show(MuMsg* msg, MuMsgPart* part, gboolean color) { /* index */ g_print(" %u ", part->index); /* filename */ color_maybe(MU_COLOR_GREEN); { gchar* fname; fname = mu_msg_part_get_filename(part, FALSE); mu_util_fputs_encoded(fname ? fname : "", stdout); g_free(fname); } /* content-type */ color_maybe(MU_COLOR_BLUE); mu_util_print_encoded(" %s/%s ", part->type ? part->type : "", part->subtype ? part->subtype : ""); /* /\* disposition *\/ */ color_maybe(MU_COLOR_MAGENTA); mu_util_print_encoded("[%s]", disp_str(part->part_type)); /* size */ if (part->size > 0) { color_maybe(MU_COLOR_CYAN); g_print(" (%s)", mu_str_size_s(part->size)); } color_maybe(MU_COLOR_DEFAULT); fputs("\n", stdout); } static gboolean show_parts(const char* path, const MuConfig* opts, GError** err) { MuMsg* msg; MuMsgOptions msgopts; msg = mu_msg_new_from_file(path, NULL, err); if (!msg) return FALSE; msgopts = mu_config_get_msg_options(opts); /* TODO: update this for crypto */ g_print("MIME-parts in this message:\n"); mu_msg_part_foreach(msg, msgopts, (MuMsgPartForeachFunc)each_part_show, GUINT_TO_POINTER(!opts->nocolor)); mu_msg_unref(msg); return TRUE; } static gboolean check_params(const MuConfig* opts, GError** err) { size_t param_num; param_num = mu_config_param_num(opts); if (param_num < 2) { mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "parameters missing"); return FALSE; } if (opts->save_attachments || opts->save_all) if (opts->parts || param_num == 3) { mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "--save-attachments and --save-all don't " "accept a filename pattern or --parts"); return FALSE; } if (opts->save_attachments && opts->save_all) { mu_util_g_set_error(err, MU_ERROR_IN_PARAMETERS, "only one of --save-attachments and" " --save-all is allowed"); return FALSE; } return TRUE; } MuError Mu::mu_cmd_extract(const MuConfig* opts, GError** err) { int rv; g_return_val_if_fail(opts, MU_ERROR_INTERNAL); g_return_val_if_fail(opts->cmd == MU_CONFIG_CMD_EXTRACT, MU_ERROR_INTERNAL); if (!check_params(opts, err)) return MU_ERROR_IN_PARAMETERS; if (!opts->params[2] && !opts->parts && !opts->save_attachments && !opts->save_all) /* show, don't save */ rv = show_parts(opts->params[1], opts, err); else { rv = mu_util_check_dir(opts->targetdir, FALSE, TRUE); if (!rv) mu_util_g_set_error(err, MU_ERROR_FILE_CANNOT_WRITE, "target '%s' is not a writable directory", opts->targetdir); else rv = save_parts(opts->params[1], opts->params[2], opts); /* save */ } return rv ? MU_OK : MU_ERROR; }