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,343 @@ +/* + 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 MimePart; +class MimePartPrivate; + +class ContentPart; +class ContentPartPrivate; + +class EncryptionPart; +class EncryptionPartPrivate; + +class AttachmentPart; +class AttachmentPartPrivate; + +class EncapsulatedPart; +class EncapsulatedPartPrivate; + +class CertPart; +class CertPartPrivate; + +class Content; +class ContentPrivate; + +class Key; +class Signature; +class Encryption; + +class Parser; +class ParserPrivate; + +class Part +{ +public: + typedef std::shared_ptr Ptr; + Part(); + virtual QByteArray type() const; + + bool hasSubParts() const; + QVector subParts() const; + Part *parent() const; + + virtual QVector signatures() const; + virtual QVector encryptions() const; +private: + std::unique_ptr d; + friend class ParserPrivate; + friend class PartPrivate; +}; + +class Content +{ +public: + typedef std::shared_ptr Ptr; + Content(const QByteArray &content, ContentPart *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; +private: + std::unique_ptr d; +}; + +/* + * A MessagePart that is based on a KMime::Content + */ +class MimePart : public Part +{ +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; + + // we wanna overrwrite the charset of the content, because some clients set the charset wrong + void setCharset(QByteArray charset); + + // 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; + + QByteArray type() const Q_DECL_OVERRIDE; +private: + std::unique_ptr d; +}; + +/* + * The main ContentPart + * is MimePart a good parent class? + * do we wanna need parts of the header of the connected KMime::Contents + * usecases: + * - + * for htmlonly it is representating only one MimePart (ok) + * for plaintext only also only one MimePart (ok) + * for alternative, we are represating three messageparts + * - "headers" do we return?, we can use setType to make it possible to select and than return these headers + */ +class ContentPart : public Part +{ +public: + typedef std::shared_ptr Ptr; + enum Type { + PlainText = 0x0001, + Html = 0x0002 + }; + Q_DECLARE_FLAGS(Types, Type) + + ContentPart(); + virtual ~ContentPart(); + + QVector content(Type ct) const; + + Types availableContents() const; + + QByteArray type() const Q_DECL_OVERRIDE; + +private: + std::unique_ptr d; + + friend class ParserPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ContentPart::Types); + +class AttachmentPart : public MimePart +{ +public: + typedef std::shared_ptr Ptr; + QByteArray type() const Q_DECL_OVERRIDE; + +private: + std::unique_ptr d; + friend class ParserPrivate; +}; + +/* + * Open Questions: + * - How to make the string translateable for multiple clients, so that multiple clients can show same error messages, + * that helps users to understand what is going on ? + * - Does openpgp have translations already? + */ +class EncryptionError +{ +public: + int errorId() const; + QString errorString() const; +}; + +class EncryptionPart : public MimePart +{ +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 AttachmentPart +{ +public: + typedef std::shared_ptr Ptr; + QByteArray type() const Q_DECL_OVERRIDE; + + //template QByteArray header(); +private: + std::unique_ptr d; +}; + +/* + * importing a cert GpgMe::ImportResult + * checking a cert (if it is a valid cert) + */ + +class CertPart : public AttachmentPart +{ +public: + typedef std::shared_ptr Ptr; + 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 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); + + template QVector collect(const Part::Ptr &start, std::function select, std::function filter) const; + /*{ + QVector ret; + foreach (const auto &part, start->subParts()) { + if (select(part)){ + const auto p = std::dynamic_pointer_cast(part); + if (p && filter(p)) { + ret.append(p); + } + ret += collect(part, select, filter); + } + } + return ret; + }*/ + + QVector collectAttachments(Part::Ptr start, std::function select, std::function filter) const; + ContentPart::Ptr collectContentPart(Part::Ptr start, std::function select, std::function filter) const; + ContentPart::Ptr collectContentPart(const Part::Ptr& start) const; + ContentPart::Ptr collectContentPart() 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; +}; + 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,378 @@ +/* + 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 "stringhtmlwriter.h" +#include "objecttreesource.h" + +#include +#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::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, ContentPart *parent) + : d(std::unique_ptr(new ContentPrivate)) +{ + d->q = this; + d->mContent = content; + d->mCodec = "utf-8"; + d->mParent = parent; +} + +Content::~Content() +{ +} + +QVector< Encryption > Content::encryptions() const +{ + if (d->mParent) { + return d->mParent->encryptions(); + } + return QVector(); +} + +QVector< Signature > 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; +} + +class ContentPartPrivate +{ +public: + void fillFrom(MimeTreeParser::TextMessagePart::Ptr part); + void fillFrom(MimeTreeParser::HtmlMessagePart::Ptr part); + void fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part); + + QVector content() const; + + ContentPart *q; + + ContentPart::Types types() const; + +private: + QVector mContent; + ContentPart::Types mTypes; +}; + +void ContentPartPrivate::fillFrom(MimeTreeParser::TextMessagePart::Ptr part) +{ + mTypes = ContentPart::PlainText; + foreach (const auto &mp, part->subParts()) { + auto content = std::make_shared(mp->text().toLocal8Bit(), q); + mContent.append(content); + } +} + +void ContentPartPrivate::fillFrom(MimeTreeParser::HtmlMessagePart::Ptr part) +{ + mTypes = ContentPart::Html; +} + +void ContentPartPrivate::fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part) +{ + mTypes = ContentPart::Html | ContentPart::PlainText; +} + +ContentPart::Types ContentPartPrivate::types() const +{ + return mTypes; +} + +QVector ContentPartPrivate::content() const +{ + return mContent; +} + +QVector ContentPart::content(ContentPart::Type ct) const +{ + return d->content(); +} + + +ContentPart::ContentPart() + : d(std::unique_ptr(new ContentPartPrivate)) +{ + d->q = this; +} + +ContentPart::~ContentPart() +{ + +} + +QByteArray ContentPart::type() const +{ + return "ContentPart"; +} + +ContentPart::Types ContentPart::availableContents() const +{ + return d->types(); +} + +class MimePartPrivate +{ +public: + void fillFrom(MimeTreeParser::MessagePart::Ptr part); +}; + +QByteArray MimePart::type() const +{ + return "MimePart"; +} + +class AttachmentPartPrivate +{ +public: + void fillFrom(MimeTreeParser::AttachmentMessagePart::Ptr part); +}; + +void AttachmentPartPrivate::fillFrom(MimeTreeParser::AttachmentMessagePart::Ptr part) +{ + +} + +QByteArray AttachmentPart::type() const +{ + return "AttachmentPart"; +} + +class ParserPrivate +{ +public: + ParserPrivate(Parser *parser); + + void setMessage(const QByteArray &mimeMessage); + void createTree(const MimeTreeParser::MessagePart::Ptr& start, const Part::Ptr& tree); + + Part::Ptr mTree; +private: + Parser *q; + + MimeTreeParser::MessagePart::Ptr mPartTree; + std::shared_ptr mNodeHelper; + QString mHtml; + QMap mEmbeddedPartMap; +}; + +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; + QImage paintDevice; + 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 (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 if (attachment) { + auto part = std::make_shared(); + part->d->fillFrom(attachment); + 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() +{ +} + +ContentPart::Ptr Parser::collectContentPart(const Part::Ptr &start) const +{ + const auto ret = collect(start, [](const Part::Ptr &p){return p->type() == "ContentPart";}, [](const ContentPart::Ptr &p){return true;}); + if (ret.size() > 0) { + return ret[0]; + }; + return ContentPart::Ptr(); +} + +ContentPart::Ptr Parser::collectContentPart() const +{ + return collectContentPart(d->mTree); +} + +template +QVector Parser::collect(const Part::Ptr &start, std::function select, std::function filter) const +{ + QVector ret; + foreach (const auto &part, start->subParts()) { + if (select(part)){ + const auto p = std::dynamic_pointer_cast(part); + if (p && filter(p)) { + ret.append(p); + } + ret += collect(part, select, filter); + } + } + return ret; +} 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,34 @@ +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/ + +=2D GOG.com Team + + +------=_Part_12345678_12345678 +Content-Transfer-Encoding: 7Bit +Content-Type: text/html; charset="windows-1252" + + + +

Some 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,19 @@ +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" + + + +

Some HTML text

diff --git a/framework/domain/mimetreeparser/tests/data/htmlonly.mbox b/framework/domain/mimetreeparser/tests/data/htmlonly.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/htmlonly.mbox @@ -0,0 +1,21 @@ +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-Type: text/html +Content-Transfer-Encoding: 7Bit + + + + + +SOME HTML text. + + diff --git a/framework/domain/mimetreeparser/tests/data/htmlonlyexternal.mbox b/framework/domain/mimetreeparser/tests/data/htmlonlyexternal.mbox new file mode 100644 --- /dev/null +++ b/framework/domain/mimetreeparser/tests/data/htmlonlyexternal.mbox @@ -0,0 +1,21 @@ +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-Type: text/html +Content-Transfer-Encoding: 7Bit + + + + + +SOME HTML text. + + 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,17 @@ +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/ + +=2D GOG.com Team + + 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,70 @@ +/* + 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 + +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 slots: + + void testTextMail() + { + Parser parser(readMailFromFile("plaintext.mbox")); + auto contentPart = parser.collectContentPart(); + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), ContentPart::PlainText); + auto contentList = contentPart->content(ContentPart::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\n- GOG.com Team\n\n").toLocal8Bit()); + QCOMPARE(contentList[0]->charset(), QStringLiteral("utf-8").toLocal8Bit()); + QCOMPARE(contentList[0]->encryptions().size(), 0); + QCOMPARE(contentList[0]->signatures().size(), 0); + } + + void testTextAlternative() + { + Parser parser(readMailFromFile("alternative.mbox")); + auto contentPart = parser.collectContentPart(); + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), ContentPart::PlainText | ContentPart::Html); + } + + void testTextHtml() + { + Parser parser(readMailFromFile("html.mbox")); + auto contentPart = parser.collectContentPart(); + QVERIFY((bool)contentPart); + QCOMPARE(contentPart->availableContents(), ContentPart::Html); + } +}; + +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