diff --git a/mu/mu-cmd-extract.cc b/mu/mu-cmd-extract.cc index f425b0cf..b82817f1 100644 --- a/mu/mu-cmd-extract.cc +++ b/mu/mu-cmd-extract.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2010-2020 Dirk-Jan C. Binnema +** Copyright (C) 2010-2022 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 @@ -18,392 +18,193 @@ */ #include "config.h" - -#include -#include - -#include "mu-msg.hh" -#include "mu-msg-part.hh" - #include "mu-cmd.hh" +#include "mu-config.hh" #include "utils/mu-util.h" #include "utils/mu-str.h" +#include "utils/mu-utils.hh" +#include +#include using namespace Mu; -static gboolean -save_part(MuMsg* msg, const char* targetdir, guint partidx, const MuConfig* opts) +static Result +save_part(const Message::Part& part, size_t idx, const MuConfig* opts) { - GError* err; - gchar* filepath; - gboolean rv; - MuMsgOptions msgopts; + const auto targetdir = std::invoke([&]{ + auto tdir{std::string{opts->targetdir ? opts->targetdir : ""}}; + return tdir.empty() ? tdir : tdir + G_DIR_SEPARATOR_S; + }); + const auto path{targetdir + + part.cooked_filename().value_or(format("part-%zu", idx))}; - err = NULL; - rv = FALSE; + if (auto&& res{part.to_file(path, opts->overwrite)}; !res) + return Err(res.error()); - 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); + if (opts->play) { + GError *err{}; + if (auto res{mu_util_play(path.c_str(), TRUE, FALSE, &err)}; + res != MU_OK) + return Err(Error::Code::Play, &err, "playing '%s' failed", + path.c_str()); } - g_free(filepath); - return rv; + return Ok(); } -static gboolean -save_numbered_parts(MuMsg* msg, const MuConfig* opts) +static Result +save_parts(const std::string& path, Option& filename_rx, + const MuConfig* opts) { - gboolean rv; - char ** parts, **cur; + auto message{Message::make_from_path(path, {})}; + if (!message) + return Err(std::move(message.error())); - parts = g_strsplit(opts->parts, ",", 0); - for (rv = TRUE, cur = parts; cur && *cur; ++cur) { - unsigned idx; - int i; - char* endptr; + size_t partnum{}, saved_num{}; + const auto partnums = std::invoke([&]()->std::vector { + std::vector nums; + for (auto&& numstr : split(opts->parts ? opts->parts : "", ',')) + nums.emplace_back( + static_cast(::atoi(numstr.c_str()))); + return nums; + }); - idx = (unsigned)(i = strtol(*cur, &endptr, 10)); - if (i < 0 || *cur == endptr) { - g_printerr("invalid MIME-part index '%s'\n", *cur); - rv = FALSE; - break; + + g_warning("partnums"); + for (auto&& p: partnums) + g_warning ("%zu", p); + + + for (auto&& part: message->parts()) { + + ++partnum; + + if (!opts->save_all) { + + if (!partnums.empty() && + !seq_some(partnums, [&](auto&& num){return num==partnum;})) + continue; // not a wanted partnum. + + if (filename_rx && (!part.raw_filename() || + !std::regex_match(*part.raw_filename(), + std::regex{*filename_rx}))) + continue; // not a wanted pattern. } - if (!save_part(msg, opts->targetdir, idx, opts)) { - g_printerr("failed to save MIME-part %d\n", idx); - rv = FALSE; - break; - } + if (auto res = save_part(part, partnum, opts); !res) + return res; + + ++saved_num; } - g_strfreev(parts); - return rv; + // if (saved_num == 0) + // return Err(Error::Code::File, + // "no %s extracted from this message", + // opts->save_attachments ? "attachments" : "parts"); + // else + return Ok(); } -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); \ +#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) +show_part(const MessagePart& part, size_t index, bool color) { /* index */ - g_print(" %u ", part->index); + g_print(" %zu ", 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); - } + const auto fname{part.raw_filename()}; + mu_util_fputs_encoded(fname ? fname->c_str() : "", stdout); + + mu_util_fputs_encoded(" ", stdout); + /* content-type */ color_maybe(MU_COLOR_BLUE); - mu_util_print_encoded(" %s/%s ", - part->type ? part->type : "", - part->subtype ? part->subtype : ""); + const auto ctype{part.mime_type()}; + mu_util_fputs_encoded(ctype ? ctype->c_str() : "", stdout); /* /\* disposition *\/ */ color_maybe(MU_COLOR_MAGENTA); - mu_util_print_encoded("[%s]", disp_str(part->part_type)); - + mu_util_print_encoded(" [%s]", part.is_attachment() ? + "attachment" : "inline"); /* size */ - if (part->size > 0) { + if (part.size() > 0) { color_maybe(MU_COLOR_CYAN); - g_print(" (%s)", mu_str_size_s(part->size)); + g_print(" (%zu bytes)", part.size()); } color_maybe(MU_COLOR_DEFAULT); fputs("\n", stdout); } -static gboolean -show_parts(const char* path, const MuConfig* opts, GError** err) +static Mu::Result +show_parts(const char* path, const MuConfig* opts) { - MuMsg* msg; - MuMsgOptions msgopts; + //msgopts = mu_config_get_msg_options(opts); - msg = mu_msg_new_from_file(path, NULL, err); - if (!msg) - return FALSE; - - msgopts = mu_config_get_msg_options(opts); + auto msg_res{Message::make_from_path(path)}; + if (!msg_res) + return Err(std::move(msg_res.error())); /* TODO: update this for crypto */ + size_t index{}; g_print("MIME-parts in this message:\n"); - mu_msg_part_foreach(msg, - msgopts, - (MuMsgPartForeachFunc)each_part_show, - GUINT_TO_POINTER(!opts->nocolor)); + for (auto&& part: msg_res->parts()) + show_part(part, ++index, !opts->nocolor); - mu_msg_unref(msg); - - return TRUE; + return Ok(); } -static gboolean -check_params(const MuConfig* opts, GError** err) +static Mu::Result +check_params(const MuConfig* opts) { 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 (param_num < 2) + return Err(Error::Code::InvalidArgument, "parameters missing"); 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->parts || param_num == 3) + return Err(Error::Code::User, + "--save-attachments and --save-all don't " + "accept a filename pattern or --parts"); - 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; + if (opts->save_attachments && opts->save_all) + return Err(Error::Code::User, + "only one of --save-attachments and" + " --save-all is allowed"); + return Ok(); } -MuError -Mu::mu_cmd_extract(const MuConfig* opts, GError** err) + +Mu::Result +Mu::mu_cmd_extract(const MuConfig* opts) { - int rv; + if (!opts || opts->cmd != MU_CONFIG_CMD_EXTRACT) + return Err(Error::Code::Internal, "error in arguments"); + if (auto res = check_params(opts); !res) + return Err(std::move(res.error())); - g_return_val_if_fail(opts, MU_ERROR_INTERNAL); - g_return_val_if_fail(opts->cmd == MU_CONFIG_CMD_EXTRACT, MU_ERROR_INTERNAL); + if (!opts->params[2] && !opts->parts && + !opts->save_attachments && !opts->save_all) + return show_parts(opts->params[1], opts); /* show, don't save */ - if (!check_params(opts, err)) - return MU_ERROR_IN_PARAMETERS; + if (!mu_util_check_dir(opts->targetdir, FALSE, TRUE)) + return Err(Error::Code::File, + "target '%s' is not a writable directory", + opts->targetdir); - 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 */ - } + Option pattern{}; + if (opts->params[2]) + pattern = opts->params[2]; - return rv ? MU_OK : MU_ERROR; + return save_parts(opts->params[1], pattern, opts); } diff --git a/mu/mu-cmd.cc b/mu/mu-cmd.cc index 6486f677..8f1e42c1 100644 --- a/mu/mu-cmd.cc +++ b/mu/mu-cmd.cc @@ -641,9 +641,12 @@ try { case MU_CONFIG_CMD_VIEW: merr = cmd_view(opts, err); break; case MU_CONFIG_CMD_VERIFY: merr = cmd_verify(opts, err); break; case MU_CONFIG_CMD_EXTRACT: - merr = mu_cmd_extract(opts, err); + if (const auto res{mu_cmd_extract(opts)}; !res) { + res.error().fill_g_error(err); + merr = MU_ERROR; + } else + merr = MU_OK; break; - /* read-only store */ case MU_CONFIG_CMD_CFIND: merr = with_readonly_store(mu_cmd_cfind, opts, err); break; diff --git a/mu/mu-cmd.hh b/mu/mu-cmd.hh index 786fe195..34669905 100644 --- a/mu/mu-cmd.hh +++ b/mu/mu-cmd.hh @@ -23,6 +23,7 @@ #include #include #include +#include namespace Mu { /** @@ -42,12 +43,10 @@ MuError mu_cmd_find(const Mu::Store& store, const MuConfig* opts, GError** err); * execute the 'extract' command * * @param opts configuration options - * @param err receives error information, or NULL * - * @return MU_OK (0) if the command succeeds, - * some error code otherwise + * @return Ok() or some error */ -MuError mu_cmd_extract(const MuConfig* opts, GError** err); +Result mu_cmd_extract(const MuConfig* opts); /** * execute the 'script' command