diff --git a/src/Cryptography/MessagePart.cpp b/src/Cryptography/MessagePart.cpp index 2f35bff5..e5777848 100644 --- a/src/Cryptography/MessagePart.cpp +++ b/src/Cryptography/MessagePart.cpp @@ -1,550 +1,553 @@ /* Copyright (C) 2014 - 2015 Stephan Platz Copyright (C) 2006 - 2016 Jan Kundrát This file is part of the Trojita Qt IMAP e-mail client, http://trojita.flaska.net/ 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include #include "MessagePart.h" #include "MessageModel.h" #include "Cryptography/PartReplacer.h" #include "Common/MetaTypes.h" #include "Imap/Encoders.h" #include "Imap/Model/MailboxTree.h" #include "Imap/Model/ItemRoles.h" #include "UiUtils/Formatting.h" namespace Cryptography { #define PART_FETCH_CHILDREN(MODEL) const_cast(this)->fetchChildren(const_cast(MODEL)) MessagePart::MessagePart(MessagePart *parent, const int row) : m_childrenState(FetchingState::NONE) , m_parent(parent) , m_children() , m_row(row) { } MessagePart::~MessagePart() { } MessagePart *MessagePart::parent() const { return m_parent; } int MessagePart::row() const { return m_row; } int MessagePart::rowCount(MessageModel *model) const { PART_FETCH_CHILDREN(model); return m_children.size(); } int MessagePart::columnCount(MessageModel *model) const { PART_FETCH_CHILDREN(model); return 1; } MessagePart *MessagePart::child(MessageModel *model, int row, int column) const { PART_FETCH_CHILDREN(model); if (row == 0) { switch (column) { case Imap::Mailbox::TreeItem::OFFSET_HEADER: return m_headerPart.get(); case Imap::Mailbox::TreeItem::OFFSET_TEXT: return m_textPart.get(); case Imap::Mailbox::TreeItem::OFFSET_MIME: return m_mimePart.get(); case Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS: return m_rawPart.get(); } } if (column != 0) { return nullptr; } PART_FETCH_CHILDREN(model); auto it = m_children.find(row); return it == m_children.end() ? nullptr : it->second.get(); } void MessagePart::setSpecialParts(Ptr headerPart, Ptr textPart, Ptr mimePart, Ptr rawPart) { Q_ASSERT(!m_headerPart); Q_ASSERT(!m_textPart); Q_ASSERT(!m_mimePart); Q_ASSERT(!m_rawPart); m_headerPart = std::move(headerPart); m_textPart = std::move(textPart); m_mimePart = std::move(mimePart); m_rawPart = std::move(rawPart); } #ifdef MIME_TREE_DEBUG QDebug operator<<(QDebug dbg, const MessagePart &part) { return dbg << part.dump(0).data(); } QByteArray MessagePart::dump(const int nestingLevel) const { const QByteArray spacing(nestingLevel, ' '); QByteArray res = spacing + QByteArray::number(row()) + ": " + dumpLocalInfo() + " (0x" + QByteArray::number(reinterpret_cast(this), 16) + ", " + data(Imap::Mailbox::RolePartMimeType).toByteArray() + ", " + data(Imap::Mailbox::RolePartPathToPart).toByteArray() + ") {\n"; for (const auto &child: m_children) { if (child.second->parent() != this) { res += spacing + " v-- next child has wrong parent " "0x" + QByteArray::number(reinterpret_cast(child.second->parent()), 16) + " --v \n"; } res += child.second->dump(nestingLevel + 2); } res += spacing + "}\n"; return res; } QByteArray MessagePart::dumpLocalInfo() const { return "MessagePart"; } #endif TopLevelMessage::TopLevelMessage(const QModelIndex &messageRoot, MessageModel *model) : MessagePart(nullptr, 0) , m_root(messageRoot) , m_model(model) { Q_ASSERT(m_root.isValid()); m_model->m_map[m_root] = this; } TopLevelMessage::~TopLevelMessage() { for (auto it = m_model->m_map.begin(); it != m_model->m_map.end(); /* nothing */) { if (*it == this) { it = m_model->m_map.erase(it); } else { ++it; } } } QVariant TopLevelMessage::data(int role) const { return m_root.data(role); } void TopLevelMessage::fetchChildren(MessageModel *model) { // This is where the magic happens -- this class is just a dumb, fake node, so we're inserting something real below. // The stuff below still points to the message root, *not* to an actual message part, and everybody is happy that way. if (m_childrenState == FetchingState::NONE) { // trigger a fetch within the original model m_root.model()->rowCount(m_root); if (m_root.child(0, 0).isValid()) { // OK, we can do it synchronously. // Note that we are *not* guarding this based on a RoleIsFetched because that thing might not be true // when the rowsInserted() is called. m_children[0] = Ptr(new ProxyMessagePart(this, 0, m_root, model)); m_childrenState = FetchingState::DONE; } else { // The upstream model is loading its stuff, okay, let's do it asynchronously. m_childrenState = FetchingState::LOADING; model->m_insertRows = model->connect(model->m_message.model(), &QAbstractItemModel::rowsInserted, model, [this, model](const QModelIndex &idx) { if (idx == model->m_message) { model->disconnect(model->m_insertRows); + if (!m_root.isValid()) { + return; + } Q_ASSERT(m_root.isValid()); m_childrenState = MessagePart::FetchingState::NONE; model->beginInsertRows(QModelIndex(), 0, 0); fetchChildren(model); model->endInsertRows(); } }); } } } #ifdef MIME_TREE_DEBUG QByteArray TopLevelMessage::dumpLocalInfo() const { return "TopLevelMessage"; } #endif ProxyMessagePart::ProxyMessagePart(MessagePart *parent, const int row, const QModelIndex &sourceIndex, MessageModel *model) : MessagePart(parent, row) , m_sourceIndex(sourceIndex) , m_model(model) { m_model->m_map[sourceIndex] = this; } ProxyMessagePart::~ProxyMessagePart() { for (auto it = m_model->m_map.begin(); it != m_model->m_map.end(); /* nothing */) { if (*it == this) { it = m_model->m_map.erase(it); } else { ++it; } } } QVariant ProxyMessagePart::data(int role) const { if (auto msg = dynamic_cast(parent())) { switch (role) { case Qt::DisplayRole: return QStringLiteral("[fake message root: UID %1]").arg( msg->data(Imap::Mailbox::RoleMessageUid).toString()); } } return m_sourceIndex.data(role); } void ProxyMessagePart::fetchChildren(MessageModel *model) { QModelIndex index = parent() ? QModelIndex(m_sourceIndex) : model->message(); if (!index.isValid()) { return; } switch (m_childrenState) { case FetchingState::DONE: case FetchingState::LOADING: case FetchingState::UNAVAILABLE: return; case FetchingState::NONE: break; } auto headerIdx = index.child(0, Imap::Mailbox::TreeItem::OFFSET_HEADER); auto textIdx = index.child(0, Imap::Mailbox::TreeItem::OFFSET_TEXT); auto mimeIdx = index.child(0, Imap::Mailbox::TreeItem::OFFSET_MIME); auto rawIdx = index.child(0, Imap::Mailbox::TreeItem::OFFSET_RAW_CONTENTS); setSpecialParts( std::unique_ptr(headerIdx.isValid() ? new ProxyMessagePart(this, 0, headerIdx, model) : nullptr), std::unique_ptr(textIdx.isValid() ? new ProxyMessagePart(this, 0, textIdx, model) : nullptr), std::unique_ptr(mimeIdx.isValid() ? new ProxyMessagePart(this, 0, mimeIdx, model) : nullptr), std::unique_ptr(rawIdx.isValid() ? new ProxyMessagePart(this, 0, rawIdx, model) : nullptr) ); m_childrenState = FetchingState::LOADING; int row = 0; while (true) { auto childIndex = index.child(row, 0); if (!childIndex.isValid()) { break; } auto part = Ptr(new ProxyMessagePart(this, row, childIndex, model)); // Find out if something wants to override our choice for (const auto &module: model->m_partHandlers) { part = module->createPart(model, this, std::move(part), childIndex, model->createIndex(this->row(), 0, this)); } m_children[row] = std::move(part); ++row; } m_childrenState = FetchingState::DONE; } #ifdef MIME_TREE_DEBUG QByteArray ProxyMessagePart::dumpLocalInfo() const { return "ProxyMessagePart"; } #endif LocalMessagePart::LocalMessagePart(MessagePart *parent, const int row, const QByteArray &mimetype) : MessagePart(parent, row) , m_localState(FetchingState::NONE) , m_hdrListPostNo(false) , m_mimetype(mimetype) , m_octets(0) { } LocalMessagePart::~LocalMessagePart() { } void LocalMessagePart::fetchChildren(MessageModel *model) { return; } void LocalMessagePart::setChild(int row, Ptr part) { Q_ASSERT((int)m_children.size() >= row); Q_ASSERT(part); Q_ASSERT(row == part->row()); m_children[row] = std::move(part); } void LocalMessagePart::setData(const QByteArray &data) { m_data = data; m_localState = FetchingState::DONE; } void LocalMessagePart::setCharset(const QByteArray &charset) { m_charset = charset; } void LocalMessagePart::setContentFormat(const QByteArray &format) { m_contentFormat = format; } void LocalMessagePart::setDelSp(const QByteArray &delSp) { m_delSp = delSp; } void LocalMessagePart::setFilename(const QString &filename) { m_filename = filename; } void LocalMessagePart::setTransferEncoding(const QByteArray &transferEncoding) { m_transferEncoding = transferEncoding; } void LocalMessagePart::setBodyFldId(const QByteArray &bodyFldId) { m_bodyFldId = bodyFldId; } void LocalMessagePart::setBodyDisposition(const QByteArray &bodyDisposition) { m_bodyDisposition = bodyDisposition; } void LocalMessagePart::setMultipartRelatedStartPart(const QByteArray &startPart) { m_multipartRelatedStartPart = startPart; } void LocalMessagePart::setOctets(uint octets) { m_octets = octets; } void LocalMessagePart::setEnvelope(std::unique_ptr envelope) { m_envelope = std::move(envelope); } void LocalMessagePart::setHdrReferences(const QList &references) { m_hdrReferences = references; } void LocalMessagePart::setHdrListPost(const QList &listPost) { m_hdrListPost = listPost; } void LocalMessagePart::setHdrListPostNo(const bool listPostNo) { m_hdrListPostNo = listPostNo; } void LocalMessagePart::setBodyFldParam(const QMap &bodyFldParam) { m_bodyFldParam = bodyFldParam; } bool LocalMessagePart::isTopLevelMultipart() const { return m_mimetype.startsWith("multipart/") && (!parent()->parent() || parent()->data(Imap::Mailbox::RolePartMimeType).toByteArray().startsWith("message/")); } QByteArray LocalMessagePart::partId() const { if (isTopLevelMultipart()) return QByteArray(); QByteArray id = QByteArray::number(row() + 1); if (parent() && !parent()->data(Imap::Mailbox::RolePartId).toByteArray().isEmpty()) id = parent()->data(Imap::Mailbox::RolePartId).toByteArray() + '.' + id; return id; } QByteArray LocalMessagePart::pathToPart() const { QByteArray parentPath; if (parent()) { parentPath = parent()->data(Imap::Mailbox::RolePartPathToPart).toByteArray(); } return parentPath + '/' + QByteArray::number(row()); } QVariant LocalMessagePart::data(int role) const { // The first three roles are for debugging purposes only switch (role) { case Qt::DisplayRole: if (isTopLevelMultipart()) { return QString::fromUtf8(m_mimetype); } else { return QString(QString::fromUtf8(partId()) + QStringLiteral(": ") + QString::fromUtf8(m_mimetype)); } case Qt::ToolTipRole: if (m_mimetype == "message/rfc822") { Q_ASSERT(m_envelope); QString buf; QTextStream stream(&buf); stream << *m_envelope; return UiUtils::Formatting::htmlEscaped(buf); } else { return m_octets > 10000 ? QStringLiteral("%1 bytes of data").arg(m_octets) : QString::fromUtf8(m_data); } case Qt::FontRole: { QFont f; f.setItalic(true); return f; } case Imap::Mailbox::RoleIsFetched: return m_localState == FetchingState::DONE; case Imap::Mailbox::RoleMessageEnvelope: if (m_envelope) { return QVariant::fromValue(*m_envelope); } else { return QVariant(); } case Imap::Mailbox::RoleMessageHeaderReferences: return QVariant::fromValue(m_hdrReferences); case Imap::Mailbox::RoleMessageHeaderListPost: { QVariantList res; Q_FOREACH(const QUrl &url, m_hdrListPost) res << url; return res; } case Imap::Mailbox::RoleMessageHeaderListPostNo: return m_hdrListPostNo; case Imap::Mailbox::RoleIsUnavailable: return m_localState == FetchingState::UNAVAILABLE; case Imap::Mailbox::RolePartData: return m_data; case Imap::Mailbox::RolePartUnicodeText: if (m_mimetype.startsWith("text/")) { return Imap::decodeByteArray(m_data, m_charset); } else { return QVariant(); } case Imap::Mailbox::RolePartMimeType: return m_mimetype; case Imap::Mailbox::RolePartCharset: return m_charset; case Imap::Mailbox::RolePartContentFormat: return m_contentFormat; case Imap::Mailbox::RolePartContentDelSp: return m_delSp; case Imap::Mailbox::RolePartTransferEncoding: return m_transferEncoding; case Imap::Mailbox::RolePartBodyFldId: return m_bodyFldId; case Imap::Mailbox::RolePartBodyDisposition: return m_bodyDisposition; case Imap::Mailbox::RolePartFileName: return m_filename; case Imap::Mailbox::RolePartOctets: return m_octets; case Imap::Mailbox::RolePartId: return partId(); case Imap::Mailbox::RolePartPathToPart: return pathToPart(); case Imap::Mailbox::RolePartMultipartRelatedMainCid: if (m_multipartRelatedStartPart.isEmpty()) { return m_multipartRelatedStartPart; } else { return QVariant(); } case Imap::Mailbox::RolePartIsTopLevelMultipart: return isTopLevelMultipart(); case Imap::Mailbox::RolePartForceFetchFromCache: return QVariant(); // Nothing to do here case Imap::Mailbox::RolePartBufferPtr: return QVariant::fromValue(const_cast(&m_data)); case Imap::Mailbox::RoleMessageDate: return m_envelope ? m_envelope->date : QDateTime(); case Imap::Mailbox::RoleMessageSubject: return m_envelope ? m_envelope->subject : QString(); case Imap::Mailbox::RoleMessageFrom: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->from) : QVariant(); case Imap::Mailbox::RoleMessageSender: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->sender) : QVariant(); case Imap::Mailbox::RoleMessageReplyTo: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->replyTo) : QVariant(); case Imap::Mailbox::RoleMessageTo: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->to) : QVariant(); case Imap::Mailbox::RoleMessageCc: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->cc) : QVariant(); case Imap::Mailbox::RoleMessageBcc: return m_envelope ? Imap::Mailbox::TreeItemMessage::addresListToQVariant(m_envelope->bcc) : QVariant(); case Imap::Mailbox::RoleMessageMessageId: return m_envelope ? m_envelope->messageId : QByteArray(); case Imap::Mailbox::RoleMessageInReplyTo: return m_envelope ? QVariant::fromValue(m_envelope->inReplyTo) : QVariant(); case Imap::Mailbox::RoleMessageFlags: return QVariant(); case Imap::Mailbox::RolePartBodyFldParam: return QVariant::fromValue(m_bodyFldParam); default: break; } return QVariant(); } #ifdef MIME_TREE_DEBUG QByteArray LocalMessagePart::dumpLocalInfo() const { return "LocalMessagePart"; } #endif }