2011-01-12 22:09:09 +01:00
|
|
|
/*
|
2023-01-14 16:11:36 +01:00
|
|
|
** Copyright (C) 2010-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
2011-01-12 22:09:09 +01:00
|
|
|
**
|
|
|
|
** 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"
|
2020-06-08 22:04:05 +02:00
|
|
|
#include "mu-cmd.hh"
|
2022-03-28 21:36:32 +02:00
|
|
|
#include "utils/mu-utils.hh"
|
2023-01-14 16:11:36 +01:00
|
|
|
#include "utils/mu-utils-file.hh"
|
2022-12-30 08:58:54 +01:00
|
|
|
#include "utils/mu-regex.hh"
|
2022-03-28 21:36:32 +02:00
|
|
|
#include <message/mu-message.hh>
|
2012-08-01 16:02:43 +02:00
|
|
|
|
2020-11-28 09:16:43 +01:00
|
|
|
using namespace Mu;
|
2012-08-01 16:02:43 +02:00
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
static Result<void>
|
2022-11-16 21:51:15 +01:00
|
|
|
save_part(const Message::Part& part, size_t idx, const Options& opts)
|
2011-01-12 22:09:09 +01:00
|
|
|
{
|
2022-03-28 21:36:32 +02:00
|
|
|
const auto targetdir = std::invoke([&]{
|
2022-11-16 21:51:15 +01:00
|
|
|
const auto tdir{opts.extract.targetdir};
|
2022-03-28 21:36:32 +02:00
|
|
|
return tdir.empty() ? tdir : tdir + G_DIR_SEPARATOR_S;
|
|
|
|
});
|
2023-02-23 19:23:25 +01:00
|
|
|
|
|
|
|
/* 'uncooked' isn't really _raw_; it means only doing some _minimal_
|
|
|
|
* cooking */
|
2022-03-28 21:36:32 +02:00
|
|
|
const auto path{targetdir +
|
2023-02-23 19:23:25 +01:00
|
|
|
part.cooked_filename(opts.extract.uncooked)
|
2023-07-20 17:36:00 +02:00
|
|
|
.value_or(mu_format("part-{}", idx))};
|
2022-03-28 21:36:32 +02:00
|
|
|
|
2022-11-16 21:51:15 +01:00
|
|
|
if (auto&& res{part.to_file(path, opts.extract.overwrite)}; !res)
|
2022-03-28 21:36:32 +02:00
|
|
|
return Err(res.error());
|
2023-01-14 16:11:36 +01:00
|
|
|
else if (opts.extract.play)
|
|
|
|
return play(path);
|
|
|
|
else
|
|
|
|
return Ok();
|
2011-01-12 22:09:09 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
static Result<void>
|
2023-04-29 21:58:55 +02:00
|
|
|
save_parts(const Message& message, const std::string& filename_rx,
|
2022-11-16 21:51:15 +01:00
|
|
|
const Options& opts)
|
2011-01-12 22:09:09 +01:00
|
|
|
{
|
2022-03-28 21:36:32 +02:00
|
|
|
size_t partnum{}, saved_num{};
|
2023-04-29 21:58:55 +02:00
|
|
|
for (auto&& part: message.parts()) {
|
2022-03-28 21:36:32 +02:00
|
|
|
++partnum;
|
2022-11-16 21:51:15 +01:00
|
|
|
// should we extract this part?
|
|
|
|
const auto do_extract = std::invoke([&]() {
|
|
|
|
|
|
|
|
if (opts.extract.save_all)
|
|
|
|
return true;
|
|
|
|
else if (opts.extract.save_attachments &&
|
|
|
|
part.looks_like_attachment())
|
|
|
|
return true;
|
|
|
|
else if (seq_some(opts.extract.parts,
|
|
|
|
[&](auto&& num){return num==partnum;}))
|
|
|
|
return true;
|
2022-12-30 08:58:54 +01:00
|
|
|
else if (!filename_rx.empty() && part.raw_filename()) {
|
|
|
|
if (auto rx = Regex::make(filename_rx); !rx)
|
|
|
|
throw rx.error();
|
|
|
|
else if (rx->matches(*part.raw_filename()))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2022-11-16 21:51:15 +01:00
|
|
|
});
|
2011-05-22 12:42:19 +02:00
|
|
|
|
2022-11-16 21:51:15 +01:00
|
|
|
if (!do_extract)
|
|
|
|
continue;
|
2011-09-03 09:43:52 +02:00
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
if (auto res = save_part(part, partnum, opts); !res)
|
|
|
|
return res;
|
2011-09-03 09:43:52 +02:00
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
++saved_num;
|
|
|
|
}
|
2011-09-03 09:43:52 +02:00
|
|
|
|
2022-11-16 21:51:15 +01:00
|
|
|
if (saved_num == 0)
|
|
|
|
return Err(Error::Code::File,
|
2023-07-05 22:10:13 +02:00
|
|
|
"no {} extracted from this message",
|
2022-11-16 21:51:15 +01:00
|
|
|
opts.extract.save_attachments ? "attachments" : "parts");
|
|
|
|
else
|
2022-03-28 21:36:32 +02:00
|
|
|
return Ok();
|
2011-01-12 22:09:09 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
#define color_maybe(C) \
|
|
|
|
do { \
|
|
|
|
if (color) \
|
|
|
|
fputs((C), stdout); \
|
2021-10-20 11:18:15 +02:00
|
|
|
} while (0)
|
2011-05-29 12:57:27 +02:00
|
|
|
|
2011-01-12 22:09:09 +01:00
|
|
|
static void
|
2022-03-28 21:36:32 +02:00
|
|
|
show_part(const MessagePart& part, size_t index, bool color)
|
2011-01-12 22:09:09 +01:00
|
|
|
{
|
2011-06-02 10:09:04 +02:00
|
|
|
/* index */
|
2023-07-05 22:10:13 +02:00
|
|
|
mu_print(" {} ", index);
|
2011-06-02 10:09:04 +02:00
|
|
|
|
|
|
|
/* filename */
|
2021-10-20 11:18:15 +02:00
|
|
|
color_maybe(MU_COLOR_GREEN);
|
2022-03-28 21:36:32 +02:00
|
|
|
const auto fname{part.raw_filename()};
|
2023-01-14 16:11:36 +01:00
|
|
|
fputs_encoded(fname.value_or("<none>"), stdout);
|
|
|
|
fputs_encoded(" ", stdout);
|
2022-03-28 21:36:32 +02:00
|
|
|
|
2011-06-02 10:09:04 +02:00
|
|
|
/* content-type */
|
2021-10-20 11:18:15 +02:00
|
|
|
color_maybe(MU_COLOR_BLUE);
|
2022-03-28 21:36:32 +02:00
|
|
|
const auto ctype{part.mime_type()};
|
2023-01-14 16:11:36 +01:00
|
|
|
fputs_encoded(ctype.value_or("<none>"), stdout);
|
2011-06-02 10:09:04 +02:00
|
|
|
|
2012-08-01 16:02:43 +02:00
|
|
|
/* /\* disposition *\/ */
|
2021-10-20 11:18:15 +02:00
|
|
|
color_maybe(MU_COLOR_MAGENTA);
|
2023-07-06 11:22:50 +02:00
|
|
|
mu_print_encoded(" [{}]", part.is_attachment() ? "attachment" : "inline");
|
2011-11-18 11:20:42 +01:00
|
|
|
/* size */
|
2022-03-28 21:36:32 +02:00
|
|
|
if (part.size() > 0) {
|
2021-10-20 11:18:15 +02:00
|
|
|
color_maybe(MU_COLOR_CYAN);
|
2023-07-05 22:10:13 +02:00
|
|
|
mu_print(" ({} bytes)", part.size());
|
2011-11-18 11:20:42 +01:00
|
|
|
}
|
|
|
|
|
2021-10-20 11:18:15 +02:00
|
|
|
color_maybe(MU_COLOR_DEFAULT);
|
|
|
|
fputs("\n", stdout);
|
2011-01-12 22:09:09 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
static Mu::Result<void>
|
2023-04-29 21:58:55 +02:00
|
|
|
show_parts(const Message& message, const Options& opts)
|
2011-01-12 22:09:09 +01:00
|
|
|
{
|
2022-03-28 21:36:32 +02:00
|
|
|
size_t index{};
|
2023-07-05 22:10:13 +02:00
|
|
|
mu_println("MIME-parts in this message:");
|
2023-04-29 21:58:55 +02:00
|
|
|
for (auto&& part: message.parts())
|
2022-11-16 21:51:15 +01:00
|
|
|
show_part(part, ++index, !opts.nocolor);
|
2011-05-26 22:37:06 +02:00
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
return Ok();
|
2011-01-12 22:09:09 +01:00
|
|
|
}
|
|
|
|
|
2022-03-28 21:36:32 +02:00
|
|
|
Mu::Result<void>
|
2022-11-16 21:51:15 +01:00
|
|
|
Mu::mu_cmd_extract(const Options& opts)
|
2011-01-12 22:09:09 +01:00
|
|
|
{
|
2023-04-29 21:58:55 +02:00
|
|
|
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 */
|
2022-03-28 21:36:32 +02:00
|
|
|
|
2023-01-14 16:11:36 +01:00
|
|
|
if (!check_dir(opts.extract.targetdir, false/*!readable*/, true/*writeable*/))
|
2022-03-28 21:36:32 +02:00
|
|
|
return Err(Error::Code::File,
|
2023-07-05 22:10:13 +02:00
|
|
|
"target '{}' is not a writable directory",
|
|
|
|
opts.extract.targetdir);
|
2011-09-03 09:43:52 +02:00
|
|
|
|
2023-04-29 21:58:55 +02:00
|
|
|
return save_parts(*message, opts.extract.filename_rx, opts);
|
2011-01-12 22:09:09 +01:00
|
|
|
}
|
2023-08-26 12:20:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef BUILD_TESTS
|
|
|
|
/*
|
|
|
|
* Tests.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <glib.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#include <utils/mu-regex.hh>
|
|
|
|
#include "utils/mu-test-utils.hh"
|
|
|
|
|
|
|
|
|
|
|
|
static gint64
|
|
|
|
get_file_size(const std::string& path)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
struct stat statbuf;
|
|
|
|
|
|
|
|
mu_info("ppatj {}", path);
|
|
|
|
|
|
|
|
rv = stat(path.c_str(), &statbuf);
|
|
|
|
if (rv != 0) {
|
|
|
|
mu_debug ("error: {}", g_strerror (errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mu_debug("{} -> {} bytes", path, statbuf.st_size);
|
|
|
|
|
|
|
|
return statbuf.st_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_mu_extract_02(void)
|
|
|
|
{
|
|
|
|
TempDir temp_dir{};
|
|
|
|
auto res= run_command({
|
|
|
|
MU_PROGRAM, "extract", "--save-attachments",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5")});
|
|
|
|
assert_valid_result(res);
|
|
|
|
g_assert_true(res->standard_err.empty());
|
|
|
|
|
|
|
|
g_assert_cmpuint(get_file_size(join_paths(temp_dir.path(), "custer.jpg")), >=, 15955);
|
|
|
|
g_assert_cmpuint(get_file_size(join_paths(temp_dir.path(), "custer.jpg")), <=, 15960);
|
|
|
|
g_assert_cmpuint(get_file_size(join_paths(temp_dir.path(), "sittingbull.jpg")), ==, 17674);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_mu_extract_03(void)
|
|
|
|
{
|
|
|
|
TempDir temp_dir{};
|
|
|
|
auto res= run_command({
|
|
|
|
MU_PROGRAM, "extract", "--parts=3",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5")});
|
|
|
|
assert_valid_result(res);
|
|
|
|
g_assert_true(res->standard_err.empty());
|
|
|
|
|
|
|
|
g_assert_true(g_access(join_paths(temp_dir.path(), "custer.jpg").c_str(), F_OK) == 0);
|
|
|
|
g_assert_false(g_access(join_paths(temp_dir.path(), "sittingbull.jpg").c_str(), F_OK) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_mu_extract_overwrite(void)
|
|
|
|
{
|
|
|
|
TempDir temp_dir{};
|
|
|
|
auto res= run_command({
|
|
|
|
MU_PROGRAM, "extract", "-a",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5")});
|
|
|
|
assert_valid_result(res);
|
|
|
|
g_assert_true(res->standard_err.empty());
|
|
|
|
|
|
|
|
g_assert_true(g_access(join_paths(temp_dir.path(), "custer.jpg").c_str(), F_OK) == 0);
|
|
|
|
g_assert_true(g_access(join_paths(temp_dir.path(), "sittingbull.jpg").c_str(), F_OK) == 0);
|
|
|
|
|
|
|
|
|
|
|
|
/* now, it should fail, because we don't allow overwrites
|
|
|
|
* without --overwrite */
|
|
|
|
auto res2 = run_command({
|
|
|
|
MU_PROGRAM, "extract", "-a",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5")});
|
|
|
|
|
|
|
|
assert_valid_result(res2);
|
|
|
|
g_assert_false(res2->standard_err.empty());
|
|
|
|
|
|
|
|
|
|
|
|
auto res3 = run_command({
|
|
|
|
MU_PROGRAM, "extract", "-a", "--overwrite",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5")});
|
|
|
|
|
|
|
|
assert_valid_result(res3);
|
|
|
|
g_assert_true(res3->standard_err.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_mu_extract_by_name(void)
|
|
|
|
{
|
|
|
|
TempDir temp_dir{};
|
|
|
|
auto res= run_command({
|
|
|
|
MU_PROGRAM, "extract",
|
|
|
|
mu_format("--target-dir='{}'", temp_dir.path()),
|
|
|
|
join_paths(MU_TESTMAILDIR2, "Foo", "cur", "mail5"),
|
|
|
|
"sittingbull.jpg"});
|
|
|
|
assert_valid_result(res);
|
|
|
|
g_assert_true(res->standard_err.empty());
|
|
|
|
|
|
|
|
g_assert_true(g_access(join_paths(temp_dir.path(), "sittingbull.jpg").c_str(), F_OK) == 0);
|
|
|
|
g_assert_false(g_access(join_paths(temp_dir.path(), "custer.jpg").c_str(), F_OK) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
mu_test_init(&argc, &argv);
|
|
|
|
|
|
|
|
g_test_add_func("/cmd/extract/02", test_mu_extract_02);
|
|
|
|
g_test_add_func("/cmd/extract/03", test_mu_extract_03);
|
|
|
|
g_test_add_func("/cmd/extract/overwrite", test_mu_extract_overwrite);
|
|
|
|
g_test_add_func("/cmd/extract/by-name", test_mu_extract_by_name);
|
|
|
|
|
|
|
|
return g_test_run();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|