Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36623876
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
11 KB
Subscribers
None
View Options
diff --git a/src/core/attachmentmodel.cpp b/src/core/attachmentmodel.cpp
index beaf439..25ddaf9 100644
--- a/src/core/attachmentmodel.cpp
+++ b/src/core/attachmentmodel.cpp
@@ -1,279 +1,349 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyCopyright: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "attachmentmodel.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"
#include <QGpgME/ImportJob>
#include <QGpgME/Protocol>
#include <KLocalizedString>
#include <KMime/Content>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QGuiApplication>
#include <QIcon>
#include <QMimeDatabase>
#include <QMimeType>
+#include <QRegularExpression>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QUrl>
+namespace
+{
+
QString sizeHuman(float size)
{
QStringList list;
list << QStringLiteral("KB") << QStringLiteral("MB") << QStringLiteral("GB") << QStringLiteral("TB");
QStringListIterator i(list);
QString unit = QStringLiteral("Bytes");
while (size >= 1024.0 && i.hasNext()) {
unit = i.next();
size /= 1024.0;
}
if (unit == QStringLiteral("Bytes")) {
return QString().setNum(size) + QStringLiteral(" ") + unit;
} else {
return QString().setNum(size, 'f', 2) + QStringLiteral(" ") + unit;
}
}
+// SPDX-SnippetBegin
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: GPL-3.0-only
+
+#define WINDOWS_DEVICES_PATTERN "(CON|AUX|PRN|NUL|COM[1-9]|LPT[1-9])(\\..*)?"
+
+// Naming a file like a device name will break on Windows, even if it is
+// "com1.txt". Since we are cross-platform, we generally disallow such file
+// names.
+const QRegularExpression &windowsDeviceNoSubDirPattern()
+{
+ static const QRegularExpression rc(QStringLiteral("^" WINDOWS_DEVICES_PATTERN "$"), QRegularExpression::CaseInsensitiveOption);
+ Q_ASSERT(rc.isValid());
+ return rc;
+}
+
+const QRegularExpression &windowsDeviceSubDirPattern()
+{
+ static const QRegularExpression rc(QStringLiteral("^.*[/\\\\]" WINDOWS_DEVICES_PATTERN "$"), QRegularExpression::CaseInsensitiveOption);
+ Q_ASSERT(rc.isValid());
+ return rc;
+}
+
+/* Validate a file base name, check for forbidden characters/strings. */
+
+#define SLASHES "/\\"
+
+static const char notAllowedCharsSubDir[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`' ";
+static const char notAllowedCharsNoSubDir[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`' " SLASHES;
+
+static const char *notAllowedSubStrings[] = {".."};
+
+bool validateFileName(const QString &name, bool allowDirectories)
+{
+ if (name.isEmpty()) {
+ return false;
+ }
+
+ // Characters
+ const char *notAllowedChars = allowDirectories ? notAllowedCharsSubDir : notAllowedCharsNoSubDir;
+ for (const char *c = notAllowedChars; *c; c++) {
+ if (name.contains(QLatin1Char(*c))) {
+ return false;
+ }
+ }
+
+ // Substrings
+ const int notAllowedSubStringCount = sizeof(notAllowedSubStrings) / sizeof(const char *);
+ for (int s = 0; s < notAllowedSubStringCount; s++) {
+ const QLatin1String notAllowedSubString(notAllowedSubStrings[s]);
+ if (name.contains(notAllowedSubString)) {
+ return false;
+ }
+ }
+
+ // Windows devices
+ bool matchesWinDevice = name.contains(windowsDeviceNoSubDirPattern());
+ if (!matchesWinDevice && allowDirectories) {
+ matchesWinDevice = name.contains(windowsDeviceSubDirPattern());
+ }
+ return !matchesWinDevice;
+}
+// SPDX-SnippetEnd
+}
+
class AttachmentModelPrivate
{
public:
AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser);
AttachmentModel *q;
QMimeDatabase mimeDb;
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
MimeTreeParser::MessagePart::List mAttachments;
};
AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser)
: q(q_ptr)
, mParser(parser)
{
mAttachments = mParser->collectAttachmentParts();
}
AttachmentModel::AttachmentModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
: QAbstractTableModel()
, d(std::unique_ptr<AttachmentModelPrivate>(new AttachmentModelPrivate(this, parser)))
{
}
AttachmentModel::~AttachmentModel()
{
}
QHash<int, QByteArray> AttachmentModel::roleNames() const
{
return {
{TypeRole, QByteArrayLiteral("type")},
{NameRole, QByteArrayLiteral("name")},
{SizeRole, QByteArrayLiteral("size")},
{IconRole, QByteArrayLiteral("iconName")},
{IsEncryptedRole, QByteArrayLiteral("encrypted")},
{IsSignedRole, QByteArrayLiteral("signed")},
};
}
QVariant AttachmentModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case NameColumn:
return i18ndc("mimetreeparser", "@title:column", "Name");
case SizeColumn:
return i18ndc("mimetreeparser", "@title:column", "Size");
case IsEncryptedColumn:
return i18ndc("mimetreeparser", "@title:column", "Encrypted");
case IsSignedColumn:
return i18ndc("mimetreeparser", "@title:column", "Signed");
}
}
return {};
}
QVariant AttachmentModel::data(const QModelIndex &index, int role) const
{
const auto row = index.row();
const auto column = index.column();
const auto part = d->mAttachments.at(row);
Q_ASSERT(part);
auto node = part->node();
if (!node) {
qWarning() << "no content for attachment";
return {};
}
const auto mimetype = d->mimeDb.mimeTypeForName(QString::fromLatin1(part->mimeType()));
const auto content = node->encodedContent();
switch (column) {
case NameColumn:
switch (role) {
case TypeRole:
return mimetype.name();
case Qt::DisplayRole:
case NameRole:
return part->filename();
case IconRole:
return mimetype.iconName();
case Qt::DecorationRole:
return QIcon::fromTheme(mimetype.iconName());
case SizeRole:
return sizeHuman(content.size());
case IsEncryptedRole:
return part->encryptions().size() > 0;
case IsSignedRole:
return part->signatures().size() > 0;
case AttachmentPartRole:
return QVariant::fromValue(part);
default:
return {};
}
case SizeColumn:
switch (role) {
case Qt::DisplayRole:
return sizeHuman(content.size());
default:
return {};
}
case IsEncryptedColumn:
switch (role) {
case Qt::CheckStateRole:
return part->encryptions().size() > 0 ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
case IsSignedColumn:
switch (role) {
case Qt::CheckStateRole:
return part->signatures().size() > 0 ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
default:
return {};
}
}
QString AttachmentModel::saveAttachmentToPath(const int row, const QString &path, bool readonly)
{
const auto part = d->mAttachments.at(row);
return saveAttachmentToPath(part, path, readonly);
}
QString AttachmentModel::saveAttachmentToPath(const MimeTreeParser::MessagePart::Ptr &part, const QString &path, bool readonly)
{
Q_ASSERT(part);
auto node = part->node();
auto data = node->decodedContent();
// This is necessary to store messages embedded messages (EncapsulatedRfc822MessagePart)
if (data.isEmpty()) {
data = node->encodedContent();
}
if (part->isText()) {
// convert CRLF to LF before writing text attachments to disk
data = KMime::CRLFtoLF(data);
}
QFile f(path);
if (!f.open(QIODevice::ReadWrite)) {
qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to write attachment to file:" << path << " Error: " << f.errorString();
Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to save attachment."));
return {};
}
f.write(data);
if (readonly) {
// make file read-only so that nobody gets the impression that he migh edit attached files
f.setPermissions(QFileDevice::ReadUser);
}
f.close();
qCInfo(MIMETREEPARSER_CORE_LOG) << "Wrote attachment to file: " << path;
return path;
}
bool AttachmentModel::openAttachment(const int row)
{
const auto part = d->mAttachments.at(row);
return openAttachment(part);
}
bool AttachmentModel::openAttachment(const MimeTreeParser::MessagePart::Ptr &message)
{
- QTemporaryFile file;
-
- const QString fileName = message->filename();
- if (fileName.isEmpty()) {
+ const QString tempDir = QDir::tempPath() + QLatin1Char('/') + qGuiApp->applicationName();
+ QString fileName = message->filename();
+ QString errorMessage;
+ if (message->filename().isEmpty() || validateFileName(fileName, false)) {
+ QTemporaryFile file;
const auto mimetype = d->mimeDb.mimeTypeForName(QString::fromLatin1(message->mimeType()));
- file.setFileTemplate(QStringLiteral("XXXXXX.") + mimetype.preferredSuffix());
+ file.setFileTemplate(tempDir + QStringLiteral("XXXXXX.") + mimetype.preferredSuffix());
+ file.setAutoRemove(false);
+ if (!file.open()) {
+ Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to create temporary file."));
+ return false;
+ }
+ fileName = file.fileName();
} else {
- file.setFileTemplate(fileName);
- }
-
- file.setAutoRemove(false);
- if (!file.open()) {
- Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to create temporary file."));
- return false;
+ fileName = tempDir + QLatin1Char('/') + message->filename();
}
- const auto filePath = saveAttachmentToPath(message, file.fileName(), true);
+ const auto filePath = saveAttachmentToPath(message, fileName, true);
if (!QDesktopServices::openUrl(QUrl(QStringLiteral("file://") + filePath))) {
Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to open attachment."));
return false;
}
return true;
}
bool AttachmentModel::importPublicKey(const int row)
{
const auto part = d->mAttachments.at(row);
return importPublicKey(part);
}
bool AttachmentModel::importPublicKey(const MimeTreeParser::MessagePart::Ptr &part)
{
Q_ASSERT(part);
const QByteArray certData = part->node()->decodedContent();
QGpgME::ImportJob *importJob = QGpgME::openpgp()->importJob();
connect(importJob, &QGpgME::AbstractImportJob::result, this, [this](const GpgME::ImportResult &result) {
if (result.numConsidered() == 0) {
Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "No keys were found in this attachment"));
return;
} else {
QString message = i18ndcp("mimetreeparser", "@info", "one key imported", "%1 keys imported", result.numImported());
if (result.numUnchanged() != 0) {
message += QStringLiteral("\n")
+ i18ndcp("mimetreeparser", "@info", "one key was already imported", "%1 keys were already imported", result.numUnchanged());
}
Q_EMIT info(message);
}
});
GpgME::Error err = importJob->start(certData);
return !err;
}
int AttachmentModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return d->mAttachments.size();
}
return 0;
}
int AttachmentModel::columnCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return ColumnCount;
}
return 0;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 26, 7:02 PM (22 h, 2 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b1/79/d9aefa30ca28856bcd04a400996f
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment