qt: Use temporary .part file names when creating archives
* lang/qt/src/util.h, lang/qt/src/util.cpp (class PartialFileGuard): New. * lang/qt/src/util.cpp (getRandomCharacters, createPartFileName): New. * lang/qt/src/qgpgmeencryptarchivejob.cpp (encrypt_to_filename): Use PartialFileGuard. * lang/qt/src/qgpgmesignarchivejob.cpp (sign_to_filename): Ditto. * lang/qt/src/qgpgmesignencryptarchivejob.cpp (sign_encrypt_to_filename): Ditto. -- When creating signed and/or encrypted archives, gpgtar now writes the result to a temporary file name. On success, the archive is renamed to the final file name. Otherwise, the (partially written) temporary file is removed (if possible). GnuPG-bug-id: 6721
This commit is contained in:
parent
8d8985bda1
commit
46f5d5eeb3
@ -133,18 +133,23 @@ static QGpgMEEncryptArchiveJob::result_type encrypt_to_filename(Context *ctx,
|
|||||||
Context::EncryptionFlags flags,
|
Context::EncryptionFlags flags,
|
||||||
const QString &baseDirectory)
|
const QString &baseDirectory)
|
||||||
{
|
{
|
||||||
|
PartialFileGuard partFileGuard{outputFileName};
|
||||||
|
if (partFileGuard.tempFileName().isEmpty()) {
|
||||||
|
return std::make_tuple(EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, QString{}, Error{});
|
||||||
|
}
|
||||||
|
|
||||||
Data outdata;
|
Data outdata;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||||
#else
|
#else
|
||||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto result = encrypt(ctx, recipients, paths, outdata, flags, baseDirectory);
|
const auto result = encrypt(ctx, recipients, paths, outdata, flags, baseDirectory);
|
||||||
const auto &encryptionResult = std::get<0>(result);
|
const auto &encryptionResult = std::get<0>(result);
|
||||||
if (encryptionResult.error().code()) {
|
if (!encryptionResult.error().code()) {
|
||||||
// ensure that the output file is removed if the operation was canceled or failed
|
// the operation succeeded -> save the result under the requested file name
|
||||||
removeFile(outputFileName);
|
partFileGuard.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -138,18 +138,23 @@ static QGpgMESignArchiveJob::result_type sign_to_filename(Context *ctx,
|
|||||||
const QString &outputFileName,
|
const QString &outputFileName,
|
||||||
const QString &baseDirectory)
|
const QString &baseDirectory)
|
||||||
{
|
{
|
||||||
|
PartialFileGuard partFileGuard{outputFileName};
|
||||||
|
if (partFileGuard.tempFileName().isEmpty()) {
|
||||||
|
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_EEXIST)}, QString{}, Error{});
|
||||||
|
}
|
||||||
|
|
||||||
Data outdata;
|
Data outdata;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||||
#else
|
#else
|
||||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto result = sign(ctx, signers, paths, outdata, baseDirectory);
|
const auto result = sign(ctx, signers, paths, outdata, baseDirectory);
|
||||||
const auto &signingResult = std::get<0>(result);
|
const auto &signingResult = std::get<0>(result);
|
||||||
if (signingResult.error().code()) {
|
if (!signingResult.error().code()) {
|
||||||
// ensure that the output file is removed if the operation was canceled or failed
|
// the operation succeeded -> save the result under the requested file name
|
||||||
removeFile(outputFileName);
|
partFileGuard.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -147,19 +147,27 @@ static QGpgMESignEncryptArchiveJob::result_type sign_encrypt_to_filename(Context
|
|||||||
Context::EncryptionFlags encryptionFlags,
|
Context::EncryptionFlags encryptionFlags,
|
||||||
const QString &baseDirectory)
|
const QString &baseDirectory)
|
||||||
{
|
{
|
||||||
|
PartialFileGuard partFileGuard{outputFileName};
|
||||||
|
if (partFileGuard.tempFileName().isEmpty()) {
|
||||||
|
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_EEXIST)},
|
||||||
|
EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)},
|
||||||
|
QString{},
|
||||||
|
Error{});
|
||||||
|
}
|
||||||
|
|
||||||
Data outdata;
|
Data outdata;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||||
#else
|
#else
|
||||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto result = sign_encrypt(ctx, signers, recipients, paths, outdata, encryptionFlags, baseDirectory);
|
const auto result = sign_encrypt(ctx, signers, recipients, paths, outdata, encryptionFlags, baseDirectory);
|
||||||
const auto &signingResult = std::get<0>(result);
|
const auto &signingResult = std::get<0>(result);
|
||||||
const auto &encryptionResult = std::get<1>(result);
|
const auto &encryptionResult = std::get<1>(result);
|
||||||
if (signingResult.error().code() || encryptionResult.error().code()) {
|
if (!signingResult.error().code() && !encryptionResult.error().code()) {
|
||||||
// ensure that the output file is removed if the operation was canceled or failed
|
// the operation succeeded -> save the result under the requested file name
|
||||||
removeFile(outputFileName);
|
partFileGuard.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -40,6 +40,8 @@
|
|||||||
#include "qgpgme_debug.h"
|
#include "qgpgme_debug.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
|
||||||
#include <key.h>
|
#include <key.h>
|
||||||
|
|
||||||
@ -76,3 +78,106 @@ void removeFile(const QString &fileName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a string of random characters for the file names of temporary files.
|
||||||
|
* Never use this for generating passwords or similar use cases requiring highly
|
||||||
|
* secure random data.
|
||||||
|
*/
|
||||||
|
static QString getRandomCharacters(const int count)
|
||||||
|
{
|
||||||
|
if (count < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString randomChars;
|
||||||
|
randomChars.reserve(count);
|
||||||
|
|
||||||
|
do {
|
||||||
|
// get a 32-bit random number to generate up to 5 random characters from
|
||||||
|
// the set {A-Z, a-z, 0-9}; set the highest bit for the break condition
|
||||||
|
for (quint32 rnd = QRandomGenerator::global()->generate() | (1 << 31); rnd > 3; rnd = rnd >> 6)
|
||||||
|
{
|
||||||
|
// take the last 6 bits; ignore 62 and 63
|
||||||
|
const char ch = rnd & ((1 << 6) - 1);
|
||||||
|
if (ch < 26) {
|
||||||
|
randomChars += QLatin1Char(ch + 'A');
|
||||||
|
} else if (ch < 26 + 26) {
|
||||||
|
randomChars += QLatin1Char(ch - 26 + 'a');
|
||||||
|
} else if (ch < 26 + 26 + 10) {
|
||||||
|
randomChars += QLatin1Char(ch - 26 - 26 + '0');
|
||||||
|
}
|
||||||
|
if (randomChars.size() >= count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (randomChars.size() < count);
|
||||||
|
|
||||||
|
return randomChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary file name with extension \c .part for the given file name
|
||||||
|
* \a fileName. The function makes sure that the created file name is not in use
|
||||||
|
* at the time the file name is chosen.
|
||||||
|
*
|
||||||
|
* Example: For the file name "this.is.an.archive.tar.gpg" the temporary file name
|
||||||
|
* "this.YHgf2tEl.is.an.archive.tar.gpg.part" could be returned.
|
||||||
|
*/
|
||||||
|
static QString createPartFileName(const QString &fileName)
|
||||||
|
{
|
||||||
|
static const int maxAttempts = 10;
|
||||||
|
|
||||||
|
const QFileInfo fi{fileName};
|
||||||
|
const QString path = fi.path(); // path without trailing '/'
|
||||||
|
const QString baseName = fi.baseName();
|
||||||
|
const QString suffix = fi.completeSuffix();
|
||||||
|
for (int attempt = 0; attempt < maxAttempts; ++attempt) {
|
||||||
|
const QString candidate = (path + QLatin1Char('/')
|
||||||
|
+ baseName + QLatin1Char('.')
|
||||||
|
+ getRandomCharacters(8) + QLatin1Char('.')
|
||||||
|
+ suffix
|
||||||
|
+ QLatin1String(".part"));
|
||||||
|
if (!QFile::exists(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCWarning(QGPGME_LOG) << __func__ << "- Failed to create temporary file name for" << fileName;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
PartialFileGuard::PartialFileGuard(const QString &fileName)
|
||||||
|
: mFileName{fileName}
|
||||||
|
, mTempFileName{createPartFileName(fileName)}
|
||||||
|
{
|
||||||
|
qCDebug(QGPGME_LOG) << __func__ << "- Using temporary file name" << mTempFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartialFileGuard::~PartialFileGuard()
|
||||||
|
{
|
||||||
|
if (!mTempFileName.isEmpty()) {
|
||||||
|
removeFile(mTempFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PartialFileGuard::tempFileName() const
|
||||||
|
{
|
||||||
|
return mTempFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PartialFileGuard::commit()
|
||||||
|
{
|
||||||
|
if (mTempFileName.isEmpty()) {
|
||||||
|
qCWarning(QGPGME_LOG) << "PartialFileGuard::commit: Called more than once";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool success = QFile::rename(mTempFileName, mFileName);
|
||||||
|
if (success) {
|
||||||
|
qCDebug(QGPGME_LOG) << __func__ << "- Renamed" << mTempFileName << "to" << mFileName;
|
||||||
|
mTempFileName.clear();
|
||||||
|
} else {
|
||||||
|
qCDebug(QGPGME_LOG) << __func__ << "- Renaming" << mTempFileName << "to" << mFileName << "failed";
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
@ -57,4 +57,26 @@ QStringList toFingerprints(const std::vector<GpgME::Key> &keys);
|
|||||||
|
|
||||||
void removeFile(const QString &fileName);
|
void removeFile(const QString &fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for using a temporary "part" file for writing a result to, similar
|
||||||
|
* to what browsers do when downloading files.
|
||||||
|
* On success, you commit() which renames the temporary file to the
|
||||||
|
* final file name. Otherwise, you do nothing and let the helper remove the
|
||||||
|
* temporary file on destruction.
|
||||||
|
*/
|
||||||
|
class PartialFileGuard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit PartialFileGuard(const QString &fileName);
|
||||||
|
~PartialFileGuard();
|
||||||
|
|
||||||
|
QString tempFileName() const;
|
||||||
|
|
||||||
|
bool commit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString mFileName;
|
||||||
|
QString mTempFileName;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // __QGPGME_UTIL_H__
|
#endif // __QGPGME_UTIL_H__
|
||||||
|
Loading…
Reference in New Issue
Block a user