QByteArray ImapPartAttachmentItem::contentDispositionHeader() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (!part) return QByteArray(); return "Content-Disposition: attachment;\r\n\tfilename=\"" + part->fileName().toUtf8() + "\"\r\n"; }
void ImapPartAttachmentItem::preload() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (part) { part->fetch(model); } }
QString ImapPartAttachmentItem::tooltip() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (!part) return QString(); return MessageComposer::tr("%1, %2").arg(part->mimeType(), Imap::Mailbox::PrettySize::prettySize(part->octets())); }
QByteArray ImapPartAttachmentItem::mimeType() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (!part) return QByteArray(); return part->mimeType().toUtf8(); }
void FullMessageCombiner::load() { Imap::Mailbox::TreeItemPart *headerPart = headerPartPtr(); headerPart->fetch(const_cast<Mailbox::Model *>(m_model)); Imap::Mailbox::TreeItemPart *bodyPart = bodyPartPtr(); bodyPart->fetch(const_cast<Mailbox::Model *>(m_model)); slotDataChanged(QModelIndex(), QModelIndex()); }
QString ImapPartAttachmentItem::caption() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (part && !part->fileName().isEmpty()) { return part->fileName(); } else { return MessageComposer::tr("IMAP part %1").arg(QString::fromUtf8(imapUrl())); } }
QSharedPointer<QIODevice> ImapPartAttachmentItem::rawData() const { Imap::Mailbox::TreeItemPart *part = partPtr(); if (!part || !part->fetched()) return QSharedPointer<QIODevice>(); QSharedPointer<QIODevice> io(new QBuffer()); static_cast<QBuffer*>(io.data())->setData(*(part->dataPtr())); io->open(QIODevice::ReadOnly); return io; }
/** @short Convert a CID-formatted specification of a MIME part to a TreeItemPart* The MIME messages contain a scheme which can be used to provide a reference from one message part to another using the content id headers. This function walks the MIME tree and tries to find a MIME part whose ID matches the requested item. */ Imap::Mailbox::TreeItemPart *MsgPartNetAccessManager::cidToPart(const QByteArray &cid, Mailbox::Model *model, Mailbox::TreeItem *root) { // A DFS search through the MIME parts tree of the current message which tries to check for a matching body part for (uint i = 0; i < root->childrenCount(model); ++i) { Imap::Mailbox::TreeItemPart *part = dynamic_cast<Imap::Mailbox::TreeItemPart *>(root->child(i, model)); Q_ASSERT(part); if (part->bodyFldId() == cid) return part; part = cidToPart(cid, model, part); if (part) return part; } return 0; }
FullMessageCombiner::FullMessageCombiner(const QModelIndex &messageIndex, QObject *parent) : QObject(parent), m_model(0), m_messageIndex(messageIndex) { Imap::Mailbox::Model::realTreeItem(messageIndex, &m_model); Q_ASSERT(m_model); Imap::Mailbox::TreeItemPart *headerPart = headerPartPtr(); Imap::Mailbox::TreeItemPart *bodyPart = bodyPartPtr(); Q_ASSERT(headerPart); Q_ASSERT(bodyPart); m_headerPartIndex = headerPart->toIndex(const_cast<Mailbox::Model *>(m_model)); Q_ASSERT(m_headerPartIndex.isValid()); m_bodyPartIndex = bodyPart->toIndex(const_cast<Mailbox::Model *>(m_model)); Q_ASSERT(m_bodyPartIndex.isValid()); connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(slotDataChanged(QModelIndex,QModelIndex))); }
void FullMessageCombiner::load() { Imap::Mailbox::TreeItemPart *headerPart = headerPartPtr(); Imap::Mailbox::TreeItemPart *bodyPart = bodyPartPtr(); headerPart->fetch(const_cast<Mailbox::Model *>(m_model)); bodyPart->fetch(const_cast<Mailbox::Model *>(m_model)); if (headerPart->fetched() && bodyPart->fetched()) { emit completed(); } }
/** @short Prepare a network request This function handles delegating access to the other body parts using various schemes (ie. the special trojita-imap:// one used by Trojita for internal purposes and the cid: one for referencing to other body parts). Policy checks for filtering access to the public Internet are also performed at this level. */ QNetworkReply *MsgPartNetAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData) { Q_UNUSED(op); Q_UNUSED(outgoingData); if (!message.isValid()) { // Our message got removed in the meanwhile // FIXME: add a better class here return new Imap::Network::ForbiddenReply(this); } Q_ASSERT(message.isValid()); const Mailbox::Model *constModel = 0; Mailbox::Model::realTreeItem(message, &constModel); Q_ASSERT(constModel); Mailbox::Model *model = const_cast<Mailbox::Model *>(constModel); Q_ASSERT(model); Imap::Mailbox::TreeItemPart *part = pathToPart(message, req.url().path()); QModelIndex partIndex = part ? part->toIndex(model) : QModelIndex(); if (req.url().scheme() == QLatin1String("trojita-imap") && req.url().host() == QLatin1String("msg")) { // Internal Trojita reference if (part) { return new Imap::Network::MsgPartNetworkReply(this, partIndex); } else { qDebug() << "No such part:" << req.url(); return new Imap::Network::ForbiddenReply(this); } } else if (req.url().scheme() == QLatin1String("cid")) { // The cid: scheme for cross-part references QByteArray cid = req.url().path().toUtf8(); if (!cid.startsWith("<")) cid = QByteArray("<") + cid; if (!cid.endsWith(">")) cid += ">"; Imap::Mailbox::TreeItemPart *target = cidToPart(cid, model, model->realTreeItem(message)); if (target) { return new Imap::Network::MsgPartNetworkReply(this, target->toIndex(model)); } else { qDebug() << "Content-ID not found" << cid; return new Imap::Network::ForbiddenReply(this); } } else if (req.url() == QUrl(QLatin1String("about:blank"))) { // about:blank is a relatively harmless URL which is used for opening an empty page return QNetworkAccessManager::createRequest(op, req, outgoingData); } else if (req.url().scheme() == QLatin1String("data")) { // data: scheme shall be safe, it's just a method of local access after all return QNetworkAccessManager::createRequest(op, req, outgoingData); } else { // Regular access -- we've got to check policy here if (req.url().scheme() == QLatin1String("http") || req.url().scheme() == QLatin1String("https")) { if (externalsEnabled) { return QNetworkAccessManager::createRequest(op, req, outgoingData); } else { emit requestingExternal(req.url()); return new Imap::Network::ForbiddenReply(this); } } else { qDebug() << "Forbidden per policy:" << req.url(); return new Imap::Network::ForbiddenReply(this); } } }
bool ImapPartAttachmentItem::isAvailableLocally() const { Imap::Mailbox::TreeItemPart *part = partPtr(); return part ? part->fetched() : false; }
QWidget *PartWidgetFactory::create(const QModelIndex &partIndex, int recursionDepth, const PartLoadingMode loadingMode) { using namespace Imap::Mailbox; Q_ASSERT(partIndex.isValid()); if (recursionDepth > 1000) { return new QLabel(tr("This message contains too deep nesting of MIME message parts.\n" "To prevent stack exhaustion and your head from exploding, only\n" "the top-most thousand items or so are shown."), 0); } bool userPrefersPlaintext = QSettings().value(Common::SettingsNames::guiPreferPlaintextRendering, QVariant(true)).toBool(); QString mimeType = partIndex.data(Imap::Mailbox::RolePartMimeType).toString(); if (mimeType.startsWith(QLatin1String("multipart/"))) { // it's a compound part if (mimeType == QLatin1String("multipart/alternative")) { return new MultipartAlternativeWidget(0, this, partIndex, recursionDepth, userPrefersPlaintext ? QLatin1String("text/plain") : QLatin1String("text/html")); } else if (mimeType == QLatin1String("multipart/signed")) { return new MultipartSignedWidget(0, this, partIndex, recursionDepth); } else if (mimeType == QLatin1String("multipart/related")) { // The purpose of this section is to find a text/html e-mail, along with its associated body parts, and hide // everything else than the HTML widget. // At this point, it might be interesting to somehow respect the user's preference about using text/plain // instead of text/html. However, things are a bit complicated; the widget used at this point really wants // to either show just a single part or alternatively all of them in a sequence. // Furthermore, if someone sends a text/plain and a text/html together inside a multipart/related, they're // just wrong. // Let's see if we know what the root part is QModelIndex mainPartIndex; QVariant mainPartCID = partIndex.data(RolePartMultipartRelatedMainCid); if (mainPartCID.isValid()) { const Imap::Mailbox::Model *constModel = 0; Imap::Mailbox::TreeItemPart *part = dynamic_cast<Imap::Mailbox::TreeItemPart *>(Imap::Mailbox::Model::realTreeItem(partIndex, &constModel)); Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(constModel); Imap::Mailbox::TreeItemPart *mainPartPtr = Imap::Network::MsgPartNetAccessManager::cidToPart(mainPartCID.toByteArray(), model, part); if (mainPartPtr) { mainPartIndex = mainPartPtr->toIndex(model); } } if (!mainPartIndex.isValid()) { // The Content-Type-based start parameter was not terribly useful. Let's find the HTML part manually. QModelIndex candidate = partIndex.child(0, 0); while (candidate.isValid()) { if (candidate.data(RolePartMimeType).toString() == QLatin1String("text/html")) { mainPartIndex = candidate; break; } candidate = candidate.sibling(candidate.row() + 1, 0); } } if (mainPartIndex.isValid()) { if (mainPartIndex.data(RolePartMimeType).toString() == QLatin1String("text/html")) { return PartWidgetFactory::create(mainPartIndex, recursionDepth+1); } else { // Sorry, but anything else than text/html is by definition suspicious here. Better than picking some random // choice, let's just show everything. return new GenericMultipartWidget(0, this, partIndex, recursionDepth); } } else { // The RFC2387's wording is clear that in absence of an explicit START argument, the first part is the starting one. // On the other hand, I've seen real-world messages whose first part is some utter garbage (an image sent as // application/octet-stream, for example) and some *other* part is an HTML text. In that case (and if we somehow // failed to pick the HTML part by a heuristic), it's better to show everything. return new GenericMultipartWidget(0, this, partIndex, recursionDepth); } } else { return new GenericMultipartWidget(0, this, partIndex, recursionDepth); } } else if (mimeType == QLatin1String("message/rfc822")) { return new Message822Widget(0, this, partIndex, recursionDepth); } else { QStringList allowedMimeTypes; allowedMimeTypes << "text/html" << "text/plain" << "image/jpeg" << "image/jpg" << "image/pjpeg" << "image/png" << "image/gif"; // The problem is that some nasty MUAs (hint hint Thunderbird) would // happily attach a .tar.gz and call it "inline" bool showInline = partIndex.data(Imap::Mailbox::RolePartBodyDisposition).toByteArray().toLower() != "attachment" && allowedMimeTypes.contains(mimeType); if (showInline) { const Imap::Mailbox::Model *constModel = 0; Imap::Mailbox::TreeItemPart *part = dynamic_cast<Imap::Mailbox::TreeItemPart *>(Imap::Mailbox::Model::realTreeItem(partIndex, &constModel)); Imap::Mailbox::Model *model = const_cast<Imap::Mailbox::Model *>(constModel); Q_ASSERT(model); Q_ASSERT(part); part->fetchFromCache(model); bool showDirectly = loadingMode == LOAD_IMMEDIATELY; if (!part->fetched()) showDirectly &= model->isNetworkOnline() || part->octets() <= ExpensiveFetchThreshold; QWidget *widget = 0; if (showDirectly) { widget = new SimplePartWidget(0, manager, partIndex, m_messageView); } else if (model->isNetworkAvailable() || part->fetched()) { widget = new LoadablePartWidget(0, manager, partIndex, m_messageView, loadingMode == LOAD_ON_SHOW && part->octets() <= ExpensiveFetchThreshold ? LoadablePartWidget::LOAD_ON_SHOW : LoadablePartWidget::LOAD_ON_CLICK); } else { widget = new QLabel(tr("Offline"), 0); } return widget; } else { return new AttachmentView(0, manager, partIndex); } } QLabel *lbl = new QLabel(mimeType, 0); return lbl; }