mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
isPerDeviceMode takes baseDir context, fix import result path
Change isPerDeviceMode(const QString&) to isPerDeviceMode(const QDir& baseDir) so the method resolves this->path against the caller-provided base directory. This fixes silent misclassification when reference paths are relative, since QFileInfo previously resolved against process CWD instead of the database directory. Override Result.path with the actual device file path in importPerDeviceShares so status messages show which specific container failed, not just the sync directory. Add regression tests for relative directory and file paths.
This commit is contained in:
parent
870bd4af38
commit
737af8798f
5 changed files with 48 additions and 25 deletions
|
|
@ -25,6 +25,7 @@
|
|||
#include "gui/DatabaseIcons.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QTextCodec>
|
||||
#include <QXmlStreamWriter>
|
||||
|
|
@ -287,9 +288,13 @@ namespace KeeShareSettings
|
|||
return (type & ImportFrom) != 0 && !path.isEmpty();
|
||||
}
|
||||
|
||||
bool Reference::isPerDeviceMode() const
|
||||
bool Reference::isPerDeviceMode(const QDir& baseDir) const
|
||||
{
|
||||
return !path.isEmpty() && QFileInfo(path).isDir();
|
||||
if (path.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const QString resolvedPath = baseDir.absoluteFilePath(path);
|
||||
return QFileInfo(resolvedPath).isDir();
|
||||
}
|
||||
|
||||
bool Reference::operator<(const Reference& other) const
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QUuid>
|
||||
|
||||
class QDir;
|
||||
|
||||
namespace Botan
|
||||
{
|
||||
class Private_Key;
|
||||
|
|
@ -133,7 +135,7 @@ namespace KeeShareSettings
|
|||
bool isValid() const;
|
||||
bool isExporting() const;
|
||||
bool isImporting() const;
|
||||
bool isPerDeviceMode() const;
|
||||
bool isPerDeviceMode(const QDir& baseDir) const;
|
||||
bool operator<(const Reference& other) const;
|
||||
bool operator==(const Reference& other) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ void ShareObserver::reinitialize()
|
|||
QMap<QString, QStringList> imported;
|
||||
QMap<QString, QStringList> exported;
|
||||
|
||||
const QDir baseDir = QFileInfo(m_db->filePath()).absoluteDir();
|
||||
|
||||
for (const auto& share : shares) {
|
||||
auto group = share.first;
|
||||
auto& reference = share.second;
|
||||
|
|
@ -113,7 +115,7 @@ void ShareObserver::reinitialize()
|
|||
if (!reference.path.isEmpty() && reference.type != KeeShareSettings::Inactive) {
|
||||
const auto newResolvedPath = resolvePath(reference.path, m_db);
|
||||
|
||||
if (reference.isPerDeviceMode()) {
|
||||
if (reference.isPerDeviceMode(baseDir)) {
|
||||
// Per-device mode: watch the directory for changes
|
||||
auto dirWatcher = QSharedPointer<QFileSystemWatcher>::create();
|
||||
if (QDir(newResolvedPath).exists()) {
|
||||
|
|
@ -137,10 +139,10 @@ void ShareObserver::reinitialize()
|
|||
|
||||
if (reference.isImporting()) {
|
||||
imported[reference.path] << group->name();
|
||||
const auto resolvedDir = resolvePath(reference.path, m_db);
|
||||
|
||||
if (reference.isPerDeviceMode()) {
|
||||
if (reference.isPerDeviceMode(baseDir)) {
|
||||
// Per-device mode: import from all device files in the directory
|
||||
const auto resolvedDir = resolvePath(reference.path, m_db);
|
||||
const auto results = importPerDeviceShares(resolvedDir, reference, group);
|
||||
for (const auto& result : results) {
|
||||
if (!result.isValid()) {
|
||||
|
|
@ -260,7 +262,8 @@ void ShareObserver::handleDirectoryUpdated(const QString& dirPath)
|
|||
return;
|
||||
}
|
||||
auto reference = KeeShare::referenceOf(group);
|
||||
if (!reference.isImporting() || !reference.isPerDeviceMode()) {
|
||||
const QDir handleBaseDir = QFileInfo(m_db->filePath()).absoluteDir();
|
||||
if (!reference.isImporting() || !reference.isPerDeviceMode(handleBaseDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +329,9 @@ QList<ShareObserver::Result> ShareObserver::importPerDeviceShares(
|
|||
continue; // Skip own device's file
|
||||
}
|
||||
const auto filePath = dir.absoluteFilePath(fileName);
|
||||
results << ShareImport::containerInto(filePath, reference, targetGroup);
|
||||
auto result = ShareImport::containerInto(filePath, reference, targetGroup);
|
||||
result.path = filePath;
|
||||
results << result;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
|
@ -398,11 +403,13 @@ QList<ShareObserver::Result> ShareObserver::exportShares()
|
|||
return results;
|
||||
}
|
||||
|
||||
const QDir exportBaseDir = QFileInfo(m_db->filePath()).absoluteDir();
|
||||
|
||||
for (auto it = references.cbegin(); it != references.cend(); ++it) {
|
||||
auto reference = it.value().first();
|
||||
const QString resolvedPath = resolvePath(reference.config.path, m_db);
|
||||
|
||||
if (reference.config.isPerDeviceMode()) {
|
||||
if (reference.config.isPerDeviceMode(exportBaseDir)) {
|
||||
// Per-device mode: export to {directory}/{DEVICE_ID}.kdbx
|
||||
QDir dir(resolvedPath);
|
||||
if (!dir.exists()) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "keeshare/KeeShare.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
|
||||
EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
|
||||
|
|
@ -110,8 +111,9 @@ void EditGroupWidgetKeeShare::updateSharingState()
|
|||
|
||||
// Custom message for active KeeShare reference
|
||||
const auto reference = KeeShare::referenceOf(m_temporaryGroup);
|
||||
const QDir uiBaseDir = QFileInfo(m_database->filePath()).absoluteDir();
|
||||
if (!reference.path.isEmpty()) {
|
||||
if (reference.isPerDeviceMode()) {
|
||||
if (reference.isPerDeviceMode(uiBaseDir)) {
|
||||
// Per-device mode: path is a directory, show info message
|
||||
m_ui->messageWidget->showMessage(
|
||||
tr("Per-device sync mode: each device writes its own container in this directory.\n"
|
||||
|
|
@ -151,7 +153,7 @@ void EditGroupWidgetKeeShare::updateSharingState()
|
|||
conflictExport |= other.isExporting() && reference.isExporting();
|
||||
// In per-device mode, import+export to the same directory is expected
|
||||
// (export writes own device file, import reads other devices' files)
|
||||
if (!reference.isPerDeviceMode()) {
|
||||
if (!reference.isPerDeviceMode(uiBaseDir)) {
|
||||
cycleImportExport |=
|
||||
(other.isImporting() && reference.isExporting()) || (other.isExporting() && reference.isImporting());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,42 +187,49 @@ void TestSharing::testSettingsSerialization_data()
|
|||
void TestSharing::testPerDeviceMode()
|
||||
{
|
||||
QFETCH(QString, path);
|
||||
QFETCH(QString, baseDir);
|
||||
QFETCH(bool, expectedPerDevice);
|
||||
|
||||
KeeShareSettings::Reference reference;
|
||||
reference.path = path;
|
||||
reference.type = KeeShareSettings::SynchronizeWith;
|
||||
|
||||
QCOMPARE(reference.isPerDeviceMode(), expectedPerDevice);
|
||||
QCOMPARE(reference.isPerDeviceMode(QDir(baseDir)), expectedPerDevice);
|
||||
}
|
||||
|
||||
void TestSharing::testPerDeviceMode_data()
|
||||
{
|
||||
QTest::addColumn<QString>("path");
|
||||
QTest::addColumn<QString>("baseDir");
|
||||
QTest::addColumn<bool>("expectedPerDevice");
|
||||
|
||||
// Classic mode paths (file-based — don't need to exist on disk)
|
||||
QTest::newRow("kdbx file") << "/some/path/share.kdbx" << false;
|
||||
QTest::newRow("kdbx.share file") << "/some/path/share.kdbx.share" << false;
|
||||
QTest::newRow("KDBX uppercase") << "/some/path/share.KDBX" << false;
|
||||
QTest::newRow("KDBX.SHARE uppercase") << "/some/path/share.KDBX.SHARE" << false;
|
||||
QTest::newRow("empty path") << "" << false;
|
||||
QTest::newRow("nonexistent path") << "/nonexistent/path/nowhere" << false;
|
||||
|
||||
// Per-device mode paths (real directories on disk)
|
||||
auto base = m_tempDir->path();
|
||||
|
||||
// Classic mode paths (file-based — don't need to exist on disk)
|
||||
QTest::newRow("kdbx file") << "/some/path/share.kdbx" << base << false;
|
||||
QTest::newRow("kdbx.share file") << "/some/path/share.kdbx.share" << base << false;
|
||||
QTest::newRow("KDBX uppercase") << "/some/path/share.KDBX" << base << false;
|
||||
QTest::newRow("KDBX.SHARE uppercase") << "/some/path/share.KDBX.SHARE" << base << false;
|
||||
QTest::newRow("empty path") << "" << base << false;
|
||||
QTest::newRow("nonexistent path") << "/nonexistent/path/nowhere" << base << false;
|
||||
|
||||
// Per-device mode paths (real directories on disk — absolute)
|
||||
QDir(base).mkpath("syncdir");
|
||||
QTest::newRow("directory path") << base + "/syncdir" << true;
|
||||
QTest::newRow("directory path") << base + "/syncdir" << base << true;
|
||||
|
||||
QDir(base).mkpath("syncdir_slash");
|
||||
QTest::newRow("directory trailing slash") << base + "/syncdir_slash/" << true;
|
||||
QTest::newRow("directory trailing slash") << base + "/syncdir_slash/" << base << true;
|
||||
|
||||
QDir(base).mkpath("sub/shared");
|
||||
QTest::newRow("nested directory") << base + "/sub/shared" << true;
|
||||
QTest::newRow("nested directory") << base + "/sub/shared" << base << true;
|
||||
|
||||
QDir(base).mkpath("path.d/sync");
|
||||
QTest::newRow("directory with dots") << base + "/path.d/sync" << true;
|
||||
QTest::newRow("directory with dots") << base + "/path.d/sync" << base << true;
|
||||
|
||||
// Per-device mode with relative path (regression test: must resolve against baseDir)
|
||||
QDir(base).mkpath("reldir");
|
||||
QTest::newRow("relative directory") << "reldir" << base << true;
|
||||
QTest::newRow("relative file") << "share.kdbx" << base << false;
|
||||
}
|
||||
|
||||
void TestSharing::testPerDeviceModeImportExport()
|
||||
|
|
|
|||
Loading…
Reference in a new issue