Compare commits

...

10 Commits

Author SHA1 Message Date
Janek Bevendorff
5332075193 Update sponsors list and translators fetch script 2025-11-12 16:24:22 +01:00
Jonathan White
964bb59f71 Fix error in hardware key detection code on Windows 2025-11-09 16:11:38 -05:00
Jonathan White
5acfcc6a1f Prevent interface lockups during startup with multiple tabs
Fixes #11998

Avoids UI lockups by removing several unnecessary mutex blocks  and avoiding redundant key detection calls.

Detect Yubikeys dynamically when challenging:

Prevents issue where correct key cannot be found if the internal state was reset prior to saving

This can occur if a user has multiple tabs open and multiple keys connected. Then switches to a locked tab without their DB key inserted which resets detection state.

Side Benefit - ensures proper cascade between USB and PC/SC interfaces so users can switch between the two modes seamlessly.
2025-11-09 16:11:38 -05:00
Jonathan White
d87554e6d2 Implement Group sync for KeeShare (#11593)
---------

Co-authored-by: ever <ever@brokenmouse.studio>
Co-authored-by: Ben Kluwe <ben.kl@go4more.de>
2025-11-09 16:10:45 -05:00
Jonathan White
e542070902 Allow for escape syntax to enable literal placeholders
* Fixes #11890
2025-11-09 10:45:07 -05:00
Jonathan White
301c64d68c Don't clear clipboard if previously cleared
* Fixes #12591
2025-11-09 10:44:33 -05:00
Janek Bevendorff
77746846da Add Liquid Glass icon 2025-11-09 15:53:11 +01:00
Sven Strickroth
002b8fee15 Do not show misleading error message if user clicked cancel
Signed-off-by: Sven Strickroth <email@cs-ware.de>
2025-11-09 09:25:00 -05:00
Sven Strickroth
44daca921a Escape accelerators
(fixes issue #12037)

Signed-off-by: Sven Strickroth <email@cs-ware.de>
2025-11-07 15:47:14 -05:00
Markus Theil
fedcbf60c5 fix build with Botan 3.10
This fixes a compiler error I got,
when trying to build with Botan 3.10.

A static_cast to RSA_PrivateKey was not possible,
as the base class is virtual.

Fix by using a dynamic_cast instead.

Signed-off-by: Markus Theil <theil.markus@gmail.com>
2025-11-07 15:46:34 -05:00
34 changed files with 10796 additions and 1250 deletions

View File

@@ -49,6 +49,8 @@ This section contains full details on advanced features available in KeePassXC.
|{DB_DIR} |Absolute directory path of database file
|===
NOTE: You can insert literal placeholder strings by escaping the beginning and ending curly braces. For example, to insert the string `{USERNAME}`, you would type `++\{USERNAME\}++`.
=== Entry Cross-Reference
A reference to another entry's field is possible using the shorthand syntax:
`{REF:&lt;FIELD&gt;@&lt;SEARCH_IN&gt;:&lt;SEARCH_TEXT&gt;}`

View File

@@ -68,6 +68,7 @@ if(UNIX AND NOT APPLE AND NOT HAIKU)
endif(UNIX AND NOT APPLE AND NOT HAIKU)
if(APPLE)
install(FILES macosx/Assets.car DESTINATION ${DATA_INSTALL_DIR})
install(FILES macosx/keepassxc.icns DESTINATION ${DATA_INSTALL_DIR})
endif()
@@ -85,7 +86,16 @@ add_custom_command(TARGET icons
if(APPLE)
add_custom_command(TARGET icons
POST_BUILD
COMMAND png2icns macosx/keepassxc.icns icons/application/256x256/apps/keepassxc.png
COMMAND xcrun actool share/macosx/keepassxc.icon
--compile share/macosx
--output-partial-info-plist /dev/null
--app-icon keepassxc
--include-all-app-icons
--enable-on-demand-resources NO
--target-device mac
--minimum-deployment-target 11.0
--platform macosx
--output-format human-readable-text
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()

File diff suppressed because one or more lines are too long

BIN
share/macosx/Assets.car Normal file

Binary file not shown.

View File

@@ -14,6 +14,8 @@
<string>${PROGNAME}</string>
<key>CFBundleIconFile</key>
<string>keepassxc.icns</string>
<key>CFBundleIconName</key>
<string>keepassxc</string>
<key>CFBundleIdentifier</key>
<string>org.keepassxc.keepassxc</string>
<key>CFBundleInfoDictionaryVersion</key>

Binary file not shown.

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 128"><defs><style>.cls-1{fill:none;}.cls-2{fill:url(#radial-gradient-2);mix-blend-mode:lighten;opacity:.7;}.cls-3{fill:url(#radial-gradient);}.cls-4{isolation:isolate;}</style><radialGradient id="radial-gradient" cx="315.9556" cy="395.3416" fx="315.9556" fy="395.3416" r="239.1689" gradientTransform="translate(-46.999 -42.8948) scale(.3539 .2026)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2e6b26"/><stop offset="1" stop-color="#6ab536"/></radialGradient><radialGradient id="radial-gradient-2" cx="314.1662" cy="394.0804" fx="314.1662" fy="394.0804" r="46.7089" gradientTransform="translate(-46.999 -102.0755) scale(.3539)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#6ab536"/><stop offset="1" stop-color="#2e6b26"/></radialGradient></defs><g class="cls-4"><g id="Layer_2"><g id="Icon_macOS"><rect class="cls-1" width="128" height="128"/><path id="Background" class="cls-3" d="M63.9999,24.1601c-21.9679,0-39.84,17.8721-39.84,39.8399s17.8721,39.8399,39.84,39.8399,39.8399-17.8722,39.8399-39.8399-17.8719-39.8399-39.8399-39.8399Z"/><path id="Lighten_Hole" class="cls-2" d="M63.9998,27.4724c-6.5434,0-11.8668,5.3234-11.8668,11.8668s5.3234,11.8668,11.8668,11.8668,11.8668-5.3235,11.8668-11.8668-5.3234-11.8668-11.8668-11.8668Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 128"><defs><style>.cls-1{fill:none;}.cls-2{fill:url(#radial-gradient);opacity:.3;}.cls-2,.cls-3{mix-blend-mode:multiply;}.cls-4{fill:url(#radial-gradient-2);}.cls-5{isolation:isolate;}.cls-3{fill:#0f0f0d;opacity:.2;}</style><radialGradient id="radial-gradient" cx="322.2841" cy="405.7418" fx="322.2841" fy="405.7418" r="68.9894" gradientTransform="translate(-44.2199 -179.8556) scale(.3356 .5572)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000"/><stop offset=".7842" stop-color="#4f4f4f" stop-opacity="0"/></radialGradient><radialGradient id="radial-gradient-2" cx="313.1713" cy="380.0413" fx="313.1713" fy="380.0413" r="159.6501" gradientTransform="translate(-46.999 -102.0755) scale(.3539)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f5f5f5"/><stop offset=".5036" stop-color="#f2f2f2"/></radialGradient></defs><g class="cls-5"><g id="Layer_2"><g id="Icon_macOS"><rect class="cls-1" width="128" height="128"/><path id="Key_Double_Shadow" class="cls-2" d="M43.0622,29.9746c-.0019.0137-.0038.027-.0058.0406-.0518.0097-.0526-.0026.0058-.0406ZM73.1463,55.5462l-.551,9.8444,6.5017,7.0832-6.5017,7.0832,4.2977,4.6821-4.2977,4.6821.551,9.764h-18.2929v-43.139c-7.2731-3.7217-12.232-11.7653-12.232-21.0094,0-1.5471.2166-2.9765.435-4.5215.789-.1487,13.5481-5.4962,13.5481-5.4962-.1059.578,14.8965.578,14.7906,0,0,0,12.3269,5.3041,13.5637,5.5642.3128,1.5189.4195,2.9293.4195,4.4534,0,9.2442-5.0691,17.2878-12.232,21.0095ZM84.9375,29.9746c.0077.0365.0138.0722.0213.1086.1354.0285.14-.0025-.0213-.1086Z"/><path id="Key_Drop_Shadow" class="cls-3" d="M42.6543,90.0228c-7.0698-6.1486-11.5457-15.2121-11.5457-25.3548,0-11.8019,6.172-22.696,16.3377-28.7786-.1815,1.1802-.3631,2.2696-.3631,3.4498,0,6.9903,4.0844,13.0729,10.0749,15.8872v31.8652l7.5335,7.5351,7.5335-7.5351-.4538-6.6272,3.5398-3.5406-3.5398-3.5406,5.3551-5.3563-5.3551-5.3563.4538-7.4443c5.8997-2.8143,10.0749-8.8968,10.0749-15.8872,0-1.1802-.0908-2.2696-.3631-3.4498,3.6888,2.227,6.8516,5.1011,9.3871,8.4266-6.1315-8.4206-16.0924-13.8991-27.3404-13.8991-18.6365,0-33.7443,15.0357-33.7443,33.5832,0,10.495,4.8383,19.8644,12.4149,26.0227ZM62.241,84.5497h-2.5414v-25.1472h2.5414v25.1472ZM58.6104,31.7133c1.9968-.3631,4.0844-.5447,6.0813-.5447,2.0876-.0908,4.0844.1815,6.0813.5447.0908.4539.1815.9078.1815,1.3617,0,3.4498-2.8137,6.2641-6.2628,6.2641s-6.2628-2.8143-6.2628-6.2641c0-.4539.0908-.9078.1815-1.3617Z"/><path id="Key" class="cls-4" d="M63.9999,24.1601c-21.9582,0-39.8225,17.8721-39.8225,39.8399s17.8643,39.8399,39.8225,39.8399,39.8223-17.8723,39.8223-39.8399-17.8641-39.8399-39.8223-39.8399ZM57.9186,31.0088c1.9968-.3631,4.0844-.5447,6.0813-.5447,2.0876-.0908,4.0844.1815,6.0813.5447.0908.4539.1815.9078.1815,1.3617,0,3.4498-2.8137,6.2641-6.2628,6.2641s-6.2628-2.8143-6.2628-6.2641c0-.4539.0908-.9078.1815-1.3617ZM61.5492,58.6979v25.1472h-2.5414v-25.1472h2.5414ZM63.9999,97.5535c-18.5161,0-33.5831-14.9794-33.5831-33.5901,0-11.8019,6.172-22.696,16.3377-28.7786-.1815,1.1802-.3631,2.2696-.3631,3.4498,0,6.9903,4.0844,13.0729,10.0749,15.8872v31.8652l7.5335,7.5351,7.5335-7.5351-.4538-6.6272,3.5398-3.5406-3.5398-3.5406,5.3551-5.3563-5.3551-5.3563.4538-7.4443c5.8997-2.8143,10.0749-8.8968,10.0749-15.8872,0-1.1802-.0908-2.2696-.3631-3.4498,10.0749,6.0826,16.247,16.9766,16.3377,28.7786,0,18.5199-14.9763,33.5901-33.5831,33.5901Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 128"><defs><style>.cls-1{fill:none;}.cls-2{fill:url(#radial-gradient-2);opacity:.44;}.cls-3{fill:#0f0f0d;opacity:.35;}.cls-4{fill:url(#linear-gradient-2);}.cls-5{fill:url(#linear-gradient);}.cls-6{isolation:isolate;}.cls-7{fill:url(#radial-gradient);mix-blend-mode:lighten;opacity:.32;}.cls-8{opacity:.6;}.cls-9{fill:rgba(15,15,13,.35);}</style><linearGradient id="linear-gradient" x1="63.9998" y1="20.4513" x2="63.9998" y2="107.5488" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000"/><stop offset="1" stop-color="#000" stop-opacity=".2"/></linearGradient><linearGradient id="linear-gradient-2" x1="63.9998" y1="15.9186" x2="63.9998" y2="102.9186" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#414141"/><stop offset=".196" stop-color="#3e3e3e"/><stop offset="1" stop-color="#3a3a3a"/></linearGradient><radialGradient id="radial-gradient" cx="305.8904" cy="449.6754" fx="305.8904" fy="449.6754" r="148.0242" gradientTransform="translate(-46.999 -102.0755) scale(.3539)" gradientUnits="userSpaceOnUse"><stop offset=".6733" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-color="#fcfcfc"/></radialGradient><radialGradient id="radial-gradient-2" cx="311.8517" cy="469.1737" fx="311.8517" fy="469.1737" r="123.1352" gradientTransform="translate(-46.999 -102.0755) scale(.3539)" gradientUnits="userSpaceOnUse"><stop offset=".1088" stop-color="#0f0f0d" stop-opacity="0"/><stop offset=".8856" stop-color="#414141" stop-opacity="0"/><stop offset=".9339" stop-color="#1e1e1d" stop-opacity=".6841"/><stop offset=".9874" stop-color="#0f0f0d"/></radialGradient></defs><g class="cls-6"><g id="Layer_2"><g id="Icon_macOS"><rect class="cls-1" width="128" height="128"/><g id="Countour"><g class="cls-8"><path class="cls-5" d="M63.9999,20.8039c23.7037,0,42.9881,19.3777,42.9881,43.1962s-19.2844,43.1962-42.9881,43.1962-42.9882-19.3777-42.9882-43.1962S40.2961,20.8039,63.9999,20.8039M63.9999,20.4513c-23.8983,0-43.3408,19.5358-43.3408,43.5487s19.4425,43.5487,43.3408,43.5487,43.3406-19.536,43.3406-43.5487-19.4423-43.5487-43.3406-43.5487h0Z"/></g></g><path id="Rim" class="cls-4" d="M63.9999,107.5c-23.9861,0-43.5001-19.5141-43.5001-43.5S40.0138,20.5,63.9999,20.5s43.4999,19.5139,43.4999,43.5-19.5138,43.5-43.4999,43.5ZM63.9999,24.1601c-21.9582,0-39.8225,17.8721-39.8225,39.8399s17.8643,39.8399,39.8225,39.8399,39.8223-17.8723,39.8223-39.8399-17.8641-39.8399-39.8223-39.8399Z"/><path id="Bottom_Light" class="cls-7" d="M63.9999,20.517c-23.9767,0-43.4831,19.5063-43.4831,43.483s19.5064,43.483,43.4831,43.483,43.4829-19.5065,43.4829-43.483-19.5062-43.483-43.4829-43.483Z"/><path id="Inner_Shadow" class="cls-2" d="M63.9999,107.5c-23.9861,0-43.5001-19.5141-43.5001-43.5S40.0138,20.5,63.9999,20.5s43.4999,19.5139,43.4999,43.5-19.5138,43.5-43.4999,43.5ZM63.9999,24.1601c-21.9582,0-39.8225,17.8721-39.8225,39.8399s17.8643,39.8399,39.8225,39.8399,39.8223-17.8723,39.8223-39.8399-17.8641-39.8399-39.8223-39.8399Z"/><path id="Bottom_Drop_Shadow" class="cls-9" d="M63.9999,24.3931c-22.1274,0-40.1292,18.0018-40.1292,40.1291s18.0018,40.1291,40.1292,40.1291,40.129-18.002,40.129-40.1291-18.0017-40.1291-40.129-40.1291Z"/><path id="Top_Drop_Shadow" class="cls-3" d="M63.9999,107.5c-23.9861,0-43.5001-19.5141-43.5001-43.5S40.0138,20.5,63.9999,20.5s43.4999,19.5139,43.4999,43.5-19.5138,43.5-43.4999,43.5ZM63.9999,21.431c-23.986,0-43.5001,19.2544-43.5001,42.9213s19.5141,42.9213,43.5001,42.9213,43.4999-19.2546,43.4999-42.9213-19.5138-42.9213-43.4999-42.9213Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,99 @@
{
"fill-specializations" : [
{
"value" : "automatic"
},
{
"appearance" : "dark",
"value" : "automatic"
}
],
"groups" : [
{
"blur-material" : null,
"layers" : [
{
"glass" : true,
"image-name" : "macos-comp-key.svg",
"name" : "key",
"position" : {
"scale" : 9.5,
"translation-in-points" : [
0,
0
]
}
}
],
"lighting" : "individual",
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : true,
"translucency" : {
"enabled" : true,
"value" : 0.3
}
},
{
"blur-material" : null,
"layers" : [
{
"glass" : true,
"image-name" : "macos-comp-bg-green.svg",
"name" : "bg-green",
"position" : {
"scale" : 9.5,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : true,
"translucency" : {
"enabled" : true,
"value" : 0.5
}
},
{
"blur-material" : 0.5,
"hidden" : false,
"layers" : [
{
"glass" : true,
"image-name" : "macos-comp-rim.svg",
"name" : "rim",
"position" : {
"scale" : 9.5,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.7
},
"specular" : true,
"translucency" : {
"enabled" : true,
"value" : 0.2
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View File

@@ -3735,6 +3735,14 @@ Supported extensions are: %1.</source>
<source>Select import/export file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Maintain group structure with shared database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Keep Group Structure</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditGroupWidgetMain</name>
@@ -10590,11 +10598,7 @@ Example: JBSWY3DPEHPK3PXP</source>
<context>
<name>YubiKey</name>
<message>
<source>General: </source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not find interface for hardware key with serial number %1. Please connect it to continue.</source>
<source>Could not find hardware key with serial number %1. Please connect it to continue.</source>
<translation type="unfinished"></translation>
</message>
</context>
@@ -10655,10 +10659,6 @@ Example: JBSWY3DPEHPK3PXP</source>
</context>
<context>
<name>YubiKeyInterfacePCSC</name>
<message>
<source>Could not find or access hardware key with serial number %1. Please present it to continue. </source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Hardware key is locked or timed out. Unlock or re-present it to continue.</source>
<translation type="unfinished"></translation>

View File

@@ -1138,6 +1138,15 @@ QString Entry::resolveMultiplePlaceholdersRecursive(const QString& str, int maxD
return str;
}
// Short circuit if we have escaped the placeholder brackets
if (str.startsWith("\\{") && str.endsWith("\\}")) {
// Replace the escaped brackets with actuals and move on
auto ret = str;
ret.replace(0, 2, "{");
ret.replace(ret.size() - 2, 2, "}");
return ret;
}
QString result;
auto matches = placeholderRegEx.globalMatch(str);
int capEnd = 0;

View File

@@ -449,6 +449,11 @@ namespace Tools
return userName;
}
QString escapeAccelerators(QString string)
{
return string.replace("&", "&&");
}
QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties)
{
QVariantMap result;

View File

@@ -49,6 +49,7 @@ namespace Tools
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment());
QString cleanFilename(QString filename);
QString cleanUsername();
QString escapeAccelerators(QString string);
template <class T> QSet<T> asSet(const QList<T>& a)
{

View File

@@ -44,29 +44,43 @@ static const QString aboutContributors = R"(
<li>Sergey Vilgelm</li>
<li>Victor Engmark</li>
<li>NarwhalOfAges</li>
<li>No Name</li>
<li>SG</li>
<li>Riley Moses</li>
<li>Esteban Martinez</li>
<li>Marc Morocutti</li>
<li>Zivix</li>
<li>Benedikt Heine</li>
<li>Hugo Locurcio</li>
<li>William Petrides</li>
<li>Gunar Gessner</li>
<li>Christian Wittenhorst</li>
<li>Matt Cardarelli</li>
<li>Paul Ammann</li>
<li>Steve Isom</li>
<li>GodSpell</li>
<li>Lionel Laské</li>
<li>Daniel Epp</li>
<li>Oleksii Aleksieiev</li>
<li>Julian Stier</li>
<li>Ruben Schade</li>
<li>Bernhard</li>
<li>Wojciech Kozlowski</li>
<li>Caleb Currie</li>
<li>Morgan Courbet</li>
<li>Kyle Kneitinger</li>
<li>Chris Sohns</li>
<li>Shmavon Gazanchyan</li>
<li>xjdwc</li>
<li>Riley Moses</li>
<li>Igor Zinovik</li>
<li>Jeff</li>
<li>Esteban Martinez</li>
<li>Max Andersen</li>
<li>Zivix</li>
<li>Marc Morocutti</li>
<li>super scampy</li>
<li>Hugo Locurcio</li>
<li>Benedikt Heine</li>
<li>Mischa Peters</li>
<li>Rainer-Maria Fritsch</li>
<li>Micha Ober</li>
<li>Ivan Gromov</li>
<li>William Petrides</li>
<li>Joshua Go</li>
<li>Gunar Gessner</li>
<li>pancakeplant</li>
<li>Hans-Joachim Forker</li>
<li>Nicolas Vandemaele</li>
@@ -75,30 +89,66 @@ static const QString aboutContributors = R"(
<li>Mike</li>
<li>Thomas Renz</li>
<li>Toby Cline</li>
<li>Christian Wittenhorst</li>
<li>Paul Ammann</li>
<li>Matt Cardarelli</li>
<li>Steve Isom</li>
<li>Emre Dessoi</li>
<li>Wojciech Kozlowski</li>
<li>Michael Babnick</li>
<li>kernellinux</li>
<li>Patrick Evans</li>
<li>Marco</li>
<li>GodSpell</li>
<li>Jeremy Rubin</li>
<li>Korbi</li>
<li>andreas</li>
<li>Tyche's tidings</li>
<li>Daniel Kuebler</li>
<li>Brandon Corujo</li>
<li>AheroX</li>
<li>Alexandre G</li>
<li>AshinaGa</li>
<li>BYTEBOLT</li>
<li>CEH</li>
<li>Chris Stone</li>
<li>Christof Böckler</li>
<li>Claude</li>
<li>CzLer</li>
<li>Daniel Burridge</li>
<li>dark</li>
<li>Dave G</li>
<li>David Bowers</li>
<li>dickv</li>
<li>fp4</li>
<li>Huser IT Solutions GmbH</li>
<li>irf</li>
<li>Isaiah Rahmany</li>
<li>JackNYC</li>
<li>Jacob Emmert-Aronson</li>
<li>John Donadeo</li>
<li>Kosta Medinsky</li>
<li>leinad987</li>
<li>Lux</li>
<li>marek</li>
<li>mattlongname</li>
<li>mattock</li>
<li>Max Christian Pohle</li>
<li>nta/norma</li>
<li>picatsv</li>
<li>proto</li>
<li>Raymond Lau</li>
<li>Waido</li>
<li>Weinmann Willy</li>
<li>WildMage</li>
</ul>
<h3>VIP GitHub Sponsors:</h3>
<ul>
<li>mercedes-benz</li>
<li>tiangolo</li>
<li>mrniko</li>
<li>rszamszur</li>
</ul>
<h3>Notable Code Contributions:</h3>
<ul>
<li>droidmonkey</li>
<li>phoerious</li>
<li>louib (CLI)</li>
<li>varjolintu (Browser Integration)</li>
<li>louib (CLI)</li>
<li>hifi (SSH Agent)</li>
<li>xvallspl (Tags)</li>
<li>Aetf (FdoSecrets Storage Server)</li>
@@ -109,6 +159,8 @@ static const QString aboutContributors = R"(
<li>brainplot (many improvements)</li>
<li>kneitinger (many improvements)</li>
<li>frostasm (many improvements)</li>
<li>libklein (many improvements)</li>
<li>w15eacre (many improvements)</li>
<li>fonic (Entry Table View)</li>
<li>kylemanna (YubiKey)</li>
<li>c4rlo (Offline HIBP Checker)</li>
@@ -121,179 +173,145 @@ static const QString aboutContributors = R"(
</ul>
<h3>Patreon Supporters:</h3>
<ul>
<li>Richard Ames</li>
<li>Bernhard</li>
<li>Christian Rasmussen</li>
<li>Nuutti Toivola</li>
<li>Lionel Laské</li>
<li>Tyler Gass</li>
<li>NZSmartie</li>
<li>Darren</li>
<li>Brad</li>
<li>Oleksii Aleksieiev</li>
<li>Julian Stier</li>
<li>Daniel Epp</li>
<li>Ruben Schade</li>
<li>William Komanetsky</li>
<li>Niels Ganser</li>
<li>judd</li>
<li>Tarek Sherif</li>
<li>Eugene</li>
<li>CYB3RL4MBD4</li>
<li>Alexanderjb</li>
<li>Justin Carroll</li>
<li>Bart Libert</li>
<li>Shintaro Matsushima</li>
<li>Thammachart Chinvarapon</li>
<li>Gernot Premper</li>
<li>SLmanDR</li>
<li>Paul Ellenbogen</li>
<li>John C</li>
<li>Markus Wochnik</li>
<li>Clark Henry</li>
<li>zapscribe</li>
<li>Salt Rock Lamp</li>
<li>Steven Crowley</li>
<li>Ralph Azucena</li>
<li>Guruprasad Kulkarni</li>
<li>jose</li>
<li>Michael Gulick</li>
<li>J Doty</li>
<li>Synchro11</li>
<li>Michael Soares</li>
<li>Johannes Felko</li>
<li>Ellie</li>
<li>David Walluscheck</li>
<li>Anthony Avina</li>
<li>pro</li>
<li>Mark Luxton</li>
<li>Markus</li>
<li>Crimson Idol</li>
<li>Björn König</li>
<li>René Weselowski</li>
<li>gonczor</li>
<li>Steven</li>
<li>Ellie</li>
<li>Anthony Avina</li>
<li>PlushElderGod</li>
<li>gilgwath</li>
<li>Tobias</li>
<li>zapscribe</li>
<li>Christopher Hillenbrand</li>
<li>Daddy's c$sh</li>
<li>Ashura</li>
<li>Florian</li>
<li>Alexandre</li>
<li>Dave Jones</li>
<li>Brett</li>
<li>Jim Vanderbilt</li>
<li>Brian McGuire</li>
<li>Sid Beske</li>
<li>Dmitrii Galinskii</li>
<li>Johannes Erchen</li>
<li>Brandon Zhang</li>
<li>Maxley Fraser</li>
<li>Nikul Savasadia</li>
<li>Claude</li>
<li>alga</li>
<li>Philipp Jetschina</li>
<li>Ralph Azucena</li>
<li>Florian</li>
<li>Kristoffer Winther Balling</li>
<li>Peter Link</li>
<li>Vlastimil Vondra</li>
<li>Tony Wang</li>
<li>John Sivak</li>
<li>Nol Aders</li>
<li>Charlie Drake</li>
<li>Ryan Goldstein</li>
<li>Doug Witt</li>
<li>David S H Rosenthal</li>
<li>Lance Simmons</li>
<li>Mathew Woodyard</li>
<li>GanderPL</li>
<li>Neša</li>
<li>tolias</li>
<li>Michael Soares</li>
<li>Vlad Didenko</li>
<li>henloo</li>
<li>Erik Rigtorp</li>
<li>Barry McKenzie</li>
<li>Sebastian van der Est</li>
<li>J.C. Polanycia</li>
<li>Peter Upfold</li>
<li>Josh Yates-Walker</li>
<li>Adam</li>
<li>HJ</li>
<li>bjorndown</li>
<li>Tony Wang</li>
<li>Nol Aders</li>
<li>Dirk Bergstrom</li>
<li>proco</li>
<li>Philipp Baderschneider</li>
<li>Neša</li>
<li>Dimitris Kogias</li>
<li>Robin Hellsten</li>
<li>Scott Williams</li>
<li>klepto68</li>
<li>Uwe S.</li>
<li>codiflow</li>
<li>eugene</li>
<li>Anton Fisher</li>
<li>David Daly</li>
<li>Crispy_Steak</li>
<li>Cilestin</li>
<li>Benjamin</li>
<li>Daniel Lakeland</li>
<li>erinacio</li>
<li>Leo</li>
<li>Payton</li>
<li>Saicxs</li>
<li>Gorund O</li>
<li>Tony G</li>
<li>Simonas S.</li>
<li>LordKnoppers</li>
<li>Fabien Duchaussois</li>
<li>Tim Bahnes</li>
<li>Aleksei Gusev</li>
<li>J Hanssen</li>
<li>schoepp</li>
<li>Klaus</li>
<li>Eric</li>
<li>Griffondale</li>
<li>Andy D</li>
<li>YAMAMOTO Yuji</li>
<li>elmiko</li>
<li>David</li>
<li>Nate Wynd</li>
<li>Nicolas</li>
<li>magila</li>
<li>Bryan Fisher</li>
<li>Mark Nicholson</li>
<li>Asperatus</li>
<li>Patrick Buchan-Hepburn</li>
<li>Richárd Faragó</li>
<li>David Koch</li>
<li>cheese_cake</li>
<li>duke_money</li>
<li>lund</li>
<li>Ivana Kellyer</li>
<li>Skullzam</li>
<li>Chris Bier</li>
<li>Gustavo</li>
<li>Henning_IdB</li>
<li>Edd</li>
<li>Net</li>
<li>Sergei Slipchenko</li>
<li>Amanita</li>
<li>Gaara</li>
<li>Max</li>
<li>5h4d3</li>
<li>James Taylor</li>
<li>Alexei Bond</li>
<li>cck</li>
<li>David L</li>
<li>devNull</li>
<li>Erica</li>
<li>Matthew O</li>
<li>Druggo Yang</li>
<li>Eric Stokes</li>
</ul>
<h3>GitHub Sponsors:</h3>
<ul>
<li>rszamszur</li>
<li>Sidicas</li>
<li>Mr-NH</li>
</ul>
<h3>Translations:</h3>
<ul>
<li><strong>العربية (Arabic)</strong>: 3eani, 3nad, AboShanab, butterflyoffire_temp, jBailony, kmutahar, m.hemoudi,
Marouane87, microtaha, mohame1d, muha_abdulaziz, Night1, omar.nsy, TheAhmed, zer0x</li>
<li><strong>euskara (Basque)</strong>: azken_tximinoa, Galaipa, Hey_neken, porrumentzio</li>
<li><strong> (Bengali)</strong>: codesmite, Foxom, rediancool, RHJihan</li>
<li><strong> (Burmese)</strong>: Christine.Ivy, hafe14, Snooooowwwwwman, tuntunaung</li>
<li><strong>català (Catalan)</strong>: antoniopolonio, benLabcat, capitantrueno, dsoms, Ecron, jamalinu, jmaribau,
MarcRiera, mcus, raulua, zeehio, ZJaume</li>
<li><strong> (Chinese (Simplified))</strong>: Biacke, Biggulu, Brandon_c, carp0129, Clafiok, deluxghost, Dy64,
ef6, Felix2yu, hoilc, jy06308127, kikyous, kofzhanganguo, ligyxy, lxx4380, oksjd, remonli, ShuiHuo, sjdhome,
slgray, Small_Ku, snhun, umi_neko, vc5, Wylmer_Wang, Z4HD</li>
<li><strong> () (Chinese (Traditional))</strong>: BestSteve, Biacke, flachesis, gojpdchx, ligyxy, MiauLightouch,
plesry, priv, raymondtau, Siriusmart, Small_Ku, ssuhung, Superbil, th3lusive, yan12125, ymhuang0808</li>
<li><strong>hrvatski jezik (Croatian)</strong>: krekrekre, mladenuzelac</li>
<li><strong>čeština (Czech)</strong>: DanielMilde, jiri.jagos, pavelb, pavelz, S474N, stps, tpavelek, vojtechjurcik</li>
<li><strong>dansk (Danish)</strong>: alfabetacain, dovecode, ebbe, ERYpTION, GimliDk, Grooty12, JakobPP, KalleDK,
MannVera, nlkl, Saustrup, thniels</li>
<li><strong>Nederlands (Dutch)</strong>: apie, bartlibert, Bubbel, bython, Dr.Default, e2jk, evanoosten, fourwood,
fvw, glotzbach, JCKalman, keunes, KnooL, ms.vd.linden, ovisicnarf, pietermj, pvdl, rigrig, srgvg, Stephan_P,
stijndubrul, theniels17, ThomasChurchman, timschreinemachers, Vistaus, wanderingidea, Zombaya1</li>
<li><strong>Esperanto (Esperanto)</strong>: batisteo</li>
<li><strong>eesti (Estonian)</strong>: Hermanio, okul, sarnane, tlend, V6lur</li>
<li><strong>suomi (Finnish)</strong>: artnay, hif1, MawKKe, petri, tomisalmi, uusijani, varjolintu</li>
<li><strong>français (French)</strong>: ayiniho, Beatussum, butterflyoffire_temp, Cabirto, francoisa, iannick,
jean_pierre_raumer, John.Mickael, Jojo6375, lacnic, Marouane87, mohame1d, orion78fr, stephanecodes, swarmpan,
t0mmy742, Takeçi, tenzap, webafrancois, x0rld</li>
<li><strong>Galego (Galician)</strong>: damufo, enfeitizador, mustek</li>
<li><strong>Deutsch (German)</strong>: andreas.maier, antsas, archer_321, ASDFGamer, Atalanttore, BasicBaer, blacksn0w,
bwolkchen, Calyrx, clonejo, codejunky, DavidHamburg, eth0, fahstat, FlotterCodername, for1real, frlan, funny0facer,
Gyges, h_h, Hativ, heynemax, hjonas, HoferJulian, hueku, janis91, jensrutschmann, jhit, joe776, kflesch, man_at_home,
marcbone, MarcEdinger, markusd112, Marouane87, maxwxyz, mcliquid, mfernau77, mircsicz, montilo, MuehlburgPhoenix,
muellerma, nautilusx, neon64, Nerzahd, Nightwriter, noodles101, NotAName, nursoda, OLLI_S, omnisome4, origin_de,
pcrcoding, PFischbeck, phallobst, philje, pqtjhhBzDd5NuJ9, r3drock, rakekniven, revoltek, rgloor, Rheggie, RogueThorn,
rugk, ScholliYT, scotwee, Silas_229, spacemanspiff, SteffoSpieler, testarossa47, TheForcer, thillux, transi_222, traschke,
Unkn0wnCat, vlenzer, vpav, waster, wolfram.roesler, Wyrrrd, xf</li>
<li><strong>ελληνικά (Greek)</strong>: anvo, arttor, Dkafetzis, giwrgosmant, GorianM, Jason_M, magkopian, nplatis, saglogog,
tassos.b, xinomilo</li>
<li><strong>עברית (Hebrew)</strong>: avimar, ronyala, shemeshg, shmag18, ThunderB0lt, tryandtry, ztwersky</li>
<li><strong>magyar (Hungarian)</strong>: andras_tim, bubu, entaevau, kempelen, meskobalazs, spammy, typingseashell, urbalazs</li>
<li><strong>Íslenska (Icelandic)</strong>: MannVera</li>
<li><strong>Bahasa Indonesia (Indonesian)</strong>: achmad, algustionesa, bora_ach, racrbmr, zk</li>
<li><strong>Italiano (Italian)</strong>: aleb2000, amaxis, bovirus, duncanmid, FranzMari, Gringoarg, idetao, lucaim, NITAL, Peo,
Pietrog, salvatorecordiano, seatedscribe, Stemby, the.sailor, tosky, VosaxAlo</li>
<li><strong> (Japanese)</strong>: AlCooo, gojpdchx, helloguys, masoo, p2635, Shinichirou_Yamada, shortarrow, ssuhung, tadasu,
take100yen, Umoxfo, vargas.peniel, vmemjp, WatanabeShint, yukinakato</li>
<li><strong>қазақ тілі (Kazakh)</strong>: sotrud_nik</li>
<li><strong> (Korean)</strong>: BraINstinct0, cancantun, peremen</li>
<li><strong>latine (Latin)</strong>: alexandercrice</li>
<li><strong>latviešu valoda (Latvian)</strong>: andis.luksho, victormeirans, wakeeshi</li>
<li><strong>lietuvių kalba (Lithuanian)</strong>: Kornelijus, Moo, pauliusbaulius, rookwood101, wakeeshi</li>
<li><strong>Norsk Bokmål (Norwegian Bokmål)</strong>: bkvamme, eirikl, eothred, haarek, JardarBolin, jumpingmushroom, sattor,
torgeirf, ysteinalver</li>
<li><strong> (Punjabi)</strong>: aalam</li>
<li><strong>فارسی (Farsi)</strong>: gnulover, siamax</li>
<li><strong>فارسی (Farsi (Iran))</strong>: magnifico</li>
<li><strong>język polski (Polish)</strong>: AreYouLoco, dedal123, EsEnZeT, hoek, keypress, konradmb, mrerexx, pabli, ply,
psobczak, SebJez, verahawk</li>
<li><strong>Português (Portuguese)</strong>: diraol, hugok, pfialho, rudahximenes, weslly, xendez</li>
<li><strong>Português (Portuguese (Brazil))</strong>: alinda, amalvarenga, andersoniop, danielbibit, diraol, fabiom, flaviobn,
fmilagres, furious_, gabrieljcs, Guilherme.Peev, guilherme__sr, Havokdan, igorruckert, josephelias94, keeBR, kiskadee, lecalam,
lucasjsoliveira, mauri.andres, newmanisaac, rafaelnp, ruanmed, rudahximenes, ul1sses, vitor895, weslly, wtuemura, xendez,
zodSilence</li>
<li><strong>Português (Portuguese (Portugal))</strong>: a.santos, American_Jesus, arainho, hds, hugok, lecalam, lmagomes, pfialho,
smarquespt, smiguel, xendez, xnenjm</li>
<li><strong>Română (Romanian)</strong>: _parasite_, aduzsardi, alexminza, polearnik</li>
<li><strong>русский (Russian)</strong>: 3nad, _nomoretears_, agag11507, alexandersokol, alexminza, anm, artemkonenko, ashed,
BANOnotIT, burningalchemist, cl0ne, cnide, denoos, DG, DmitriyMaksimov, egorrabota, injseon, Japet, JayDi85, KekcuHa, kerastinell,
laborxcom, leo9uinuo98, Mogost, Mr.GreyWolf, MustangDSG, netforhack, NetWormKido, nibir, Olesya_Gerasimenko, onix, Orianti,
RKuchma, ruslan.denisenko, ShareDVI, Shevchuk, solodyagin, talvind, treylav, upupa, VictorR2007, vsvyatski, wakeeshi, Walter.S,
wkill95, wtigga, zOrg1331</li>
<li><strong>српски језик (Serbian)</strong>: ArtBIT, ozzii</li>
<li><strong>Slovenčina (Slovak)</strong>: Asprotes, crazko, jose1711, l.martinicky, pecer, reisuya, Slavko</li>
<li><strong>Slovenščina (Slovenian)</strong>: asasdasd, samodekleva</li>
<li><strong>Español (Spanish)</strong>: adolfogc, antifaz, capitantrueno, cquike, cyphra, DarkHolme, doubleshuffle, e2jk,
EdwardNavarro, fserrador, gabeweb, gonrial, jjtp, jorpilo, LeoBeltran, mauri.andres, piegope, pquin, puchrojo, rodolfo.guagnini,
tierracomun, vsvyatski</li>
<li><strong>Svenska (Swedish)</strong>: 0x9fff00, aiix, Anders_Bergqvist, ArmanB, Autom, baxtex, eson, henziger, jpyllman, malkus,
merikan, peron, peterkz, Thelin, theschitz, victorhggqvst</li>
<li><strong> (Thai)</strong>: arthit, ben_cm, chumaporn.t, darika, digitalthailandproject, GitJirasamatakij, ll3an, minoplhy,
muhammadmumean, nimid, nipattra, ordinaryjane, rayg, sirawat, Socialister, Wipanee</li>
<li><strong>Türkçe (Turkish)</strong>: abcmen, ahmed.ulusoy, cagries, denizoglu, desc4rtes, etc, ethem578, kayazeren, mcveri, N3pp,
rgucluer, SeLeNLeR, sprlptr48, TeknoMobil, Ven_Zallow, veysiertekin</li>
<li><strong>Українська (Ukrainian)</strong>: brisk022, chulivska, cl0ne, exlevan, m0stik, moudrick, netforhack, olko, onix, paul_sm,
ShareDVI, upupa, zoresvit</li>
<li><strong>Arabic:</strong> kmutahar</li>
<li><strong>Chinese (China):</strong> Biggulu, Brandon_c, hoilc, ligyxy, Small_Ku, umi_neko, vc5</li>
<li><strong>Chinese (Taiwan):</strong> BestSteve, flachesis, MiauLightouch, Small_Ku, yan12125, ymhuang0808</li>
<li><strong>Czech:</strong> DanielMilde, pavelb, tpavelek</li>
<li><strong>English (United Kingdom):</strong> YCMHARHZ</li>
<li><strong>English (United States):</strong> alexandercrice, DarkHolme, nguyenlekhtn</li>
<li><strong>Finnish:</strong> artnay, hif1, MawKKe, varjolintu</li>
<li><strong>German:</strong> antsas, BasicBaer, Calyrx, codejunky, DavidHamburg, eth0, for1real, jensrutschmann,
joe776, kflesch, marcbone, MarcEdinger, mcliquid, mfernau77, montilo, nursoda, omnisome4, origin_de, pcrcoding,
rgloor, vlenzer, waster, Wyrrrd</li>
<li><strong>Greek:</strong> magkopian, nplatis, tassos.b, xinomilo</li>
<li><strong>Hungarian:</strong> bubu, meskobalazs, urbalazs</li>
<li><strong>Indonesian:</strong> zk</li>
<li><strong>Italian:</strong> amaxis, duncanmid, FranzMari, lucaim, NITAL, Peo, tosky, VosaxAlo</li>
<li><strong>Japanese:</strong> masoo, p2635, Shinichirou_Yamada, vmemjp, yukinakato</li>
<li><strong>Korean:</strong> cancantun, peremen</li>
<li><strong>Lithuanian:</strong> Moo</li>
<li><strong>Norwegian Bokmål:</strong> eothred, haarek, torgeirf</li>
<li><strong>Polish:</strong> keypress, mrerexx, psobczak</li>
<li><strong>Portuguese (Brazil):</strong> danielbibit, fabiom, flaviobn, newmanisaac, vitor895, weslly</li>
<li><strong>Portuguese (Portugal):</strong> a.santos, American_Jesus, hds, lmagomes, smarquespt</li>
<li><strong>Romanian:</strong> alexminza</li>
<li><strong>Russian:</strong> _nomoretears_, agag11507, alexminza, anm, artemkonenko, denoos, KekcuHa, Mogost,
netforhack, NetWormKido, RKuchma, ShareDVI, talvind, VictorR2007, vsvyatski, wkill95</li>
<li><strong>Serbian:</strong> ArtBIT</li>
<li><strong>Swedish:</strong> Anders_Bergqvist, henziger, jpyllman, peron, Thelin</li>
<li><strong>Turkish:</strong> etc, N3pp</li>
<li><strong>Ukrainian:</strong> brisk022, netforhack, ShareDVI, zoresvit</li>
</ul>
)";

View File

@@ -100,8 +100,9 @@ void Clipboard::clearCopiedText()
return;
}
if (m_lastCopied == clipboard->text(QClipboard::Clipboard)
|| m_lastCopied == clipboard->text(QClipboard::Selection)) {
if (!m_lastCopied.isEmpty()
&& (m_lastCopied == clipboard->text(QClipboard::Clipboard)
|| m_lastCopied == clipboard->text(QClipboard::Selection))) {
clipboard->clear(QClipboard::Clipboard);
clipboard->clear(QClipboard::Selection);
#ifdef Q_OS_UNIX

View File

@@ -674,7 +674,7 @@ void DatabaseTabWidget::updateTabName(int index)
return;
}
index = indexOf(dbWidget);
setTabText(index, tabName(index));
setTabText(index, Tools::escapeAccelerators(tabName(index)));
setTabToolTip(index, dbWidget->displayFilePath());
auto iconIndex = dbWidget->database()->publicIcon();
if (iconIndex >= 0 && iconIndex < databaseIcons()->count()) {

View File

@@ -38,6 +38,7 @@
#include "autotype/AutoType.h"
#include "core/InactivityTimer.h"
#include "core/Resources.h"
#include "core/Tools.h"
#include "gui/AboutDialog.h"
#include "gui/ActionCollection.h"
#include "gui/Icons.h"
@@ -789,7 +790,7 @@ void MainWindow::updateLastDatabasesMenu()
const QStringList lastDatabases = config()->get(Config::LastDatabases).toStringList();
for (const QString& database : lastDatabases) {
QAction* action = m_ui->menuRecentDatabases->addAction(database);
QAction* action = m_ui->menuRecentDatabases->addAction(Tools::escapeAccelerators(database));
action->setData(database);
m_lastDatabasesActions->addAction(action);
}

View File

@@ -132,6 +132,9 @@ void KeyFileEditWidget::browseKeyFile()
QString filters = QString("%1 (*.keyx *.key);;%2 (*)").arg(tr("Key files"), tr("All files"));
QString fileName = fileDialog()->getOpenFileName(this, tr("Select a key file"), QString(), filters);
if (fileName.isEmpty()) { // user clicked on cancel
return;
}
if (QFileInfo(fileName).canonicalFilePath() == m_parent->getDatabase()->canonicalFilePath()) {
MessageBox::critical(getMainWindow(),
tr("Invalid Key File"),

View File

@@ -56,7 +56,6 @@ void DeviceListenerWin::registerHotplugCallback(bool arrived,
regex += QString("PID_%1&").arg(productId, 0, 16).toUpper();
}
}
regex += QString(".*$"); // Qt won't match otherwise
m_deviceIdMatch = QRegularExpression(regex);
DEV_BROADCAST_DEVICEINTERFACE_W notificationFilter{
@@ -95,7 +94,7 @@ bool DeviceListenerWin::nativeEventFilter(const QByteArray& eventType, void* mes
|| (m_handleRemoval && m->wParam == DBT_DEVICEREMOVECOMPLETE)) {
const auto pBrHdr = reinterpret_cast<PDEV_BROADCAST_HDR>(m->lParam);
const auto pDevIface = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE_W>(pBrHdr);
const auto name = QString::fromWCharArray(pDevIface->dbcc_name, pDevIface->dbcc_size);
const auto name = QString::fromWCharArray(pDevIface->dbcc_name);
if (m_deviceIdMatch.match(name).hasMatch()) {
emit devicePlugged(m->wParam == DBT_DEVICEARRIVAL, nullptr, pDevIface);
return true;

View File

@@ -213,7 +213,7 @@ namespace KeeShareSettings
}
}
} else {
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
qDebug("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
@@ -253,7 +253,7 @@ namespace KeeShareSettings
} else if (reader.name() == "PublicKey") {
own.certificate = Certificate::deserialize(reader);
} else {
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
qDebug("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
@@ -262,8 +262,7 @@ namespace KeeShareSettings
}
Reference::Reference()
: type(Inactive)
, uuid(QUuid::createUuid())
: uuid(QUuid::createUuid())
{
}
@@ -320,12 +319,21 @@ namespace KeeShareSettings
writer.writeStartElement("Password");
writer.writeCharacters(reference.password.toUtf8().toBase64());
writer.writeEndElement();
writer.writeStartElement("KeepGroups");
writer.writeCharacters(reference.keepGroups ? "True" : "False");
writer.writeEndElement();
});
}
Reference Reference::deserialize(const QString& raw)
{
if (raw.isEmpty()) {
return {};
}
Reference reference;
// If KeepGroups is not present, default to false for backward compatibility
reference.keepGroups = false;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Type") {
@@ -346,8 +354,10 @@ namespace KeeShareSettings
reference.path = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "Password") {
reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "KeepGroups") {
reference.keepGroups = reader.readElementText().compare("True") == 0;
} else {
qWarning("Unknown Reference element %s", qPrintable(reader.name().toString()));
qDebug("Unknown Reference element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
@@ -363,7 +373,11 @@ namespace KeeShareSettings
// Extract RSA key data to serialize an ssh-rsa public key.
// ssh-rsa keys are currently not built into Botan
const auto rsaKey = static_cast<Botan::RSA_PrivateKey*>(sign.certificate.key.data());
// need a dynamic_cast here, because the base class is virtual
const auto rsaKey = dynamic_cast<Botan::RSA_PrivateKey*>(sign.certificate.key.data());
if (!rsaKey) {
return {};
}
std::vector<uint8_t> rsaE(rsaKey->get_e().bytes());
rsaKey->get_e().binary_encode(rsaE.data());

View File

@@ -122,10 +122,11 @@ namespace KeeShareSettings
struct Reference
{
Type type;
Type type = Inactive;
QUuid uuid;
QString path;
QString password;
bool keepGroups = true;
Reference();
bool isNull() const;

View File

@@ -62,6 +62,39 @@ namespace
}
}
void cloneIcon(Metadata* targetMetadata, const Database* sourceDb, const QUuid& iconUuid)
{
if (!iconUuid.isNull() && !targetMetadata->hasCustomIcon(iconUuid)) {
targetMetadata->addCustomIcon(iconUuid, sourceDb->metadata()->customIcon(iconUuid));
}
}
void cloneEntries(Metadata* targetMetadata, const Group* sourceGroup, Group* targetGroup)
{
for (const Entry* sourceEntry : sourceGroup->entries()) {
auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
const bool updateTimeinfoEntry = targetEntry->canUpdateTimeinfo();
targetEntry->setUpdateTimeinfo(false);
targetEntry->setGroup(targetGroup);
targetEntry->setUpdateTimeinfo(updateTimeinfoEntry);
cloneIcon(targetMetadata, sourceEntry->database(), targetEntry->iconUuid());
}
}
void cloneChildren(Metadata* targetMetadata, const Group* sourceRoot, Group* targetRoot)
{
for (const Group* sourceGroup : sourceRoot->children()) {
auto* targetGroup = sourceGroup->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
const bool updateTimeinfo = targetGroup->canUpdateTimeinfo();
targetGroup->setUpdateTimeinfo(false);
targetGroup->setParent(targetRoot);
targetGroup->setUpdateTimeinfo(updateTimeinfo);
cloneIcon(targetMetadata, sourceRoot->database(), targetGroup->iconUuid());
cloneEntries(targetMetadata, sourceGroup, targetGroup);
cloneChildren(targetMetadata, sourceGroup, targetGroup);
}
}
Database* extractIntoDatabase(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
{
const auto* sourceDb = sourceRoot->database();
@@ -75,17 +108,10 @@ namespace
targetRoot->setUpdateTimeinfo(false);
KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
targetRoot->setUpdateTimeinfo(updateTimeinfo);
const auto sourceEntries = sourceRoot->entriesRecursive(false);
for (const Entry* sourceEntry : sourceEntries) {
auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
const bool updateTimeinfoEntry = targetEntry->canUpdateTimeinfo();
targetEntry->setUpdateTimeinfo(false);
targetEntry->setGroup(targetRoot);
targetEntry->setUpdateTimeinfo(updateTimeinfoEntry);
const auto iconUuid = targetEntry->iconUuid();
if (!iconUuid.isNull() && !targetMetadata->hasCustomIcon(iconUuid)) {
targetMetadata->addCustomIcon(iconUuid, sourceEntry->database()->metadata()->customIcon(iconUuid));
}
cloneIcon(targetMetadata, sourceRoot->database(), targetRoot->iconUuid());
cloneEntries(targetMetadata, sourceRoot, targetRoot);
if (reference.keepGroups) {
cloneChildren(targetMetadata, sourceRoot, targetRoot);
}
auto key = QSharedPointer<CompositeKey>::create();

View File

@@ -43,6 +43,7 @@ EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
connect(m_ui->pathEdit, SIGNAL(editingFinished()), SLOT(selectPath()));
connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(launchPathSelectionDialog()));
connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(selectType()));
connect(m_ui->keepGroupsCheckbox, SIGNAL(toggled(bool)), SLOT(keepGroupsToggled(bool)));
connect(m_ui->clearButton, SIGNAL(clicked(bool)), SLOT(clearInputs()));
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(updateSharingState()));
@@ -97,6 +98,7 @@ void EditGroupWidgetKeeShare::updateSharingState()
m_ui->pathEdit->setEnabled(isEnabled);
m_ui->pathSelectionButton->setEnabled(isEnabled);
m_ui->passwordEdit->setEnabled(isEnabled);
m_ui->keepGroupsCheckbox->setEnabled(isEnabled);
if (!m_temporaryGroup || !isEnabled) {
m_ui->messageWidget->hideMessage();
@@ -188,6 +190,7 @@ void EditGroupWidgetKeeShare::update()
m_ui->typeComboBox->setCurrentIndex(reference.type);
m_ui->passwordEdit->setText(reference.password);
m_ui->pathEdit->setText(reference.path);
m_ui->keepGroupsCheckbox->setChecked(reference.keepGroups);
}
updateSharingState();
@@ -291,3 +294,13 @@ void EditGroupWidgetKeeShare::selectType()
updateSharingState();
}
void EditGroupWidgetKeeShare::keepGroupsToggled(bool toggled)
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.keepGroups = toggled;
KeeShare::setReferenceTo(m_temporaryGroup, reference);
}

View File

@@ -48,6 +48,7 @@ private slots:
void selectPassword();
void launchPathSelectionDialog();
void selectPath();
void keepGroupsToggled(bool);
private:
QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui;

View File

@@ -138,7 +138,7 @@
</item>
</layout>
</item>
<item row="3" column="1">
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
@@ -171,7 +171,7 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="5" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -184,6 +184,19 @@
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="keepGroupsCheckbox">
<property name="toolTip">
<string>Maintain group structure with shared database</string>
</property>
<property name="text">
<string>Keep Group Structure</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@@ -73,31 +73,29 @@ bool YubiKey::isInitialized()
bool YubiKey::findValidKeys()
{
// Block operations on hardware keys while scanning
QMutexLocker lock(&s_interfaceMutex);
findValidKeys(lock);
m_connectedKeys = 0;
m_findingKeys = true;
m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys(m_connectedKeys);
m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys(m_connectedKeys);
m_findingKeys = false;
return !m_usbKeys.isEmpty() || !m_pcscKeys.isEmpty();
}
void YubiKey::findValidKeys(const QMutexLocker& locker)
{
// Check QMutexLocker since version 6.4
Q_UNUSED(locker);
m_connectedKeys = 0;
m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys(m_connectedKeys);
m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys(m_connectedKeys);
}
void YubiKey::findValidKeysAsync()
{
// Don't start another scan if we are already doing one
if (!m_findingKeys) {
m_findingKeys = true;
QtConcurrent::run([this] { emit detectComplete(findValidKeys()); });
}
}
YubiKey::KeyMap YubiKey::foundKeys()
{
QMutexLocker lock(&s_interfaceMutex);
KeyMap foundKeys = m_usbKeys;
foundKeys.unite(m_pcscKeys);
@@ -106,38 +104,12 @@ YubiKey::KeyMap YubiKey::foundKeys()
int YubiKey::connectedKeys()
{
QMutexLocker lock(&s_interfaceMutex);
return m_connectedKeys;
}
QString YubiKey::errorMessage()
{
QMutexLocker lock(&s_interfaceMutex);
QString error;
error.clear();
if (!m_error.isNull()) {
error += tr("General: ") + m_error;
}
QString usb_error = YubiKeyInterfaceUSB::instance()->errorMessage();
if (!usb_error.isNull()) {
if (!error.isNull()) {
error += " | ";
}
error += "USB: " + usb_error;
}
QString pcsc_error = YubiKeyInterfacePCSC::instance()->errorMessage();
if (!pcsc_error.isNull()) {
if (!error.isNull()) {
error += " | ";
}
error += "PCSC: " + pcsc_error;
}
return error;
return m_error;
}
/**
@@ -175,25 +147,31 @@ bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
YubiKey::ChallengeResult
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
{
QMutexLocker lock(&s_interfaceMutex);
m_error.clear();
// Make sure we tried to find available keys
if (m_usbKeys.isEmpty() && m_pcscKeys.isEmpty()) {
findValidKeys(lock);
// Prevent re-entrant access to hardware keys
QMutexLocker lock(&s_interfaceMutex);
// Try finding key on the USB interface first
auto ret = YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
if (ret == ChallengeResult::YCR_ERROR) {
m_error = YubiKeyInterfaceUSB::instance()->errorMessage();
return ret;
}
if (m_usbKeys.contains(slot)) {
return YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
// If a USB key was not found, try PC/SC interface
if (ret == ChallengeResult::YCR_KEYNOTFOUND) {
ret = YubiKeyInterfacePCSC::instance()->challenge(slot, challenge, response);
if (ret == ChallengeResult::YCR_ERROR) {
m_error = YubiKeyInterfacePCSC::instance()->errorMessage();
return ret;
}
}
if (m_pcscKeys.contains(slot)) {
return YubiKeyInterfacePCSC::instance()->challenge(slot, challenge, response);
if (ret == ChallengeResult::YCR_KEYNOTFOUND) {
m_error =
tr("Could not find hardware key with serial number %1. Please connect it to continue.").arg(slot.first);
}
m_error = tr("Could not find interface for hardware key with serial number %1. Please connect it to continue.")
.arg(slot.first);
return ChallengeResult::YCR_ERROR;
return ret;
}

View File

@@ -44,7 +44,8 @@ public:
{
YCR_ERROR = 0,
YCR_SUCCESS = 1,
YCR_WOULDBLOCK = 2
YCR_WOULDBLOCK = 2,
YCR_KEYNOTFOUND = 3,
};
static YubiKey* instance();
@@ -84,14 +85,14 @@ signals:
private:
explicit YubiKey();
void findValidKeys(const QMutexLocker& locker);
static YubiKey* m_instance;
QTimer m_interactionTimer;
bool m_initialized = false;
bool m_findingKeys = false;
QString m_error;
// Prevents multiple simultaneous operations on hardware keys
static QMutex s_interfaceMutex;
KeyMap m_usbKeys;

View File

@@ -679,7 +679,7 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
m_error.clear();
if (!m_initialized) {
m_error = tr("The YubiKey PC/SC interface has not been initialized.");
return YubiKey::ChallengeResult::YCR_ERROR;
return YubiKey::ChallengeResult::YCR_KEYNOTFOUND;
}
// Try for a few seconds to find the key
@@ -710,11 +710,8 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
}
}
m_error = tr("Could not find or access hardware key with serial number %1. Please present it to continue. ")
.arg(slot.first)
+ m_error;
emit challengeCompleted();
return YubiKey::ChallengeResult::YCR_ERROR;
return YubiKey::ChallengeResult::YCR_KEYNOTFOUND;
}
YubiKey::ChallengeResult YubiKeyInterfacePCSC::performChallenge(void* key,

View File

@@ -237,7 +237,7 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
m_error.clear();
if (!m_initialized) {
m_error = tr("The YubiKey USB interface has not been initialized.");
return YubiKey::ChallengeResult::YCR_ERROR;
return YubiKey::ChallengeResult::YCR_KEYNOTFOUND;
}
auto yk_key = openKeySerial(slot.first);
@@ -245,12 +245,11 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
// Key with specified serial number is not connected
m_error =
tr("Could not find hardware key with serial number %1. Please plug it in to continue.").arg(slot.first);
return YubiKey::ChallengeResult::YCR_ERROR;
return YubiKey::ChallengeResult::YCR_KEYNOTFOUND;
}
emit challengeStarted();
auto ret = performChallenge(yk_key.get(), slot.second, true, challenge, response);
emit challengeCompleted();
return ret;

View File

@@ -626,6 +626,17 @@ void TestEntry::testResolveReplacePlaceholders()
// Test complicated and nested replacements
QCOMPARE(entry2->resolveMultiplePlaceholders(entry2->url()),
QString("cmd://sap.exe -system=server1 -client=12345 -user=Username2 -pw=Password1"));
auto* entry3 = new Entry();
entry3->setGroup(root);
entry3->setUuid(QUuid::createUuid());
entry3->setTitle("Entry 3");
entry3->setUsername("HMAC-SHA-256");
entry3->setUrl("{T-REPLACE-RX:!{USERNAME}!\\{USERNAME\\}!!}");
// Test escaped enclosures
QCOMPARE(entry3->resolveMultiplePlaceholders(entry3->url()), entry3->username());
// Test invalid syntax
QString error;
entry1->resolveRegexPlaceholder("{T-REPLACE-RX:/{USERNAME}/.*+?/test/}", &error); // invalid regex

View File

@@ -451,3 +451,14 @@ void TestTools::testCleanUsername_data()
QTest::newRow("Trailing dots and spaces") << "username... " << "username";
QTest::newRow("Combination of issues") << R"( user<>:"/\|?*name... )" << "user_________name";
}
void TestTools::testEscapeAccelerators()
{
QCOMPARE(Tools::escapeAccelerators(""), "");
QCOMPARE(Tools::escapeAccelerators("NoAccelerator"), "NoAccelerator");
QCOMPARE(Tools::escapeAccelerators("&Accelerator"), "&&Accelerator");
QCOMPARE(Tools::escapeAccelerators("Accelerator&"), "Accelerator&&");
QCOMPARE(Tools::escapeAccelerators("Accel&erator&"), "Accel&&erator&&");
QCOMPARE(Tools::escapeAccelerators("Accel&&erator"), "Accel&&&&erator");
QCOMPARE(Tools::escapeAccelerators("Some & text"), "Some && text");
}

View File

@@ -43,6 +43,7 @@ private slots:
void testIsTextMimeType();
void testCleanUsername();
void testCleanUsername_data();
void testEscapeAccelerators();
};
#endif // KEEPASSX_TESTTOOLS_H

View File

@@ -1,77 +1,70 @@
#!/usr/bin/env python3
from collections import defaultdict
import json
import os
import sys
from pathlib import Path
from urllib import request
# Download Transifex languages dump at: https://www.transifex.com/api/2/project/keepassxc/languages
# Language information from https://www.wikiwand.com/en/List_of_ISO_639-1_codes and http://www.lingoes.net/en/translator/langcode.htm
txrc = Path.home() / '.transifexrc'
if not txrc.exists():
print('No Transifex config found. Run tx init first.')
sys.exit(1)
LANGS = {
"ar" : "العربية (Arabic)",
"bn" : "বাংলা (Bengali)",
"ca" : "català (Catalan)",
"cs" : "čeština (Czech)",
"da" : "dansk (Danish)",
"de" : "Deutsch (German)",
"el" : "ελληνικά (Greek)",
"eo" : "Esperanto (Esperanto)",
"es" : "Español (Spanish)",
"et" : "eesti (Estonian)",
"eu" : "euskara (Basque)",
"fa" : "فارسی (Farsi)",
"fa_IR" : "فارسی (Farsi (Iran))",
"fi" : "suomi (Finnish)",
"fr" : "français (French)",
"gl" : "Galego (Galician)",
"he" : "עברית (Hebrew)",
"hr_HR" : "hrvatski jezik (Croatian)",
"hu" : "magyar (Hungarian)",
"id" : "Bahasa Indonesia (Indonesian)",
"is_IS" : "Íslenska (Icelandic)",
"it" : "Italiano (Italian)",
"ja" : "日本語 (Japanese)",
"kk" : "қазақ тілі (Kazakh)",
"ko" : "한국어 (Korean)",
"la" : "latine (Latin)",
"lt" : "lietuvių kalba (Lithuanian)",
"lv" : "latviešu valoda (Latvian)",
"nb" : "Norsk Bokmål (Norwegian Bokmål)",
"nl_NL" : "Nederlands (Dutch)",
"my" : "ဗမာစာ (Burmese)",
"pa" : "ਪੰਜਾਬੀ (Punjabi)",
"pa_IN" : "ਪੰਜਾਬੀ (Punjabi (India))",
"pl" : "język polski (Polish)",
"pt" : "Português (Portuguese)",
"pt_BR" : "Português (Portuguese (Brazil))",
"pt_PT" : "Português (Portuguese (Portugal))",
"ro" : "Română (Romanian)",
"ru" : "русский (Russian)",
"sk" : "Slovenčina (Slovak)",
"sl_SI" : "Slovenščina (Slovenian)",
"sr" : "српски језик (Serbian)",
"sv" : "Svenska (Swedish)",
"th" : "ไทย (Thai)",
"tr" : "Türkçe (Turkish)",
"uk" : "Українська (Ukrainian)",
"zh_CN" : "中文 (Chinese (Simplified))",
"zh_TW" : "中文 (台灣) (Chinese (Traditional))",
}
org = 'o:keepassxc'
proj = f'{org}:p:keepassxc'
resource = f'{proj}:r:share-translations-keepassxc-en-ts--master'
token = [l for l in open(txrc, 'r') if l.startswith('token')][0].split('=', 1)[1].strip()
member_blacklist = ['u:droidmonkey', 'u:phoerious']
TEMPLATE = "<li><strong>{0}</strong>: {1}</li>\n"
if not os.path.exists("languages.json"):
print("Could not find 'languages.json' in current directory!")
print("Save the output from https://www.transifex.com/api/2/project/keepassxc/languages")
exit(0)
def get_url(url):
req = request.Request(url)
req.add_header('Content-Type', 'application/vnd.api+json')
req.add_header('Authorization', f'Bearer {token}')
with request.urlopen(req) as resp:
return json.load(resp)
with open("languages.json") as json_file:
output = open("translators.html", "w", encoding="utf-8")
languages = json.load(json_file)
for lang in languages:
code = lang["language_code"]
if code not in LANGS:
print("WARNING: Could not find language code:", code)
print('Fetching languages...', file=sys.stderr)
languages_json = get_url(f'https://rest.api.transifex.com/projects/{proj}/languages')
languages = {}
for lang in languages_json['data']:
languages[lang['id']] = lang['attributes']['name']
print('Fetching language stats...', file=sys.stderr)
language_stats_json = get_url('https://rest.api.transifex.com/resource_language_stats?'
f'filter[project]={proj}&filter[resource]={resource}')
completion = {}
for stat in language_stats_json['data']:
completion = stat['attributes']['translated_strings'] / stat['attributes']['total_strings']
if completion < .6:
languages.pop(stat['relationships']['language']['data']['id'])
print('Fetching language members...', end='', file=sys.stderr)
members_json = get_url(f'https://rest.api.transifex.com/team_memberships?filter[organization]={org}')
members = defaultdict(set)
for member in members_json['data']:
print('.', end='', file=sys.stderr)
if member['relationships']['user']['data']['id'] in member_blacklist:
continue
translators = ", ".join(sorted(lang["reviewers"] + lang["translators"], key=str.casefold))
output.write(TEMPLATE.format(LANGS[code], translators))
output.close()
print("Language translators written to 'translators.html'!")
lid = member['relationships']['language']['data']['id']
if lid not in languages:
continue
user = get_url(member['relationships']['user']['links']['related'])['data']['attributes']['username']
members[lid].add(user)
print(file=sys.stderr)
print('<ul>')
for lang in sorted(languages, key=lambda x: languages[x]):
if not members[lang]:
continue
lines = [f' <li><strong>{languages[lang]}:</strong> ']
for i, m in enumerate(sorted(members[lang], key=lambda x: x.lower())):
if len(lines[-1]) + len(m) >= 120:
lines.append(' ')
lines[-1] += m
if i < len(members[lang]) - 1:
lines[-1] += ', '
lines[-1] += '</li>'
print('\n'.join(lines))
print('</ul>')