mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-24 23:09:44 -05:00
Implement T-CONV and T-REPLACE-RX entry placeholders
* Closes #7293 * Move existing T-CONV and T-REPLACE-RX code from AutoType to Entry. Replumb AutoType to use the entry functions. * Improve placeholder code in various place
This commit is contained in:
parent
4acb3774e6
commit
b9c5869806
@ -18,6 +18,8 @@ This section contains full details on advanced features available in KeePassXC.
|
||||
|{NOTES} |Notes
|
||||
|{TOTP} |Current TOTP value (if configured)
|
||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|
||||
|{T-CONV:/<PLACEHOLDER>/<METHOD>/} |Text conversion for resolved placeholder (eg, {USERNAME}) using the following methods: UPPER, LOWER, BASE64, HEX, URI, URI-DEC
|
||||
|{T-REPLACE-RX:/<PLACEHOLDER>/<REGEX>/<REPLACE>/} |Use a regular expression to find and replace data from a resolved placeholder (eg, {USERNAME}). Refer to match groups using $1, $2, etc.
|
||||
|{URL:RMVSCM} |URL without scheme (e.g., https)
|
||||
|{URL:WITHOUTSCHEME} |URL without scheme
|
||||
|{URL:SCM} |URL Scheme
|
||||
|
@ -689,74 +689,23 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
} else if (placeholder.startsWith("t-conv:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else {
|
||||
error = tr("Invalid conversion type: %1").arg(type);
|
||||
auto resolved = entry->resolveConversionPlaceholder(placeholder, &error);
|
||||
if (!error.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else if (placeholder.startsWith("t-replace-rx:")) {
|
||||
// Reset to the original capture to preserve case
|
||||
placeholder = match.captured(3);
|
||||
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
|
||||
if (!placeholder.isEmpty()) {
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = entry->resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = entry->resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = entry->resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \\<num> to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
|
||||
|
||||
auto searchRegex = QRegularExpression(resolvedSearch);
|
||||
if (!searchRegex.isValid()) {
|
||||
error = tr("Invalid regular expression syntax %1\n%2")
|
||||
.arg(resolvedSearch, searchRegex.errorString());
|
||||
auto resolved = entry->resolveRegexPlaceholder(placeholder, &error);
|
||||
if (!error.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto resolved = resolvedText.replace(searchRegex, resolvedReplace);
|
||||
for (const QChar& ch : resolved) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
error = tr("Invalid conversion syntax: %1").arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
} else if (placeholder.startsWith("mode=")) {
|
||||
auto mode = AutoTypeExecutor::Mode::NORMAL;
|
||||
if (placeholder.endsWith("virtual")) {
|
||||
|
@ -1030,9 +1030,9 @@ void Entry::updateModifiedSinceBegin()
|
||||
|
||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
||||
{
|
||||
static const QRegularExpression placeholderRegEx(R"(\{[^}]+\})");
|
||||
static const QRegularExpression placeholderRegEx("({(?>[^{}]+?|(?1))+?})");
|
||||
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return str;
|
||||
}
|
||||
@ -1043,7 +1043,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
|
||||
while (matches.hasNext()) {
|
||||
const auto match = matches.next();
|
||||
result += str.midRef(capEnd, match.capturedStart() - capEnd);
|
||||
result += resolvePlaceholderRecursive(match.captured(), maxDepth - 1);
|
||||
result += resolvePlaceholderRecursive(match.captured(), maxDepth);
|
||||
capEnd = match.capturedEnd();
|
||||
}
|
||||
result += str.rightRef(str.length() - capEnd);
|
||||
@ -1052,7 +1052,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
|
||||
|
||||
QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return placeholder;
|
||||
}
|
||||
@ -1060,21 +1060,20 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
|
||||
switch (typeOfPlaceholder) {
|
||||
case PlaceholderType::NotPlaceholder:
|
||||
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth);
|
||||
case PlaceholderType::Unknown: {
|
||||
return "{" % resolveMultiplePlaceholdersRecursive(placeholder.mid(1, placeholder.length() - 2), maxDepth - 1)
|
||||
% "}";
|
||||
return "{" % resolveMultiplePlaceholdersRecursive(placeholder.mid(1, placeholder.length() - 2), maxDepth) % "}";
|
||||
}
|
||||
case PlaceholderType::Title:
|
||||
return resolveMultiplePlaceholdersRecursive(title(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(title(), maxDepth);
|
||||
case PlaceholderType::UserName:
|
||||
return resolveMultiplePlaceholdersRecursive(username(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(username(), maxDepth);
|
||||
case PlaceholderType::Password:
|
||||
return resolveMultiplePlaceholdersRecursive(password(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(password(), maxDepth);
|
||||
case PlaceholderType::Notes:
|
||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth);
|
||||
case PlaceholderType::Url:
|
||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||
case PlaceholderType::DbDir: {
|
||||
QFileInfo fileInfo(database()->filePath());
|
||||
return fileInfo.absoluteDir().absolutePath();
|
||||
@ -1089,7 +1088,7 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
case PlaceholderType::UrlUserInfo:
|
||||
case PlaceholderType::UrlUserName:
|
||||
case PlaceholderType::UrlPassword: {
|
||||
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
||||
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
|
||||
}
|
||||
case PlaceholderType::Totp:
|
||||
@ -1097,10 +1096,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
return totp();
|
||||
case PlaceholderType::CustomAttribute: {
|
||||
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
|
||||
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
|
||||
return attributes()->hasKey(key) ? resolveMultiplePlaceholdersRecursive(attributes()->value(key), maxDepth)
|
||||
: QString();
|
||||
}
|
||||
case PlaceholderType::Reference:
|
||||
return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
|
||||
return resolveReferencePlaceholderRecursive(placeholder, ++maxDepth);
|
||||
case PlaceholderType::DateTimeSimple:
|
||||
case PlaceholderType::DateTimeYear:
|
||||
case PlaceholderType::DateTimeMonth:
|
||||
@ -1115,7 +1115,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
||||
case PlaceholderType::DateTimeUtcHour:
|
||||
case PlaceholderType::DateTimeUtcMinute:
|
||||
case PlaceholderType::DateTimeUtcSecond:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveDateTimePlaceholder(typeOfPlaceholder), maxDepth - 1);
|
||||
return resolveMultiplePlaceholdersRecursive(resolveDateTimePlaceholder(typeOfPlaceholder), maxDepth);
|
||||
case PlaceholderType::Conversion:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveConversionPlaceholder(placeholder), maxDepth);
|
||||
case PlaceholderType::Regex:
|
||||
return resolveMultiplePlaceholdersRecursive(resolveRegexPlaceholder(placeholder), maxDepth);
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
@ -1164,9 +1168,93 @@ QString Entry::resolveDateTimePlaceholder(Entry::PlaceholderType placeholderType
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveConversionPlaceholder(const QString& str, QString* error) const
|
||||
{
|
||||
if (error) {
|
||||
error->clear();
|
||||
}
|
||||
|
||||
// Extract the inner conversion from the placeholder
|
||||
QRegularExpression conversionRegEx("^{?t-conv:(.*)}?$", QRegularExpression::CaseInsensitiveOption);
|
||||
auto placeholder = conversionRegEx.match(str).captured(1);
|
||||
if (!placeholder.isEmpty()) {
|
||||
// Determine the separator character and split, include empty groups
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 4) {
|
||||
auto resolved = resolveMultiplePlaceholders(parts[1]);
|
||||
auto type = parts[2].toLower();
|
||||
|
||||
if (type == "base64") {
|
||||
resolved = resolved.toUtf8().toBase64();
|
||||
} else if (type == "hex") {
|
||||
resolved = resolved.toUtf8().toHex();
|
||||
} else if (type == "uri") {
|
||||
resolved = QUrl::toPercentEncoding(resolved.toUtf8());
|
||||
} else if (type == "uri-dec") {
|
||||
resolved = QUrl::fromPercentEncoding(resolved.toUtf8());
|
||||
} else if (type.startsWith("u")) {
|
||||
resolved = resolved.toUpper();
|
||||
} else if (type.startsWith("l")) {
|
||||
resolved = resolved.toLower();
|
||||
} else {
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion type: %1").arg(type);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(str);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveRegexPlaceholder(const QString& str, QString* error) const
|
||||
{
|
||||
if (error) {
|
||||
error->clear();
|
||||
}
|
||||
|
||||
// Extract the inner regex from the placeholder
|
||||
QRegularExpression conversionRegEx("^{?t-replace-rx:(.*)}?$", QRegularExpression::CaseInsensitiveOption);
|
||||
auto placeholder = conversionRegEx.match(str).captured(1);
|
||||
if (!placeholder.isEmpty()) {
|
||||
// Determine the separator character and split, include empty groups
|
||||
auto sep = placeholder[0];
|
||||
auto parts = placeholder.split(sep);
|
||||
if (parts.size() >= 5) {
|
||||
auto resolvedText = resolveMultiplePlaceholders(parts[1]);
|
||||
auto resolvedSearch = resolveMultiplePlaceholders(parts[2]);
|
||||
auto resolvedReplace = resolveMultiplePlaceholders(parts[3]);
|
||||
// Replace $<num> with \\<num> to support Qt substitutions
|
||||
resolvedReplace.replace(QRegularExpression(R"(\$(\d+))"), R"(\\1)");
|
||||
|
||||
auto searchRegex = QRegularExpression(resolvedSearch);
|
||||
if (!searchRegex.isValid()) {
|
||||
if (error) {
|
||||
*error =
|
||||
tr("Invalid regular expression syntax %1\n%2").arg(resolvedSearch, searchRegex.errorString());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
return resolvedText.replace(searchRegex, resolvedReplace);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = tr("Invalid conversion syntax: %1").arg(str);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const
|
||||
{
|
||||
if (maxDepth <= 0) {
|
||||
if (--maxDepth < 0) {
|
||||
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||
return placeholder;
|
||||
}
|
||||
@ -1194,7 +1282,7 @@ QString Entry::resolveReferencePlaceholderRecursive(const QString& placeholder,
|
||||
// Referencing fields of other entries only works with standard fields, not with custom user strings.
|
||||
// If you want to reference a custom user string, you need to place a redirection in a standard field
|
||||
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
|
||||
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -1369,15 +1457,21 @@ QString Entry::resolveUrlPlaceholder(const QString& str, Entry::PlaceholderType
|
||||
|
||||
Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
|
||||
{
|
||||
if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
|
||||
if (!placeholder.startsWith(QStringLiteral("{")) || !placeholder.endsWith(QStringLiteral("}"))) {
|
||||
return PlaceholderType::NotPlaceholder;
|
||||
}
|
||||
if (placeholder.startsWith(QLatin1Literal("{S:"))) {
|
||||
if (placeholder.startsWith(QStringLiteral("{S:"))) {
|
||||
return PlaceholderType::CustomAttribute;
|
||||
}
|
||||
if (placeholder.startsWith(QLatin1Literal("{REF:"))) {
|
||||
if (placeholder.startsWith(QStringLiteral("{REF:"))) {
|
||||
return PlaceholderType::Reference;
|
||||
}
|
||||
if (placeholder.startsWith(QStringLiteral("{T-CONV:"), Qt::CaseInsensitive)) {
|
||||
return PlaceholderType::Conversion;
|
||||
}
|
||||
if (placeholder.startsWith(QStringLiteral("{T-REPLACE-RX:"), Qt::CaseInsensitive)) {
|
||||
return PlaceholderType::Regex;
|
||||
}
|
||||
|
||||
static const QMap<QString, PlaceholderType> placeholders{
|
||||
{QStringLiteral("{TITLE}"), PlaceholderType::Title},
|
||||
|
@ -225,7 +225,9 @@ public:
|
||||
DateTimeUtcHour,
|
||||
DateTimeUtcMinute,
|
||||
DateTimeUtcSecond,
|
||||
DbDir
|
||||
DbDir,
|
||||
Conversion,
|
||||
Regex
|
||||
};
|
||||
|
||||
static const int DefaultIconNumber;
|
||||
@ -244,6 +246,8 @@ public:
|
||||
QString resolvePlaceholder(const QString& str) const;
|
||||
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
||||
QString resolveDateTimePlaceholder(PlaceholderType placeholderType) const;
|
||||
QString resolveConversionPlaceholder(const QString& str, QString* error = nullptr) const;
|
||||
QString resolveRegexPlaceholder(const QString& str, QString* error = nullptr) const;
|
||||
PlaceholderType placeholderType(const QString& placeholder) const;
|
||||
QString resolveUrl(const QString& url) const;
|
||||
|
||||
|
@ -514,6 +514,86 @@ void TestEntry::testResolveNonIdPlaceholdersToUuid()
|
||||
}
|
||||
}
|
||||
|
||||
void TestEntry::testResolveConversionPlaceholders()
|
||||
{
|
||||
Database db;
|
||||
auto* root = db.rootGroup();
|
||||
|
||||
auto* entry1 = new Entry();
|
||||
entry1->setGroup(root);
|
||||
entry1->setUuid(QUuid::createUuid());
|
||||
entry1->setTitle("Title1 {T-CONV:/{USERNAME}/lower/} {T-CONV:/{PASSWORD}/upper/}");
|
||||
entry1->setUsername("Username1");
|
||||
entry1->setPassword("Password1");
|
||||
entry1->setUrl("https://example.com/?test=3423&h=sdsds");
|
||||
|
||||
auto* entry2 = new Entry();
|
||||
entry2->setGroup(root);
|
||||
entry2->setUuid(QUuid::createUuid());
|
||||
entry2->setTitle("Title2");
|
||||
entry2->setUsername(QString("{T-CONV:/{REF:U@I:%1}/UPPER/}").arg(entry1->uuidToHex()));
|
||||
entry2->setPassword(QString("{REF:P@I:%1}").arg(entry1->uuidToHex()));
|
||||
entry2->setUrl("cmd://ssh {USERNAME}@server.com -p {PASSWORD}");
|
||||
|
||||
// Test complicated and nested conversions
|
||||
QCOMPARE(entry1->resolveMultiplePlaceholders(entry1->title()), QString("Title1 username1 PASSWORD1"));
|
||||
QCOMPARE(entry2->resolveMultiplePlaceholders(entry2->url()),
|
||||
QString("cmd://ssh USERNAME1@server.com -p Password1"));
|
||||
// Test base64 and hex conversions
|
||||
QCOMPARE(entry1->resolveMultiplePlaceholders("{T-CONV:/{PASSWORD}/base64/}"), QString("UGFzc3dvcmQx"));
|
||||
QCOMPARE(entry1->resolveMultiplePlaceholders("{T-CONV:/{PASSWORD}/hex/}"), QString("50617373776f726431"));
|
||||
// Test URL encode and decode
|
||||
auto encodedURL = entry1->resolveMultiplePlaceholders("{T-CONV:/{URL}/uri/}");
|
||||
QCOMPARE(encodedURL, QString("https%3A%2F%2Fexample.com%2F%3Ftest%3D3423%26h%3Dsdsds"));
|
||||
QCOMPARE(entry1->resolveMultiplePlaceholders(
|
||||
"{T-CONV:/https%3A%2F%2Fexample.com%2F%3Ftest%3D3423%26h%3Dsdsds/uri-dec/}"),
|
||||
entry1->url());
|
||||
// Test invalid syntax
|
||||
QString error;
|
||||
entry1->resolveConversionPlaceholder("{T-CONV:/{USERNAME}/junk/}", &error);
|
||||
QVERIFY(!error.isEmpty());
|
||||
entry1->resolveConversionPlaceholder("{T-CONV:}", &error);
|
||||
QVERIFY(!error.isEmpty());
|
||||
// Check that error gets cleared
|
||||
entry1->resolveConversionPlaceholder("{T-CONV:/a/upper/}", &error);
|
||||
QVERIFY(error.isEmpty());
|
||||
}
|
||||
|
||||
void TestEntry::testResolveReplacePlaceholders()
|
||||
{
|
||||
Database db;
|
||||
auto* root = db.rootGroup();
|
||||
|
||||
auto* entry1 = new Entry();
|
||||
entry1->setGroup(root);
|
||||
entry1->setUuid(QUuid::createUuid());
|
||||
entry1->setTitle("Title1");
|
||||
entry1->setUsername("Username1");
|
||||
entry1->setPassword("Password1");
|
||||
|
||||
auto* entry2 = new Entry();
|
||||
entry2->setGroup(root);
|
||||
entry2->setUuid(QUuid::createUuid());
|
||||
entry2->setTitle("SAP server1 12345");
|
||||
entry2->setUsername(QString("{T-REPLACE-RX:/{REF:U@I:%1}/\\d$/2/}").arg(entry1->uuidToHex()));
|
||||
entry2->setPassword(QString("{REF:P@I:%1}").arg(entry1->uuidToHex()));
|
||||
entry2->setUrl(
|
||||
R"(cmd://sap.exe -system={T-REPLACE-RX:/{Title}/(?i)^(.* )?(\w+(?=(\s* \d+$)))\3/$2/} -client={T-REPLACE-RX:/{Title}/(?i)^.* (?=\d+$)//} -user={USERNAME} -pw={PASSWORD})");
|
||||
|
||||
// Test complicated and nested replacements
|
||||
QCOMPARE(entry2->resolveMultiplePlaceholders(entry2->url()),
|
||||
QString("cmd://sap.exe -system=server1 -client=12345 -user=Username2 -pw=Password1"));
|
||||
// Test invalid syntax
|
||||
QString error;
|
||||
entry1->resolveRegexPlaceholder("{T-REPLACE-RX:/{USERNAME}/.*+?/test/}", &error); // invalid regex
|
||||
QVERIFY(!error.isEmpty());
|
||||
entry1->resolveRegexPlaceholder("{T-REPLACE-RX:/{USERNAME}/.*/}", &error); // no replacement
|
||||
QVERIFY(!error.isEmpty());
|
||||
// Check that error gets cleared
|
||||
entry1->resolveRegexPlaceholder("{T-REPLACE-RX:/{USERNAME}/\\d/2/}", &error);
|
||||
QVERIFY(error.isEmpty());
|
||||
}
|
||||
|
||||
void TestEntry::testResolveClonedEntry()
|
||||
{
|
||||
Database db;
|
||||
|
@ -36,6 +36,8 @@ private slots:
|
||||
void testResolveRecursivePlaceholders();
|
||||
void testResolveReferencePlaceholders();
|
||||
void testResolveNonIdPlaceholdersToUuid();
|
||||
void testResolveConversionPlaceholders();
|
||||
void testResolveReplacePlaceholders();
|
||||
void testResolveClonedEntry();
|
||||
void testIsRecycled();
|
||||
void testMoveUpDown();
|
||||
|
Loading…
Reference in New Issue
Block a user