extract: rework in terms of Mu::Message

Use the new message class
This commit is contained in:
Dirk-Jan C. Binnema 2022-03-28 22:36:32 +03:00
parent da8eee0e69
commit b21e5a57b8
3 changed files with 132 additions and 329 deletions

View File

@ -1,5 +1,5 @@
/*
** Copyright (C) 2010-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** Copyright (C) 2010-2022 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
@ -18,392 +18,193 @@
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#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 <message/mu-message.hh>
#include <regex>
using namespace Mu;
static gboolean
save_part(MuMsg* msg, const char* targetdir, guint partidx, const MuConfig* opts)
static Result<void>
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<void>
save_parts(const std::string& path, Option<std::string>& 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<size_t> {
std::vector<size_t> nums;
for (auto&& numstr : split(opts->parts ? opts->parts : "", ','))
nums.emplace_back(
static_cast<size_t>(::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 "<none>";
}
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 : "<none>", stdout);
g_free(fname);
}
const auto fname{part.raw_filename()};
mu_util_fputs_encoded(fname ? fname->c_str() : "<none>", stdout);
mu_util_fputs_encoded(" ", stdout);
/* content-type */
color_maybe(MU_COLOR_BLUE);
mu_util_print_encoded(" %s/%s ",
part->type ? part->type : "<none>",
part->subtype ? part->subtype : "<none>");
const auto ctype{part.mime_type()};
mu_util_fputs_encoded(ctype ? ctype->c_str() : "<none>", 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<void>
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<void>
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<void>
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<std::string> pattern{};
if (opts->params[2])
pattern = opts->params[2];
return rv ? MU_OK : MU_ERROR;
return save_parts(opts->params[1], pattern, opts);
}

View File

@ -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;

View File

@ -23,6 +23,7 @@
#include <glib.h>
#include <mu-config.hh>
#include <mu-store.hh>
#include <utils/mu-result.hh>
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<void> mu_cmd_extract(const MuConfig* opts);
/**
* execute the 'script' command