Commit c8ba80ba authored by Volker Krause's avatar Volker Krause

Look up BPFs by using the proper mimetype hierarchy

This includes resolving mimetype aliases, which means we don't have to
register BPFs for all of those anymore.

BPFs now also have a priority, which allows BPF plugins to control their
order (which so far depended on their non-deterministic loading order).
parent 074bdb6a
......@@ -73,22 +73,17 @@ void BodyPartFormatterFactory::loadPlugins()
const MimeTreeParser::Interface::BodyPartFormatter *bfp = nullptr;
for (int i = 0; (bfp = plugin->bodyPartFormatter(i)) && i < formatterData.size(); ++i) {
const auto metaData = formatterData.at(i).toObject();
const auto type = metaData.value(QLatin1String("type")).toString().toUtf8();
if (type.isEmpty()) {
const auto mimetype = metaData.value(QLatin1String("mimetype")).toString();
if (mimetype.isEmpty()) {
qCWarning(MESSAGEVIEWER_LOG) << "BodyPartFormatterFactory: plugin" << path
<< "returned empty type specification for index"
<< "returned empty mimetype specification for index"
<< i;
break;
}
const auto subtype = metaData.value(QLatin1String("subtype")).toString().toUtf8();
if (subtype.isEmpty()) {
qCWarning(MESSAGEVIEWER_LOG) << "BodyPartFormatterFactory: plugin" << path
<< "returned empty subtype specification for index"
<< i;
break;
}
qCDebug(MESSAGEVIEWER_LOG) << "plugin for " << type << subtype;
insert(type, subtype, bfp);
// priority should always be higher than the built-in ones, otherwise what's the point?
const auto priority = metaData.value(QLatin1String("priority")).toInt() + 100;
qCDebug(MESSAGEVIEWER_LOG) << "plugin for " << mimetype << priority;
insert(mimetype, bfp, priority);
}
const MimeTreeParser::Interface::BodyPartURLHandler *handler = nullptr;
......
......@@ -34,7 +34,7 @@ public:
void loadPlugins() override
{
textCalFormatter = new DummyFormatter;
insert("text", "calendar", textCalFormatter);
insert(QStringLiteral("TEXT/CALENDAR"), textCalFormatter, 100);
}
DummyFormatter *textCalFormatter = nullptr;
......@@ -49,73 +49,83 @@ private Q_SLOTS:
void testFactory()
{
TestFactory fac;
auto l = fac.formattersForType("application", "octet-stream");
QCOMPARE(l.size(), 2); // ### should be one, no duplicates
auto l = fac.formattersForType(QStringLiteral("application/octet-stream"));
QCOMPARE(l.size(), 1);
const auto application_octet_stream_f = l.at(0);
QVERIFY(application_octet_stream_f);
l = fac.formattersForType("application", "pgp-encrypted");
QCOMPARE(l.size(), 2);
QCOMPARE(l.at(1), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("application/pgp-encrypted"));
QCOMPARE(l.size(), 4);
QVERIFY(l.at(0) != application_octet_stream_f);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType("application", "unknown");
l = fac.formattersForType(QStringLiteral("application/unknown"));
QCOMPARE(l.size(), 1);
QCOMPARE(l.at(0), application_octet_stream_f);
l = fac.formattersForType("text", "plain");
QCOMPARE(l.size(), 4); // ### should be 3, no duplicates, application/octet-stream missing
l = fac.formattersForType(QStringLiteral("text/plain"));
QCOMPARE(l.size(), 3);
const auto text_plain_f1 = l.at(0);
const auto text_plain_f2 = l.at(1);
QVERIFY(text_plain_f1);
QVERIFY(text_plain_f2);
QVERIFY(text_plain_f1 != text_plain_f2);
QCOMPARE(l.at(2), application_octet_stream_f);
l = fac.formattersForType("text", "calendar");
QCOMPARE(l.size(), 3); // ### should be 4, application/octet-stream missing
l = fac.formattersForType(QStringLiteral("text/calendar"));
QCOMPARE(l.size(), 4);
QVERIFY(fac.textCalFormatter);
QCOMPARE(l.at(0), fac.textCalFormatter);
QCOMPARE(l.at(1), text_plain_f1);
QCOMPARE(l.at(2), text_plain_f2);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType("text", "x-vcalendar");
QCOMPARE(l.size(), 2); // ### should be same as above, mimetype alias resolution missing
l = fac.formattersForType(QStringLiteral("text/x-vcalendar"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(0), fac.textCalFormatter);
l = fac.formattersForType(QStringLiteral("TEXT/X-VCALENDAR"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(0), fac.textCalFormatter);
l = fac.formattersForType("text", "html");
QCOMPARE(l.size(), 3); // ### see above
l = fac.formattersForType(QStringLiteral("text/html"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(1), text_plain_f1);
QCOMPARE(l.at(2), text_plain_f2);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType("text", "rtf");
QCOMPARE(l.size(), 3); // ### this is octet-stream + 2x text, which makes little sense
l = fac.formattersForType(QStringLiteral("text/rtf"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(0), application_octet_stream_f);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType("multipart", "mixed");
l = fac.formattersForType(QStringLiteral("multipart/mixed"));
QCOMPARE(l.size(), 1);
const auto multipart_mixed_f = l.at(0);
QVERIFY(multipart_mixed_f);
l = fac.formattersForType("multipart", "random");
l = fac.formattersForType(QStringLiteral("multipart/random"));
QCOMPARE(l.size(), 1);
QCOMPARE(l.at(0), multipart_mixed_f);
l = fac.formattersForType("multipart", "encrypted");
l = fac.formattersForType(QStringLiteral("multipart/encrypted"));
QCOMPARE(l.size(), 2);
QVERIFY(l.at(0) != multipart_mixed_f);
QCOMPARE(l.at(1), multipart_mixed_f);
l = fac.formattersForType("image", "png");
QCOMPARE(l.size(), 1); // ### misses application/octet-stream fallback
l = fac.formattersForType(QStringLiteral("image/png"));
QCOMPARE(l.size(), 2);
QCOMPARE(l.at(1), application_octet_stream_f);
l = fac.formattersForType("vendor", "random");
l = fac.formattersForType(QStringLiteral("vendor/random"));
QCOMPARE(l.size(), 1);
QCOMPARE(l.at(0), application_octet_stream_f);
l = fac.formattersForType("message", "rfc822");
QCOMPARE(l.size(), 2);
QCOMPARE(l.at(1), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("message/rfc822"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType("message", "news");
QCOMPARE(l.size(), 1); // ### fails due to missing mimetype inheritance
l = fac.formattersForType(QStringLiteral("message/news"));
QCOMPARE(l.size(), 3); // ### news does not inherit rfc822
}
};
......
......@@ -163,28 +163,27 @@ typedef TextPlainBodyPartFormatter ApplicationPgpBodyPartFormatter;
void BodyPartFormatterBaseFactoryPrivate::messageviewer_create_builtin_bodypart_formatters()
{
insert("application", "octet-stream", AnyTypeBodyPartFormatter::create());
insert("application", "pgp", ApplicationPgpBodyPartFormatter::create());
insert("application", "pkcs7-mime", ApplicationPkcs7MimeBodyPartFormatter::create());
insert("application", "x-pkcs7-mime", ApplicationPkcs7MimeBodyPartFormatter::create());
insert("application", "pgp-encrypted", ApplicationPGPEncryptedBodyPartFormatter::create());
insert("application", "*", AnyTypeBodyPartFormatter::create());
insert("text", "html", TextHtmlBodyPartFormatter::create());
insert("text", "rtf", AnyTypeBodyPartFormatter::create());
insert("text", "plain", MailmanBodyPartFormatter::create());
insert("text", "plain", TextPlainBodyPartFormatter::create());
insert("text", "*", MailmanBodyPartFormatter::create());
insert("text", "*", TextPlainBodyPartFormatter::create());
insert("image", "*", ImageTypeBodyPartFormatter::create());
insert("message", "rfc822", MessageRfc822BodyPartFormatter::create());
insert("message", "*", AnyTypeBodyPartFormatter::create());
insert("multipart", "alternative", MultiPartAlternativeBodyPartFormatter::create());
insert("multipart", "encrypted", MultiPartEncryptedBodyPartFormatter::create());
insert("multipart", "signed", MultiPartSignedBodyPartFormatter::create());
insert("multipart", "*", MultiPartMixedBodyPartFormatter::create());
insert("*", "*", AnyTypeBodyPartFormatter::create());
insert(QStringLiteral("application/octet-stream"), AnyTypeBodyPartFormatter::create());
insert(QStringLiteral("application/pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create());
insert(QStringLiteral("application/x-pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create());
insert(QStringLiteral("application/pgp-encrypted"), ApplicationPGPEncryptedBodyPartFormatter::create());
insert(QStringLiteral("text/html"), TextHtmlBodyPartFormatter::create());
insert(QStringLiteral("text/rtf"), AnyTypeBodyPartFormatter::create());
insert(QStringLiteral("text/plain"), MailmanBodyPartFormatter::create());
insert(QStringLiteral("text/plain"), TextPlainBodyPartFormatter::create());
insert(QStringLiteral("image/png"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/jpeg"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/gif"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/svg+xml"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/bmp"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/vnd.microsoft.icon"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("message/rfc822"), MessageRfc822BodyPartFormatter::create());
insert(QStringLiteral("multipart/alternative"), MultiPartAlternativeBodyPartFormatter::create());
insert(QStringLiteral("multipart/encrypted"), MultiPartEncryptedBodyPartFormatter::create());
insert(QStringLiteral("multipart/signed"), MultiPartSignedBodyPartFormatter::create());
insert(QStringLiteral("multipart/mixed"), MultiPartMixedBodyPartFormatter::create());
}
......@@ -35,6 +35,9 @@
#include "bodypartformatterbasefactory_p.h"
#include "mimetreeparser_debug.h"
#include <QMimeDatabase>
#include <QMimeType>
#include <assert.h>
using namespace MimeTreeParser;
......@@ -46,30 +49,38 @@ BodyPartFormatterBaseFactoryPrivate::BodyPartFormatterBaseFactoryPrivate(BodyPar
void BodyPartFormatterBaseFactoryPrivate::setup()
{
if (all.empty()) {
if (registry.empty()) {
messageviewer_create_builtin_bodypart_formatters();
q->loadPlugins();
}
assert(!all.empty());
assert(!registry.empty());
}
void BodyPartFormatterBaseFactoryPrivate::insert(const QByteArray &type, const QByteArray &subtype, const Interface::BodyPartFormatter *formatter)
void BodyPartFormatterBaseFactoryPrivate::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
{
if (type.isEmpty() || subtype.isEmpty() || !formatter) {
if (mimeType.isEmpty() || !formatter)
return;
}
TypeRegistry::iterator type_it = all.find(type);
if (type_it == all.end()) {
qCDebug(MIMETREEPARSER_LOG) << "BodyPartFormatterBaseFactory: instantiating new Subtype Registry for \""
<< type << "\"";
type_it = all.insert(std::make_pair(type, SubtypeRegistry())).first;
assert(type_it != all.end());
}
SubtypeRegistry &subtype_reg = type_it->second;
QMimeDatabase db;
const auto mt = db.mimeTypeForName(mimeType);
FormatterInfo info;
info.formatter = formatter;
info.priority = priority;
auto &v = registry[mt.isValid() ? mt.name() : mimeType];
v.push_back(info);
std::stable_sort(v.begin(), v.end(), [](const FormatterInfo &lhs, const FormatterInfo &rhs) {
return lhs.priority > rhs.priority;
});
}
subtype_reg.insert(std::make_pair(subtype, formatter));
void BodyPartFormatterBaseFactoryPrivate::appendFormattersForType(const QString &mimeType, QVector<const Interface::BodyPartFormatter*> &formatters)
{
const auto it = registry.constFind(mimeType);
if (it == registry.constEnd())
return;
for (const auto &f : it.value())
formatters.push_back(f.formatter);
}
BodyPartFormatterBaseFactory::BodyPartFormatterBaseFactory()
......@@ -82,35 +93,44 @@ BodyPartFormatterBaseFactory::~BodyPartFormatterBaseFactory()
delete d;
}
void BodyPartFormatterBaseFactory::insert(const QByteArray &type, const QByteArray &subtype, const Interface::BodyPartFormatter *formatter)
void BodyPartFormatterBaseFactory::insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority)
{
d->insert(type, subtype, formatter);
d->insert(mimeType.toLower(), formatter, priority);
}
QVector<const Interface::BodyPartFormatter*> BodyPartFormatterBaseFactory::formattersForType(const QByteArray &type, const QByteArray &subtype) const
QVector<const Interface::BodyPartFormatter*> BodyPartFormatterBaseFactory::formattersForType(const QString &mimeType) const
{
QVector<const Interface::BodyPartFormatter*> r;
d->setup();
auto type_it = d->all.find(type);
if (type_it == d->all.end()) {
type_it = d->all.find("*");
QMimeDatabase db;
std::vector<QString> processedTypes;
processedTypes.push_back(mimeType.toLower());
// add all formatters we have along the mimetype hierarchy
for (std::size_t i = 0; i < processedTypes.size(); ++i) {
const auto mt = db.mimeTypeForName(processedTypes[i]);
if (mt.isValid())
processedTypes[i] = mt.name(); // resolve alias if necessary
if (processedTypes[i] == QLatin1String("application/octet-stream")) // we'll deal with that later
continue;
d->appendFormattersForType(processedTypes[i], r);
const auto parentTypes = mt.parentMimeTypes();
for (const auto &parentType : parentTypes) {
if (std::find(processedTypes.begin(), processedTypes.end(), parentType) != processedTypes.end())
continue;
processedTypes.push_back(parentType);
}
}
assert(type_it != d->all.end()); // cannot happen, */* always exists
const auto &subtype_reg = type_it->second;
assert(!subtype_reg.empty()); // same
// exact match
auto range = subtype_reg.equal_range(subtype);
for (auto it = range.first; it != range.second; ++it)
r.push_back((*it).second);
// wildcard match
range = subtype_reg.equal_range("*");
for (auto it = range.first; it != range.second; ++it)
r.push_back((*it).second);
// make sure we always have a suitable fallback formatter
if (mimeType.startsWith(QLatin1String("multipart/"))) {
if (mimeType != QLatin1String("multipart/mixed"))
d->appendFormattersForType(QStringLiteral("multipart/mixed"), r);
} else {
d->appendFormattersForType(QStringLiteral("application/octet-stream"), r);
}
assert(!r.empty());
return r;
}
......
......@@ -58,10 +58,10 @@ public:
* The candidates are ordered by priority, with the catch-call
* formatter coming last.
*/
QVector<const Interface::BodyPartFormatter*> formattersForType(const QByteArray &type, const QByteArray &subtype) const;
QVector<const Interface::BodyPartFormatter*> formattersForType(const QString &mimeType) const;
protected:
void insert(const QByteArray &type, const QByteArray &subType, const Interface::BodyPartFormatter *formatter);
void insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority);
virtual void loadPlugins();
private:
......
......@@ -34,21 +34,12 @@
#ifndef __MIMETREEPARSER_BODYPARTFORMATTERBASEFACTORY_P_H__
#define __MIMETREEPARSER_BODYPARTFORMATTERBASEFACTORY_P_H__
#include <map>
#include <QHash>
#include <vector>
namespace MimeTreeParser {
class BodyPartFormatterBaseFactory;
struct ltstr {
bool operator()(const QByteArray &s1, const QByteArray &s2) const
{
return qstricmp(s1.constData(), s2.constData()) < 0;
}
};
typedef std::multimap<QByteArray, const Interface::BodyPartFormatter*, ltstr> SubtypeRegistry;
typedef std::map<QByteArray, MimeTreeParser::SubtypeRegistry, MimeTreeParser::ltstr> TypeRegistry;
class BodyPartFormatterBaseFactoryPrivate
{
public:
......@@ -57,10 +48,15 @@ public:
void setup();
void messageviewer_create_builtin_bodypart_formatters(); //defined in bodypartformatter.cpp
void insert(const QByteArray &type, const QByteArray &subType, const Interface::BodyPartFormatter *formatter);
void insert(const QString &mimeType, const Interface::BodyPartFormatter *formatter, int priority = 0);
void appendFormattersForType(const QString &mimeType, QVector<const Interface::BodyPartFormatter*> &formatters);
BodyPartFormatterBaseFactory *q;
TypeRegistry all;
struct FormatterInfo {
const Interface::BodyPartFormatter* formatter = nullptr;
int priority = 0;
};
QHash<QString, std::vector<FormatterInfo>> registry;
};
}
......
......@@ -198,10 +198,10 @@ MessagePartPtr ObjectTreeParser::parsedPart() const
return mParsedPart;
}
bool ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mediaType, const QByteArray &subType, Interface::MessagePartPtr &mpRet, bool onlyOneMimePart)
bool ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mimeType, Interface::MessagePartPtr &mpRet, bool onlyOneMimePart)
{
bool bRendered = false;
const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(mediaType, subType);
const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(QString::fromUtf8(mimeType));
for (auto formatter : formatters) {
PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper);
// Set the default display strategy for this body part relying on the
......@@ -288,21 +288,19 @@ MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node,
ProcessResult processResult(mNodeHelper);
QByteArray mediaType("text");
QByteArray mimeType("text/plain");
QByteArray subType("plain");
if (node->contentType(false) && !node->contentType()->mediaType().isEmpty()
&& !node->contentType()->subType().isEmpty()) {
mediaType = node->contentType()->mediaType();
subType = node->contentType()->subType();
if (node->contentType(false) && !node->contentType()->mimeType().isEmpty()) {
mimeType = node->contentType()->mimeType();
}
Interface::MessagePartPtr mp;
if (processType(node, processResult, mediaType, subType, mp, onlyOneMimePart)) {
if (processType(node, processResult, mimeType, mp, onlyOneMimePart)) {
if (mp) {
parsedPart->appendSubPart(mp);
}
} else {
qCWarning(MIMETREEPARSER_LOG) << "THIS SHOULD NO LONGER HAPPEN:" << mediaType << '/' << subType;
qCWarning(MIMETREEPARSER_LOG) << "THIS SHOULD NO LONGER HAPPEN:" << mimeType;
const auto mp = defaultHandling(node, processResult, onlyOneMimePart);
if (mp) {
if (auto _mp = mp.dynamicCast<MessagePart>()) {
......@@ -334,7 +332,7 @@ Interface::MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *no
|| node->contentType()->name().endsWith(QLatin1String("p7s"))
|| node->contentType()->name().endsWith(QLatin1String("p7c"))
)
&& processType(node, processResult, "application", "pkcs7-mime", mp, onlyOneMimePart)) {
&& processType(node, processResult, "application/pkcs7-mime", mp, onlyOneMimePart)) {
return mp;
}
......
......@@ -337,7 +337,7 @@ private:
* top-level content.
*/
MessagePartPtr parseObjectTreeInternal(KMime::Content *node, bool mOnlyOneMimePart);
bool processType(KMime::Content *node, MimeTreeParser::ProcessResult &processResult, const QByteArray &mediaType, const QByteArray &subType, Interface::MessagePartPtr &mpRet,
bool processType(KMime::Content *node, MimeTreeParser::ProcessResult &processResult, const QByteArray &mimeType, Interface::MessagePartPtr &mpRet,
bool onlyOneMimePart);
Interface::MessagePartPtr defaultHandling(KMime::Content *node, MimeTreeParser::ProcessResult &result, bool onlyOneMimePart);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment