diff --git a/lib/message/meson.build b/lib/message/meson.build index 3997fe28..cab52eec 100644 --- a/lib/message/meson.build +++ b/lib/message/meson.build @@ -48,48 +48,55 @@ lib_mu_message_dep = declare_dependency( test('test-contact', executable('test-contact', - 'mu-contact.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'mu-contact.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-document', executable('test-document', - 'mu-document.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'mu-document.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-fields', executable('test-fields', - 'mu-fields.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'mu-fields.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-flags', executable('test-flags', - 'mu-flags.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'mu-flags.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-message', executable('test-message', - 'test-mu-message.cc', - install: false, - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'test-mu-message.cc', + install: false, + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-priority', executable('test-priority', - 'mu-priority.cc', - install: false, - cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) + 'mu-priority.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_message_dep])) test('test-message-file', executable('test-message-file', - 'mu-message-file.cc', - install: false, + 'mu-message-file.cc', + install: false, cpp_args: ['-DBUILD_TESTS'], - dependencies: [glib_dep, lib_mu_message_dep])) + dependencies: [glib_dep, lib_mu_message_dep])) + +test('test-message-part', + executable('test-message-part', + 'mu-message-part.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, lib_mu_message_dep])) diff --git a/lib/message/mu-message-part.cc b/lib/message/mu-message-part.cc index 8f0b7638..0d59c580 100644 --- a/lib/message/mu-message-part.cc +++ b/lib/message/mu-message-part.cc @@ -42,27 +42,48 @@ MessagePart::mime_object() const noexcept return *mime_obj; } -Option -MessagePart::cooked_filename() const noexcept +static std::string +cook(const std::string& fname, const std::vector& forbidden) { - // make a bit more pallatble. - auto cleanup = [](const std::string& name)->std::string { - std::string clean; - clean.reserve(name.length()); - for (auto& c: name) { - auto taboo{(::iscntrl(c) || c == G_DIR_SEPARATOR || - c == ' ' || c == '\\' || c == ':')}; - clean += (taboo ? '-' : c); - } - if (clean.size() > 1 && clean[0] == '-') - clean.erase(0, 1); + std::string clean; + clean.reserve(fname.length()); + for (auto& c: to_string_gchar(g_path_get_basename(fname.c_str()))) + if (seq_some(forbidden,[&](char fc){return ::iscntrl(c) || c == fc;})) + clean += '-'; + else + clean += c; + + if (clean[0] == '.' && (clean == "." || clean == "..")) + return "-"; + else return clean; - }; +} + +static std::string +cook_minimal(const std::string& fname) +{ + return cook(fname, { '/' }); +} + +static std::string +cook_full(const std::string& fname) +{ + auto cooked = cook(fname, { '/', ' ', '\\', ':' }); + if (cooked.size() > 1 && cooked[0] == '-') + cooked.erase(0, 1); + + return cooked; +} + +Option +MessagePart::cooked_filename(bool minimal) const noexcept +{ + auto&& cooker{minimal ? cook_minimal : cook_full}; // a MimePart... use the name if there is one. if (mime_object().is_part()) - return MimePart{mime_object()}.filename().map(cleanup); + return MimePart{mime_object()}.filename().map(cooker); // MimeMessagepart. Construct a name based on subject. if (mime_object().is_message_part()) { @@ -71,7 +92,7 @@ MessagePart::cooked_filename() const noexcept return Nothing; else return msg->subject() - .map(cleanup) + .map(cooker) .value_or("no-subject") + ".eml"; } @@ -186,3 +207,48 @@ MessagePart::looks_like_attachment() const noexcept // otherwise, rely on the disposition return is_attachment(); } + + + +#ifdef BUILD_TESTS +#include "utils/mu-test-utils.hh" + +static void +test_cooked_full() +{ + std::array, 4> cases = {{ + { "/hello/world/foo", "foo" }, + { "foo:/\n/bar", "bar"}, + { "Aap Noot Mies", "Aap-Noot-Mies"}, + { "..", "-"} + }}; + + for (auto&& test: cases) + assert_equal(cook_full(test.first), test.second); +} + +static void +test_cooked_minimal() +{ + std::array, 4> cases = {{ + { "/hello/world/foo", "foo" }, + { "foo:/\n/bar", "bar"}, + { "Aap Noot Mies.doc", "Aap Noot Mies.doc"}, + { "..", "-"} + }}; + + for (auto&& test: cases) + assert_equal(cook_minimal(test.first), test.second); +} + +int +main(int argc, char* argv[]) +{ + mu_test_init(&argc, &argv); + + g_test_add_func("/message/message-part/cooked-full", test_cooked_full); + g_test_add_func("/message/message-part/cooked-minimal", test_cooked_minimal); + + return g_test_run(); +} +#endif /*BUILD_TESTS*/ diff --git a/lib/message/mu-message-part.hh b/lib/message/mu-message-part.hh index b955fc88..53367233 100644 --- a/lib/message/mu-message-part.hh +++ b/lib/message/mu-message-part.hh @@ -65,13 +65,16 @@ public: /** * Filename for the mime-part file. This is a "cooked" filename with * unallowed characters removed. If there's no filename specified, - * construct one (such as in the case of MimeMessagePart). + * construct one (such as in the case of a MimeMessagePart). + * + * @param minimal if true, only perform *minimal* cookiing, where we + * only remove forward-slashes. * * @see raw_filename() * * @return the name */ - Option cooked_filename() const noexcept; + Option cooked_filename(bool minimal=false) const noexcept; /** * Name for the mime-part file, i.e., MimePart::filename diff --git a/lib/message/test-mu-message.cc b/lib/message/test-mu-message.cc index 26bb82d3..c363d031 100644 --- a/lib/message/test-mu-message.cc +++ b/lib/message/test-mu-message.cc @@ -235,7 +235,7 @@ World! { auto&& part{message->parts().at(2)}; assert_equal(part.raw_filename().value(), "/tmp/file-02.bin"); - assert_equal(part.cooked_filename().value(), "tmp-file-02.bin"); + assert_equal(part.cooked_filename().value(), "file-02.bin"); assert_equal(part.mime_type().value(), "audio/ogg"); // file consistso of 4 bytes 4..7 assert_equal(part.to_string().value(), "\004\005\006\007");