mu extract/view/verify: allow reading message from stdin

Fixes #1463.
This commit is contained in:
Dirk-Jan C. Binnema
2023-04-29 22:58:55 +03:00
parent 3a05dd8725
commit 9544473e35
7 changed files with 135 additions and 57 deletions

View File

@ -49,15 +49,11 @@ save_part(const Message::Part& part, size_t idx, const Options& opts)
}
static Result<void>
save_parts(const std::string& path, const std::string& filename_rx,
save_parts(const Message& message, const std::string& filename_rx,
const Options& opts)
{
auto message{Message::make_from_path(path, message_options(opts.extract))};
if (!message)
return Err(std::move(message.error()));
size_t partnum{}, saved_num{};
for (auto&& part: message->parts()) {
for (auto&& part: message.parts()) {
++partnum;
// should we extract this part?
const auto do_extract = std::invoke([&]() {
@ -134,15 +130,11 @@ show_part(const MessagePart& part, size_t index, bool color)
}
static Mu::Result<void>
show_parts(const std::string& path, const Options& opts)
show_parts(const Message& message, const Options& opts)
{
auto msg_res{Message::make_from_path(path, message_options(opts.extract))};
if (!msg_res)
return Err(std::move(msg_res.error()));
size_t index{};
g_print("MIME-parts in this message:\n");
for (auto&& part: msg_res->parts())
for (auto&& part: message.parts())
show_part(part, ++index, !opts.nocolor);
return Ok();
@ -151,15 +143,29 @@ show_parts(const std::string& path, const Options& opts)
Mu::Result<void>
Mu::mu_cmd_extract(const Options& opts)
{
if (opts.extract.parts.empty() &&
!opts.extract.save_attachments && !opts.extract.save_all &&
opts.extract.filename_rx.empty())
return show_parts(opts.extract.message, opts); /* show, don't save */
auto message = std::invoke([&]()->Result<Message>{
const auto mopts{message_options(opts.extract)};
if (!opts.extract.message.empty())
return Message::make_from_path(opts.extract.message, mopts);
const auto msgtxt = read_from_stdin();
if (!msgtxt)
return Err(msgtxt.error());
else
return Message::make_from_text(*msgtxt, {}, mopts);
});
if (!message)
return Err(message.error());
else if (opts.extract.parts.empty() &&
!opts.extract.save_attachments && !opts.extract.save_all &&
opts.extract.filename_rx.empty())
return show_parts(*message, opts); /* show, don't save */
if (!check_dir(opts.extract.targetdir, false/*!readable*/, true/*writeable*/))
return Err(Error::Code::File,
"target '%s' is not a writable directory",
opts.extract.targetdir.c_str());
return save_parts(opts.extract.message, opts.extract.filename_rx, opts);
return save_parts(*message, opts.extract.filename_rx, opts);
}

View File

@ -36,6 +36,7 @@
#include "message/mu-mime-object.hh"
#include "utils/mu-error.hh"
#include "utils/mu-utils-file.hh"
#include "utils/mu-utils.hh"
#include "message/mu-message.hh"
@ -152,19 +153,15 @@ view_msg_plain(const Message& message, const Options& opts)
}
static Mu::Result<void>
handle_msg(const std::string& fname, const Options& opts)
handle_msg(const Message& message, const Options& opts)
{
using Format = Options::View::Format;
auto message{Message::make_from_path(fname, message_options(opts.view))};
if (!message)
return Err(message.error());
switch (opts.view.format) {
case Format::Plain:
return view_msg_plain(*message, opts);
return view_msg_plain(message, opts);
case Format::Sexp:
return view_msg_sexp(*message, opts);
return view_msg_sexp(message, opts);
default:
g_critical("bug: should not be reached");
return Err(Error::Code::Internal, "error");
@ -175,13 +172,29 @@ static Mu::Result<void>
cmd_view(const Options& opts)
{
for (auto&& file: opts.view.files) {
if (auto res = handle_msg(file, opts); !res)
auto message{Message::make_from_path(
file, message_options(opts.view))};
if (!message)
return Err(message.error());
if (auto res = handle_msg(*message, opts); !res)
return res;
/* add a separator between two messages? */
if (opts.view.terminate)
g_print("%c", VIEW_TERMINATOR);
}
// no files? read from stding
if (opts.view.files.empty()) {
const auto msgtxt = read_from_stdin();
if (!msgtxt)
return Err(msgtxt.error());
auto message = Message::make_from_text(*msgtxt,{}, message_options(opts.view));
if (!message)
return Err(message.error());
else
return handle_msg(*message, opts);
}
return Ok();
}
@ -312,6 +325,35 @@ verify(const MimeMultipartSigned& sigpart, const Options& opts)
return valid;
}
static bool
verify_message(const Message& message, const Options& opts, const std::string& name)
{
if (none_of(message.flags() & Flags::Signed)) {
if (!opts.quiet)
g_print("%s: no signed parts found\n", name.c_str());
return false;
}
bool verified{true}; /* innocent until proven guilty */
for(auto&& part: message.parts()) {
if (!part.is_signed())
continue;
const auto& mobj{part.mime_object()};
if (!mobj.is_multipart_signed())
continue;
if (!verify(MimeMultipartSigned(mobj), opts))
verified = false;
}
return verified;
}
static Mu::Result<void>
cmd_verify(const Options& opts)
{
@ -325,29 +367,22 @@ cmd_verify(const Options& opts)
return Err(message.error());
if (!opts.quiet && opts.verify.files.size() > 1)
g_print("verifying %sn\n", file.c_str());
g_print("verifying %s\n", file.c_str());
if (none_of(message->flags() & Flags::Signed)) {
if (!opts.quiet)
g_print("%s: no signed parts found\n", file.c_str());
continue;
}
if (!verify_message(*message, opts, file))
all_ok = false;
}
bool verified{true}; /* innocent until proven guilty */
for(auto&& part: message->parts()) {
// when no messages provided, read from stdin
if (opts.verify.files.empty()) {
const auto msgtxt = read_from_stdin();
if (!msgtxt)
return Err(msgtxt.error());
auto message{Message::make_from_text(*msgtxt, {}, mopts)};
if (!message)
return Err(message.error());
if (!part.is_signed())
continue;
const auto& mobj{part.mime_object()};
if (!mobj.is_multipart_signed())
continue;
if (!verify(MimeMultipartSigned(mobj), opts))
verified = false;
}
all_ok = all_ok && verified;
all_ok = verify_message(*message, opts, "<stdin>");
}
if (all_ok)

View File

@ -223,16 +223,27 @@ sub_extract(CLI::App& sub, Options& opts)
"Target directory for saving")
->type_name("<dir>")
->default_str("<current>")->default_val(".");
sub.add_option("message", opts.extract.message,
"Path to message file")->required()
->type_name("<message-path>");
sub.add_flag("--uncooked,-u", opts.extract.uncooked,
"Avoid massaging extracted file-names");
// optional; otherwise use standard-input
sub.add_option("message-path", opts.extract.message,
"Path to message file")
->type_name("<message-path>");
sub.add_option("--matches", opts.extract.filename_rx,
"Regular expression for files to save")
->type_name("<filename-rx>")
->excludes("--parts")
->excludes("--save-attachments")
->excludes("--save-all");
// backward compat: filename-rx as non-option
sub.add_option("filename-rx", opts.extract.filename_rx,
"Regular expression for files to save")
->type_name("<filename-rx>")
->excludes("--parts")
->excludes("--save-attachments")
->excludes("--matches")
->excludes("--save-all");
}
@ -421,10 +432,10 @@ sub_verify(CLI::App& sub, Options& opts)
{
sub_crypto(sub, opts.verify);
sub.add_option("files", opts.verify.files,
// optional; otherwise use standard-input
sub.add_option("message-paths", opts.verify.files,
"Message files to verify")
->type_name("<message-file>")
->required();
->type_name("<message-path>");
}
static void
@ -460,10 +471,10 @@ sub_view(CLI::App& sub, Options& opts)
sub.add_flag("--terminate", opts.view.terminate,
"Insert form-feed after each message");
sub.add_option("files", opts.view.files,
// optional; otherwise use standard-input
sub.add_option("message-paths", opts.view.files,
"Message files to view")
->type_name("<file>")
->required();
->type_name("<message-path>");
}