diff --git a/framework/domain/CMakeLists.txt b/framework/domain/CMakeLists.txt --- a/framework/domain/CMakeLists.txt +++ b/framework/domain/CMakeLists.txt @@ -26,3 +26,5 @@ install(TARGETS mailplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/domain) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/domain) + +add_subdirectory(mimetreeparser) \ No newline at end of file diff --git a/framework/domain/mimetreeparser/CMakeLists.txt b/framework/domain/mimetreeparser/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/CMakeLists.txt @@ -0,0 +1,12 @@ +set(mimetreeparser_SRCS + interface.cpp + objecttreesource.cpp + stringhtmlwriter.cpp +) + +add_library(mimetreeparser SHARED ${mimetreeparser_SRCS}) + +qt5_use_modules(mimetreeparser Core Gui) +target_link_libraries(mimetreeparser KF5::Mime KF5::MimeTreeParser) + +add_subdirectory(tests) \ No newline at end of file diff --git a/framework/domain/mimetreeparser/interface.h b/framework/domain/mimetreeparser/interface.h new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/interface.h @@ -0,0 +1,333 @@ +/* + Copyright (c) 2016 Sandro Knauß + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +class Part; +class PartPrivate; + +class MailMime; +class MailMimePrivate; + +class AlternativePart; +class AlternativePartPrivate; + +class SinglePart; +class SinglePartPrivate; + +class EncryptionPart; +class EncryptionPartPrivate; + +class EncapsulatedPart; +class EncapsulatedPartPrivate; + +class Content; +class ContentPrivate; + +class CertContent; +class CertContentPrivate; + +class EncryptionError; + +class Key; +class Signature; +class Encryption; + +class Parser; +class ParserPrivate; + +/* + * A MessagePart that is based on a KMime::Content + */ +class MailMime +{ +public: + typedef std::shared_ptr Ptr; + /** + * Various possible values for the "Content-Disposition" header. + */ + enum Disposition { + Invalid, ///< Default, invalid value + Inline, ///< inline + Attachment ///< attachment + }; + + // interessting header parts of a KMime::Content + QMimeType mimetype() const; + Disposition disposition() const; + QUrl label() const; + QByteArray cid() const; + QByteArray charset() const; + QByteArray filename() const; + + // Unique identifier to ecactly this KMime::Content + QByteArray link() const; + + QByteArray content() const; + //Use default charset + QString encodedContent() const; + + // overwrite default charset with given charset + QString encodedContent(QByteArray charset) const; + + bool isFirstTextPart() const; + +private: + std::unique_ptr d; +}; + +class Content +{ +public: + typedef std::shared_ptr Ptr; + Content(const QByteArray &content, Part *parent); + virtual ~Content(); + + QByteArray content() const; + + QByteArray charset() const; + + //Use default charset + QString encodedContent() const; + + // overwrite default charset with given charset + QString encodedContent(QByteArray charset) const; + + virtual QVector signatures() const; + virtual QVector encryptions() const; + MailMime::Ptr mailMime() const; + virtual QByteArray type() const; +private: + std::unique_ptr d; +}; + +class PlainTextContent : public Content +{ +public: + PlainTextContent(const QByteArray &content, Part *parent); + QByteArray type() const Q_DECL_OVERRIDE; +}; + +class HtmlContent : public Content +{ +public: + HtmlContent(const QByteArray &content, Part *parent); + QByteArray type() const Q_DECL_OVERRIDE; +}; + +/* + * importing a cert GpgMe::ImportResult + * checking a cert (if it is a valid cert) + */ + +class CertContent : public Content +{ +public: + typedef std::shared_ptr Ptr; + CertContent(const QByteArray &content, Part *parent); + + QByteArray type() const Q_DECL_OVERRIDE; + enum CertType { + Pgp, + SMime + }; + + enum CertSubType { + Public, + Private + }; + + CertType certType() const; + CertSubType certSubType() const; + int keyLength() const; + +private: + std::unique_ptr d; +}; + +class Part +{ +public: + typedef std::shared_ptr Ptr; + Part(); + virtual QByteArray type() const; + + virtual QVector availableContents() const; + virtual QVector content(const QByteArray& ct) const; + QVector content() const; + + bool hasSubParts() const; + QVector subParts() const; + Part *parent() const; + + virtual QVector signatures() const; + virtual QVector encryptions() const; + virtual MailMime::Ptr mailMime() const; +private: + std::unique_ptr d; + friend class ParserPrivate; + friend class PartPrivate; +}; + +class AlternativePart : public Part +{ +public: + typedef std::shared_ptr Ptr; + + AlternativePart(); + virtual ~AlternativePart(); + + QVector availableContents() const Q_DECL_OVERRIDE; + QVector content(const QByteArray& ct) const Q_DECL_OVERRIDE; + + QByteArray type() const Q_DECL_OVERRIDE; + +private: + std::unique_ptr d; + + friend class ParserPrivate; +}; + +class SinglePart : public Part +{ + public: + typedef std::shared_ptr Ptr; + + SinglePart(); + virtual ~SinglePart(); + + QVector content(const QByteArray& ct) const Q_DECL_OVERRIDE; + QVector availableContents() const Q_DECL_OVERRIDE; + + QByteArray type() const Q_DECL_OVERRIDE; +private: + std::unique_ptr d; + + friend class ParserPrivate; +}; + + +class EncryptionPart : public Part +{ +public: + typedef std::shared_ptr Ptr; + QByteArray type() const Q_DECL_OVERRIDE; + + EncryptionError error() const; +private: + std::unique_ptr d; +}; + + +/* + * we want to request complete headers like: + * from/to... + */ + +class EncapsulatedPart : public SinglePart +{ +public: + typedef std::shared_ptr Ptr; + QByteArray type() const Q_DECL_OVERRIDE; + + //template QByteArray header(); +private: + std::unique_ptr d; +}; + +class EncryptionError +{ +public: + int errorId() const; + QString errorString() const; +}; + +class Key +{ + QString keyid() const; + QString name() const; + QString email() const; + QString comment() const; + QVector emails() const; + enum KeyTrust { + Unknown, Undefined, Never, Marginal, Full, Ultimate + }; + KeyTrust keyTrust() const; + + bool isRevokation() const; + bool isInvalid() const; + bool isExpired() const; + + std::vector subkeys(); + Key parentkey() const; +}; + +class Signature +{ + Key key() const; + QDateTime creationDateTime() const; + QDateTime expirationTime() const; + bool neverExpires() const; + + //template <> StatusObject verify() const; +}; + +/* + * Normally the Keys for encryption are subkeys + * for clients the parentkeys are "more interessting", because they store the name, email etc. + * but a client may also wants show to what subkey the mail is really encrypted, an if this subkey isRevoked or something else + */ +class Encryption +{ + std::vector recipients() const; +}; + +class Parser +{ +public: + typedef std::shared_ptr Ptr; + Parser(const QByteArray &mimeMessage); + ~Parser(); + + Part::Ptr getPart(QUrl url); + + QVector collect(const Part::Ptr &start, std::function select, std::function filter) const; + QVector collectContentParts() const; + QVector collectAttachmentParts() const; + //template <> QVector collect() const; + + //template <> static StatusObject verifySignature(const Signature signature) const; + //template <> static StatusObject decrypt(const EncryptedPart part) const; + +signals: + void partsChanged(); + +private: + std::unique_ptr d; + + friend class InterfaceTest; +}; + diff --git a/framework/domain/mimetreeparser/interface.cpp b/framework/domain/mimetreeparser/interface.cpp new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/interface.cpp @@ -0,0 +1,431 @@ +/* + Copyright (c) 2016 Sandro Knauß + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "interface.h" +#include "interface_p.h" + +#include "stringhtmlwriter.h" +#include "objecttreesource.h" + +#include +#include +#include +#include + +#include + +class PartPrivate +{ +public: + PartPrivate(Part *part); + void appendSubPart(Part::Ptr subpart); + + QVector subParts(); + + Part *parent() const; +private: + Part *q; + Part *mParent; + QVector mSubParts; +}; + +PartPrivate::PartPrivate(Part* part) + : q(part) + , mParent(Q_NULLPTR) +{ + +} + +void PartPrivate::appendSubPart(Part::Ptr subpart) +{ + subpart->d->mParent = q; + mSubParts.append(subpart); +} + +Part *PartPrivate::parent() const +{ + return mParent; +} + +QVector< Part::Ptr > PartPrivate::subParts() +{ + return mSubParts; +} + +Part::Part() + : d(std::unique_ptr(new PartPrivate(this))) +{ + +} + +bool Part::hasSubParts() const +{ + return !subParts().isEmpty(); +} + +QVector Part::subParts() const +{ + return d->subParts(); +} + +QByteArray Part::type() const +{ + return "Part"; +} + +QVector Part::availableContents() const +{ + return QVector(); +} + +QVector Part::content() const +{ + return QVector(); +} + +QVector Part::encryptions() const +{ + auto parent = d->parent(); + if (parent) { + return parent->encryptions(); + } else { + return QVector(); + } +} + +QVector Part::signatures() const +{ + auto parent = d->parent(); + if (parent) { + return parent->signatures(); + } else { + return QVector(); + } +} + +class ContentPrivate +{ +public: + QByteArray mContent; + QByteArray mCodec; + Part *mParent; + Content *q; +}; + +Content::Content(const QByteArray& content, Part *parent) + : d(std::unique_ptr(new ContentPrivate)) +{ + d->q = this; + d->mContent = content; + d->mCodec = "utf-8"; + d->mParent = parent; +} + +Content::~Content() +{ +} + +QVector Content::encryptions() const +{ + if (d->mParent) { + return d->mParent->encryptions(); + } + return QVector(); +} + +QVector Content::signatures() const +{ + if (d->mParent) { + return d->mParent->signatures(); + } + return QVector(); +} + +QByteArray Content::content() const +{ + return d->mContent; +} + +QByteArray Content::charset() const +{ + return d->mCodec; +} + +HtmlContent::HtmlContent(const QByteArray& content, Part* parent) + : Content(content, parent) +{ + +} + +PlainTextContent::PlainTextContent(const QByteArray& content, Part* parent) + : Content(content, parent) +{ + +} + + +class AlternativePartPrivate +{ +public: + void fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part); + + QVector content(const QByteArray &ct) const; + + AlternativePart *q; + + QVector types() const; + +private: + QMap> mContent; + QVector mTypes; +}; + +void AlternativePartPrivate::fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part) +{ + mTypes = QVector() << "html" << "plaintext"; + + Content::Ptr content = std::make_shared(part->htmlContent().toLocal8Bit(), q); + mContent["html"].append(content); + content = std::make_shared(part->plaintextContent().toLocal8Bit(), q); + mContent["plaintext"].append(content); +} + +QVector AlternativePartPrivate::types() const +{ + return mTypes; +} + +QVector AlternativePartPrivate::content(const QByteArray& ct) const +{ + return mContent[ct]; +} + +AlternativePart::AlternativePart() + : d(std::unique_ptr(new AlternativePartPrivate)) +{ + d->q = this; +} + +AlternativePart::~AlternativePart() +{ + +} + +QByteArray AlternativePart::type() const +{ + return "AlternativePart"; +} + +QVector AlternativePart::availableContents() const +{ + return d->types(); +} + +QVector AlternativePart::content(const QByteArray& ct) const +{ + return d->content(ct); +} + +class SinglePartPrivate +{ +public: + void fillFrom(MimeTreeParser::TextMessagePart::Ptr part); + void fillFrom(MimeTreeParser::HtmlMessagePart::Ptr part); + void fillFrom(MimeTreeParser::AttachmentMessagePart::Ptr part); + SinglePart *q; + + QVector mContent; + QByteArray mType; +}; + +void SinglePartPrivate::fillFrom(MimeTreeParser::TextMessagePart::Ptr part) +{ + mType = "plaintext"; + mContent.clear(); + foreach (const auto &mp, part->subParts()) { + mContent.append(std::make_shared(mp->text().toLocal8Bit(), q)); + } +} + +void SinglePartPrivate::fillFrom(MimeTreeParser::HtmlMessagePart::Ptr part) +{ + mType = "html"; + mContent.clear(); + mContent.append(std::make_shared(part->text().toLocal8Bit(), q)); +} + +void SinglePartPrivate::fillFrom(MimeTreeParser::AttachmentMessagePart::Ptr part) +{ + +} + +SinglePart::SinglePart() + : d(std::unique_ptr(new SinglePartPrivate)) +{ + d->q = this; +} + +SinglePart::~SinglePart() +{ + +} + +QVector SinglePart::availableContents() const +{ + return QVector() << d->mType; +} + +QVector< Content::Ptr > SinglePart::content(const QByteArray &ct) const +{ + if (ct == d->mType) { + return d->mContent; + } + return QVector(); +} + +QByteArray SinglePart::type() const +{ + return "SinglePart"; +} + +ParserPrivate::ParserPrivate(Parser* parser) + : q(parser) + , mNodeHelper(std::make_shared()) +{ + +} + +void ParserPrivate::setMessage(const QByteArray& mimeMessage) +{ + const auto mailData = KMime::CRLFtoLF(mimeMessage); + KMime::Message::Ptr msg(new KMime::Message); + msg->setContent(mailData); + msg->parse(); + + // render the mail + StringHtmlWriter htmlWriter; + ObjectTreeSource source(&htmlWriter); + MimeTreeParser::ObjectTreeParser otp(&source, mNodeHelper.get()); + + otp.parseObjectTree(msg.data()); + mPartTree = otp.parsedPart().dynamicCast(); + + mEmbeddedPartMap = htmlWriter.embeddedParts(); + mHtml = htmlWriter.html(); + + mTree = std::make_shared(); + createTree(mPartTree, mTree); +} + + +void ParserPrivate::createTree(const MimeTreeParser::MessagePart::Ptr &start, const Part::Ptr &tree) +{ + foreach (const auto &mp, start->subParts()) { + const auto m = mp.dynamicCast(); + const auto text = mp.dynamicCast(); + const auto alternative = mp.dynamicCast(); + const auto html = mp.dynamicCast(); + const auto attachment = mp.dynamicCast(); + if (attachment) { + auto part = std::make_shared(); + part->d->fillFrom(attachment); + mTree->d->appendSubPart(part); + } else if (text) { + auto part = std::make_shared(); + part->d->fillFrom(text); + mTree->d->appendSubPart(part); + } else if (alternative) { + auto part = std::make_shared(); + part->d->fillFrom(alternative); + mTree->d->appendSubPart(part); + } else if (html) { + auto part = std::make_shared(); + part->d->fillFrom(html); + mTree->d->appendSubPart(part); + } else { + createTree(m, tree); + } + } +} + +Parser::Parser(const QByteArray& mimeMessage) + :d(std::unique_ptr(new ParserPrivate(this))) +{ + d->setMessage(mimeMessage); +} + +Parser::~Parser() +{ +} + +QVector Parser::collectContentParts() const +{ + return collect(d->mTree, [](const Part::Ptr &p){return p->type() != "EncapsulatedPart";}, + [](const Content::Ptr &content){ + const auto mime = content->mailMime(); + + if (!mime) { + return true; + } + + if (mime->isFirstTextPart()) { + return true; + } + const auto cd = mime->disposition(); + if (cd && cd == MailMime::Inline) { + // explict "inline" disposition: + return true; + } + if (cd && cd == MailMime::Attachment) { + // explicit "attachment" disposition: + return false; + } + + const auto ct = mime->mimetype(); + if (ct.name().trimmed().toLower() == "text" && ct.name().trimmed().isEmpty() && + (!mime || mime->filename().trimmed().isEmpty())) { + // text/* w/o filename parameter: + return true; + } + return false; + }); +} + +QVector Parser::collect(const Part::Ptr &start, std::function select, std::function filter) const +{ + QVector ret; + foreach (const auto &part, start->subParts()) { + QVector contents; + foreach(const auto &ct, part->availableContents()) { + foreach(const auto &content, part->content(ct)) { + if (filter(content)) { + contents.append(ct); + break; + } + } + } + if (!contents.isEmpty()) { + ret.append(part); + } + if (select(part)){ + ret += collect(part, select, filter); + } + } + return ret; +} \ No newline at end of file diff --git a/framework/domain/mimetreeparser/interface_p.h b/framework/domain/mimetreeparser/interface_p.h new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/interface_p.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2016 Sandro Knauß + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#pragma once + +#include "interface.h" + +#include +#include + +namespace MimeTreeParser +{ + class MessagePart; + class NodeHelper; + typedef QSharedPointer MessagePartPtr; +} + +class ParserPrivate +{ +public: + ParserPrivate(Parser *parser); + + void setMessage(const QByteArray &mimeMessage); + void createTree(const MimeTreeParser::MessagePartPtr& start, const Part::Ptr& tree); + + Part::Ptr mTree; +private: + Parser *q; + + MimeTreeParser::MessagePartPtr mPartTree; + std::shared_ptr mNodeHelper; + QString mHtml; + QMap mEmbeddedPartMap; +}; \ No newline at end of file diff --git a/framework/domain/mimetreeparser/objecttreesource.h b/framework/domain/mimetreeparser/objecttreesource.h new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/objecttreesource.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + + 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 2 of the License, 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. +*/ + +#ifndef MAILVIEWER_OBJECTTREEEMPTYSOURCE_H +#define MAILVIEWER_OBJECTTREEEMPTYSOURCE_H + +#include + +class QString; + +class ObjectSourcePrivate; +class ObjectTreeSource : public MimeTreeParser::Interface::ObjectTreeSource +{ +public: + ObjectTreeSource(MimeTreeParser::HtmlWriter *writer); + virtual ~ObjectTreeSource(); + void setHtmlLoadExternal(bool loadExternal); + void setHtmlMail(bool htmlMail); + bool htmlMail() const Q_DECL_OVERRIDE; + bool decryptMessage() const Q_DECL_OVERRIDE; + bool htmlLoadExternal() const Q_DECL_OVERRIDE; + bool showSignatureDetails() const Q_DECL_OVERRIDE; + void setHtmlMode(MimeTreeParser::Util::HtmlMode mode) Q_DECL_OVERRIDE; + void setAllowDecryption(bool allowDecryption); + int levelQuote() const Q_DECL_OVERRIDE; + const QTextCodec *overrideCodec() Q_DECL_OVERRIDE; + QString createMessageHeader(KMime::Message *message) Q_DECL_OVERRIDE; + const MimeTreeParser::AttachmentStrategy *attachmentStrategy() Q_DECL_OVERRIDE; + MimeTreeParser::HtmlWriter *htmlWriter() Q_DECL_OVERRIDE; + QObject *sourceObject() Q_DECL_OVERRIDE; + bool autoImportKeys() const Q_DECL_OVERRIDE; + bool showEmoticons() const Q_DECL_OVERRIDE; + bool showExpandQuotesMark() const Q_DECL_OVERRIDE; + const MimeTreeParser::BodyPartFormatterBaseFactory *bodyPartFormatterFactory() Q_DECL_OVERRIDE; + MimeTreeParser::Interface::MessagePartRendererPtr messagePartTheme(MimeTreeParser::Interface::MessagePartPtr msgPart) Q_DECL_OVERRIDE; +private: + ObjectSourcePrivate *const d; +}; + +#endif + diff --git a/framework/domain/mimetreeparser/objecttreesource.cpp b/framework/domain/mimetreeparser/objecttreesource.cpp new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/objecttreesource.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net + Copyright (c) 2009 Andras Mantia + + 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 2 of the License, 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 "objecttreesource.h" + +#include +#include +#include +#include + +class ObjectSourcePrivate +{ +public: + ObjectSourcePrivate() + : mWriter(0) + , mAllowDecryption(true) + , mHtmlLoadExternal(true) + , mHtmlMail(true) + { + + } + MimeTreeParser::HtmlWriter *mWriter; + MimeTreeParser::BodyPartFormatterBaseFactory mBodyPartFormatterBaseFactory; + bool mAllowDecryption; + bool mHtmlLoadExternal; + bool mHtmlMail; +}; + +ObjectTreeSource::ObjectTreeSource(MimeTreeParser::HtmlWriter *writer) + : MimeTreeParser::Interface::ObjectTreeSource() + , d(new ObjectSourcePrivate) + { + d->mWriter = writer; + } + +ObjectTreeSource::~ObjectTreeSource() +{ + delete d; +} + +void ObjectTreeSource::setAllowDecryption(bool allowDecryption) +{ + d->mAllowDecryption = allowDecryption; +} + +MimeTreeParser::HtmlWriter *ObjectTreeSource::htmlWriter() +{ + return d->mWriter; +} + +bool ObjectTreeSource::htmlLoadExternal() const +{ + return d->mHtmlLoadExternal; +} + +void ObjectTreeSource::setHtmlLoadExternal(bool loadExternal) +{ + d->mHtmlLoadExternal = loadExternal; +} + +bool ObjectTreeSource::htmlMail() const +{ + return d->mHtmlMail; +} + +void ObjectTreeSource::setHtmlMail(bool htmlMail) +{ + d->mHtmlMail = htmlMail; +} + +bool ObjectTreeSource::decryptMessage() const +{ + return d->mAllowDecryption; +} + +bool ObjectTreeSource::showSignatureDetails() const +{ + return true; +} + +int ObjectTreeSource::levelQuote() const +{ + return 1; +} + +const QTextCodec *ObjectTreeSource::overrideCodec() +{ + return Q_NULLPTR; +} + +QString ObjectTreeSource::createMessageHeader(KMime::Message *message) +{ + return QString(); +} + +const MimeTreeParser::AttachmentStrategy *ObjectTreeSource::attachmentStrategy() +{ + return MimeTreeParser::AttachmentStrategy::smart(); +} + +QObject *ObjectTreeSource::sourceObject() +{ + return Q_NULLPTR; +} + +void ObjectTreeSource::setHtmlMode(MimeTreeParser::Util::HtmlMode mode) +{ + Q_UNUSED(mode); +} + +bool ObjectTreeSource::autoImportKeys() const +{ + return false; +} + +bool ObjectTreeSource::showEmoticons() const +{ + return false; +} + +bool ObjectTreeSource::showExpandQuotesMark() const +{ + return false; +} + +const MimeTreeParser::BodyPartFormatterBaseFactory *ObjectTreeSource::bodyPartFormatterFactory() +{ + return &(d->mBodyPartFormatterBaseFactory); +} + +MimeTreeParser::Interface::MessagePartRenderer::Ptr ObjectTreeSource::messagePartTheme(MimeTreeParser::Interface::MessagePart::Ptr msgPart) +{ + Q_UNUSED(msgPart); + return MimeTreeParser::Interface::MessagePartRenderer::Ptr(); +} diff --git a/framework/domain/mimetreeparser/stringhtmlwriter.h b/framework/domain/mimetreeparser/stringhtmlwriter.h new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/stringhtmlwriter.h @@ -0,0 +1,71 @@ +/* -*- c++ -*- + + Copyright (c) 2016 Sandro Knauß + + Kube is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + Kube 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 + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ +#define __KUBE_FRAMEWORK_MAIL_STRINGHTMLWRITER_H__ + +#include + +#include +#include + +class QString; + +class StringHtmlWriter : public MimeTreeParser::HtmlWriter +{ +public: + explicit StringHtmlWriter(); + virtual ~StringHtmlWriter(); + + void begin(const QString &cssDefs) Q_DECL_OVERRIDE; + void end() Q_DECL_OVERRIDE; + void reset() Q_DECL_OVERRIDE; + void write(const QString &str) Q_DECL_OVERRIDE; + void queue(const QString &str) Q_DECL_OVERRIDE; + void flush() Q_DECL_OVERRIDE; + void embedPart(const QByteArray &contentId, const QString &url) Q_DECL_OVERRIDE; + void extraHead(const QString &str) Q_DECL_OVERRIDE; + + QString html() const; + QMap embeddedParts() const; +private: + void insertExtraHead(); + void resolveCidUrls(); + + QString mHtml; + QString mExtraHead; + enum State { + Begun, + Queued, + Ended + } mState; + QMap mEmbeddedPartMap; +}; + +#endif // __MESSAGEVIEWER_FILEHTMLWRITER_H__ diff --git a/framework/domain/mimetreeparser/stringhtmlwriter.cpp b/framework/domain/mimetreeparser/stringhtmlwriter.cpp new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/stringhtmlwriter.cpp @@ -0,0 +1,150 @@ +/* -*- c++ -*- + filehtmlwriter.cpp + + This file is part of KMail, the KDE mail client. + Copyright (c) 2003 Marc Mutz + + KMail is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMail 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 + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "stringhtmlwriter.h" + +#include +#include +#include + +StringHtmlWriter::StringHtmlWriter() + : MimeTreeParser::HtmlWriter() + , mState(Ended) +{ +} + +StringHtmlWriter::~StringHtmlWriter() +{ +} + +void StringHtmlWriter::begin(const QString &css) +{ + if (mState != Ended) { + qWarning() << "begin() called on non-ended session!"; + reset(); + } + + mState = Begun; + mExtraHead.clear(); + mHtml.clear(); + + if (!css.isEmpty()) { + write(QLatin1String("\n")); + } +} + +void StringHtmlWriter::end() +{ + if (mState != Begun) { + qWarning() << "Called on non-begun or queued session!"; + } + + if (!mExtraHead.isEmpty()) { + insertExtraHead(); + mExtraHead.clear(); + } + resolveCidUrls(); + mState = Ended; +} + +void StringHtmlWriter::reset() +{ + if (mState != Ended) { + mHtml.clear(); + mExtraHead.clear(); + mState = Begun; // don't run into end()'s warning + end(); + mState = Ended; + } +} + +void StringHtmlWriter::write(const QString &str) +{ + if (mState != Begun) { + qWarning() << "Called in Ended or Queued state!"; + } + mHtml.append(str); +} + +void StringHtmlWriter::queue(const QString &str) +{ + write(str); +} + +void StringHtmlWriter::flush() +{ + mState = Begun; // don't run into end()'s warning + end(); +} + +void StringHtmlWriter::embedPart(const QByteArray &contentId, const QString &url) +{ + write("\n"); + mEmbeddedPartMap.insert(contentId, url); +} + +void StringHtmlWriter::resolveCidUrls() +{ + for (const auto &cid : mEmbeddedPartMap.keys()) { + mHtml.replace(QString("src=\"cid:%1\"").arg(QString(cid)), QString("src=\"%1\"").arg(mEmbeddedPartMap.value(cid).toString())); + } +} + +void StringHtmlWriter::extraHead(const QString &extraHead) +{ + if (mState != Ended) { + qWarning() << "Called on non-started session!"; + } + mExtraHead.append(extraHead); +} + + +void StringHtmlWriter::insertExtraHead() +{ + const QString headTag(QStringLiteral("")); + const int index = mHtml.indexOf(headTag); + if (index != -1) { + mHtml.insert(index + headTag.length(), mExtraHead); + } +} + +QMap StringHtmlWriter::embeddedParts() const +{ + return mEmbeddedPartMap; +} + +QString StringHtmlWriter::html() const +{ + if (mState != Ended) { + qWarning() << "Called on non-ended session!"; + } + return mHtml; +} diff --git a/framework/domain/mimetreeparser/tests/CMakeLists.txt b/framework/domain/mimetreeparser/tests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ) + +add_executable(mimetreeparsertest interfacetest.cpp) +add_test(mimetreeparsertest mimetreeparsertest) +qt5_use_modules(mimetreeparsertest Core Test) +target_link_libraries(mimetreeparsertest mimetreeparser) \ No newline at end of file diff --git a/framework/domain/mimetreeparser/tests/data/alternative.mbox b/framework/domain/mimetreeparser/tests/data/alternative.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/alternative.mbox @@ -0,0 +1,28 @@ +Return-Path: +Date: Wed, 8 Jun 2016 20:34:44 -0700 +From: Konqi +To: konqi@kde.org +Subject: A random subject with alternative contenttype +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_12345678_12345678" + + +------=_Part_12345678_12345678 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +If you can see this text it means that your email client couldn't display o= +ur newsletter properly. +Please visit this link to view the newsletter on our website: http://www.go= +g.com/newsletter/ + + +------=_Part_12345678_12345678 +Content-Transfer-Encoding: 7Bit +Content-Type: text/html; charset="windows-1252" + +

HTML text

+ + +------=_Part_12345678_12345678-- diff --git a/framework/domain/mimetreeparser/tests/data/html.mbox b/framework/domain/mimetreeparser/tests/data/html.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/html.mbox @@ -0,0 +1,15 @@ +From foo@example.com Thu, 26 May 2011 01:16:54 +0100 +From: Thomas McGuire +Subject: HTML test +Date: Thu, 26 May 2011 01:16:54 +0100 +Message-ID: <1501334.pROlBb7MZF@herrwackelpudding.localhost> +X-KMail-Transport: GMX +X-KMail-Fcc: 28 +X-KMail-Drafts: 7 +X-KMail-Templates: 9 +User-Agent: KMail/4.6 beta5 (Linux/2.6.34.7-0.7-desktop; KDE/4.6.41; x86_64; git-0269848; 2011-04-19) +MIME-Version: 1.0 +Content-Transfer-Encoding: 7Bit +Content-Type: text/html; charset="windows-1252" + +

HTML text

\ No newline at end of file diff --git a/framework/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox b/framework/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox @@ -0,0 +1,115 @@ +From test@kolab.org Fri May 01 15:12:47 2015 +From: testkey +To: you@you.com +Subject: enc & non enc attachment +Date: Fri, 01 May 2015 17:12:47 +0200 +Message-ID: <13897561.XENKdJMSlR@tabin.local> +X-KMail-Identity: 1197256126 +User-Agent: KMail/4.13.0.1 (Linux/3.19.1-towo.1-siduction-amd64; KDE/4.14.2; x86_64; git-cd33034; 2015-04-11) +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="nextPart1939768.sIoLGH0PD8" +Content-Transfer-Encoding: 7Bit + +This is a multi-part message in MIME format. + +--nextPart1939768.sIoLGH0PD8 +Content-Type: multipart/encrypted; boundary="nextPart2814166.CHKktCGlQ3"; protocol="application/pgp-encrypted" + + +--nextPart2814166.CHKktCGlQ3 +Content-Type: application/pgp-encrypted +Content-Disposition: attachment +Content-Transfer-Encoding: 7Bit + +Version: 1 +--nextPart2814166.CHKktCGlQ3 +Content-Type: application/octet-stream +Content-Disposition: inline; filename="msg.asc" +Content-Transfer-Encoding: 7Bit + +-----BEGIN PGP MESSAGE----- +Version: GnuPG v2 + +hIwDGJlthTT7oq0BA/9cXFQ6mN9Vxnc2B9M10odS3/6z1tsIY9oJdsiOjpfxqapX +P7nOzR/jNWdFQanXoG1SjAcY2FeZEN0c3SkxEM6R5QVF1vMh/Xsni1clI+peZyVT +Z4OSU74YCfYLg+cgDnPCF3kyNPVe6Z1pnfWOCZNCG3rpApw6UVLN63ScWC6eQIUB +DAMMzkNap8zaOwEIANKHn1svvj+hBOIZYf8R+q2Bw7cd4xEChiJ7uQLnD98j0Fh1 +85v7/8JbZx6rEDDenPp1mCciDodb0aCmi0XLuzJz2ANGTVflfq+ZA+v1pwLksWCs +0YcHLEjOJzjr3KKmvu6wqnun5J2yV69K3OW3qTTGhNvcYZulqQ617pPa48+sFCgh +nM8TMAD0ElVEwmMtrS3AWoJz52Af+R3YzpAnX8NzV317/JG+b6e2ksl3tR7TWp1q +2FOqC1sXAxuv+DIz4GgRfaK1+xYr2ckkg+H/3HJqa5LmJ7rGCyv+Epfp9u+OvdBG +PBvuCtO3tm0crmnttMw57Gy35BKutRf/8MpBj/nS6QFX0t7XOLeL4Me7/a2H20wz +HZsuRGDXMCh0lL0FYCBAwdbbYvvy0gz/5iaNvoADtaIu+VtbFNrTUN0SwuL+AIFS ++WIiaSbFt4Ng3t9YmqL6pqB7fjxI10S+PK0s7ABqe4pgbzUWWt1yzBcxfk8l/47Q +JrlvcE7HuDOhNOHfZIgUP2Dbeu+pVvHIJbmLsNWpl4s+nHhoxc9HrVhYG/MTZtQ3 +kkUWviegO6mwEZjQvgBxjWib7090sCxkO847b8A93mfQNHnuy2ZEEJ+9xyk7nIWs +4RsiNR8pYc/SMvdocyAvQMH/qSvmn/IFJ+jHhtT8UJlXJ0bHvXTHjHMqBp6fP69z +Jh1ERadWQdMaTkzQ+asl+kl/x3p6RZP8MEVbZIl/3pcV+xiFCYcFu2TETKMtbW+b +NYOlrltFxFDvyu3WeNNp0g9k0nFpD/T1OXHRBRcbUDWE4QF6NWTm6NO9wy2UYHCi +7QTSecBWgMaw7cUdwvnW6chIVoov1pm69BI9D0PoV76zCI7KzpiDsTFxdilKwbQf +K/PDnv9Adx3ERh0/F8llBHrj2UGsRs4aHSEBDBJIHDCp8+lqtsRcINQBKEU3qIjt +wf5vizdaVIgQnsD2z8QmBQ7QCCipI0ur6GKl+YWDDOSDLDUs9dK4A6xo/4Q0bsnI +rH63ti5HslGq6uArfFkewH2MWff/8Li3uGEqzpK5NhP5UpbArelK+QaQQP5SdsmW +XFwUqDS4QTCKNJXw/5SQMl8UE10l2Xaav3TkiOYTcBcvPNDovYgnMyRff/tTeFa8 +83STkvpGtkULkCntp22fydv5rg6DZ7eJrYfC2oZXdM87hHhUALUO6Y/VtVmNdNYw +F3Uim4PDuLIKt+mFqRtFqnWm+5X/AslC31qLkjH+Fbb83TY+mC9gbIn7CZGJRCjn +zzzMX2h15V/VHzNUgx9V/h28T0/z25FxoozZiJxpmhOtqoxMHp+y6nXXfMoIAD1D +963Pc7u1HS0ny54A7bqc6KKd4W9IF7HkXn3SoBwCyn0IOPoKQTDD8mW3lbBI6+h9 +vP+MAQpfD8s+3VZ9r7OKYCVmUv47ViTRlf428Co6WT7rTHjGM09tqz826fTOXA== +=6Eu9 +-----END PGP MESSAGE----- + +--nextPart2814166.CHKktCGlQ3-- + +--nextPart1939768.sIoLGH0PD8 +Content-Disposition: attachment; filename="image.png" +Content-Transfer-Encoding: base64 +Content-Type: image/png; name="image.png" + +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 +o/0d59+Lh3Y1HHcAAAAASUVORK5CYII= + +--nextPart1939768.sIoLGH0PD8-- + diff --git a/framework/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox b/framework/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/openpgp-inline-charset-encrypted.mbox @@ -0,0 +1,40 @@ +From test@example.com Thu, 17 Oct 2013 02:13:03 +0200 +Return-Path: +Delivered-To: you@you.com +Received: from localhost (localhost [127.0.0.1]) + by test@example.com (Postfix) with ESMTP id B30D8120030 + for ; Thu, 17 Oct 2013 02:13:05 +0200 (CEST) +From: test +To: you@you.com +Subject: charset +Date: Thu, 17 Oct 2013 02:13:03 +0200 +Message-ID: <4081645.yGjUJ4o4Se@example.local> +User-Agent: KMail/4.12 pre (Linux/3.11-4.towo-siduction-amd64; KDE/4.11.2; x86_64; git-f7f14e3; 2013-10-15) +MIME-Version: 1.0 +Content-Transfer-Encoding: 7Bit +Content-Type: text/plain; charset="ISO-8859-15" + +-----BEGIN PGP MESSAGE----- +Version: GnuPG v2.0.22 (GNU/Linux) + +hIwDGJlthTT7oq0BBACbaRZudMigMTetPZNRgkfEXv4QQowR1jborw0dcgKKqMQ1 +6o67NkpxvmXKGJTfTVCLBX3nk6FKYo6NwlPCyU7X9X0DDk8hvaBdR9wGfrdm5YWX +GKOzcqJY1EypiMsspXeZvjzEW7O8I956c3vBb/2pM3xqYEK1kh8+d9bVH+cjf4UB +DAMMzkNap8zaOwEH/1rPShyYL8meJN+/GGgS8+Nf1BW5pSHdAPCg0dnX4QCLEx7u +GkBU6N4JGYayaCBofibOLacQPhYZdnR5Xb/Pvrx03GrzyzyDp0WyeI9nGNfkani7 +sCRWbzlMPsEvGEvJVnMLNRSk4xhPIWumL4APkw+Mgi6mf+Br8z0RhfnGwyMA53Mr +pG9VQKlq3v7/aaN40pMjAsxiytcHS515jXrb3Ko4pWbTlAr/eytOEfkLRJgSOpQT +BY7lWs+UQJqiG8Yn65vS9LMDNJgX9EOGx77Z4u9wvv4ZieOxzgbHGg5kYCoae7ba +hxZeNjYKscH+E6epbOxM/wlTdr4UTiiW9dMsH0zSwMUB891gToeXq+LDGEPTKVSX +tsJm4HS/kISJBwrCI4EUqWZML6xQ427NkZGmF2z/sD3kmL66GjspIKnb4zHmXacp +84n2KrI9s7p6AnKnQjsxvB/4/lpXPCIY5GH7KjySEJiMsHECzeN1dJSL6keykBsx +DtmYDA+dhZ6UWbwzx/78+mjNREhyp/UiSAmLzlJh89OH/xelAPvKcIosYwz4cY9N +wjralTmL+Y0aHKeZJOeqPLaXADcPFiZrCNPCH65Ey5GEtDpjLpEbjVbykPV9+YkK +7JKW6bwMraOl5zmAoR77PWMo3IoYb9q4GuqDr1V2ZGlb7eMH1gj1nfgfVintKC1X +3jFfy7aK6LIQDVKEwbi0SxVXTKStuliVUy5oX4woDOxmTEotJf1QlKZpn5oF20UP +tumYrp0SPoP8Bo4EVRVaLupduI5cYce1q/kFj9Iho/wk56MoG9PxMMfsH7oKg3AA +CqQ6/kM4oJNdN5xIf1EH5HeaNFkDy1jlLznnhwVAZKPo/9ffpg== +=bPqu +-----END PGP MESSAGE----- + + diff --git a/framework/domain/mimetreeparser/tests/data/plaintext.mbox b/framework/domain/mimetreeparser/tests/data/plaintext.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/plaintext.mbox @@ -0,0 +1,13 @@ +Return-Path: +Date: Wed, 8 Jun 2016 20:34:44 -0700 +From: Konqi +To: konqi@kde.org +Subject: A random subject with alternative contenttype +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +If you can see this text it means that your email client couldn't display o= +ur newsletter properly. +Please visit this link to view the newsletter on our website: http://www.go= +g.com/newsletter/ diff --git a/framework/domain/mimetreeparser/tests/data/smime-encrypted.mbox b/framework/domain/mimetreeparser/tests/data/smime-encrypted.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/smime-encrypted.mbox @@ -0,0 +1,22 @@ +From test@example.com Sat, 13 Apr 2013 01:54:30 +0200 +From: test +To: you@you.com +Subject: test +Date: Sat, 13 Apr 2013 01:54:30 +0200 +Message-ID: <1576646.QQxzHWx8dA@tabin> +X-KMail-Identity: 505942601 +User-Agent: KMail/4.10.2 (Linux/3.9.0-rc4-experimental-amd64; KDE/4.10.60; x86_64; git-fc9b82c; 2013-04-11) +MIME-Version: 1.0 +Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type="enveloped-data" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7m" + +MIAGCSqGSIb3DQEHA6CAMIACAQAxgfwwgfkCAQAwYjBVMQswCQYDVQQGEwJVUzENMAsGA1UECgwE +S0RBQjEWMBQGA1UEAwwNdW5pdHRlc3QgY2VydDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl +LmNvbQIJANNFIDoYY4XJMA0GCSqGSIb3DQEBAQUABIGAJwmmaOeidXUHSQGOf2OBIsPYafVqdORe +y54pEXbXiAfSVUWgI4a9CsiWwcDX8vlaX9ZLLr+L2VmOfr6Yc5214yxzausZVvnUFjy6LUXotuEX +tSar4EW7XI9DjaZc1l985naMsTx9JUa5GyQ9J6PGqhosAKpKMGgKkFAHaOwE1/IwgAYJKoZIhvcN +AQcBMBQGCCqGSIb3DQMHBAieDfmz3WGbN6CABHgEpsLrNn0PAZTDUfNomDypvSCl5bQH+9cKm80m +upMV2r8RBiXS7OaP4SpCxq18afDTTPatvboHIoEX92taTbq8soiAgEs6raSGtEYZNvFL0IYqm7MA +o5HCOmjiEcInyPf14lL3HnPk10FaP3hh58qTHUh4LPYtL7UECOZELYnUfUVhAAAAAAAAAAAAAA== + diff --git a/framework/domain/mimetreeparser/tests/interfacetest.cpp b/framework/domain/mimetreeparser/tests/interfacetest.cpp new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/interfacetest.cpp @@ -0,0 +1,157 @@ +/* + Copyright (c) 2016 Sandro Knauß + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library 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 Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "interface.h" +#include "interface_p.h" + +#include + +QByteArray readMailFromFile(const QString &mailFile) +{ + QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); + file.open(QIODevice::ReadOnly); + Q_ASSERT(file.isOpen()); + return file.readAll(); +} + + +class InterfaceTest : public QObject +{ + Q_OBJECT +private: + void printTree(const Part::Ptr &start, QString pre) + { + foreach (const auto &part, start->subParts()) { + qWarning() << QStringLiteral("%1* %2").arg(pre).arg(QString::fromLatin1(part->type())); + printTree(part,pre + QStringLiteral(" ")); + } + } + +private slots: + + void testTextMail() + { + Parser parser(readMailFromFile("plaintext.mbox")); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), "plaintext"); + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + QCOMPARE(contentList[0]->encryptions().size(), 0); + QCOMPARE(contentList[0]->signatures().size(), 0); + + contentList = contentPart->content("html"); + QCOMPARE(contentList.size(), 0); + } + + void testTextAlternative() + { + Parser parser(readMailFromFile("alternative.mbox")); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), QVector() << "html" << "plaintext"); + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter on our website: http://www.gog.com/newsletter/\n").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + QCOMPARE(contentList[0]->encryptions().size(), 0); + QCOMPARE(contentList[0]->signatures().size(), 0); + + contentList = contentPart->content("html"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("

HTML text

\n\n").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + QCOMPARE(contentList[0]->encryptions().size(), 0); + QCOMPARE(contentList[0]->signatures().size(), 0); + } + + void testTextHtml() + { + Parser parser(readMailFromFile("html.mbox")); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), "html"); + + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 0); + + contentList = contentPart->content("html"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("

HTML text

").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + QCOMPARE(contentList[0]->encryptions().size(), 0); + QCOMPARE(contentList[0]->signatures().size(), 0); + } + + void testSMimeEncrypted() + { + Parser parser(readMailFromFile("smime-encrypted.mbox")); + printTree(parser.d->mTree,QString()); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), "plaintext"); + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("The quick brown fox jumped over the lazy dog.").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + } + + void testOpenPGPEncryptedAttachment() + { + Parser parser(readMailFromFile("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox")); + printTree(parser.d->mTree,QString()); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), "plaintext"); + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("test text").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + } + + void testOpenPPGInline() + { + Parser parser(readMailFromFile("openpgp-inline-charset-encrypted.mbox")); + printTree(parser.d->mTree,QString()); + auto contentPartList = parser.collectContentParts(); + QCOMPARE(contentPartList.size(), 1); + auto contentPart = contentPartList[0]; + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), "plaintext"); + auto contentList = contentPart->content("plaintext"); + QCOMPARE(contentList.size(), 1); + QCOMPARE(contentList[0]->content(), QStringLiteral("asdasd asd asd asdf sadf sdaf sadf äöü").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + } +}; + +QTEST_GUILESS_MAIN(InterfaceTest) +#include "interfacetest.moc" \ No newline at end of file diff --git a/framework/domain/mimetreeparser/thoughts.txt b/framework/domain/mimetreeparser/thoughts.txt new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/thoughts.txt @@ -0,0 +1,148 @@ +Usecases: + +# plaintext msg + attachment +* ContentPart => cp1 +* AttachmentPart => ap1 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1) == collect(select=NoEncapsulatedMessages) + +(PlainText) == cp1.availableContent() + +# html msg + related attachment + normal attachment +* ContentPart => cp1 +* AttachmentPart(mimetype="*/related", cid="12345678") => ap1 +* AttachmentPart => ap2 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1, ap2) == collect(select=NoEncapsulatedMessages) +(ap2) == collect(select=NoEncapsulatedMessages, filter=filterelated) + +ap1 == getPart("cid:12345678") + +(Html) == cp1.availableContent() + +# alternative msg + attachment +* ContentPart(html=[Content("HTML"),], plaintext=[Content("Text"),]) => cp1 +* AttachmentPart => ap1 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1) == collect(select=NoEncapsulatedMessages) + +(Html, PlainText) == cp1.availableContent() +[Content("HTML"),] == cp1.content(Html) +[Content("Text"),] == cp1.content(Plaintext) + +# alternative msg with GPGInlin +* ContentPart( + plaintext=[Content("Text"), Content("foo", encryption=(enc1))], + html=[Content("HTML"),] + ) => cp1 + +(Html, PlainText) == cp1.availableContent() + +[Content("HTML"),] == cp1.content(Html) +[Content("Text"),Content("foo", encryption=(enc1))] == cp1.content(Plaintext) + + +# encrypted msg (not encrypted/error) with unencrypted attachment +* EncryptionErrorPart => cp1 +* AttachmentPart => ap1 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1) == collect(select=NoEncapsulatedMessages) + +#encrypted msg (decrypted with attachment) + unencrypted attachment +* encrytion=(rec1,rec2) => enc1 + * ContentPart(encrytion = (enc1,)) => cp1 + * AttachmentPart(encryption = (enc1,)) => ap1 +* AttachmentPart => ap2 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1, ap2) == collect(select=NoEncapsulatedMessages) + +#INLINE GPG encrypted msg + attachment +* ContentPart => cp1 with + plaintext=[Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] +* AttachmentPart => ap1 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1) == collect(select=NoEncapsulatedMessages) + +[Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] == cp1.content(Plaintext) + +#forwared encrypted msg + attachments +* ContentPart => cp1 +* EncapsulatedPart => ep1 + * Encrytion=(rec1,rec2) => enc1 + * Signature => sig1 + * ContentPart(encrytion = (enc1,), signature = (sig1,)) => cp2 + * Content(encrytion = (enc1,), signature = (sig1,)) + * Content(encrytion = (enc1, enc2(rec3,rec4),), signature = (sig1,)) + * AttachmentPart(encrytion = (enc1,), signature = (sig1,)) => ap1 +* AttachmentPart => ap2 + +(cp1) = collect(select=NoEncapsulatedMessages) +(ap2) = collect(select=NoEncapsulatedMessages) + +(cp2) = collect(ep1, select=NoEncapsulatedMessages) +(ap1) = collect(ep1, select=NoEncapsulatedMessages) + +(cp1, cp2) == collect() +(ap1, ap2) == collect()[Content, Content(encrytion = (enc1(rec1,rec2),)), Content(signed = (sig1,)), Content] + + +# plaintext msg + attachment + cert +* ContentPart => cp1 +* AttachmentPart => ap1 +* CertPart => cep1 + +(cp1) == collect(select=NoEncapsulatedMessages) +(ap1, cep1) == collect(select=NoEncapsulatedMessages) +(ap1) == collect(select=NoEncapsulatedMessages, filter=filterSubAttachmentParts) + +(cep1) == collect(select=NoEncapsulatedMessages) + + +collect function: + +bool noEncapsulatedMessages(Part part) +{ + if (is(part)) { + return false; + } + return true; +} + +bool filterRelated(T part) +{ + if (part.mimetype == related && !part.cid.isEmpty()) { + return false; //filter out related parts + } + return true; +} + +bool filterSubAttachmentParts(AttachmentPart part) +{ + if (isSubPart(part)) { + return false; // filter out CertPart f.ex. + } + return true; +} + +List collect(Part start, std::function select, std::function &)> filter) { + List col; + if (!select(start)) { + return col; + } + + if(isOrSubTypeIs(start) && filter(start.staticCast)){ + col.append(p); + } + foreach(childs as child) { + if (select(child)) { + col.expand(collect(child,select,filter); + } + } + return col; +} \ No newline at end of file