Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34572449
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
13 KB
Subscribers
None
View Options
diff --git a/src/core/attachmentmodel.cpp b/src/core/attachmentmodel.cpp
index e2298f5..6cc61bd 100644
--- a/src/core/attachmentmodel.cpp
+++ b/src/core/attachmentmodel.cpp
@@ -1,416 +1,416 @@
// 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 <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 <QTemporaryDir>
#include <QUrl>
#ifdef Q_OS_WIN
#include <cstdio>
#include <string>
#include <vector>
#include <windows.h>
#endif
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 QLatin1StringView 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
}
#ifdef Q_OS_WIN
struct WindowFile {
std::wstring fileName;
std::wstring dirName;
HANDLE handle;
};
#endif
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;
#ifdef Q_OS_WIN
std::vector<WindowFile> mOpenFiles;
#endif
};
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()
{
#ifdef Q_OS_WIN
for (const auto &file : d->mOpenFiles) {
// As owner of the file we need to close our handle first
// With FILE_SHARE_DELETE we have ensured that all _other_ processes must
// have opened the file with FILE_SHARE_DELETE, too.
if (!CloseHandle(file.handle)) {
// Always get the last error before calling any Qt functions that may
// use Windows system calls.
DWORD err = GetLastError();
qWarning() << "Unable to close handle for file" << QString::fromStdWString(file.fileName) << err;
}
if (!DeleteFileW(file.fileName.c_str())) {
DWORD err = GetLastError();
qWarning() << "Unable to delete file" << QString::fromStdWString(file.fileName) << err;
}
if (!RemoveDirectoryW(file.dirName.c_str())) {
DWORD err = GetLastError();
qWarning() << "Unable to delete temporary directory" << QString::fromStdWString(file.dirName) << err;
}
}
#endif
}
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)
{
const auto part = d->mAttachments.at(row);
return saveAttachmentToPath(part, path);
}
QString AttachmentModel::saveAttachmentToPath(const MimeTreeParser::MessagePart::Ptr &part, const QString &path)
{
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)) {
+ if (!f.open(QIODevice::WriteOnly)) {
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);
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)
{
QString fileName = message->filename();
QTemporaryDir tempDir(QDir::tempPath() + QLatin1Char('/') + qGuiApp->applicationName() + QStringLiteral(".XXXXXX"));
// TODO: We need some cleanup here. Otherwise the files will stay forever on Windows.
tempDir.setAutoRemove(false);
if (message->filename().isEmpty() || !validateFileName(fileName, false)) {
const auto mimetype = d->mimeDb.mimeTypeForName(QString::fromLatin1(message->mimeType()));
fileName = tempDir.filePath(i18n("attachment") + QLatin1Char('.') + mimetype.preferredSuffix());
} else {
fileName = tempDir.filePath(message->filename());
}
const auto filePath = saveAttachmentToPath(message, fileName);
if (filePath.isEmpty()) {
Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to write attachment for opening."));
return false;
}
#ifdef Q_OS_WIN
std::wstring fileNameStr = filePath.toStdWString();
HANDLE hFile = CreateFileW(fileNameStr.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_DELETE, // allow other processes to delete it
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, // Using FILE_FLAG_DELETE_ON_CLOSE causes some
// applications like windows zip not to open the
// file.
NULL // no template
);
if (hFile == INVALID_HANDLE_VALUE) {
Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to open attachment."));
QFile file(fileName);
file.remove();
return false;
}
d->mOpenFiles.push_back({fileNameStr, tempDir.path().toStdWString(), hFile});
#endif
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(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 certificates were found in this attachment"));
return;
} else {
QString message = i18ndcp("mimetreeparser", "@info", "one certificate imported", "%1 certificates imported", result.numImported());
if (result.numUnchanged() != 0) {
message += QStringLiteral("\n")
+ i18ndcp("mimetreeparser",
"@info",
"one certificate was already imported",
"%1 certificates 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;
}
#include "moc_attachmentmodel.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 17, 2:44 AM (1 d, 8 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
15/eb/138f9ec3ead2b0d27ef8b75d5ae5
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment