mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-11-27 18:10:31 -05:00
Implement {UUID} placeholder and nested reference support (#12511)
* Closes #12509 * Implement the {UUID} placeholder * Implement nested placeholder de-referencing when resolving entry references to support a reference like {REF:U@I:{UUID}} which is equivalent to {USERNAME} placeholder. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: droidmonkey <2809491+droidmonkey@users.noreply.github.com> Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
parent
fd2a729677
commit
5736c6379c
5 changed files with 54 additions and 2 deletions
|
|
@ -1167,6 +1167,8 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth);
|
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth);
|
||||||
case PlaceholderType::Url:
|
case PlaceholderType::Url:
|
||||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
return resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||||
|
case PlaceholderType::Uuid:
|
||||||
|
return uuidToHex();
|
||||||
case PlaceholderType::DbDir: {
|
case PlaceholderType::DbDir: {
|
||||||
QFileInfo fileInfo(database()->filePath());
|
QFileInfo fileInfo(database()->filePath());
|
||||||
return fileInfo.absoluteDir().absolutePath();
|
return fileInfo.absoluteDir().absolutePath();
|
||||||
|
|
@ -1362,7 +1364,10 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
|
||||||
|
|
||||||
QString result;
|
QString result;
|
||||||
const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
|
const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
|
||||||
const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
|
QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
|
||||||
|
|
||||||
|
// Resolve placeholders in the search text (e.g., {UUID} -> actual UUID)
|
||||||
|
searchText = resolvePlaceholder(searchText);
|
||||||
|
|
||||||
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
|
const EntryReferenceType searchInType = Entry::referenceType(searchIn);
|
||||||
|
|
||||||
|
|
@ -1573,6 +1578,7 @@ Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
|
||||||
{QStringLiteral("{NOTES}"), PlaceholderType::Notes},
|
{QStringLiteral("{NOTES}"), PlaceholderType::Notes},
|
||||||
{QStringLiteral("{TOTP}"), PlaceholderType::Totp},
|
{QStringLiteral("{TOTP}"), PlaceholderType::Totp},
|
||||||
{QStringLiteral("{URL}"), PlaceholderType::Url},
|
{QStringLiteral("{URL}"), PlaceholderType::Url},
|
||||||
|
{QStringLiteral("{UUID}"), PlaceholderType::Uuid},
|
||||||
{QStringLiteral("{URL:RMVSCM}"), PlaceholderType::UrlWithoutScheme},
|
{QStringLiteral("{URL:RMVSCM}"), PlaceholderType::UrlWithoutScheme},
|
||||||
{QStringLiteral("{URL:WITHOUTSCHEME}"), PlaceholderType::UrlWithoutScheme},
|
{QStringLiteral("{URL:WITHOUTSCHEME}"), PlaceholderType::UrlWithoutScheme},
|
||||||
{QStringLiteral("{URL:SCM}"), PlaceholderType::UrlScheme},
|
{QStringLiteral("{URL:SCM}"), PlaceholderType::UrlScheme},
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@ public:
|
||||||
Notes,
|
Notes,
|
||||||
Totp,
|
Totp,
|
||||||
Url,
|
Url,
|
||||||
|
Uuid,
|
||||||
UrlWithoutScheme,
|
UrlWithoutScheme,
|
||||||
UrlScheme,
|
UrlScheme,
|
||||||
UrlHost,
|
UrlHost,
|
||||||
|
|
|
||||||
|
|
@ -323,8 +323,9 @@ bool EntryAttributes::operator!=(const EntryAttributes& other) const
|
||||||
|
|
||||||
QRegularExpressionMatch EntryAttributes::matchReference(const QString& text)
|
QRegularExpressionMatch EntryAttributes::matchReference(const QString& text)
|
||||||
{
|
{
|
||||||
|
// Updated regex to handle nested braces in SearchText (e.g., {UUID})
|
||||||
static const QRegularExpression referenceRegExp(
|
static const QRegularExpression referenceRegExp(
|
||||||
R"(\{REF:(?<WantedField>[TUPANI])@(?<SearchIn>[TUPANIO]):(?<SearchText>[^}]+)\})",
|
R"(\{REF:(?<WantedField>[TUPANI])@(?<SearchIn>[TUPANIO]):(?<SearchText>(?:[^{}]|\{[^}]*\})+)\})",
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
return referenceRegExp.match(text);
|
return referenceRegExp.match(text);
|
||||||
|
|
|
||||||
|
|
@ -445,6 +445,49 @@ void TestEntry::testResolveReferencePlaceholders()
|
||||||
entry3->attributes()->value("AttributeNotes"));
|
entry3->attributes()->value("AttributeNotes"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestEntry::testResolveUuidPlaceholder()
|
||||||
|
{
|
||||||
|
Database db;
|
||||||
|
auto* root = db.rootGroup();
|
||||||
|
|
||||||
|
auto* entry = new Entry();
|
||||||
|
entry->setGroup(root);
|
||||||
|
entry->setUuid(QUuid::createUuid());
|
||||||
|
entry->setTitle("Test Entry");
|
||||||
|
entry->setUsername("TestUser");
|
||||||
|
entry->setPassword("TestPass");
|
||||||
|
entry->setNotes("Test with UUID: {UUID}");
|
||||||
|
|
||||||
|
// Test that {UUID} placeholder resolves to the entry's UUID
|
||||||
|
QString expectedUuid = entry->uuidToHex();
|
||||||
|
QString resolvedNotes = entry->resolveMultiplePlaceholders(entry->notes());
|
||||||
|
QCOMPARE(resolvedNotes, QString("Test with UUID: %1").arg(expectedUuid));
|
||||||
|
|
||||||
|
// Test {UUID} placeholder directly
|
||||||
|
QCOMPARE(entry->resolveMultiplePlaceholders("{UUID}"), expectedUuid);
|
||||||
|
|
||||||
|
// Test case insensitivity
|
||||||
|
QCOMPARE(entry->resolveMultiplePlaceholders("{uuid}"), expectedUuid);
|
||||||
|
QCOMPARE(entry->resolveMultiplePlaceholders("{Uuid}"), expectedUuid);
|
||||||
|
|
||||||
|
// Test mixed case in text
|
||||||
|
QCOMPARE(entry->resolveMultiplePlaceholders("UUID is {UUID} here"), QString("UUID is %1 here").arg(expectedUuid));
|
||||||
|
|
||||||
|
// Test advanced attribute with {REF:U@I:{UUID}} - should resolve to the entry's own username
|
||||||
|
entry->attributes()->set("SelfReference", "{REF:U@I:{UUID}}");
|
||||||
|
QString attributeValue = entry->attributes()->value("SelfReference");
|
||||||
|
QString resolvedSelfRef = entry->resolveMultiplePlaceholders(attributeValue);
|
||||||
|
|
||||||
|
// Test the manual reference to confirm it works as before
|
||||||
|
QString manualReference = QString("{REF:U@I:%1}").arg(entry->uuidToHex());
|
||||||
|
entry->attributes()->set("ManualReference", manualReference);
|
||||||
|
QString resolvedManualRef = entry->resolveMultiplePlaceholders(entry->attributes()->value("ManualReference"));
|
||||||
|
|
||||||
|
// Test that both approaches work
|
||||||
|
QCOMPARE(resolvedManualRef, entry->username());
|
||||||
|
QCOMPARE(resolvedSelfRef, entry->username());
|
||||||
|
}
|
||||||
|
|
||||||
void TestEntry::testResolveNonIdPlaceholdersToUuid()
|
void TestEntry::testResolveNonIdPlaceholdersToUuid()
|
||||||
{
|
{
|
||||||
Database db;
|
Database db;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ private slots:
|
||||||
void testResolveUrlPlaceholders();
|
void testResolveUrlPlaceholders();
|
||||||
void testResolveRecursivePlaceholders();
|
void testResolveRecursivePlaceholders();
|
||||||
void testResolveReferencePlaceholders();
|
void testResolveReferencePlaceholders();
|
||||||
|
void testResolveUuidPlaceholder();
|
||||||
void testResolveNonIdPlaceholdersToUuid();
|
void testResolveNonIdPlaceholdersToUuid();
|
||||||
void testResolveConversionPlaceholders();
|
void testResolveConversionPlaceholders();
|
||||||
void testResolveReplacePlaceholders();
|
void testResolveReplacePlaceholders();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue