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