File indexing completed on 2025-01-19 04:51:56

0001 /*
0002     Copyright (c) 2016 Sandro Knauß <knauss@kolabsys.com>
0003     Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
0004 
0005     This library is free software; you can redistribute it and/or modify it
0006     under the terms of the GNU Library General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or (at your
0008     option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful, but WITHOUT
0011     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0012     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0013     License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to the
0017     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0018     02110-1301, USA.
0019 */
0020 
0021 #include "attachmentmodel.h"
0022 
0023 #include <sink/mimetreeparser/objecttreeparser.h>
0024 #include <fabric.h>
0025 #include <mailcrypto.h>
0026 
0027 #include <QDebug>
0028 #include <KMime/Content>
0029 #include <QFile>
0030 #include <QStandardPaths>
0031 #include <QDesktopServices>
0032 #include <QDir>
0033 #include <QUrl>
0034 #include <QMimeDatabase>
0035 
0036 #include <memory>
0037 
0038 QString sizeHuman(float size)
0039 {
0040     QStringList list;
0041     list << "KB" << "MB" << "GB" << "TB";
0042 
0043     QStringListIterator i(list);
0044     QString unit("Bytes");
0045 
0046     while(size >= 1024.0 && i.hasNext())
0047      {
0048         unit = i.next();
0049         size /= 1024.0;
0050     }
0051 
0052     if (unit == "Bytes") {
0053         return QString().setNum(size) + " " + unit;
0054     } else {
0055         return QString().setNum(size,'f',2)+" "+unit;
0056     }
0057 }
0058 
0059 class AttachmentModelPrivate
0060 {
0061 public:
0062     AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser);
0063 
0064     AttachmentModel *q;
0065     std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
0066     QVector<MimeTreeParser::MessagePartPtr> mAttachments;
0067 };
0068 
0069 AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel* q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser>& parser)
0070     : q(q_ptr)
0071     , mParser(parser)
0072 {
0073     mAttachments = mParser->collectAttachmentParts();
0074 }
0075 
0076 AttachmentModel::AttachmentModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
0077     : d(std::unique_ptr<AttachmentModelPrivate>(new AttachmentModelPrivate(this, parser)))
0078 {
0079 }
0080 
0081 AttachmentModel::~AttachmentModel()
0082 {
0083 }
0084 
0085 QHash<int, QByteArray> AttachmentModel::roleNames() const
0086 {
0087     QHash<int, QByteArray> roles;
0088     roles[TypeRole] = "type";
0089     roles[NameRole] = "name";
0090     roles[SizeRole] = "size";
0091     roles[IconRole] = "iconName";
0092     roles[IsEncryptedRole] = "encrypted";
0093     roles[IsSignedRole] = "signed";
0094     return roles;
0095 }
0096 
0097 QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &) const
0098 {
0099     if (row < 0 || column != 0) {
0100         return QModelIndex();
0101     }
0102 
0103     if (row < d->mAttachments.size()) {
0104         return createIndex(row, column, d->mAttachments.at(row).data());
0105     }
0106     return QModelIndex();
0107 }
0108 
0109 QVariant AttachmentModel::data(const QModelIndex &index, int role) const
0110 {
0111     if (!index.isValid()) {
0112         switch (role) {
0113         case Qt::DisplayRole:
0114             return QString("root");
0115         }
0116         return QVariant();
0117     }
0118 
0119     if (index.internalPointer()) {
0120         const auto part = static_cast<MimeTreeParser::MessagePart*>(index.internalPointer());
0121         Q_ASSERT(part);
0122         auto node = part->node();
0123         if (!node) {
0124             qWarning() << "no content for attachment";
0125             return {};
0126         }
0127         QMimeDatabase mimeDb;
0128         const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(part->mimeType()));
0129         const auto content = node->encodedContent();
0130         switch(role) {
0131         case TypeRole:
0132             return mimetype.name();
0133         case NameRole:
0134             return part->filename();
0135         case IconRole:
0136             return mimetype.iconName();
0137         case SizeRole:
0138             return sizeHuman(content.size());
0139         case IsEncryptedRole:
0140             return part->encryptions().size() > 0;
0141         case IsSignedRole:
0142             return part->signatures().size() > 0;
0143         }
0144     }
0145     return QVariant();
0146 }
0147 
0148 static QString saveAttachmentToDisk(const QModelIndex &index, const QString &path, bool readonly = false)
0149 {
0150     if (index.internalPointer()) {
0151         const auto part = static_cast<MimeTreeParser::MessagePart*>(index.internalPointer());
0152         Q_ASSERT(part);
0153         auto node = part->node();
0154         auto data = node->decodedContent();
0155         //This is necessary to store messages embedded messages (EncapsulatedRfc822MessagePart)
0156         if (data.isEmpty()) {
0157             data = node->encodedContent();
0158         }
0159         if (part->isText()) {
0160             // convert CRLF to LF before writing text attachments to disk
0161             data = KMime::CRLFtoLF(data);
0162         }
0163         const auto name = part->filename();
0164         auto fname = path + name;
0165 
0166         //Fallback name should we end up with an empty name
0167         if (name.isEmpty()) {
0168             fname = path + "unnamed";
0169             while (QFileInfo{fname}.exists()) {
0170                 fname = fname + "_1";
0171             }
0172         }
0173 
0174         //A file with that name already exists, we assume it's the right file
0175         if (QFileInfo{fname}.exists()) {
0176             return fname;
0177         }
0178         QFile f(fname);
0179         if (!f.open(QIODevice::ReadWrite)) {
0180             qWarning() << "Failed to write attachment to file:" << fname << " Error: " << f.errorString();
0181             Kube::Fabric::Fabric{}.postMessage("notification", {{"message", QObject::tr("Failed to save attachment.")}});
0182             return {};
0183         }
0184         f.write(data);
0185         if (readonly) {
0186             // make file read-only so that nobody gets the impression that he migh edit attached files
0187             f.setPermissions(QFileDevice::ReadUser);
0188         }
0189         f.close();
0190         qInfo() << "Wrote attachment to file: " << fname;
0191         return fname;
0192     }
0193     return {};
0194 
0195 }
0196 
0197 bool AttachmentModel::saveAttachmentToDisk(const QModelIndex &index)
0198 {
0199     auto downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
0200     if (downloadDir.isEmpty()) {
0201         downloadDir = "~";
0202     }
0203     downloadDir += "/kube/";
0204     QDir{}.mkpath(downloadDir);
0205 
0206     auto path = ::saveAttachmentToDisk(index, downloadDir);
0207     if (path.isEmpty()) {
0208         return false;
0209     }
0210     Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Saved the attachment to disk: %1").arg(path)}});
0211     return true;
0212 }
0213 
0214 bool AttachmentModel::openAttachment(const QModelIndex &index)
0215 {
0216     auto downloadDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation)+ "/kube/";
0217     QDir{}.mkpath(downloadDir);
0218     const auto filePath = ::saveAttachmentToDisk(index, downloadDir, true);
0219     if (!filePath.isEmpty()) {
0220         if (!QDesktopServices::openUrl(QUrl("file://" + filePath))) {
0221             Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Failed to open attachment.")}});
0222             return false;
0223         }
0224         return true;
0225     }
0226     Kube::Fabric::Fabric{}.postMessage("notification", {{"message", tr("Failed to save attachment for opening.")}});
0227     return false;
0228 }
0229 
0230 bool AttachmentModel::importPublicKey(const QModelIndex &index)
0231 {
0232     Q_ASSERT(index.internalPointer());
0233     const auto part = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer());
0234     Q_ASSERT(part);
0235     auto result = Crypto::importKey(Crypto::OpenPGP, part->node()->decodedContent());
0236 
0237     bool success = true;
0238     QString message;
0239     if(result.considered == 0) {
0240         message = tr("No keys were found in this attachment");
0241         success = false;
0242     } else {
0243         //~ singular %n key imported
0244         //~ plural %n keys imported
0245         message = tr("%n key(s) imported", "", result.imported);
0246         if(result.unchanged != 0) {
0247             //~ singular %n key was already imported
0248             //~ plural %n keys were already imported
0249             message += "\n" + tr("%n key(s) were already imported", "", result.unchanged);
0250         }
0251     }
0252 
0253     Kube::Fabric::Fabric{}.postMessage("notification",
0254         {{"message", message}});
0255 
0256     return success;
0257 }
0258 
0259 QModelIndex AttachmentModel::parent(const QModelIndex &) const
0260 {
0261     return QModelIndex();
0262 }
0263 
0264 int AttachmentModel::rowCount(const QModelIndex &parent) const
0265 {
0266     if (!parent.isValid()) {
0267         return d->mAttachments.size();
0268     }
0269     return 0;
0270 }
0271 
0272 int AttachmentModel::columnCount(const QModelIndex &) const
0273 {
0274     return 1;
0275 }