File indexing completed on 2024-11-24 04:53:15

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 #include <QNetworkRequest>
0023 #include <QStringList>
0024 #include <QDebug>
0025 
0026 #include "MsgPartNetAccessManager.h"
0027 #include "ForbiddenReply.h"
0028 #include "MsgPartNetworkReply.h"
0029 #include "Imap/Model/ItemRoles.h"
0030 #include "Imap/Model/MailboxTree.h"
0031 #include "QQuickNetworkReplyWrapper.h"
0032 
0033 namespace Imap
0034 {
0035 
0036 /** @short Classes associated with the implementation of the QNetworkAccessManager */
0037 namespace Network
0038 {
0039 
0040 MsgPartNetAccessManager::MsgPartNetAccessManager(QObject *parent):
0041     QNetworkAccessManager(parent), externalsEnabled(false)
0042 {
0043     // The "image/pjpeg" nonsense is non-standard kludge produced by Micorosft Internet Explorer
0044     // (http://msdn.microsoft.com/en-us/library/ms775147(VS.85).aspx#_replace). As of May 2011, it is not listed in
0045     // the official list of assigned MIME types (http://www.iana.org/assignments/media-types/image/index.html), but generated
0046     // by MSIE nonetheless. Users of e-mail can see it for example in messages produced by webmails which do not check the
0047     // client-provided MIME types. QWebView would (arguably correctly) refuse to display such a blob, but the damned users
0048     // typically want to see their images (I certainly do), even though they are not standards-compliant. Hence we fix the
0049     // header here.
0050     registerMimeTypeTranslation(QStringLiteral("image/pjpeg"), QStringLiteral("image/jpeg"));
0051 }
0052 
0053 void MsgPartNetAccessManager::setModelMessage(const QModelIndex &message_)
0054 {
0055     message = message_;
0056 }
0057 
0058 /** @short Prepare a network request
0059 
0060 This function handles delegating access to the other body parts using various schemes (ie. the special trojita-imap:// one used
0061 by Trojita for internal purposes and the cid: one for referencing to other body parts).  Policy checks for filtering access to
0062 the public Internet are also performed at this level.
0063 */
0064 QNetworkReply *MsgPartNetAccessManager::createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
0065 {
0066     Q_UNUSED(op);
0067     Q_UNUSED(outgoingData);
0068 
0069     if (!message.isValid()) {
0070         // Our message got removed in the meanwhile
0071         // FIXME: add a better class here
0072         return new Imap::Network::ForbiddenReply(this);
0073     }
0074 
0075     Q_ASSERT(message.isValid());
0076 
0077     if (req.url().scheme() == QLatin1String("trojita-imap") && req.url().host() == QLatin1String("msg")) {
0078         // Internal Trojita reference
0079         QModelIndex partIndex = pathToPart(message, req.url().path().toUtf8());
0080         if (partIndex.isValid()) {
0081             return new Imap::Network::MsgPartNetworkReply(this, partIndex);
0082         } else {
0083             qDebug() << "No such part:" << req.url();
0084             return new Imap::Network::ForbiddenReply(this);
0085         }
0086     } else if (req.url().scheme() == QLatin1String("cid")) {
0087         // The cid: scheme for cross-part references
0088         QByteArray cid = req.url().path().toUtf8();
0089         if (!cid.startsWith("<"))
0090             cid = QByteArray("<") + cid;
0091         if (!cid.endsWith(">"))
0092             cid += ">";
0093         QModelIndex target = cidToPart(message, cid);
0094         if (target.isValid()) {
0095             return new Imap::Network::MsgPartNetworkReply(this, target);
0096         } else {
0097             qDebug() << "Content-ID not found" << cid;
0098             return new Imap::Network::ForbiddenReply(this);
0099         }
0100     } else if (req.url() == QUrl(QStringLiteral("about:blank"))) {
0101         // about:blank is a relatively harmless URL which is used for opening an empty page
0102         return QNetworkAccessManager::createRequest(op, req, outgoingData);
0103     } else if (req.url().scheme() == QLatin1String("data")) {
0104         // data: scheme shall be safe, it's just a method of local access after all
0105         return QNetworkAccessManager::createRequest(op, req, outgoingData);
0106     } else {
0107         // Regular access -- we've got to check policy here
0108         if (req.url().scheme() == QLatin1String("http") || req.url().scheme() == QLatin1String("https")) {
0109             if (externalsEnabled) {
0110                 return QNetworkAccessManager::createRequest(op, req, outgoingData);
0111             } else {
0112                 emit requestingExternal(req.url());
0113                 return new Imap::Network::ForbiddenReply(this);
0114             }
0115         } else {
0116             qDebug() << "Forbidden per policy:" << req.url();
0117             return new Imap::Network::ForbiddenReply(this);
0118         }
0119     }
0120 }
0121 
0122 /** @short Find a message body part through its slash-separated string path */
0123 QModelIndex MsgPartNetAccessManager::pathToPart(const QModelIndex &message, const QByteArray &path)
0124 {
0125     if (!path.startsWith("/"))
0126         return QModelIndex();
0127 
0128     QModelIndex target = message;
0129     QList<QByteArray> items = path.mid(1).split('/'); // mid(1) to get rid of the leading slash
0130 
0131     for (QList<QByteArray>::const_iterator it = items.constBegin(); target.isValid() && it != items.constEnd(); ++it) {
0132         const QAbstractItemModel *model = target.model();
0133         bool ok;
0134         int offset = it->toInt(&ok);
0135         if (ok)
0136             target = model->index(offset, 0, target);
0137         else if (*it == "HEADER")
0138             target = model->index(0, Imap::Mailbox::TreeItem::OFFSET_HEADER, target);
0139         else if (*it == "TEXT")
0140             target = model->index(0, Imap::Mailbox::TreeItem::OFFSET_TEXT, target);
0141         else if (*it == "MIME")
0142             target = model->index(0, Imap::Mailbox::TreeItem::OFFSET_MIME, target);
0143         else
0144             return QModelIndex();
0145     }
0146     return target;
0147 }
0148 
0149 /** @short Convert a CID-formatted specification of a MIME part to a QModelIndex
0150 
0151 The MIME messages contain a scheme which can be used to provide a reference from one message part to another using the content id
0152 headers.  This function walks the MIME tree and tries to find a MIME part whose ID matches the requested item.
0153 */
0154 QModelIndex MsgPartNetAccessManager::cidToPart(const QModelIndex& rootIndex, const QByteArray &cid)
0155 {
0156     // A DFS search through the MIME parts tree of the current message which tries to check for a matching body part
0157     for (int i = 0; i < rootIndex.model()->rowCount(rootIndex); ++i) {
0158         QModelIndex partIndex = rootIndex.model()->index(i, 0, rootIndex);
0159         Q_ASSERT(partIndex.isValid());
0160         if (partIndex.data(Imap::Mailbox::RolePartBodyFldId).toByteArray() == cid)
0161             return partIndex;
0162         partIndex = cidToPart(partIndex, cid);
0163         if (partIndex.isValid())
0164             return partIndex;
0165     }
0166     return QModelIndex();
0167 }
0168 
0169 /** @short Enable/disable fetching of external items
0170 
0171 The external items are anything on the Internet which is outside of the scope of the current message.  At this time, we support
0172 fetching contents via HTTP and FTP protocols.
0173 */
0174 void MsgPartNetAccessManager::setExternalsEnabled(bool enabled)
0175 {
0176     externalsEnabled = enabled;
0177 }
0178 
0179 /** @short Look for registered translations of MIME types
0180 
0181 Certain renderers (the QWebView, most notably) are rather picky about the content they can render.
0182 For example, a C++ header file's MIME type inherits from text/plain, but QWebView would still treat
0183 it as a file to download. The image/pjpeg "type" is another example.
0184 
0185 This MIME type translation apparently has to happen at the QNetworkReply layer, so it makes sense to
0186 track the registered translations within the QNAM subclass.
0187 */
0188 QString MsgPartNetAccessManager::translateToSupportedMimeType(const QString &originalMimeType) const
0189 {
0190     QMap<QString, QString>::const_iterator it = m_mimeTypeFixups.constFind(originalMimeType);
0191     return it == m_mimeTypeFixups.constEnd() ? originalMimeType : *it;
0192 }
0193 
0194 /** @short Register a MIME type for an automatic translation to one that is recognized by the renderers */
0195 void MsgPartNetAccessManager::registerMimeTypeTranslation(const QString &originalMimeType, const QString &translatedMimeType)
0196 {
0197     m_mimeTypeFixups[originalMimeType] = translatedMimeType;
0198 }
0199 
0200 void MsgPartNetAccessManager::wrapQmlWebViewRequest(QObject *request, QObject *reply)
0201 {
0202     QNetworkRequest qnr(request->property("url").toUrl());
0203     QNetworkReply *qnreply = get(qnr);
0204     new QQuickNetworkReplyWrapper(reply, qnreply);
0205 }
0206 }
0207 }
0208 
0209