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 }