mirror of https://github.com/djcb/mu.git
extract: rework in terms of Mu::Message
Use the new message class
This commit is contained in:
parent
da8eee0e69
commit
b21e5a57b8
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue