Fix backup file path substitution

Previously, in a pattern like "{TIME:yy} {TIME}",
substituteBackupFilePath() would greedily use the entire string
"yy} {TIME" as the format specifier for the first TIME template, instead
of just "yy". Fix this, by adjusting the regular expression.

This ends up changing the behaviour of a weird corner case that is
covered in the tests, so change the test. I don't think anyone cares
about that case, and I think the current behaviour is better there.

Fixes #10505 (proved by adding a test case very similar to what was
reported there).
This commit is contained in:
Carlo Teubner 2024-05-30 21:39:47 +01:00 committed by Jonathan White
parent 34808a2caa
commit dad8b1d2ea
2 changed files with 17 additions and 12 deletions

View File

@ -449,33 +449,32 @@ namespace Tools
QString substituteBackupFilePath(QString pattern, const QString& databasePath)
{
// Fail if substitution fails
if (databasePath.isEmpty()) {
return {};
}
// Replace backup pattern
QFileInfo dbFileInfo(databasePath);
QString baseName = dbFileInfo.completeBaseName();
const QString baseName = QFileInfo{databasePath}.completeBaseName();
pattern.replace(QString("{DB_FILENAME}"), baseName);
pattern.replace(QStringLiteral("{DB_FILENAME}"), baseName);
auto re = QRegularExpression(R"(\{TIME(?::([^\\]*))?\})");
const QDateTime now = Clock::currentDateTime();
const QRegularExpression re(R"(\{TIME(?::([^\\{}]*))?\})");
auto match = re.match(pattern);
while (match.hasMatch()) {
// Extract time format specifier
auto formatSpecifier = QString("dd_MM_yyyy_hh-mm-ss");
// Extract time format specifier, or use default value if absent
QString formatSpecifier = "dd_MM_yyyy_hh-mm-ss";
if (!match.captured(1).isEmpty()) {
formatSpecifier = match.captured(1);
}
auto replacement = Clock::currentDateTime().toString(formatSpecifier);
const auto replacement = now.toString(formatSpecifier);
pattern.replace(match.capturedStart(), match.capturedLength(), replacement);
match = re.match(pattern);
}
// Replace escaped braces
pattern.replace("\\{", "{");
pattern.replace("\\}", "}");
pattern.replace(QStringLiteral("\\{"), QStringLiteral("{"));
pattern.replace(QStringLiteral("\\}"), QStringLiteral("}"));
return pattern;
}

View File

@ -150,6 +150,12 @@ void TestTools::testBackupFilePatternSubstitution_data()
QTest::newRow("Default time pattern (empty formatter)")
<< "{TIME:}" << DEFAULT_DB_FILE_PATH << DEFAULT_FORMATTED_TIME;
QTest::newRow("Custom time pattern") << "{TIME:dd-ss}" << DEFAULT_DB_FILE_PATH << NOW.toString("dd-ss");
QTest::newRow("Time pattern twice") << "{TIME:yy} {TIME}" << DEFAULT_DB_FILE_PATH
<< NOW.toString("yy") + QStringLiteral(" ") + DEFAULT_FORMATTED_TIME;
QTest::newRow("Complex custom time pattern")
<< "./{TIME:yy}/{DB_FILENAME}_{TIME:yyyyMMdd_HHmmss}.old.kdbx" << DEFAULT_DB_FILE_PATH
<< QStringLiteral("./") + NOW.toString("yy") + QStringLiteral("/") + DEFAULT_DB_FILE_NAME + QStringLiteral("_")
+ NOW.toString("yyyyMMdd_HHmmss") + QStringLiteral(".old.kdbx");
QTest::newRow("Invalid custom time pattern") << "{TIME:dd/-ss}" << DEFAULT_DB_FILE_PATH << NOW.toString("dd/-ss");
QTest::newRow("Recursive substitution") << "{TIME:'{TIME}'}" << DEFAULT_DB_FILE_PATH << DEFAULT_FORMATTED_TIME;
QTest::newRow("{DB_FILENAME} substitution")
@ -161,7 +167,7 @@ void TestTools::testBackupFilePatternSubstitution_data()
// Not relevant right now, added test anyway
QTest::newRow("There should be no substitution loops") << "{DB_FILENAME}"
<< "{TIME:'{DB_FILENAME}'}.ext"
<< "{DB_FILENAME}";
<< "{TIME:'{DB_FILENAME}'}";
}
void TestTools::testBackupFilePatternSubstitution()