mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-25 07:19:42 -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
|
|{NOTES} |Notes
|
||||||
|{TOTP} |Current TOTP value (if configured)
|
|{TOTP} |Current TOTP value (if configured)
|
||||||
|{S:<ATTRIBUTE_NAME>} |Value for the given attribute (case sensitive)
|
|{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:RMVSCM} |URL without scheme (e.g., https)
|
||||||
|{URL:WITHOUTSCHEME} |URL without scheme
|
|{URL:WITHOUTSCHEME} |URL without scheme
|
||||||
|{URL:SCM} |URL Scheme
|
|{URL:SCM} |URL Scheme
|
||||||
|
@ -689,74 +689,23 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
|||||||
} else if (placeholder.startsWith("t-conv:")) {
|
} else if (placeholder.startsWith("t-conv:")) {
|
||||||
// Reset to the original capture to preserve case
|
// Reset to the original capture to preserve case
|
||||||
placeholder = match.captured(3);
|
placeholder = match.captured(3);
|
||||||
placeholder.replace("t-conv:", "", Qt::CaseInsensitive);
|
auto resolved = entry->resolveConversionPlaceholder(placeholder, &error);
|
||||||
if (!placeholder.isEmpty()) {
|
if (!error.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);
|
|
||||||
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
for (const QChar& ch : resolved) {
|
||||||
|
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||||
|
}
|
||||||
} else if (placeholder.startsWith("t-replace-rx:")) {
|
} else if (placeholder.startsWith("t-replace-rx:")) {
|
||||||
// Reset to the original capture to preserve case
|
// Reset to the original capture to preserve case
|
||||||
placeholder = match.captured(3);
|
placeholder = match.captured(3);
|
||||||
placeholder.replace("t-replace-rx:", "", Qt::CaseInsensitive);
|
auto resolved = entry->resolveRegexPlaceholder(placeholder, &error);
|
||||||
if (!placeholder.isEmpty()) {
|
if (!error.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());
|
|
||||||
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
for (const QChar& ch : resolved) {
|
||||||
|
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||||
|
}
|
||||||
} else if (placeholder.startsWith("mode=")) {
|
} else if (placeholder.startsWith("mode=")) {
|
||||||
auto mode = AutoTypeExecutor::Mode::NORMAL;
|
auto mode = AutoTypeExecutor::Mode::NORMAL;
|
||||||
if (placeholder.endsWith("virtual")) {
|
if (placeholder.endsWith("virtual")) {
|
||||||
|
@ -1030,9 +1030,9 @@ void Entry::updateModifiedSinceBegin()
|
|||||||
|
|
||||||
QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const
|
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());
|
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@ -1043,7 +1043,7 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
|
|||||||
while (matches.hasNext()) {
|
while (matches.hasNext()) {
|
||||||
const auto match = matches.next();
|
const auto match = matches.next();
|
||||||
result += str.midRef(capEnd, match.capturedStart() - capEnd);
|
result += str.midRef(capEnd, match.capturedStart() - capEnd);
|
||||||
result += resolvePlaceholderRecursive(match.captured(), maxDepth - 1);
|
result += resolvePlaceholderRecursive(match.captured(), maxDepth);
|
||||||
capEnd = match.capturedEnd();
|
capEnd = match.capturedEnd();
|
||||||
}
|
}
|
||||||
result += str.rightRef(str.length() - capEnd);
|
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
|
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());
|
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||||
return placeholder;
|
return placeholder;
|
||||||
}
|
}
|
||||||
@ -1060,21 +1060,20 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
|||||||
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
|
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
|
||||||
switch (typeOfPlaceholder) {
|
switch (typeOfPlaceholder) {
|
||||||
case PlaceholderType::NotPlaceholder:
|
case PlaceholderType::NotPlaceholder:
|
||||||
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(placeholder, maxDepth);
|
||||||
case PlaceholderType::Unknown: {
|
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:
|
case PlaceholderType::Title:
|
||||||
return resolveMultiplePlaceholdersRecursive(title(), maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(title(), maxDepth);
|
||||||
case PlaceholderType::UserName:
|
case PlaceholderType::UserName:
|
||||||
return resolveMultiplePlaceholdersRecursive(username(), maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(username(), maxDepth);
|
||||||
case PlaceholderType::Password:
|
case PlaceholderType::Password:
|
||||||
return resolveMultiplePlaceholdersRecursive(password(), maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(password(), maxDepth);
|
||||||
case PlaceholderType::Notes:
|
case PlaceholderType::Notes:
|
||||||
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(notes(), maxDepth);
|
||||||
case PlaceholderType::Url:
|
case PlaceholderType::Url:
|
||||||
return resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
return resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||||
case PlaceholderType::DbDir: {
|
case PlaceholderType::DbDir: {
|
||||||
QFileInfo fileInfo(database()->filePath());
|
QFileInfo fileInfo(database()->filePath());
|
||||||
return fileInfo.absoluteDir().absolutePath();
|
return fileInfo.absoluteDir().absolutePath();
|
||||||
@ -1089,7 +1088,7 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
|||||||
case PlaceholderType::UrlUserInfo:
|
case PlaceholderType::UrlUserInfo:
|
||||||
case PlaceholderType::UrlUserName:
|
case PlaceholderType::UrlUserName:
|
||||||
case PlaceholderType::UrlPassword: {
|
case PlaceholderType::UrlPassword: {
|
||||||
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
|
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth);
|
||||||
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
|
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
|
||||||
}
|
}
|
||||||
case PlaceholderType::Totp:
|
case PlaceholderType::Totp:
|
||||||
@ -1097,10 +1096,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
|||||||
return totp();
|
return totp();
|
||||||
case PlaceholderType::CustomAttribute: {
|
case PlaceholderType::CustomAttribute: {
|
||||||
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
|
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:
|
case PlaceholderType::Reference:
|
||||||
return resolveReferencePlaceholderRecursive(placeholder, maxDepth);
|
return resolveReferencePlaceholderRecursive(placeholder, ++maxDepth);
|
||||||
case PlaceholderType::DateTimeSimple:
|
case PlaceholderType::DateTimeSimple:
|
||||||
case PlaceholderType::DateTimeYear:
|
case PlaceholderType::DateTimeYear:
|
||||||
case PlaceholderType::DateTimeMonth:
|
case PlaceholderType::DateTimeMonth:
|
||||||
@ -1115,7 +1115,11 @@ QString Entry::resolvePlaceholderRecursive(const QString& placeholder, int maxDe
|
|||||||
case PlaceholderType::DateTimeUtcHour:
|
case PlaceholderType::DateTimeUtcHour:
|
||||||
case PlaceholderType::DateTimeUtcMinute:
|
case PlaceholderType::DateTimeUtcMinute:
|
||||||
case PlaceholderType::DateTimeUtcSecond:
|
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;
|
return placeholder;
|
||||||
@ -1164,9 +1168,93 @@ QString Entry::resolveDateTimePlaceholder(Entry::PlaceholderType placeholderType
|
|||||||
return {};
|
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
|
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());
|
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", uuid().toString().toLatin1().data());
|
||||||
return placeholder;
|
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.
|
// 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
|
// 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.
|
// 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;
|
return result;
|
||||||
@ -1369,15 +1457,21 @@ QString Entry::resolveUrlPlaceholder(const QString& str, Entry::PlaceholderType
|
|||||||
|
|
||||||
Entry::PlaceholderType Entry::placeholderType(const QString& placeholder) const
|
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;
|
return PlaceholderType::NotPlaceholder;
|
||||||
}
|
}
|
||||||
if (placeholder.startsWith(QLatin1Literal("{S:"))) {
|
if (placeholder.startsWith(QStringLiteral("{S:"))) {
|
||||||
return PlaceholderType::CustomAttribute;
|
return PlaceholderType::CustomAttribute;
|
||||||
}
|
}
|
||||||
if (placeholder.startsWith(QLatin1Literal("{REF:"))) {
|
if (placeholder.startsWith(QStringLiteral("{REF:"))) {
|
||||||
return PlaceholderType::Reference;
|
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{
|
static const QMap<QString, PlaceholderType> placeholders{
|
||||||
{QStringLiteral("{TITLE}"), PlaceholderType::Title},
|
{QStringLiteral("{TITLE}"), PlaceholderType::Title},
|
||||||
|
@ -225,7 +225,9 @@ public:
|
|||||||
DateTimeUtcHour,
|
DateTimeUtcHour,
|
||||||
DateTimeUtcMinute,
|
DateTimeUtcMinute,
|
||||||
DateTimeUtcSecond,
|
DateTimeUtcSecond,
|
||||||
DbDir
|
DbDir,
|
||||||
|
Conversion,
|
||||||
|
Regex
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int DefaultIconNumber;
|
static const int DefaultIconNumber;
|
||||||
@ -244,6 +246,8 @@ public:
|
|||||||
QString resolvePlaceholder(const QString& str) const;
|
QString resolvePlaceholder(const QString& str) const;
|
||||||
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
|
||||||
QString resolveDateTimePlaceholder(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;
|
PlaceholderType placeholderType(const QString& placeholder) const;
|
||||||
QString resolveUrl(const QString& url) 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()
|
void TestEntry::testResolveClonedEntry()
|
||||||
{
|
{
|
||||||
Database db;
|
Database db;
|
||||||
|
@ -36,6 +36,8 @@ private slots:
|
|||||||
void testResolveRecursivePlaceholders();
|
void testResolveRecursivePlaceholders();
|
||||||
void testResolveReferencePlaceholders();
|
void testResolveReferencePlaceholders();
|
||||||
void testResolveNonIdPlaceholdersToUuid();
|
void testResolveNonIdPlaceholdersToUuid();
|
||||||
|
void testResolveConversionPlaceholders();
|
||||||
|
void testResolveReplacePlaceholders();
|
||||||
void testResolveClonedEntry();
|
void testResolveClonedEntry();
|
||||||
void testIsRecycled();
|
void testIsRecycled();
|
||||||
void testMoveUpDown();
|
void testMoveUpDown();
|
||||||
|
Loading…
Reference in New Issue
Block a user