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,
|
||||
const QString &baseDirectory)
|
||||
{
|
||||
PartialFileGuard partFileGuard{outputFileName};
|
||||
if (partFileGuard.tempFileName().isEmpty()) {
|
||||
return std::make_tuple(EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, QString{}, Error{});
|
||||
}
|
||||
|
||||
Data outdata;
|
||||
#ifdef Q_OS_WIN
|
||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
||||
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||
#else
|
||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
||||
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||
#endif
|
||||
|
||||
const auto result = encrypt(ctx, recipients, paths, outdata, flags, baseDirectory);
|
||||
const auto &encryptionResult = std::get<0>(result);
|
||||
if (encryptionResult.error().code()) {
|
||||
// ensure that the output file is removed if the operation was canceled or failed
|
||||
removeFile(outputFileName);
|
||||
if (!encryptionResult.error().code()) {
|
||||
// the operation succeeded -> save the result under the requested file name
|
||||
partFileGuard.commit();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -138,18 +138,23 @@ static QGpgMESignArchiveJob::result_type sign_to_filename(Context *ctx,
|
||||
const QString &outputFileName,
|
||||
const QString &baseDirectory)
|
||||
{
|
||||
PartialFileGuard partFileGuard{outputFileName};
|
||||
if (partFileGuard.tempFileName().isEmpty()) {
|
||||
return std::make_tuple(SigningResult{Error::fromCode(GPG_ERR_EEXIST)}, QString{}, Error{});
|
||||
}
|
||||
|
||||
Data outdata;
|
||||
#ifdef Q_OS_WIN
|
||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
||||
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||
#else
|
||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
||||
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||
#endif
|
||||
|
||||
const auto result = sign(ctx, signers, paths, outdata, baseDirectory);
|
||||
const auto &signingResult = std::get<0>(result);
|
||||
if (signingResult.error().code()) {
|
||||
// ensure that the output file is removed if the operation was canceled or failed
|
||||
removeFile(outputFileName);
|
||||
if (!signingResult.error().code()) {
|
||||
// the operation succeeded -> save the result under the requested file name
|
||||
partFileGuard.commit();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -147,19 +147,27 @@ static QGpgMESignEncryptArchiveJob::result_type sign_encrypt_to_filename(Context
|
||||
Context::EncryptionFlags encryptionFlags,
|
||||
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;
|
||||
#ifdef Q_OS_WIN
|
||||
outdata.setFileName(outputFileName.toUtf8().constData());
|
||||
outdata.setFileName(partFileGuard.tempFileName().toUtf8().constData());
|
||||
#else
|
||||
outdata.setFileName(QFile::encodeName(outputFileName).constData());
|
||||
outdata.setFileName(QFile::encodeName(partFileGuard.tempFileName()).constData());
|
||||
#endif
|
||||
|
||||
const auto result = sign_encrypt(ctx, signers, recipients, paths, outdata, encryptionFlags, baseDirectory);
|
||||
const auto &signingResult = std::get<0>(result);
|
||||
const auto &encryptionResult = std::get<1>(result);
|
||||
if (signingResult.error().code() || encryptionResult.error().code()) {
|
||||
// ensure that the output file is removed if the operation was canceled or failed
|
||||
removeFile(outputFileName);
|
||||
if (!signingResult.error().code() && !encryptionResult.error().code()) {
|
||||
// the operation succeeded -> save the result under the requested file name
|
||||
partFileGuard.commit();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include "qgpgme_debug.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#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);
|
||||
|
||||
/**
|
||||
* 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__
|
||||
|
Loading…
Reference in New Issue
Block a user