message: support cooked/raw filenames

Supported a "cooked" mode for attachment filenames, which gets rid of any
unacceptable characters. Add "raw_filename" to get the filename as specified in
the part.

Update tests.
This commit is contained in:
Dirk-Jan C. Binnema 2022-03-28 22:22:36 +03:00
parent 9f062ae482
commit da8eee0e69
3 changed files with 77 additions and 10 deletions

View File

@ -19,6 +19,7 @@
#include "mu-message-part.hh"
#include "glibconfig.h"
#include "mu-mime-object.hh"
#include "utils/mu-utils.hh"
@ -36,7 +37,41 @@ MessagePart::~MessagePart() = default;
Option<std::string>
MessagePart::filename() const noexcept
MessagePart::cooked_filename() const noexcept
{
// 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);
return clean;
};
// a MimePart... use the name if there is one.
if (mime_obj->is_part())
return MimePart(*mime_obj).filename().map(cleanup);
// MimeMessagepart. Construct a name based on subject.
if (mime_obj->is_message_part()) {
auto msg{MimeMessagePart(*mime_obj).get_message()};
return msg.subject()
.map(cleanup)
.value_or("no-subject") + ".eml";
}
return Nothing;
}
Option<std::string>
MessagePart::raw_filename() const noexcept
{
if (!mime_obj->is_part())
return Nothing;
@ -44,6 +79,8 @@ MessagePart::filename() const noexcept
return MimePart(*mime_obj).filename();
}
Option<std::string>
MessagePart::mime_type() const noexcept
{
@ -62,6 +99,15 @@ MessagePart::size() const noexcept
return MimePart(*mime_obj).size();
}
bool
MessagePart::is_attachment() const noexcept
{
if (!mime_obj->is_part())
return false;
else
return MimePart(*mime_obj).is_attachment();
}
Option<std::string>
MessagePart::to_string() const noexcept

View File

@ -53,11 +53,23 @@ public:
~MessagePart();
/**
* Filename for the mime-part
* 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).
*
* @see raw_filename()
*
* @return the name
*/
Option<std::string> cooked_filename() const noexcept;
/**
* Name for the mime-part file, i.e., MimePart::filename
*
* @return the filename or Nothing if there is none
*/
Option<std::string> filename() const noexcept;
Option<std::string> raw_filename() const noexcept;
/**
* Mime-type for the mime-part (e.g. "text/plain")
@ -73,6 +85,15 @@ public:
*/
size_t size() const noexcept;
/**
* Does this part have an "attachment" disposition? Otherwise it is
* "inline". Note that does *not* map 1:1 to a message's HasAttachment
* flag.
*
* @return true or false.
*/
bool is_attachment() const noexcept;
/**
* Write (decoded) mime-part contents to string
*
@ -80,7 +101,6 @@ public:
*/
Option<std::string> to_string() const noexcept;
/**
* Write (decoded) mime part to a file
*

View File

@ -438,7 +438,7 @@ fill_document(Message::Private& priv)
break;
case Field::Id::File:
for (auto&& part: priv.parts)
doc.add(field.id, part.filename());
doc.add(field.id, part.raw_filename());
break;
case Field::Id::Flags:
doc.add(priv.flags);
@ -684,7 +684,7 @@ Content-Description: test file 1
MDAwAQID
--=-=-=
Content-Type: audio/ogg
Content-Disposition: inline; filename=file-02.bin
Content-Disposition: inline; filename=/tmp/file-02.bin
Content-Transfer-Encoding: base64
MDA0BQYH
@ -721,27 +721,28 @@ R"(Hello,World!)");
g_assert_cmpuint(message->parts().size(),==,4);
{
auto&& part{message->parts().at(0)};
g_assert_false(!!part.filename());
g_assert_false(!!part.raw_filename());
assert_equal(part.mime_type().value(), "text/plain");
assert_equal(part.to_string().value(), "Hello,");
}
{
auto&& part{message->parts().at(1)};
assert_equal(part.filename().value(), "file-01.bin");
assert_equal(part.raw_filename().value(), "file-01.bin");
assert_equal(part.mime_type().value(), "image/jpeg");
// file consist of 6 bytes "000" 0x01,0x02.0x03.
assert_equal(part.to_string().value(), "000\001\002\003");
}
{
auto&& part{message->parts().at(2)};
assert_equal(part.filename().value(), "file-02.bin");
assert_equal(part.raw_filename().value(), "/tmp/file-02.bin");
assert_equal(part.cooked_filename().value(), "tmp-file-02.bin");
assert_equal(part.mime_type().value(), "audio/ogg");
// file consist of the string "004" followed by 0x5,0x6,0x7.
assert_equal(part.to_string().value(), "004\005\006\007");
}
{
auto&& part{message->parts().at(3)};
g_assert_false(!!part.filename());
g_assert_false(!!part.raw_filename());
g_assert_true(!!part.mime_type());
assert_equal(part.mime_type().value(), "text/plain");
assert_equal(part.to_string().value(), "World!");