mirror of https://github.com/djcb/mu.git
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:
parent
9f062ae482
commit
da8eee0e69
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include "mu-message-part.hh"
|
#include "mu-message-part.hh"
|
||||||
|
#include "glibconfig.h"
|
||||||
#include "mu-mime-object.hh"
|
#include "mu-mime-object.hh"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
|
|
||||||
|
@ -36,7 +37,41 @@ MessagePart::~MessagePart() = default;
|
||||||
|
|
||||||
|
|
||||||
Option<std::string>
|
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())
|
if (!mime_obj->is_part())
|
||||||
return Nothing;
|
return Nothing;
|
||||||
|
@ -44,6 +79,8 @@ MessagePart::filename() const noexcept
|
||||||
return MimePart(*mime_obj).filename();
|
return MimePart(*mime_obj).filename();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Option<std::string>
|
Option<std::string>
|
||||||
MessagePart::mime_type() const noexcept
|
MessagePart::mime_type() const noexcept
|
||||||
{
|
{
|
||||||
|
@ -62,6 +99,15 @@ MessagePart::size() const noexcept
|
||||||
return MimePart(*mime_obj).size();
|
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>
|
Option<std::string>
|
||||||
MessagePart::to_string() const noexcept
|
MessagePart::to_string() const noexcept
|
||||||
|
|
|
@ -53,11 +53,23 @@ public:
|
||||||
~MessagePart();
|
~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
|
* @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")
|
* Mime-type for the mime-part (e.g. "text/plain")
|
||||||
|
@ -73,6 +85,15 @@ public:
|
||||||
*/
|
*/
|
||||||
size_t size() const noexcept;
|
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
|
* Write (decoded) mime-part contents to string
|
||||||
*
|
*
|
||||||
|
@ -80,7 +101,6 @@ public:
|
||||||
*/
|
*/
|
||||||
Option<std::string> to_string() const noexcept;
|
Option<std::string> to_string() const noexcept;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write (decoded) mime part to a file
|
* Write (decoded) mime part to a file
|
||||||
*
|
*
|
||||||
|
|
|
@ -438,7 +438,7 @@ fill_document(Message::Private& priv)
|
||||||
break;
|
break;
|
||||||
case Field::Id::File:
|
case Field::Id::File:
|
||||||
for (auto&& part: priv.parts)
|
for (auto&& part: priv.parts)
|
||||||
doc.add(field.id, part.filename());
|
doc.add(field.id, part.raw_filename());
|
||||||
break;
|
break;
|
||||||
case Field::Id::Flags:
|
case Field::Id::Flags:
|
||||||
doc.add(priv.flags);
|
doc.add(priv.flags);
|
||||||
|
@ -684,7 +684,7 @@ Content-Description: test file 1
|
||||||
MDAwAQID
|
MDAwAQID
|
||||||
--=-=-=
|
--=-=-=
|
||||||
Content-Type: audio/ogg
|
Content-Type: audio/ogg
|
||||||
Content-Disposition: inline; filename=file-02.bin
|
Content-Disposition: inline; filename=/tmp/file-02.bin
|
||||||
Content-Transfer-Encoding: base64
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
MDA0BQYH
|
MDA0BQYH
|
||||||
|
@ -721,27 +721,28 @@ R"(Hello,World!)");
|
||||||
g_assert_cmpuint(message->parts().size(),==,4);
|
g_assert_cmpuint(message->parts().size(),==,4);
|
||||||
{
|
{
|
||||||
auto&& part{message->parts().at(0)};
|
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.mime_type().value(), "text/plain");
|
||||||
assert_equal(part.to_string().value(), "Hello,");
|
assert_equal(part.to_string().value(), "Hello,");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto&& part{message->parts().at(1)};
|
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");
|
assert_equal(part.mime_type().value(), "image/jpeg");
|
||||||
// file consist of 6 bytes "000" 0x01,0x02.0x03.
|
// file consist of 6 bytes "000" 0x01,0x02.0x03.
|
||||||
assert_equal(part.to_string().value(), "000\001\002\003");
|
assert_equal(part.to_string().value(), "000\001\002\003");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto&& part{message->parts().at(2)};
|
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");
|
assert_equal(part.mime_type().value(), "audio/ogg");
|
||||||
// file consist of the string "004" followed by 0x5,0x6,0x7.
|
// file consist of the string "004" followed by 0x5,0x6,0x7.
|
||||||
assert_equal(part.to_string().value(), "004\005\006\007");
|
assert_equal(part.to_string().value(), "004\005\006\007");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto&& part{message->parts().at(3)};
|
auto&& part{message->parts().at(3)};
|
||||||
g_assert_false(!!part.filename());
|
g_assert_false(!!part.raw_filename());
|
||||||
g_assert_true(!!part.mime_type());
|
g_assert_true(!!part.mime_type());
|
||||||
assert_equal(part.mime_type().value(), "text/plain");
|
assert_equal(part.mime_type().value(), "text/plain");
|
||||||
assert_equal(part.to_string().value(), "World!");
|
assert_equal(part.to_string().value(), "World!");
|
||||||
|
|
Loading…
Reference in New Issue