Release 2.5.2

Added

- Browser: Show UI warning when entering invalid URLs [#3912]
- Browser: Option to use an entry only for HTTP auth [#3927]

Changed

- Disable the user interface when merging or saving the database [#3991]
- Ability to hide protected attribute after reveal [#3877]
- Remove mention of "snaps" in Windows and macOS [#3879]
- CLI: Merge parameter for source database key file (--key-file-from) [#3961]
- Improve GUI tests reliability on Hi-DPI displays [#4075]
- Disable deprecation warnings to allow building with Qt 5.14+ [#4075]
- OPVault: Use 'otp' attribute for TOTP field imports [#4075]

Fixed

- Fix crashes when saving a database to cloud storage [#3991]
- Fix crash when pressing enter twice while opening database [#3885]
- Fix handling of HTML when displayed in the entry preview panel [#3910]
- Fix start minimized to tray on Linux [#3899]
- Fix Auto Open with key file only databases [#4075]
- Fix escape key closing the standalone password generator [#3892]
- macOS: Fix monospace font usage in password field and notes [#4075]
- macOS: Fix building on macOS 10.9 to 10.11 [#3946]
- Fix TOTP setup dialog not closing on database lock [#4075]
- Browser: Fix condition where additional URLs are ignored [#4033]
- Browser: Fix subdomain matching to return only relevant site entries [#3854]
- Secret Service: Fix multiple crashes and incompatibilities [#3871, #4009, #4074]
- Secret Service: Fix searching of entries [#4008, #4036]
- Secret Service: Fix behavior when exposed group is recycled [#3914]
- CLI: Release the database instance before exiting interactive mode [#3889]
- Fix (most) memory leaks in tests [#3922]
This commit is contained in:
Jonathan White 2020-01-04 09:09:38 -05:00
commit 62cda9dd40
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
97 changed files with 5168 additions and 3896 deletions

View File

@ -1,5 +1,41 @@
# Changelog
## 2.5.2 (2020-01-04)
### Added
- Browser: Show UI warning when entering invalid URLs [#3912]
- Browser: Option to use an entry only for HTTP auth [#3927]
### Changed
- Disable the user interface when merging or saving the database [#3991]
- Ability to hide protected attribute after reveal [#3877]
- Remove mention of "snaps" in Windows and macOS [#3879]
- CLI: Merge parameter for source database key file (--key-file-from) [#3961]
- Improve GUI tests reliability on Hi-DPI displays [#4075]
- Disable deprecation warnings to allow building with Qt 5.14+ [#4075]
- OPVault: Use 'otp' attribute for TOTP field imports [#4075]
### Fixed
- Fix crashes when saving a database to cloud storage [#3991]
- Fix crash when pressing enter twice while opening database [#3885]
- Fix handling of HTML when displayed in the entry preview panel [#3910]
- Fix start minimized to tray on Linux [#3899]
- Fix Auto Open with key file only databases [#4075]
- Fix escape key closing the standalone password generator [#3892]
- macOS: Fix monospace font usage in password field and notes [#4075]
- macOS: Fix building on macOS 10.9 to 10.11 [#3946]
- Fix TOTP setup dialog not closing on database lock [#4075]
- Browser: Fix condition where additional URLs are ignored [#4033]
- Browser: Fix subdomain matching to return only relevant site entries [#3854]
- Secret Service: Fix multiple crashes and incompatibilities [#3871, #4009, #4074]
- Secret Service: Fix searching of entries [#4008, #4036]
- Secret Service: Fix behavior when exposed group is recycled [#3914]
- CLI: Release the database instance before exiting interactive mode [#3889]
- Fix (most) memory leaks in tests [#3922]
## 2.5.1 (2019-11-11)
### Added

View File

@ -36,6 +36,7 @@ endif (CCACHE_FOUND)
# Support Visual Studio Code
include(CMakeToolsHelpers OPTIONAL)
include(FeatureSummary)
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
@ -94,7 +95,7 @@ endif()
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "5")
set(KEEPASSXC_VERSION_PATCH "1")
set(KEEPASSXC_VERSION_PATCH "2")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
@ -299,6 +300,9 @@ endif()
if(WITH_DEV_BUILD)
add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED)
else()
add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
add_gcc_compiler_cxxflags("-Wno-deprecated-declarations")
endif()
if(MINGW)
@ -465,8 +469,6 @@ endif()
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
include(FeatureSummary)
add_subdirectory(src)
add_subdirectory(share)
if(WITH_TESTS)

View File

@ -248,3 +248,8 @@ Comment: from Freedesktop.org website
Files: share/icons/application/32x32/actions/statistics.png
Copyright: Icon made by Freepik from https://www.flaticon.com/free-icon/bars-chart_265733
Files: share/icons/application/scalable/actions/object-locked.svg
share/icons/application/scalable/actions/object-unlocked.svg
License: LGPL-3
Comment: from Breeze icon theme (https://github.com/KDE/breeze-icons)

View File

@ -150,9 +150,15 @@ You can create a package to redistribute KeePassXC (zip, deb, rpm, dmg, etc..).
Testing
=======
You can perform test on the executable
You can perform tests on the built executables with:
```
make test
make test ARGS+="--output-on-failure"
```
If you are not currently running on an X Server or Wayland, run the tests as follows:
```
make test ARGS+="-E test\(cli\|gui\) --output-on-failure"
xvfb-run -e errors -a --server-args="-screen 0 1024x768x24" make test ARGS+="-R test\(cli\|gui\) --output-on-failure"
```
Common parameters:

View File

@ -117,7 +117,7 @@ Displays the program version.
.IP "-d, --dry-run <path>"
Prints the changes detected by the merge operation without making any changes to the database.
.IP "-f, --key-file-from <path>"
.IP "--key-file-from <path>"
Sets the path of the key file for the second database.
.IP "--no-password-from"

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11,3 C 8.784,3 7,4.784 7,7 l 0,4 -2,0 c 0,2.666667 0,5.333333 0,8 4,0 8,0 12,0 l 0,-8 c -0.666667,0 -1.333333,0 -2,0 L 15,7 C 15,4.784 13.216,3 11,3 m 0,1 c 1.662,0 3,1.561 3,3.5 L 14,11 8,11 8,7.5 C 8,5.561 9.338,4 11,4"
class="ColorScheme-Text"
transform="translate(1,1)"
/>
</svg>

After

Width:  |  Height:  |  Size: 592 B

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m11 3c-2.216 0-4 1.784-4 4v1h1v-.5c0-1.939 1.338-3.5 3-3.5 1.662 0 3 1.561 3 3.5v3.5h-5-1-1-1-1v1 7h1 10 1v-8h-1-1v-4c0-2.216-1.784-4-4-4m-5 9h10v6h-10v-6"
class="ColorScheme-Text"
transform="translate(1,1)"
/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@ -50,6 +50,37 @@
</screenshots>
<releases>
<release version="2.5.2" date="2020-01-04">
<description>
<ul>
<li>Browser: Show UI warning when entering invalid URLs [#3912]</li>
<li>Browser: Option to use an entry only for HTTP auth [#3927]</li>
<li>Disable the user interface when merging or saving the database [#3991]</li>
<li>Ability to hide protected attribute after reveal [#3877]</li>
<li>Remove mention of "snaps" in Windows and macOS [#3879]</li>
<li>CLI: Merge parameter for source database key file (--key-file-from) [#3961]</li>
<li>Improve GUI tests reliability on Hi-DPI displays [#4075]</li>
<li>Disable deprecation warnings to allow building with Qt 5.14+ [#4075]</li>
<li>OPVault: Use 'otp' attribute for TOTP field imports [#4075]</li>
<li>Fix crashes when saving a database to cloud storage [#3991]</li>
<li>Fix crash when pressing enter twice while opening database [#3885]</li>
<li>Fix handling of HTML when displayed in the entry preview panel [#3910]</li>
<li>Fix start minimized to tray on Linux [#3899]</li>
<li>Fix Auto Open with key file only databases [#4075]</li>
<li>Fix escape key closing the standalone password generator [#3892]</li>
<li>macOS: Fix monospace font usage in password field and notes [#4075]</li>
<li>macOS: Fix building on macOS 10.9 to 10.11 [#3946]</li>
<li>Fix TOTP setup dialog not closing on database lock [#4075]</li>
<li>Browser: Fix condition where additional URLs are ignored [#4033]</li>
<li>Browser: Fix subdomain matching to return only relevant site entries [#3854]</li>
<li>Secret Service: Fix multiple crashes and incompatibilities [#3871, #4009, #4074]</li>
<li>Secret Service: Fix searching of entries [#4008, #4036]</li>
<li>Secret Service: Fix behavior when exposed group is recycled [#3914]</li>
<li>CLI: Release the database instance before exiting interactive mode [#3889]</li>
<li>Fix (most) memory leaks in tests [#3922]</li>
</ul>
</description>
</release>
<release version="2.5.1" date="2019-11-11">
<description>
<ul>

View File

@ -23,7 +23,7 @@
</message>
<message>
<source>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;See Contributions on GitHub&lt;/a&gt;</source>
<translation>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;إستكشاف المساهمين عبر GitHub&lt;/a&gt;</translation>
<translation>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;إستكشاف المساهمات عبر GitHub&lt;/a&gt;</translation>
</message>
<message>
<source>Debug Info</source>
@ -43,7 +43,7 @@
</message>
<message>
<source>Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.</source>
<translation>شكر خاص من فريق KeePassXC يذهب إلى debfx لإنشاء KeePassX الأصلي.</translation>
<translation>شكر خاص من فريق KeePassXC إلى debfx لإنشاء KeePassX الأصلي.</translation>
</message>
</context>
<context>
@ -77,23 +77,23 @@
</message>
<message>
<source>Icon only</source>
<translation type="unfinished"/>
<translation>الأيقونة فقط</translation>
</message>
<message>
<source>Text only</source>
<translation type="unfinished"/>
<translation>الوصف بالكلمات فقط </translation>
</message>
<message>
<source>Text beside icon</source>
<translation type="unfinished"/>
<translation>وصف حرفي بجانب الأيقونة</translation>
</message>
<message>
<source>Text under icon</source>
<translation type="unfinished"/>
<translation>الوصف أسفل الأيقونة</translation>
</message>
<message>
<source>Follow style</source>
<translation type="unfinished"/>
<translation>تابع النمط</translation>
</message>
<message>
<source>Reset Settings?</source>
@ -101,7 +101,7 @@
</message>
<message>
<source>Are you sure you want to reset all general and security settings to default?</source>
<translation type="unfinished"/>
<translation>هل أنت متأكد من رغبتك في إعادة جميع الإعدادات العامة وإعدادات الأمن الى وضعها النمطي الأولي ؟</translation>
</message>
</context>
<context>
@ -116,7 +116,7 @@
</message>
<message>
<source>Start only a single instance of KeePassXC</source>
<translation>شغل تطبيق واحد فقط من KeePassXC</translation>
<translation>شغل نسخة واحدة فقط من تطبيق KeePassXC</translation>
</message>
<message>
<source>Minimize window at application startup</source>
@ -132,7 +132,7 @@
</message>
<message>
<source>Backup database file before saving</source>
<translation>إحتفظ بنسخة من ملف قاعدة البيانات قبل الحفظ</translation>
<translation>عمل نسخة إحتياطية من ملف قاعدة البيانات قبل الحفظ</translation>
</message>
<message>
<source>Automatically save after every change</source>
@ -152,15 +152,15 @@
</message>
<message>
<source>Entry Management</source>
<translation>إدارة الإدخالات</translation>
<translation>إدارة ادخال البيانات</translation>
</message>
<message>
<source>Use group icon on entry creation</source>
<translation>استخدم رمز المجموعة عند إنشاء الإدخال</translation>
<translation>استخدم شعار المجموعة عند إنشاء المدخلات</translation>
</message>
<message>
<source>Hide the entry preview panel</source>
<translation type="unfinished"/>
<translation>إخفاء إستعراض لوحة المدخلات </translation>
</message>
<message>
<source>General</source>
@ -168,15 +168,15 @@
</message>
<message>
<source>Hide toolbar (icons)</source>
<translation type="unfinished"/>
<translation>إخفاء شريط العُدد (الأيقونات)</translation>
</message>
<message>
<source>Minimize instead of app exit</source>
<translation type="unfinished"/>
<translation> تصغيرالتطبيق بدل الخروج منه </translation>
</message>
<message>
<source>Show a system tray icon</source>
<translation>اظهر أيقونة البرنامج في صينية النظام</translation>
<translation>اظهار أيقونة البرنامج في شريط واجهة النظام</translation>
</message>
<message>
<source>Dark system tray icon</source>
@ -188,7 +188,7 @@
</message>
<message>
<source>Auto-Type</source>
<translation>الطباعة التلقائية</translation>
<translation> نمط تلقائي</translation>
</message>
<message>
<source>Use entry title to match windows for global Auto-Type</source>
@ -200,7 +200,7 @@
</message>
<message>
<source>Always ask before performing Auto-Type</source>
<translation>اسأل دائما قبل تنفيذ الطباعة التلقائية</translation>
<translation>السؤأل دائما قبل التغيير للنمط التلقائي</translation>
</message>
<message>
<source>Global Auto-Type shortcut</source>
@ -213,7 +213,7 @@
<message>
<source> ms</source>
<comment>Milliseconds</comment>
<translation>مل.ثانية</translation>
<translation>جزء الثانية</translation>
</message>
<message>
<source>Auto-Type start delay</source>
@ -221,15 +221,15 @@
</message>
<message>
<source>Movable toolbar</source>
<translation type="unfinished"/>
<translation> شريط عُدد قابل للتحريك</translation>
</message>
<message>
<source>Remember previously used databases</source>
<translation type="unfinished"/>
<translation> تذكُر قاعدة البيانات المستخدمة أخيراً</translation>
</message>
<message>
<source>Load previously open databases on startup</source>
<translation type="unfinished"/>
<translation> عند بدء التشغيل إعادة فتح قواعد البيانات التي مفتوحة سابقاً</translation>
</message>
<message>
<source>Remember database key files and security dongles</source>
@ -237,7 +237,7 @@
</message>
<message>
<source>Check for updates at application startup once per week</source>
<translation type="unfinished"/>
<translation> فحص وجود تحديثات للبرامج عند بدء تشغيل التطبيق مرة واحدة في الأسبوع </translation>
</message>
<message>
<source>Include beta releases when checking for updates</source>
@ -249,39 +249,39 @@
</message>
<message>
<source>Language:</source>
<translation type="unfinished"/>
<translation> اللغة</translation>
</message>
<message>
<source>(restart program to activate)</source>
<translation type="unfinished"/>
<translation> ( للتفعيل يرجى إعادة التشغيل ) </translation>
</message>
<message>
<source>Minimize window after unlocking database</source>
<translation type="unfinished"/>
<translation> تصغيرنافذة التطبيق بعد فتح قاعدة البيانات</translation>
</message>
<message>
<source>Minimize when opening a URL</source>
<translation type="unfinished"/>
<translation> تصغير البرنامج عند فتح رابط URL من النت </translation>
</message>
<message>
<source>Hide window when copying to clipboard</source>
<translation type="unfinished"/>
<translation> إخفاء نافذة البرنامج عند النسخ الى الحافظة </translation>
</message>
<message>
<source>Minimize</source>
<translation type="unfinished"/>
<translation> تصغير</translation>
</message>
<message>
<source>Drop to background</source>
<translation type="unfinished"/>
<translation> الإرجاع الى الخلفية </translation>
</message>
<message>
<source>Favicon download timeout:</source>
<translation type="unfinished"/>
<translation> فوت الوقت لتنزيل أيقونة الشعارالمفضل</translation>
</message>
<message>
<source>Website icon download timeout in seconds</source>
<translation type="unfinished"/>
<translation> الزمن بالثانية لفوت الإتصال لتنزيل أيقونة الموقع</translation>
</message>
<message>
<source> sec</source>
@ -290,38 +290,39 @@
</message>
<message>
<source>Toolbar button style</source>
<translation type="unfinished"/>
<translation> نمط مفاتيح شريط العُدد </translation>
</message>
<message>
<source>Use monospaced font for Notes</source>
<translation type="unfinished"/>
<translation> إستخدام بنط ذا مسافات أحادية في الملاحظات</translation>
</message>
<message>
<source>Language selection</source>
<translation type="unfinished"/>
<translation> إختيار اللغة</translation>
</message>
<message>
<source>Reset Settings to Default</source>
<translation type="unfinished"/>
<translation> إعادة الإعدادات الى وضعها الأولي</translation>
</message>
<message>
<source>Global auto-type shortcut</source>
<translation type="unfinished"/>
<translation> المختصر الى النمط-التلقائي العمومي</translation>
</message>
<message>
<source>Auto-type character typing delay milliseconds</source>
<translation type="unfinished"/>
<translation> تأخيرالطباعة لأحرف النمط-التلقائي
بأجزاء الثانية </translation>
</message>
<message>
<source>Auto-type start delay milliseconds</source>
<translation type="unfinished"/>
<translation> تأخير بدء النمط-التلقائي بأجزاء الثانية</translation>
</message>
</context>
<context>
<name>ApplicationSettingsWidgetSecurity</name>
<message>
<source>Timeouts</source>
<translation>مهلة نفاد الوقت</translation>
<translation> نفاذ الوقت</translation>
</message>
<message>
<source>Clear clipboard after</source>
@ -334,11 +335,11 @@
</message>
<message>
<source>Lock databases after inactivity of</source>
<translation>أغلق قواعد البيانات بعد حالة عدم النشاط ل</translation>
<translation>أغلق قواعد البيانات بعد حالة عدم النشاط لـ</translation>
</message>
<message>
<source> min</source>
<translation type="unfinished"/>
<translation> دقيقة</translation>
</message>
<message>
<source>Forget TouchID after inactivity of</source>
@ -354,7 +355,7 @@
</message>
<message>
<source>Forget TouchID when session is locked or lid is closed</source>
<translation type="unfinished"/>
<translation> إنسى هوية البصمة عند قفل حصة التفاعل أو عند إغلاق الشاشة </translation>
</message>
<message>
<source>Lock databases after minimizing the window</source>
@ -362,7 +363,7 @@
</message>
<message>
<source>Re-lock previously locked database after performing Auto-Type</source>
<translation>أعد قفل قاعدة البيانات التي تم تأمينها سابقًا بعد تنفيذ الطباعة التلقائية</translation>
<translation>أعد قفل قاعدة البيانات التي تم تأمينها سابقًا بعدالتغيير للنمط التلقائي</translation>
</message>
<message>
<source>Don&apos;t require password repeat when it is visible</source>
@ -370,15 +371,15 @@
</message>
<message>
<source>Don&apos;t hide passwords when editing them</source>
<translation type="unfinished"/>
<translation> لا تخفي كلمات السر عند تعديلها </translation>
</message>
<message>
<source>Don&apos;t use placeholder for empty password fields</source>
<translation type="unfinished"/>
<translation> لا تستعمل محددات أقواس أو مزدوجات لحقول كلمات السر الفارغة </translation>
</message>
<message>
<source>Hide passwords in the entry preview panel</source>
<translation type="unfinished"/>
<translation> اخفي كلمات السر في لوحة إستعراض المدخلات</translation>
</message>
<message>
<source>Hide entry notes by default</source>
@ -390,28 +391,28 @@
</message>
<message>
<source>Use DuckDuckGo service to download website icons</source>
<translation type="unfinished"/>
<translation> استعمل خدمة DuckDuckGo لتنزيل أيقونات مواقع الإنترنت</translation>
</message>
<message>
<source>Clipboard clear seconds</source>
<translation type="unfinished"/>
<translation> مسح لوحة الحفظ بالثواني</translation>
</message>
<message>
<source>Touch ID inactivity reset</source>
<translation type="unfinished"/>
<translation> المهلة الزمنية لعدم التفاعل لنسيان هوية البصمة</translation>
</message>
<message>
<source>Database lock timeout seconds</source>
<translation type="unfinished"/>
<translation> المهلة الزنسة لقفل قواعد البيانات </translation>
</message>
<message>
<source> min</source>
<comment>Minutes</comment>
<translation type="unfinished"/>
<translation> دقيقة</translation>
</message>
<message>
<source>Clear search query after</source>
<translation type="unfinished"/>
<translation> مسح محددات البحث بعد مضي </translation>
</message>
</context>
<context>
@ -446,11 +447,12 @@
</message>
<message>
<source>Permission Required</source>
<translation type="unfinished"/>
<translation>إذن السماح مطلوب</translation>
</message>
<message>
<source>KeePassXC requires the Accessibility permission in order to perform entry level Auto-Type. If you already granted permission, you may have to restart KeePassXC.</source>
<translation type="unfinished"/>
<translation> KeePassXC يطلب إذن السماح بالدخول لإنجاز التغييرات المبدئية وفق النمط-التلقائي . إذا كنت قد أعطيت الإذن بالسماح
فقد يستوجب منك ذلك إعادة تشغيل KeePassXC </translation>
</message>
</context>
<context>
@ -491,18 +493,18 @@
<name>AutoTypeMatchView</name>
<message>
<source>Copy &amp;username</source>
<translation>نسخ &amp;اسم المستخدم</translation>
<translation> نسخ و اسم المستخدم</translation>
</message>
<message>
<source>Copy &amp;password</source>
<translation type="unfinished"/>
<translation> نسخ و كلمة السر</translation>
</message>
</context>
<context>
<name>AutoTypePlatformMac</name>
<message>
<source>Permission Required</source>
<translation type="unfinished"/>
<translation>إذن السماح مطلوب</translation>
</message>
<message>
<source>KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global Auto-Type. Screen Recording is necessary to use the window title to find entries. If you already granted permission, you may have to restart KeePassXC.</source>
@ -513,11 +515,11 @@
<name>AutoTypeSelectDialog</name>
<message>
<source>Auto-Type - KeePassXC</source>
<translation>الطباعة التلقائية - KeePassXC</translation>
<translation>النمط التلقائي - KeePassXC</translation>
</message>
<message>
<source>Select entry to Auto-Type:</source>
<translation>حدد مدخل للطباعة التلقائية:</translation>
<translation>إختيار مدخلات الى النمط التلقائي:</translation>
</message>
<message>
<source>Search...</source>
@ -550,31 +552,32 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Allow access</source>
<translation type="unfinished"/>
<translation> أسمح بالدخول</translation>
</message>
<message>
<source>Deny access</source>
<translation type="unfinished"/>
<translation> أمنع الدخول</translation>
</message>
</context>
<context>
<name>BrowserEntrySaveDialog</name>
<message>
<source>KeePassXC-Browser Save Entry</source>
<translation type="unfinished"/>
<translation> متصفح KeePassXC إحفظ المدخلات</translation>
</message>
<message>
<source>Ok</source>
<translation type="unfinished"/>
<translation> تمام</translation>
</message>
<message>
<source>Cancel</source>
<translation>ألغ</translation>
<translation>ألغاء</translation>
</message>
<message>
<source>You have multiple databases open.
Please select the correct database for saving credentials.</source>
<translation type="unfinished"/>
<translation> لديك العديد من قواعد البيانات مفتوحة
الرجاء إختيار قاعدة البيانات الصحيحة لحفظ المسوغات </translation>
</message>
</context>
<context>
@ -597,19 +600,19 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>&amp;Google Chrome</source>
<translation>&amp;Google Chrome</translation>
<translation>&amp;قوقل كروم</translation>
</message>
<message>
<source>&amp;Firefox</source>
<translation>&amp;Firefox</translation>
<translation>&amp;فايرفوكس</translation>
</message>
<message>
<source>&amp;Chromium</source>
<translation>&amp;Chromium</translation>
<translation>&amp;كروميوم</translation>
</message>
<message>
<source>&amp;Vivaldi</source>
<translation>&amp;Vivaldi</translation>
<translation>&amp;فافيلادي</translation>
</message>
<message>
<source>Show a &amp;notification when credentials are requested</source>
@ -713,20 +716,20 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>&amp;Tor Browser</source>
<translation type="unfinished"/>
<translation>متصفح تور</translation>
</message>
<message>
<source>Executable Files</source>
<translation type="unfinished"/>
<translation> ملفات برامج </translation>
</message>
<message>
<source>All Files</source>
<translation type="unfinished"/>
<translation> جميع الملفات</translation>
</message>
<message>
<source>Do not ask permission for HTTP &amp;Basic Auth</source>
<extracomment>An extra HTTP Basic Auth setting</extracomment>
<translation type="unfinished"/>
<translation> لا تطلب الإذن لصفحات النترنت العادية HTTP&amp; و عند الإذن الأساسي</translation>
</message>
<message>
<source>Due to Snap sandboxing, you must run a script to enable browser integration.&lt;br /&gt;You can obtain this script from %1</source>
@ -742,7 +745,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>&amp;Brave</source>
<translation type="unfinished"/>
<translation> متصفح &amp;Brave</translation>
</message>
<message>
<source>Returns expired credentials. String [expired] is added to the title.</source>
@ -3477,7 +3480,7 @@ You can enable the DuckDuckGo website icon service in the security section of th
</message>
<message>
<source>Ok</source>
<translation type="unfinished"/>
<translation> تمام</translation>
</message>
<message>
<source>Already Exists</source>
@ -4477,7 +4480,7 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>Copy &amp;password</source>
<translation type="unfinished"/>
<translation> نسخ و كلمة السر</translation>
</message>
<message>
<source>Perform &amp;Auto-Type</source>

View File

@ -116,7 +116,7 @@
</message>
<message>
<source>Start only a single instance of KeePassXC</source>
<translation>Obriu només una sola instància del KeePassXC</translation>
<translation>Obre només una sola instància del KeePassXC</translation>
</message>
<message>
<source>Minimize window at application startup</source>
@ -136,7 +136,7 @@
</message>
<message>
<source>Automatically save after every change</source>
<translation>Deseu després de cada canvi de forma automàtica</translation>
<translation>Desa després de cada canvi de forma automàtica</translation>
</message>
<message>
<source>Automatically save on exit</source>
@ -144,11 +144,11 @@
</message>
<message>
<source>Don&apos;t mark database as modified for non-data changes (e.g., expanding groups)</source>
<translation>No marqueu la base de dades com a modificada si no han hagut canvis en les dades (per exemple, a l&apos;expandir grups)</translation>
<translation>No marquis la base de dades com a modificada si no hi han hagut canvis en les dades (per exemple, a l&apos;expandir grups)</translation>
</message>
<message>
<source>Automatically reload the database when modified externally</source>
<translation>Torneu a carregar automàticament la base de dades quan siga modificada de forma externa</translation>
<translation>Torna a carregar automàticament la base de dades quan sigui modificada de forma externa</translation>
</message>
<message>
<source>Entry Management</source>
@ -334,7 +334,7 @@
</message>
<message>
<source>Lock databases after inactivity of</source>
<translation>Bloquegeu les bases de dades després d&apos;estar inactives per</translation>
<translation>Bloqueja les bases de dades després d&apos;estar inactives</translation>
</message>
<message>
<source> min</source>
@ -350,7 +350,7 @@
</message>
<message>
<source>Lock databases when session is locked or lid is closed</source>
<translation>Bloquegeu les bases de dades quan sessió siga bloquejada o es tanque la tapa</translation>
<translation>Bloqueja les bases de dades quan la sessió sigui bloquejada o es tanqui la tapa</translation>
</message>
<message>
<source>Forget TouchID when session is locked or lid is closed</source>
@ -358,7 +358,7 @@
</message>
<message>
<source>Lock databases after minimizing the window</source>
<translation>Bloquegeu les bases de dades després minimitzar la finestra</translation>
<translation>Bloqueja les bases de dades després minimitzar la finestra</translation>
</message>
<message>
<source>Re-lock previously locked database after performing Auto-Type</source>
@ -411,7 +411,7 @@
</message>
<message>
<source>Clear search query after</source>
<translation type="unfinished"/>
<translation>Elimina la teva cerca després de</translation>
</message>
</context>
<context>
@ -434,15 +434,15 @@
</message>
<message>
<source>This Auto-Type command contains a very long delay. Do you really want to proceed?</source>
<translation type="unfinished"/>
<translation>Aquesta ordre de compleció automàtica conté un retard molt llarg. Esteu segur que voleu continuar?</translation>
</message>
<message>
<source>This Auto-Type command contains very slow key presses. Do you really want to proceed?</source>
<translation type="unfinished"/>
<translation>Aquesta comanda de compleció automàtica conté pulsacions de tecles molt lentes. Esteu segur que voleu continuar?</translation>
</message>
<message>
<source>This Auto-Type command contains arguments which are repeated very often. Do you really want to proceed?</source>
<translation type="unfinished"/>
<translation>Aquesta comanda de compleció automàtica conté arguments que es repeteixen molt sovint. Esteu segur que voleu continuar?</translation>
</message>
<message>
<source>Permission Required</source>
@ -574,7 +574,8 @@ Seleccioneu si voleu permetre l&apos;accés.</translation>
<message>
<source>You have multiple databases open.
Please select the correct database for saving credentials.</source>
<translation type="unfinished"/>
<translation>Teniu diverses bases de dades obertes.
Seleccioneu la base de dades correcta per desar les credencials.</translation>
</message>
</context>
<context>
@ -675,11 +676,11 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>Updates KeePassXC or keepassxc-proxy binary path automatically to native messaging scripts on startup.</source>
<translation type="unfinished"/>
<translation>Actualitza la ruta binària de KeePassXC o keepassxc-proxy automàticament als scripts de missatgeria nativa a l&apos;inici.</translation>
</message>
<message>
<source>Update &amp;native messaging manifest files at startup</source>
<translation type="unfinished"/>
<translation>Actualitza el fitxers de manifest de la missatgeria &amp;nativa a l&apos;inici</translation>
</message>
<message>
<source>Support a proxy application between KeePassXC and browser extension.</source>
@ -738,7 +739,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>KeePassXC-Browser is needed for the browser integration to work. &lt;br /&gt;Download it for %1 and %2. %3</source>
<translation type="unfinished"/>
<translation>Perquè funcioni la integració del navegador cal el KeePassXC-Browse.&lt;br&gt;Baixeu-lo de %1 i de %2. %3</translation>
</message>
<message>
<source>&amp;Brave</source>
@ -750,7 +751,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>&amp;Allow returning expired credentials.</source>
<translation type="unfinished"/>
<translation>&amp;Permet el retorn de credencials caducades.</translation>
</message>
<message>
<source>Enable browser integration</source>
@ -758,7 +759,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>Browsers installed as snaps are currently not supported.</source>
<translation type="unfinished"/>
<translation>Actualment, no s&apos;admeten els navegadors instal·lats com a Snaps.</translation>
</message>
<message>
<source>All databases connected to the extension will return matching credentials.</source>
@ -1167,7 +1168,7 @@ Please consider generating a new key file.</source>
</message>
<message>
<source>TouchID for Quick Unlock</source>
<translation>TouchID per desbloquejar ràpidament</translation>
<translation>Usa el TouchID per desbloquejar ràpidament</translation>
</message>
<message>
<source>Clear</source>
@ -1179,7 +1180,7 @@ Please consider generating a new key file.</source>
</message>
<message>
<source>Unlock failed and no password given</source>
<translation type="unfinished"/>
<translation>El desbloqueig ha fallat i no s&apos;ha donat cap contrasenya</translation>
</message>
<message>
<source>Unlocking the database failed and you did not enter a password.
@ -1306,7 +1307,7 @@ This may prevent connection to the browser plugin.</source>
</message>
<message>
<source>Enable Browser Integration to access these settings.</source>
<translation type="unfinished"/>
<translation>Permet que la integració del navegador accedeixi a aquesta configuració.</translation>
</message>
<message>
<source>Disconnect all browsers</source>
@ -1594,7 +1595,7 @@ If you keep this number, your database may be too easy to crack!</source>
</message>
<message>
<source>History Settings</source>
<translation type="unfinished"/>
<translation>Configuració de l&apos;historial</translation>
</message>
<message>
<source>Max. history items:</source>
@ -1937,7 +1938,7 @@ This is definitely a bug, please report it to the developers.</source>
</message>
<message>
<source>Export database to HTML file</source>
<translation type="unfinished"/>
<translation>Exporta la base de dades a un fitxer HTML</translation>
</message>
<message>
<source>HTML file</source>
@ -2034,11 +2035,11 @@ Voleu fusionar els canvis?</translation>
</message>
<message numerus="yes">
<source>Do you really want to delete %n entry(s) for good?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Esteu segur que voleu suprimir% n entrades per al Good?</numerusform><numerusform>Esteu segur que voleu suprimir %n entrada/es permanentment?</numerusform></translation>
</message>
<message numerus="yes">
<source>Delete entry(s)?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Voleu suprimir les entrades?</numerusform><numerusform>Voleu suprimir la/es entrada/es?</numerusform></translation>
</message>
<message numerus="yes">
<source>Move entry(s) to recycle bin?</source>
@ -2102,7 +2103,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop?</translation>
</message>
<message numerus="yes">
<source>Entry &quot;%1&quot; has %2 reference(s). Do you want to overwrite references with values, skip this entry, or delete anyway?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>L&apos;entrada &quot;%1&quot; %2 referències. Voleu sobreescriure les referències amb valors, ometre aquesta entrada o suprimir igualment?</numerusform><numerusform>L&apos;entrada &quot;%1&quot; %2 referència/es. Voleu sobreescriure les referències amb valors, ometre aquesta entrada, o suprimir igualment?</numerusform></translation>
</message>
<message>
<source>Delete group</source>
@ -2126,7 +2127,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop?</translation>
</message>
<message>
<source>Shared group...</source>
<translation type="unfinished"/>
<translation>Grup compartit...</translation>
</message>
<message>
<source>Writing the database failed: %1</source>
@ -2181,7 +2182,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop?</translation>
</message>
<message>
<source>File too large to be a private key</source>
<translation type="unfinished"/>
<translation>Fitxer massa gran per ser una clau privada</translation>
</message>
<message>
<source>Failed to open private key</source>
@ -2603,7 +2604,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop?</translation>
</message>
<message>
<source>Remove key from agent when database is closed/locked</source>
<translation type="unfinished"/>
<translation>Elimina la clau de l&apos;agent quan la base de dades està tancada/bloquejada</translation>
</message>
<message>
<source>Public key</source>
@ -2611,7 +2612,7 @@ Voleu deshabilitar el desat segur i provar-ho un altre cop?</translation>
</message>
<message>
<source>Add key to agent when database is opened/unlocked</source>
<translation type="unfinished"/>
<translation>Afegeix la clau a l&apos;agent quan la base de dades s&apos;obri o es desbloquegi</translation>
</message>
<message>
<source>Comment</source>
@ -2942,7 +2943,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>No icons were loaded</source>
<translation type="unfinished"/>
<translation>No s&apos;ha carrega cap icona</translation>
</message>
<message numerus="yes">
<source>%n icon(s) already exist in the database</source>
@ -2954,7 +2955,7 @@ Supported extensions are: %1.</source>
</message>
<message numerus="yes">
<source>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Aquesta icona s&apos;utilitza en% n entrada (s), i serà substituïda per la icona per defecte. Esteu segur que voleu suprimir-la?</numerusform><numerusform>Aquesta icona s&apos;usa en %n entrada/es, i serà substituïda per la icona per defecte. Esteu segur que voleu suprimir-la?</numerusform></translation>
</message>
<message>
<source>You can enable the DuckDuckGo website icon service under Tools -&gt; Settings -&gt; Security</source>
@ -2970,7 +2971,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Apply icon &amp;to ...</source>
<translation type="unfinished"/>
<translation>Usa la &amp;icona a...</translation>
</message>
<message>
<source>Apply to this only</source>
@ -3013,7 +3014,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Plugin Data</source>
<translation type="unfinished"/>
<translation>Dades del connector</translation>
</message>
<message>
<source>Remove</source>
@ -3065,7 +3066,7 @@ This may cause the affected plugins to malfunction.</source>
<name>Entry</name>
<message>
<source>%1 - Clone</source>
<translation type="unfinished"/>
<translation>%1 - Clon</translation>
</message>
</context>
<context>
@ -3076,7 +3077,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Size</source>
<translation type="unfinished"/>
<translation>Mida</translation>
</message>
</context>
<context>
@ -3111,12 +3112,13 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Save attachments</source>
<translation type="unfinished"/>
<translation>Desa els fitxers adjunts</translation>
</message>
<message>
<source>Unable to create directory:
%1</source>
<translation type="unfinished"/>
<translation>No s&apos;ha pogut crear el directori:
%1</translation>
</message>
<message>
<source>Are you sure you want to overwrite the existing file &quot;%1&quot; with the attachment?</source>
@ -3124,7 +3126,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Confirm overwrite</source>
<translation type="unfinished"/>
<translation>Confirma la sobreescriptura</translation>
</message>
<message>
<source>Unable to save attachments:
@ -3143,7 +3145,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Confirm remove</source>
<translation type="unfinished"/>
<translation>Confirma l&apos;eliminació</translation>
</message>
<message numerus="yes">
<source>Unable to open file(s):
@ -3254,7 +3256,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Yes</source>
<translation type="unfinished"/>
<translation></translation>
</message>
<message>
<source>TOTP</source>
@ -3329,12 +3331,12 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>[PROTECTED]</source>
<translation type="unfinished"/>
<translation>[PROTEGIT]</translation>
</message>
<message>
<source>&lt;b&gt;%1&lt;/b&gt;: %2</source>
<comment>attributes line</comment>
<translation type="unfinished"/>
<translation>&lt;b&gt;%1&lt;/b&gt;: %2</translation>
</message>
<message>
<source>Enabled</source>
@ -3346,7 +3348,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Share</source>
<translation type="unfinished"/>
<translation>Comparteix</translation>
</message>
<message>
<source>Display current TOTP value</source>
@ -3361,7 +3363,7 @@ This may cause the affected plugins to malfunction.</source>
<name>EntryView</name>
<message>
<source>Customize View</source>
<translation type="unfinished"/>
<translation>Personalitza la visualització</translation>
</message>
<message>
<source>Hide Usernames</source>
@ -3373,19 +3375,19 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Fit to window</source>
<translation type="unfinished"/>
<translation>Ajusta a la finestra</translation>
</message>
<message>
<source>Fit to contents</source>
<translation type="unfinished"/>
<translation>Ajusta al contingut</translation>
</message>
<message>
<source>Reset to defaults</source>
<translation type="unfinished"/>
<translation>Reinicialitza-la als valors predeterminats</translation>
</message>
<message>
<source>Attachments (icon)</source>
<translation type="unfinished"/>
<translation>Fitxers adjunts (icona)</translation>
</message>
</context>
<context>
@ -3419,14 +3421,14 @@ This may cause the affected plugins to malfunction.</source>
<message>
<source>[empty]</source>
<comment>group has no children</comment>
<translation type="unfinished"/>
<translation>[buit]</translation>
</message>
</context>
<context>
<name>HostInstaller</name>
<message>
<source>KeePassXC: Cannot save file!</source>
<translation type="unfinished"/>
<translation>KeePassXC: no s&apos;ha pogut desar el fitxer!</translation>
</message>
<message>
<source>Cannot save the native messaging script file.</source>
@ -3512,19 +3514,19 @@ You can enable the DuckDuckGo website icon service in the security section of th
</message>
<message>
<source>Header doesn&apos;t match hash</source>
<translation type="unfinished"/>
<translation>La capçalera no coincideix amb el hash</translation>
</message>
<message>
<source>Invalid header id size</source>
<translation type="unfinished"/>
<translation>Mida de l&apos;id de capçalera no vàlid</translation>
</message>
<message>
<source>Invalid header field length</source>
<translation type="unfinished"/>
<translation>Longitud del camp de capçalera no vàlid</translation>
</message>
<message>
<source>Invalid header data length</source>
<translation type="unfinished"/>
<translation>Longitud de dades de la capçalera no vàlida</translation>
</message>
<message>
<source>Invalid credentials were provided, please try again.
@ -3567,15 +3569,15 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Invalid header id size</source>
<translation type="unfinished"/>
<translation>Mida de l&apos;id de capçalera no vàlid</translation>
</message>
<message>
<source>Invalid header field length</source>
<translation type="unfinished"/>
<translation>Longitud del camp de capçalera no vàlid</translation>
</message>
<message>
<source>Invalid header data length</source>
<translation type="unfinished"/>
<translation>Longitud de dades de la capçalera no vàlida</translation>
</message>
<message>
<source>Failed to open buffer for KDF parameters in header</source>
@ -3591,7 +3593,7 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Invalid inner header id size</source>
<translation type="unfinished"/>
<translation>Mida de l&apos;identificador intern d&apos;encapçalament no vàlida</translation>
</message>
<message>
<source>Invalid inner header field length</source>
@ -3793,7 +3795,7 @@ Es tracta d&apos;una migració unidireccional. No obrir la base de dades importa
</message>
<message>
<source>Invalid EnableAutoType value</source>
<translation>Valor d&apos;EnableAutoType invàlid</translation>
<translation>Valor no vàlid de compleció automàtica</translation>
</message>
<message>
<source>Invalid EnableSearching value</source>
@ -4071,7 +4073,7 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Exported to %1</source>
<translation type="unfinished"/>
<translation>Exportat a %1</translation>
</message>
<message>
<source>Synchronized with %1</source>
@ -4079,11 +4081,11 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Import is disabled in settings</source>
<translation type="unfinished"/>
<translation>La importació està inhabilitada a la configuració</translation>
</message>
<message>
<source>Export is disabled in settings</source>
<translation type="unfinished"/>
<translation>L&apos;exportació està deshabilitada a la configuració</translation>
</message>
<message>
<source>Inactive share</source>
@ -4095,7 +4097,7 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Exported to</source>
<translation type="unfinished"/>
<translation>Exportat a</translation>
</message>
<message>
<source>Synchronized with</source>
@ -4410,7 +4412,7 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>&amp;Import</source>
<translation type="unfinished"/>
<translation>&amp; Importa</translation>
</message>
<message>
<source>Copy att&amp;ribute...</source>
@ -4446,11 +4448,11 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>&amp;Edit entry</source>
<translation type="unfinished"/>
<translation>&amp;Edita l&apos;entrada</translation>
</message>
<message>
<source>View or edit entry</source>
<translation type="unfinished"/>
<translation>Mostra o edita l&apos;entrada</translation>
</message>
<message>
<source>&amp;New group</source>
@ -4466,7 +4468,7 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>&amp;Database settings...</source>
<translation type="unfinished"/>
<translation>Configuració de la base de &amp;dades...</translation>
</message>
<message>
<source>Copy &amp;password</source>
@ -4474,7 +4476,7 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>Perform &amp;Auto-Type</source>
<translation>Realitza una compleció &amp;automàtic</translation>
<translation>Fes una compleció &amp;automàtica</translation>
</message>
<message>
<source>Open &amp;URL</source>
@ -4490,11 +4492,11 @@ We recommend you use the AppImage available on our downloads page.</source>
</message>
<message>
<source>CSV file...</source>
<translation type="unfinished"/>
<translation>Fitxer CSV...</translation>
</message>
<message>
<source>Import a CSV file</source>
<translation type="unfinished"/>
<translation>Importa un fitxer CSV</translation>
</message>
<message>
<source>Show TOTP...</source>
@ -4524,7 +4526,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>&amp;Export</source>
<translation type="unfinished"/>
<translation>&amp;Exporta</translation>
</message>
<message>
<source>&amp;Check for Updates...</source>
@ -4552,7 +4554,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>&amp;Export to HTML file...</source>
<translation type="unfinished"/>
<translation>&amp;Exporta a un fitxer HTML...</translation>
</message>
<message>
<source>1Password Vault...</source>
@ -4560,7 +4562,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Import a 1Password Vault</source>
<translation type="unfinished"/>
<translation>Importa un 1Password Vault</translation>
</message>
<message>
<source>&amp;Getting Started</source>
@ -4836,7 +4838,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Passphrase is required to decrypt this key</source>
<translation type="unfinished"/>
<translation>Cal la frase de contrasenya per desxifrar aquesta clau</translation>
</message>
<message>
<source>Key derivation failed, key file corrupted?</source>
@ -4844,7 +4846,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Decryption failed, wrong passphrase?</source>
<translation type="unfinished"/>
<translation>El desxifrat ha fallat, la frase de contrasenya és errònia?</translation>
</message>
<message>
<source>Unexpected EOF while reading public key</source>
@ -4982,7 +4984,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Exclude look-alike characters</source>
<translation>Excloure caràcters d&apos;aspecte semblant</translation>
<translation>Exclou caràcters d&apos;aspecte similar</translation>
</message>
<message>
<source>Pick characters from every group</source>
@ -4994,7 +4996,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Passphrase</source>
<translation>Contrasenya</translation>
<translation>Frase de contrasenya</translation>
</message>
<message>
<source>Wordlist:</source>
@ -5150,11 +5152,11 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Upper-case letters</source>
<translation type="unfinished"/>
<translation>Lletra majúscula</translation>
</message>
<message>
<source>Lower-case letters</source>
<translation type="unfinished"/>
<translation>Lletra minúscula</translation>
</message>
<message>
<source>Special characters</source>
@ -5170,7 +5172,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Excluded characters</source>
<translation type="unfinished"/>
<translation>Caràcters exclosos</translation>
</message>
<message>
<source>Hex Passwords</source>
@ -5198,11 +5200,11 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>lower case</source>
<translation type="unfinished"/>
<translation>minúscules</translation>
</message>
<message>
<source>UPPER CASE</source>
<translation type="unfinished"/>
<translation>MAJÚSCULES</translation>
</message>
<message>
<source>Title Case</source>
@ -5376,7 +5378,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Edit an entry.</source>
<translation type="unfinished"/>
<translation>Edita una entrada.</translation>
</message>
<message>
<source>Title for the entry.</source>
@ -5384,11 +5386,11 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>title</source>
<translation type="unfinished"/>
<translation>títol</translation>
</message>
<message>
<source>Path of the entry to edit.</source>
<translation type="unfinished"/>
<translation>Ruta de l&apos;entrada a editar.</translation>
</message>
<message>
<source>Estimate the entropy of a password.</source>
@ -5400,7 +5402,7 @@ Podeu esperar alguns errors i incidències menors. Aquesta versió no està pens
</message>
<message>
<source>Perform advanced analysis on the password.</source>
<translation type="unfinished"/>
<translation>Fes una anàlisi avançada de la contrasenya.</translation>
</message>
<message>
<source>WARNING: You are using a legacy key file format which may become
@ -5530,16 +5532,17 @@ Available commands:
</message>
<message>
<source>Generate a new random diceware passphrase.</source>
<translation type="unfinished"/>
<translation>Genera una frase de contrasenya nova amb llançament de daus.</translation>
</message>
<message>
<source>Word count for the diceware passphrase.</source>
<translation type="unfinished"/>
<translation>Recompte de paraules per a la frase de contrasenya amb llançament de daus.</translation>
</message>
<message>
<source>Wordlist for the diceware generator.
[Default: EFF English]</source>
<translation type="unfinished"/>
<translation>Llista de paraules per al generador de llançament de daus.
[Per defecte: EFF English]</translation>
</message>
<message>
<source>Generate a new random password.</source>
@ -5620,7 +5623,7 @@ Available commands:
</message>
<message>
<source>Successfully edited entry %1.</source>
<translation type="unfinished"/>
<translation>L&apos;entrada %1 s&apos;ha editat correctament.</translation>
</message>
<message>
<source>Length %1</source>
@ -5728,35 +5731,35 @@ Available commands:
</message>
<message>
<source>Length of the generated password</source>
<translation type="unfinished"/>
<translation>Longitud de la contrasenya generada</translation>
</message>
<message>
<source>Use lowercase characters</source>
<translation type="unfinished"/>
<translation>Usa caràcters en minúscula</translation>
</message>
<message>
<source>Use uppercase characters</source>
<translation type="unfinished"/>
<translation>Usa caràcters en majúscules</translation>
</message>
<message>
<source>Use special characters</source>
<translation type="unfinished"/>
<translation>Usa caràcters especials</translation>
</message>
<message>
<source>Use extended ASCII</source>
<translation type="unfinished"/>
<translation>Usa l&apos;ASCII ampliat</translation>
</message>
<message>
<source>Exclude character set</source>
<translation type="unfinished"/>
<translation>Exclou el conjunt de caràcters</translation>
</message>
<message>
<source>chars</source>
<translation type="unfinished"/>
<translation>caràcters</translation>
</message>
<message>
<source>Exclude similar looking characters</source>
<translation type="unfinished"/>
<translation>Exclou caràcters d&apos;aspecte similar</translation>
</message>
<message>
<source>Include characters from every selected group</source>
@ -5790,7 +5793,7 @@ Available commands:
</message>
<message>
<source>Successfully deleted entry %1.</source>
<translation type="unfinished"/>
<translation>L&apos;entrada %1 s&apos;ha suprimit correctament.</translation>
</message>
<message>
<source>Show the entry&apos;s current TOTP.</source>
@ -5810,7 +5813,7 @@ Available commands:
</message>
<message>
<source>file empty</source>
<translation type="unfinished"/>
<translation>fitxer buit</translation>
</message>
<message>
<source>%1: (row, col) %2,%3</source>
@ -5818,15 +5821,15 @@ Available commands:
</message>
<message>
<source>AES: 256-bit</source>
<translation type="unfinished"/>
<translation>AES: 256-bit</translation>
</message>
<message>
<source>Twofish: 256-bit</source>
<translation type="unfinished"/>
<translation>Twofish: 256-bit</translation>
</message>
<message>
<source>ChaCha20: 256-bit</source>
<translation type="unfinished"/>
<translation>ChaCha20:256-bits</translation>
</message>
<message>
<source>Argon2 (KDBX 4 recommended)</source>
@ -5834,21 +5837,21 @@ Available commands:
</message>
<message>
<source>AES-KDF (KDBX 4)</source>
<translation type="unfinished"/>
<translation>AES-KDF (KDBX 4)</translation>
</message>
<message>
<source>AES-KDF (KDBX 3.1)</source>
<translation type="unfinished"/>
<translation>AES-KDF (KDBX 3,1)</translation>
</message>
<message>
<source>Invalid Settings</source>
<comment>TOTP</comment>
<translation type="unfinished"/>
<translation>La configuració no és vàlida</translation>
</message>
<message>
<source>Invalid Key</source>
<comment>TOTP</comment>
<translation type="unfinished"/>
<translation>Clau invàlida</translation>
</message>
<message>
<source>Message encryption failed.</source>
@ -5860,7 +5863,7 @@ Available commands:
</message>
<message>
<source>Create a new database.</source>
<translation type="unfinished"/>
<translation>Crea una nova base de dades.</translation>
</message>
<message>
<source>File %1 already exists.</source>
@ -5892,7 +5895,7 @@ Available commands:
</message>
<message>
<source>Path of the entry to remove.</source>
<translation type="unfinished"/>
<translation>Ruta de l&apos;entrada a eliminar.</translation>
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
@ -6100,7 +6103,7 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Format to use when exporting. Available choices are xml or csv. Defaults to xml.</source>
<translation type="unfinished"/>
<translation>Format a utilitzar en l&apos;exportació. Les opcions disponibles són XML o CSV. Per defect s&apos;usa XML.</translation>
</message>
<message>
<source>Exports the content of a database to standard output in the specified format.</source>
@ -6348,31 +6351,31 @@ Nucli: %3 %4</translation>
<name>SearchHelpWidget</name>
<message>
<source>Search Help</source>
<translation type="unfinished"/>
<translation>Cerca l&apos;ajuda</translation>
</message>
<message>
<source>Search terms are as follows: [modifiers][field:][&quot;]term[&quot;]</source>
<translation type="unfinished"/>
<translation>Els termes de cerca són així: [Modificadors] [camp:] [&quot;] terme [&quot;]</translation>
</message>
<message>
<source>Every search term must match (ie, logical AND)</source>
<translation type="unfinished"/>
<translation>Cada terme de cerca ha de coincidir (p.e. I lògica)</translation>
</message>
<message>
<source>Modifiers</source>
<translation type="unfinished"/>
<translation>Modificadors</translation>
</message>
<message>
<source>exclude term from results</source>
<translation type="unfinished"/>
<translation>exclou el terme dels resultats</translation>
</message>
<message>
<source>match term exactly</source>
<translation type="unfinished"/>
<translation>el terme ha de coincidir exactament</translation>
</message>
<message>
<source>use regex in term</source>
<translation type="unfinished"/>
<translation>usa regex al terme</translation>
</message>
<message>
<source>Fields</source>
@ -6380,19 +6383,19 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Term Wildcards</source>
<translation type="unfinished"/>
<translation>Comodins</translation>
</message>
<message>
<source>match anything</source>
<translation type="unfinished"/>
<translation>concorda amb qualsevol cosa</translation>
</message>
<message>
<source>match one</source>
<translation type="unfinished"/>
<translation>coincideix amb un</translation>
</message>
<message>
<source>logical OR</source>
<translation type="unfinished"/>
<translation>operador lògic O</translation>
</message>
<message>
<source>Examples</source>
@ -6415,7 +6418,7 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Search Help</source>
<translation type="unfinished"/>
<translation>Cerca l&apos;ajuda</translation>
</message>
<message>
<source>Search (%1)...</source>
@ -6530,7 +6533,7 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Own certificate</source>
<translation type="unfinished"/>
<translation>Certificat propi</translation>
</message>
<message>
<source>Fingerprint:</source>
@ -6538,11 +6541,11 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Certificate:</source>
<translation type="unfinished"/>
<translation>Certificat:</translation>
</message>
<message>
<source>Signer</source>
<translation type="unfinished"/>
<translation>Signant</translation>
</message>
<message>
<source>Key:</source>
@ -6594,7 +6597,7 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Certificate</source>
<translation type="unfinished"/>
<translation>Certificat</translation>
</message>
<message>
<source>Trusted</source>
@ -6627,23 +6630,23 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Exporting changed certificate</source>
<translation type="unfinished"/>
<translation>Exportant el certificat modificat</translation>
</message>
<message>
<source>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</source>
<translation type="unfinished"/>
<translation>El certificat exportat no és el mateix que el que està en ús. Voleu exportar el certificat actual?</translation>
</message>
<message>
<source>Signer:</source>
<translation type="unfinished"/>
<translation>Signant:</translation>
</message>
<message>
<source>Allow KeeShare imports</source>
<translation type="unfinished"/>
<translation>Permet les importacions de KeeShare</translation>
</message>
<message>
<source>Allow KeeShare exports</source>
<translation type="unfinished"/>
<translation>Permet les exportacions de KeeShare</translation>
</message>
<message>
<source>Only show warnings and errors</source>
@ -6741,7 +6744,7 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Import from container with certificate</source>
<translation type="unfinished"/>
<translation>Importa des del contenidor amb certificat</translation>
</message>
<message>
<source>Do you want to trust %1 with the fingerprint of %2 from %3?</source>
@ -6820,11 +6823,11 @@ Nucli: %3 %4</translation>
</message>
<message>
<source>Export to %1 failed (%2)</source>
<translation type="unfinished"/>
<translation>L&apos;exportació a %1 ha fallat (%2)</translation>
</message>
<message>
<source>Export to %1 successful (%2)</source>
<translation type="unfinished"/>
<translation>L&apos;exportació a %1 s&apos;ha realitzat amb èxit (%2)</translation>
</message>
<message>
<source>Export to %1</source>
@ -6952,7 +6955,7 @@ Example: JBSWY3DPEHPK3PXP</source>
</message>
<message>
<source>Are you sure you want to delete TOTP settings for this entry?</source>
<translation type="unfinished"/>
<translation>Esteu segur que voleu suprimir la configuració TOTP de l&apos;entrada?</translation>
</message>
</context>
<context>
@ -7038,7 +7041,7 @@ Example: JBSWY3DPEHPK3PXP</source>
</message>
<message>
<source>Import from 1Password</source>
<translation type="unfinished"/>
<translation>Importa des de 1Password</translation>
</message>
<message>
<source>Open a recent database</source>

View File

@ -3462,7 +3462,7 @@ Det kan få de påvirkede plugins til at svigte.</translation>
<name>IconDownloaderDialog</name>
<message>
<source>Download Favicons</source>
<translation type="unfinished"/>
<translation>Download faviconer</translation>
</message>
<message>
<source>Cancel</source>
@ -4556,27 +4556,27 @@ Forvent nogle fejl og mindre problemer. Denne version er ikke beregnet til produ
</message>
<message>
<source>&amp;Export</source>
<translation type="unfinished"/>
<translation>&amp;Eksportér</translation>
</message>
<message>
<source>&amp;Check for Updates...</source>
<translation type="unfinished"/>
<translation>&amp;Søg efter opdateringer ...</translation>
</message>
<message>
<source>Downlo&amp;ad all favicons</source>
<translation type="unfinished"/>
<translation>&amp;Download alle faviconer</translation>
</message>
<message>
<source>Sort &amp;A-Z</source>
<translation type="unfinished"/>
<translation>Sortér &amp;A-Å</translation>
</message>
<message>
<source>Sort &amp;Z-A</source>
<translation type="unfinished"/>
<translation>Sortér &amp;Å-A</translation>
</message>
<message>
<source>&amp;Password Generator</source>
<translation type="unfinished"/>
<translation>&amp;Adgangskodegenerator</translation>
</message>
<message>
<source>Download favicon</source>
@ -4584,19 +4584,19 @@ Forvent nogle fejl og mindre problemer. Denne version er ikke beregnet til produ
</message>
<message>
<source>&amp;Export to HTML file...</source>
<translation type="unfinished"/>
<translation>&amp;Eksportér til HTML-fil ...</translation>
</message>
<message>
<source>1Password Vault...</source>
<translation type="unfinished"/>
<translation>1Password-boks ...</translation>
</message>
<message>
<source>Import a 1Password Vault</source>
<translation type="unfinished"/>
<translation>Importér en 1Password-boks</translation>
</message>
<message>
<source>&amp;Getting Started</source>
<translation type="unfinished"/>
<translation>&amp;Kom godt i gang</translation>
</message>
<message>
<source>Open Getting Started Guide PDF</source>
@ -4604,7 +4604,7 @@ Forvent nogle fejl og mindre problemer. Denne version er ikke beregnet til produ
</message>
<message>
<source>&amp;Online Help...</source>
<translation type="unfinished"/>
<translation>&amp;Onlinehjælp ...</translation>
</message>
<message>
<source>Go to online documentation (opens browser)</source>
@ -4612,7 +4612,7 @@ Forvent nogle fejl og mindre problemer. Denne version er ikke beregnet til produ
</message>
<message>
<source>&amp;User Guide</source>
<translation type="unfinished"/>
<translation>&amp;Brugerguide</translation>
</message>
<message>
<source>Open User Guide PDF</source>
@ -4620,7 +4620,7 @@ Forvent nogle fejl og mindre problemer. Denne version er ikke beregnet til produ
</message>
<message>
<source>&amp;Keyboard Shortcuts</source>
<translation type="unfinished"/>
<translation>&amp;Tastaturgenveje</translation>
</message>
</context>
<context>
@ -7077,7 +7077,7 @@ Example: JBSWY3DPEHPK3PXP</source>
</message>
<message>
<source>Import from 1Password</source>
<translation type="unfinished"/>
<translation>Importér fra 1Password</translation>
</message>
<message>
<source>Open a recent database</source>

View File

@ -574,8 +574,8 @@ Bitte wählen Sie, ob Sie den Zugriff erlauben möchten.</translation>
<message>
<source>You have multiple databases open.
Please select the correct database for saving credentials.</source>
<translation>Du hast mehrere Datenbanken geöffnet.
Bitte wähle die richtige Datenbank zum speichern der Anmeldedaten.</translation>
<translation>Sie haben mehrere Datenbanken geöffnet.
Bitte wählen Sie die richtige Datenbank zum Speichern der Anmeldedaten.</translation>
</message>
</context>
<context>
@ -1070,7 +1070,7 @@ Sicherungsdatenbank bei %2</translation>
</message>
<message>
<source>Database file has unmerged changes.</source>
<translation>Die Datenbankdatei hat Änderungen die noch nicht gemergt wurden.</translation>
<translation>Die Datenbankdatei hat Änderungen die noch nicht zusammengeführt wurden.</translation>
</message>
<message>
<source>Recycle Bin</source>
@ -1217,7 +1217,7 @@ Um zu verhindern, dass dieser Fehler auftritt, müssen Sie zu &quot;Datenbankein
</message>
<message>
<source>&lt;p&gt;In addition to your master password, you can use a secret file to enhance the security of your database. Such a file can be generated in your database&apos;s security settings.&lt;/p&gt;&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; your *.kdbx database file!&lt;br&gt;If you do not have a key file, leave the field empty.&lt;/p&gt;&lt;p&gt;Click for more information...&lt;/p&gt;</source>
<translation>&lt;p&gt;Zusätzlich zu ihrem Masterpasswort können sie eine geheime Datei verwenden um die Sicherheit ihrer Datenbank zu erhöhen. Eine solche Datei kann in den Sicherheitseinstellung ihrer Datenbank generiert werden.&lt;/p&gt; &lt;p&gt;Diese Datei ist jedoch &lt;strong&gt;nicht&lt;/strong&gt; Ihre *.kdbx Datei!&lt;br&gt; Wenn sie keine Schlüsseldatei haben lassen sie das Feld leer.&lt;/p&gt; &lt;p&gt;Klicken sie hier für weitere Informationen...&lt;/p&gt;</translation>
<translation>&lt;p&gt;Zusätzlich zu Ihrem Masterpasswort können Sie eine geheime Datei verwenden, um die Sicherheit Ihrer Datenbank zu erhöhen. Eine solche Datei kann in den Sicherheitseinstellungen Ihrer Datenbank generiert werden.&lt;/p&gt; &lt;p&gt;Diese Datei ist jedoch &lt;strong&gt;nicht&lt;/strong&gt; Ihre *.kdbx-Datei!&lt;br&gt; Wenn Sie keine Schlüsseldatei haben, lassen Sie das Feld leer.&lt;/p&gt; &lt;p&gt;Klicken Sie hier für weitere Informationen...&lt;/p&gt;</translation>
</message>
<message>
<source>Key file help</source>
@ -1331,7 +1331,7 @@ Das wird die Verbindung zum Browser-Plugin verhindern.</translation>
<message>
<source>Do you really want to disconnect all browsers?
This may prevent connection to the browser plugin.</source>
<translation>Möchtest du wirklich alle Browserverbindungen entfernen?
<translation>Möchten Sie wirklich alle Browserverbindungen entfernen?
Das wird die Verbindung zu dem Browser-Plugin verhindern.</translation>
</message>
<message>
@ -3094,7 +3094,7 @@ Dies kann dazu führen, dass die jeweiligen Plugins nicht mehr richtig funktioni
<name>Entry</name>
<message>
<source>%1 - Clone</source>
<translation>%1 - Kopie</translation>
<translation>%1 - Klon</translation>
</message>
</context>
<context>
@ -3608,7 +3608,7 @@ Falls dies wiederholt passiert, dann könnte Ihre Datenbank beschädigt sein.</t
</message>
<message>
<source>Invalid header field length</source>
<translation>Ungültiger Header-Feldlänge</translation>
<translation>Ungültige Header-Feldlänge</translation>
</message>
<message>
<source>Invalid header data length</source>
@ -4451,8 +4451,8 @@ Diese Version ist nicht für den Produktiveinsatz gedacht.</translation>
<message>
<source>WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard!
We recommend you use the AppImage available on our downloads page.</source>
<translation>WARNUNG: Deine Qt Version könnte KeePassXC mit einer Bildschirmtastatur zu abstürzen bringen!
Wir empfehlen dir die Verwendung des auf unserer Downloadseite verfügbaren AppImage.</translation>
<translation>WARNUNG: Ihre Qt Version könnte KeePassXC mit einer Bildschirmtastatur zu abstürzen bringen!
Wir empfehlen Ihnen die Verwendung des auf unserer Downloadseite verfügbaren AppImage.</translation>
</message>
<message>
<source>&amp;Import</source>
@ -4665,7 +4665,7 @@ Da sie Fehler beinhalten könnte, ist diese Version nicht für den Produktiveins
</message>
<message>
<source>Reapplying older target entry on top of newer source %1 [%2]</source>
<translation>Älterer Ziel-Eintrag wird auf neuere Quellen-Eintrag angewendet %1 [%2]</translation>
<translation>Älterer Ziel-Eintrag wird auf neueren Quellen-Eintrag angewendet %1 [%2]</translation>
</message>
<message>
<source>Reapplying older source entry on top of newer target %1 [%2]</source>
@ -4758,7 +4758,7 @@ Da sie Fehler beinhalten könnte, ist diese Version nicht für den Produktiveins
</message>
<message>
<source>A master key known only to you protects your database.</source>
<translation>Ein nur dir bekannter Schlüssel schützt die Datenbank.</translation>
<translation>Ein nur Ihnen bekannter Schlüssel schützt die Datenbank.</translation>
</message>
</context>
<context>
@ -5287,7 +5287,7 @@ Da sie Fehler beinhalten könnte, ist diese Version nicht für den Produktiveins
</message>
<message>
<source>Empty</source>
<translation>Leer</translation>
<translation>Leeren</translation>
</message>
<message>
<source>Remove</source>

View File

@ -2340,6 +2340,14 @@ Disable safe saves and try again?</translation>
<source>Are you sure you want to remove this URL?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reveal</source>
<translation type="unfinished">Reveal</translation>
</message>
<message>
<source>Hide</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetAdvanced</name>
@ -2521,6 +2529,14 @@ Disable safe saves and try again?</translation>
<source>Edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only send this setting to the browser for HTTP Auth dialogs. If enabled, normal login forms will not show this entry for selection.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use this entry only with HTTP Basic Auth</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetHistory</name>
@ -3455,6 +3471,13 @@ This may cause the affected plugins to malfunction.</translation>
<translation type="unfinished">Advanced</translation>
</message>
</context>
<context>
<name>EntryURLModel</name>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EntryView</name>
<message>
@ -3508,6 +3531,40 @@ This may cause the affected plugins to malfunction.</translation>
</translation>
</message>
</context>
<context>
<name>FdoSecrets::SettingsDatabaseModel</name>
<message>
<source>File Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Group</source>
<translation type="unfinished">Group</translation>
</message>
<message>
<source>Manage</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock to show</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FdoSecrets::SettingsSessionModel</name>
<message>
<source>Application</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Manage</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FdoSecretsPlugin</name>
<message>
@ -4699,6 +4756,40 @@ Expect some bugs and minor issues, this version is not meant for production use.
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageDatabase</name>
<message>
<source>Database settings</source>
<translation type="unfinished">Database settings</translation>
</message>
<message>
<source>Edit database settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock database to show more information</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Lock database</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageSession</name>
<message>
<source>Disconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Disconnect this application</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Merger</name>
<message>
@ -6576,18 +6667,6 @@ Kernel: %3 %4</source>
<source>Exposed database groups:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Group</source>
<translation type="unfinished">Group</translation>
</message>
<message>
<source>Manage</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Authorization</source>
<translation type="unfinished"></translation>
@ -6596,42 +6675,6 @@ Kernel: %3 %4</source>
<source>These applications are currently connected:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Application</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Disconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database settings</source>
<translation type="unfinished">Database settings</translation>
</message>
<message>
<source>Edit database settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock database to show more information</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Lock database</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unlock to show</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsWidgetKeeShare</name>
@ -7077,6 +7120,13 @@ Example: JBSWY3DPEHPK3PXP</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>URLEdit</name>
<message>
<source>Invalid URL</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>UpdateCheckDialog</name>
<message>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6199,7 +6199,7 @@ CPU アーキテクチャー: %2
</message>
<message>
<source>Unable to import XML database export %1</source>
<translation type="unfinished"/>
<translation>XMLデータベース %1 </translation>
</message>
<message>
<source>Successfully imported database.</source>

View File

@ -450,7 +450,7 @@
</message>
<message>
<source>KeePassXC requires the Accessibility permission in order to perform entry level Auto-Type. If you already granted permission, you may have to restart KeePassXC.</source>
<translation type="unfinished"/>
<translation> KeePassXC에 . KeePassXC를 .</translation>
</message>
</context>
<context>
@ -506,7 +506,7 @@
</message>
<message>
<source>KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global Auto-Type. Screen Recording is necessary to use the window title to find entries. If you already granted permission, you may have to restart KeePassXC.</source>
<translation type="unfinished"/>
<translation> KeePassXC에 . . KeePassXC를 .</translation>
</message>
</context>
<context>
@ -878,7 +878,11 @@ Would you like to migrate your existing settings now?</source>
Give the connection a unique name or ID, for example:
chrome-laptop.</source>
<translation type="unfinished"/>
<translation> :
%1
ID를 . :
chrome-laptop.</translation>
</message>
</context>
<context>
@ -1211,11 +1215,12 @@ To prevent this error from appearing, you must go to &quot;Database Settings / S
<message>
<source>&lt;p&gt;You can use a hardware security key such as a &lt;strong&gt;YubiKey&lt;/strong&gt; or &lt;strong&gt;OnlyKey&lt;/strong&gt; with slots configured for HMAC-SHA1.&lt;/p&gt;
&lt;p&gt;Click for more information...&lt;/p&gt;</source>
<translation type="unfinished"/>
<translation>&lt;p&gt;&lt;strong&gt;YubiKey&lt;/strong&gt; &lt;strong&gt;OnlyKey&lt;/strong&gt; HMAC-SHA1 .&lt;/p&gt;
&lt;p&gt; ...&lt;/p&gt;</translation>
</message>
<message>
<source>&lt;p&gt;In addition to your master password, you can use a secret file to enhance the security of your database. Such a file can be generated in your database&apos;s security settings.&lt;/p&gt;&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; your *.kdbx database file!&lt;br&gt;If you do not have a key file, leave the field empty.&lt;/p&gt;&lt;p&gt;Click for more information...&lt;/p&gt;</source>
<translation type="unfinished"/>
<translation>&lt;p&gt; . .&lt;/p&gt;&lt;p&gt; *.kdbx &lt;strong&gt;!&lt;/strong&gt;&lt;br&gt; .&lt;/p&gt;&lt;p&gt; ...&lt;/p&gt; </translation>
</message>
<message>
<source>Key file help</source>
@ -1881,7 +1886,7 @@ Are you sure you want to continue without a password?</source>
</message>
<message>
<source>Please wait, database statistics are being calculated...</source>
<translation type="unfinished"/>
<translation> ...</translation>
</message>
</context>
<context>
@ -6322,7 +6327,7 @@ CPU 아키텍처: %2
</message>
<message>
<source>Show the protected attributes in clear text.</source>
<translation type="unfinished"/>
<translation> .</translation>
</message>
</context>
<context>

View File

@ -35,7 +35,7 @@
</message>
<message>
<source>Copy to clipboard</source>
<translation>Kopier til utklippstavla</translation>
<translation>Kopier til utklippstavle</translation>
</message>
<message>
<source>Project Maintainers:</source>
@ -97,11 +97,11 @@
</message>
<message>
<source>Reset Settings?</source>
<translation type="unfinished"/>
<translation>Tilbakestill innstillinger?</translation>
</message>
<message>
<source>Are you sure you want to reset all general and security settings to default?</source>
<translation type="unfinished"/>
<translation>Er du sikker at du vil tilbakestille alle generelle og sikkerhetsinnstillinger til standard?</translation>
</message>
</context>
<context>
@ -249,7 +249,7 @@
</message>
<message>
<source>Language:</source>
<translation type="unfinished"/>
<translation>Språk:</translation>
</message>
<message>
<source>(restart program to activate)</source>
@ -269,7 +269,7 @@
</message>
<message>
<source>Minimize</source>
<translation type="unfinished"/>
<translation>Minimer</translation>
</message>
<message>
<source>Drop to background</source>
@ -298,7 +298,7 @@
</message>
<message>
<source>Language selection</source>
<translation type="unfinished"/>
<translation>Valg av språk</translation>
</message>
<message>
<source>Reset Settings to Default</source>
@ -411,7 +411,7 @@
</message>
<message>
<source>Clear search query after</source>
<translation type="unfinished"/>
<translation>Tøm søket etter</translation>
</message>
</context>
<context>
@ -446,7 +446,7 @@
</message>
<message>
<source>Permission Required</source>
<translation type="unfinished"/>
<translation>Tillatelse kreves</translation>
</message>
<message>
<source>KeePassXC requires the Accessibility permission in order to perform entry level Auto-Type. If you already granted permission, you may have to restart KeePassXC.</source>
@ -502,7 +502,7 @@
<name>AutoTypePlatformMac</name>
<message>
<source>Permission Required</source>
<translation type="unfinished"/>
<translation>Tillatelse kreves</translation>
</message>
<message>
<source>KeePassXC requires the Accessibility and Screen Recorder permission in order to perform global Auto-Type. Screen Recording is necessary to use the window title to find entries. If you already granted permission, you may have to restart KeePassXC.</source>
@ -550,7 +550,7 @@ Velg om du vil gi tilgang eller ikke.</translation>
</message>
<message>
<source>Allow access</source>
<translation type="unfinished"/>
<translation>Gi tilgang</translation>
</message>
<message>
<source>Deny access</source>
@ -743,7 +743,7 @@ Vennligst velg riktig database for å lagre legitimasjon.</translation>
</message>
<message>
<source>&amp;Brave</source>
<translation type="unfinished"/>
<translation>&amp;Brave</translation>
</message>
<message>
<source>Returns expired credentials. String [expired] is added to the title.</source>
@ -1124,15 +1124,15 @@ Vurder å opprette en ny nøkkelfil.</translation>
</message>
<message>
<source>Unlock KeePassXC Database</source>
<translation type="unfinished"/>
<translation>Låse opp KeePassXC Database</translation>
</message>
<message>
<source>Enter Password:</source>
<translation type="unfinished"/>
<translation>Oppgi passord:</translation>
</message>
<message>
<source>Password field</source>
<translation type="unfinished"/>
<translation>Passord felt</translation>
</message>
<message>
<source>Toggle password visibility</source>
@ -1191,7 +1191,7 @@ To prevent this error from appearing, you must go to &quot;Database Settings / S
</message>
<message>
<source>Retry with empty password</source>
<translation type="unfinished"/>
<translation>Prøv igjen med tomt passord</translation>
</message>
<message>
<source>Enter Additional Credentials (if any):</source>
@ -1726,7 +1726,7 @@ Er du sikker på at du vil fortsette uten passord?</translation>
</message>
<message>
<source>Continue without password</source>
<translation type="unfinished"/>
<translation>Fortsett uten passord</translation>
</message>
</context>
<context>
@ -2550,7 +2550,7 @@ Deaktivere sikker lagring og prøve igjen?</translation>
</message>
<message>
<source>Password field</source>
<translation type="unfinished"/>
<translation>Passord felt</translation>
</message>
<message>
<source>Toggle password visibility</source>
@ -2826,7 +2826,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Password field</source>
<translation type="unfinished"/>
<translation>Passord felt</translation>
</message>
<message>
<source>Toggle password visibility</source>
@ -4555,7 +4555,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>&amp;Password Generator</source>
<translation type="unfinished"/>
<translation>&amp;Passord generator</translation>
</message>
<message>
<source>Download favicon</source>
@ -4906,7 +4906,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
<name>PasswordEdit</name>
<message>
<source>Passwords do not match</source>
<translation type="unfinished"/>
<translation>Passordene er ikke like</translation>
</message>
<message>
<source>Passwords match so far</source>
@ -4941,7 +4941,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Password field</source>
<translation type="unfinished"/>
<translation>Passord felt</translation>
</message>
<message>
<source>Toggle password visibility</source>
@ -6248,7 +6248,7 @@ Kjerne: %3 %4</translation>
</message>
<message>
<source>Enter password to unlock %1: </source>
<translation type="unfinished"/>
<translation>Skriv inn passord for å låse opp %1:</translation>
</message>
<message>
<source>Invalid YubiKey slot %1</source>

File diff suppressed because it is too large Load Diff

View File

@ -747,11 +747,11 @@ Selecione a base de dados correta para guardar as credenciais.</translation>
</message>
<message>
<source>Returns expired credentials. String [expired] is added to the title.</source>
<translation>Devolve as credenciais expiradas. Adiciona [expirada] ao título.</translation>
<translation>Devolve as credenciais caducadas. Adiciona [caducada] ao título.</translation>
</message>
<message>
<source>&amp;Allow returning expired credentials.</source>
<translation>Permitir devolução de credencias expir&amp;adas</translation>
<translation>Permitir devolução de credencias caduc&amp;adas</translation>
</message>
<message>
<source>Enable browser integration</source>
@ -1830,11 +1830,11 @@ Tem a certeza de que deseja continuar?</translation>
</message>
<message>
<source>Number of expired entries</source>
<translation>Número de entradas expiradas</translation>
<translation>Número de entradas caducadas</translation>
</message>
<message>
<source>The database contains entries that have expired.</source>
<translation>A base de dados contém entradas expiradas.</translation>
<translation>A base de dados contém entradas caducadas.</translation>
</message>
<message>
<source>Unique passwords</source>
@ -2553,7 +2553,7 @@ Desativar salvaguardas e tentar novamente?</translation>
</message>
<message>
<source>Expires</source>
<translation>Expira</translation>
<translation>Caduca</translation>
</message>
<message>
<source>Url field</source>
@ -2585,15 +2585,15 @@ Desativar salvaguardas e tentar novamente?</translation>
</message>
<message>
<source>Expiration field</source>
<translation>Campo Expira</translation>
<translation>Campo Caduca</translation>
</message>
<message>
<source>Expiration Presets</source>
<translation>Predefinições de expiração</translation>
<translation>Predefinições de caducidade</translation>
</message>
<message>
<source>Expiration presets</source>
<translation>Predefinições de expiração</translation>
<translation>Predefinições de caducidade</translation>
</message>
<message>
<source>Notes field</source>
@ -2609,7 +2609,7 @@ Desativar salvaguardas e tentar novamente?</translation>
</message>
<message>
<source>Toggle expiration</source>
<translation>Alternar expiração</translation>
<translation>Alternar caducidade</translation>
</message>
</context>
<context>
@ -2813,11 +2813,11 @@ As extensões suportadas são: %1.</translation>
</message>
<message>
<source>%1 is already being exported by this database.</source>
<translation>%1 está a ser exportada para esta base de dados.</translation>
<translation>%1 está a ser exportado para esta base de dados.</translation>
</message>
<message>
<source>%1 is already being imported by this database.</source>
<translation>%1 está a ser importada para esta base de dados.</translation>
<translation>%1 está a ser importado para esta base de dados.</translation>
</message>
<message>
<source>%1 is being imported and exported by different groups in this database.</source>
@ -2877,7 +2877,7 @@ As extensões suportadas são: %1.</translation>
</message>
<message>
<source>Expires</source>
<translation>Expira</translation>
<translation>Caduca</translation>
</message>
<message>
<source>Search</source>
@ -2905,7 +2905,7 @@ As extensões suportadas são: %1.</translation>
</message>
<message>
<source>Toggle expiration</source>
<translation>Alternar expiração</translation>
<translation>Alternar caducidade</translation>
</message>
<message>
<source>Auto-Type toggle for this and sub groups</source>
@ -2913,7 +2913,7 @@ As extensões suportadas são: %1.</translation>
</message>
<message>
<source>Expiration field</source>
<translation>Campo Expira</translation>
<translation>Campo Caduca</translation>
</message>
<message>
<source>Search toggle for this and sub groups</source>
@ -3271,7 +3271,7 @@ Esta ação pode implicar um funcionamento errático.</translation>
</message>
<message>
<source>Expires</source>
<translation>Expira</translation>
<translation>Caduca</translation>
</message>
<message>
<source>Created</source>
@ -3318,7 +3318,7 @@ Esta ação pode implicar um funcionamento errático.</translation>
</message>
<message>
<source>Expiration</source>
<translation>Expira</translation>
<translation>Caducidade</translation>
</message>
<message>
<source>URL</source>
@ -4029,7 +4029,7 @@ Linha %2, coluna %3</translation>
</message>
<message>
<source>Incorrect group expiry time field size</source>
<translation>Tamanho de campo de tempo de expiração de grupo incorreto</translation>
<translation>Tamanho inválido para o campo de caducidade do grupo</translation>
</message>
<message>
<source>Incorrect group icon field size</source>
@ -4081,7 +4081,7 @@ Linha %2, coluna %3</translation>
</message>
<message>
<source>Invalid entry expiry time field size</source>
<translation>Tamanho da entrada para o campo tempo de expiração inválido</translation>
<translation>Tamanho inválido para o campo de caducidade da entrada</translation>
</message>
<message>
<source>Invalid entry field type</source>
@ -4181,7 +4181,7 @@ Caso isto volte a acontecer, pode ser que a base de dados esteja danificada.</tr
<message>
<source>%1 set, click to change or remove</source>
<comment>Change or remove a key component</comment>
<translation>%1 definido, clique para alterar ou remover</translation>
<translation>%1 definida, clique para alterar ou remover</translation>
</message>
</context>
<context>
@ -6912,7 +6912,7 @@ Kernel: %3 %4</translation>
</message>
<message numerus="yes">
<source>Expires in &lt;b&gt;%n&lt;/b&gt; second(s)</source>
<translation><numerusform>Expira em &lt;b&gt;%n&lt;/b&gt; segundo</numerusform><numerusform>Expira dentro de &lt;b&gt;%n&lt;/b&gt; segundos</numerusform></translation>
<translation><numerusform>Caduca dentro de &lt;b&gt;%n&lt;/b&gt; segundo</numerusform><numerusform>Caduca dentro de &lt;b&gt;%n&lt;/b&gt; segundos</numerusform></translation>
</message>
</context>
<context>

File diff suppressed because it is too large Load Diff

View File

@ -101,7 +101,7 @@
</message>
<message>
<source>Are you sure you want to reset all general and security settings to default?</source>
<translation>Вы уверены, что вы хотите сбросить по умолчанию все общие настройки и настройки безопасности? </translation>
<translation>Действительно сбросить все общие параметры и параметры безопасности к значениям, заданным по умолчанию? </translation>
</message>
</context>
<context>
@ -325,7 +325,7 @@
</message>
<message>
<source>Clear clipboard after</source>
<translation>Задержка очистки поискового запроса:</translation>
<translation>Задержка очистки буфера обмена:</translation>
</message>
<message>
<source> sec</source>
@ -382,7 +382,7 @@
</message>
<message>
<source>Hide entry notes by default</source>
<translation>Скрывать заметки по умолчанию</translation>
<translation>Не показывать текст заметкок </translation>
</message>
<message>
<source>Privacy</source>
@ -521,7 +521,7 @@
</message>
<message>
<source>Search...</source>
<translation>Поиск...</translation>
<translation>Поиск</translation>
</message>
</context>
<context>
@ -640,12 +640,12 @@ Please select the correct database for saving credentials.</source>
<message>
<source>Sort &amp;matching credentials by title</source>
<extracomment>Credentials mean login data requested via browser extension</extracomment>
<translation>Сортировать &amp;подходящие учётные данные по названию</translation>
<translation>Сортировать возвращаемые данные по &amp;названию</translation>
</message>
<message>
<source>Sort matching credentials by &amp;username</source>
<extracomment>Credentials mean login data requested via browser extension</extracomment>
<translation>Сортировать по &amp;имени пользователя</translation>
<translation>Сортировать возвращаемые данные по &amp;имени пользователя</translation>
</message>
<message>
<source>Advanced</source>
@ -654,17 +654,17 @@ Please select the correct database for saving credentials.</source>
<message>
<source>Never &amp;ask before accessing credentials</source>
<extracomment>Credentials mean login data requested via browser extension</extracomment>
<translation>Никогда не &amp;спрашивать перед доступом к учётным данным</translation>
<translation>Не подтверждать &amp;доступ к записям</translation>
</message>
<message>
<source>Never ask before &amp;updating credentials</source>
<extracomment>Credentials mean login data requested via browser extension</extracomment>
<translation>Никогда не спрашивать перед &amp;обновлением учётных данных</translation>
<translation>Не подтверждать &amp;обновление записей</translation>
</message>
<message>
<source>Searc&amp;h in all opened databases for matching credentials</source>
<extracomment>Credentials mean login data requested via browser extension</extracomment>
<translation>&amp;Поиск во всех открытых базах для сопоставления учётных данных</translation>
<translation>Искать во &amp;всех открытых базах</translation>
</message>
<message>
<source>Automatically creating or updating string fields is not supported.</source>
@ -688,7 +688,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>Use a &amp;proxy application between KeePassXC and browser extension</source>
<translation>Использование &amp;прокси-приложения между KeePassXC и расширением браузера</translation>
<translation>Использовать &amp;прокси-приложение между KeePassXC и расширением браузера</translation>
</message>
<message>
<source>Use a custom proxy location if you installed a proxy manually.</source>
@ -697,12 +697,12 @@ Please select the correct database for saving credentials.</source>
<message>
<source>Use a &amp;custom proxy location</source>
<comment>Meant is the proxy for KeePassXC-Browser</comment>
<translation>Использовать &amp;пользовательское местоположение прокси</translation>
<translation>Задать &amp;своё расположение прокси</translation>
</message>
<message>
<source>Browse...</source>
<extracomment>Button for opening file dialog</extracomment>
<translation>Обзор...</translation>
<translation>Обзор</translation>
</message>
<message>
<source>&lt;b&gt;Warning:&lt;/b&gt; The following options can be dangerous!</source>
@ -739,7 +739,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>KeePassXC-Browser is needed for the browser integration to work. &lt;br /&gt;Download it for %1 and %2. %3</source>
<translation>Для интеграции с браузерами требуется KeePassXC-Browser. &lt;br /&gt;Скачайте его для %1 и %2. %3</translation>
<translation>Для интеграции требуется установить расширение для браузера «KeePassXC-Browser». &lt;br /&gt;Установите его для %1 и %2. %3</translation>
</message>
<message>
<source>&amp;Brave</source>
@ -751,7 +751,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>&amp;Allow returning expired credentials.</source>
<translation>Разрешить возвращать &amp;истёкшие записи</translation>
<translation>Возвращать &amp;истёкшие записи</translation>
</message>
<message>
<source>Enable browser integration</source>
@ -763,7 +763,7 @@ Please select the correct database for saving credentials.</source>
</message>
<message>
<source>All databases connected to the extension will return matching credentials.</source>
<translation type="unfinished"/>
<translation>Получать результаты поиска из всех подключённых баз данных</translation>
</message>
<message>
<source>Don&apos;t display the popup suggesting migration of legacy KeePassHTTP settings.</source>
@ -1129,11 +1129,11 @@ Please consider generating a new key file.</source>
</message>
<message>
<source>Select slot...</source>
<translation>Выбрать слот...</translation>
<translation>Выберите слот</translation>
</message>
<message>
<source>Unlock KeePassXC Database</source>
<translation>Разблокировать базу данных KeePassXC</translation>
<translation>Открытие базы данных KeePassXC</translation>
</message>
<message>
<source>Enter Password:</source>
@ -1157,7 +1157,7 @@ Please consider generating a new key file.</source>
</message>
<message>
<source>Browse for key file</source>
<translation>Открытие диалога выбора файла-ключа</translation>
<translation>Открыть диалога выбора файла-ключа</translation>
</message>
<message>
<source>Browse...</source>
@ -1308,7 +1308,7 @@ If you do not have a key file, please leave the field empty.</source>
<message>
<source>Do you really want to delete the selected key?
This may prevent connection to the browser plugin.</source>
<translation>Вы действительно хотите удалить выбранный ключ?
<translation>Действительно удалить выбранный ключ?
Это может воспрепятствовать соединению с подключаемым модулем браузера.</translation>
</message>
<message>
@ -1580,19 +1580,19 @@ If you keep this number, your database may be too easy to crack!</source>
<name>DatabaseSettingsWidgetFdoSecrets</name>
<message>
<source>Exposed Entries</source>
<translation type="unfinished"/>
<translation>Доступ к записям</translation>
</message>
<message>
<source>Don&apos;t e&amp;xpose this database</source>
<translation type="unfinished"/>
<translation>Не &amp;публиковать эту базу данных</translation>
</message>
<message>
<source>Expose entries &amp;under this group:</source>
<translation type="unfinished"/>
<translation>&amp;Предоставить доступ к записям выбранной группы:</translation>
</message>
<message>
<source>Enable fd.o Secret Service to access these settings.</source>
<translation type="unfinished"/>
<translation>Для доступа к этим параметрам включите службу Secret Service.</translation>
</message>
</context>
<context>
@ -1774,7 +1774,7 @@ Are you sure you want to continue without a password?</source>
</message>
<message>
<source>Hover over lines with error icons for further information.</source>
<translation type="unfinished"/>
<translation>Для получения дополнительной информации наведите курсор мыши на строки, содержащие значок ошибки.</translation>
</message>
<message>
<source>Name</source>
@ -1958,7 +1958,7 @@ This is definitely a bug, please report it to the developers.</source>
</message>
<message>
<source>Failed to open %1. It either does not exist or is not accessible.</source>
<translation type="unfinished"/>
<translation>Не удалось открыть «%1», файл не существует или недоступен.</translation>
</message>
<message>
<source>Export database to HTML file</source>
@ -2222,7 +2222,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Edit entry</source>
<translation>Редактировать запись</translation>
<translation>Редактирование записи</translation>
</message>
<message>
<source>Different passwords supplied.</source>
@ -2262,7 +2262,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Entry has unsaved changes</source>
<translation>В записи есть несохранённые изменения</translation>
<translation>Запись содержит несохранённые изменения.</translation>
</message>
<message>
<source>New attribute %1</source>
@ -2278,7 +2278,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Confirm Removal</source>
<translation>Подтвердите удаление</translation>
<translation>Подтверждение удаления</translation>
</message>
<message>
<source>Browser Integration</source>
@ -2408,7 +2408,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Open Auto-Type help webpage</source>
<translation type="unfinished"/>
<translation>Открыть страницу справки по авто-вводу</translation>
</message>
<message>
<source>Existing window associations</source>
@ -2685,7 +2685,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Remove key from agent after specified seconds</source>
<translation type="unfinished"/>
<translation>Убрать ключ из агента по истечению заданного интервала времени</translation>
</message>
<message>
<source>Browser for key file</source>
@ -2763,7 +2763,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Inactive</source>
<translation>Неактивные</translation>
<translation>Не участвует в обмене</translation>
</message>
<message>
<source>KeeShare unsigned container</source>
@ -2791,32 +2791,32 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Import</source>
<translation>Импортировать</translation>
<translation>Импортируется</translation>
</message>
<message>
<source>Export</source>
<translation>Экспортировать</translation>
<translation>Экспортируется</translation>
</message>
<message>
<source>Synchronize</source>
<translation>Синхронизировать</translation>
<translation>Синхронизируется</translation>
</message>
<message>
<source>Your KeePassXC version does not support sharing this container type.
Supported extensions are: %1.</source>
<translation type="unfinished"/>
<translation>Установленная версия KeePassXC не поддерживает совместное использование контейнера такого типа. Список поддерживаемых расширений: %1.</translation>
</message>
<message>
<source>%1 is already being exported by this database.</source>
<translation type="unfinished"/>
<translation>%1 уже экспортируется этой базой данных.</translation>
</message>
<message>
<source>%1 is already being imported by this database.</source>
<translation type="unfinished"/>
<translation>%1 уже импортируется этой базой данных.</translation>
</message>
<message>
<source>%1 is being imported and exported by different groups in this database.</source>
<translation type="unfinished"/>
<translation>%1 уже экспортируется и импортируется различными группами этой базы данных.</translation>
</message>
<message>
<source>KeeShare is currently disabled. You can enable import/export in the application settings.</source>
@ -2833,15 +2833,15 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Sharing mode field</source>
<translation type="unfinished"/>
<translation>Поле состояния общего доступа</translation>
</message>
<message>
<source>Path to share file field</source>
<translation type="unfinished"/>
<translation>Поле пути к общему файлу</translation>
</message>
<message>
<source>Browser for share file</source>
<translation type="unfinished"/>
<translation>Запуск диалога открытия общего файла</translation>
</message>
<message>
<source>Password field</source>
@ -3038,7 +3038,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Plugin Data</source>
<translation>Данные подключаемых модулей</translation>
<translation>Данные подключаемого модуля браузера</translation>
</message>
<message>
<source>Remove</source>
@ -3046,13 +3046,13 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Delete plugin data?</source>
<translation>Удалить данные подключаемых модулей?</translation>
<translation>Удалить данные подключаемого модуля браузера?</translation>
</message>
<message>
<source>Do you really want to delete the selected plugin data?
This may cause the affected plugins to malfunction.</source>
<translation>Действительно удалить выбранные данные подключаемых модулей?
Это действие может привести к неработоспособности подключаемых модулей.</translation>
<translation>Действительно удалить выбранные данные подключаемого модуля?
Это действие может привести к неработоспособности подключаемых модулей в браузерах.</translation>
</message>
<message>
<source>Key</source>
@ -3064,15 +3064,15 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Datetime created</source>
<translation type="unfinished"/>
<translation>Дата и время создания</translation>
</message>
<message>
<source>Datetime modified</source>
<translation type="unfinished"/>
<translation>Дата и время изменения</translation>
</message>
<message>
<source>Datetime accessed</source>
<translation type="unfinished"/>
<translation>Дата и время последнего использования</translation>
</message>
<message>
<source>Unique ID</source>
@ -3080,11 +3080,11 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Plugin data</source>
<translation type="unfinished"/>
<translation>Данные подключаемого модуля </translation>
</message>
<message>
<source>Remove selected plugin data</source>
<translation type="unfinished"/>
<translation>Удаление выбранных данных подключаемого модуля браузера</translation>
</message>
</context>
<context>
@ -3133,7 +3133,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message numerus="yes">
<source>Are you sure you want to remove %n attachment(s)?</source>
<translation><numerusform>Вы уверены, что вы хотите удалить %n вложение?</numerusform><numerusform>Вы уверены, что вы хотите удалить %n вложения?</numerusform><numerusform>Вы уверены, что вы хотите удалить %n вложений?</numerusform><numerusform>Вы действительно хотите удалить вложения (%n шт.)?</numerusform></translation>
<translation><numerusform>Действительно удалить это вложение?</numerusform><numerusform>Действительно удалить %n вложения?</numerusform><numerusform>Действительно удалить %n вложений?</numerusform><numerusform>Действительно удалить %n вложения?</numerusform></translation>
</message>
<message>
<source>Save attachments</source>
@ -3172,7 +3172,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Confirm remove</source>
<translation>Подтвердите удаление</translation>
<translation>Подтверждение удаления</translation>
</message>
<message numerus="yes">
<source>Unable to open file(s):
@ -3189,19 +3189,19 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Add new attachment</source>
<translation type="unfinished"/>
<translation>Добавить вложение</translation>
</message>
<message>
<source>Remove selected attachment</source>
<translation type="unfinished"/>
<translation>Удалить выбранное вложение</translation>
</message>
<message>
<source>Open selected attachment</source>
<translation type="unfinished"/>
<translation>Открыть выбранное вложение</translation>
</message>
<message>
<source>Save selected attachment to disk</source>
<translation type="unfinished"/>
<translation>Сохранить выбранное вложение на диск</translation>
</message>
</context>
<context>
@ -3379,7 +3379,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message>
<source>Share</source>
<translation>Предоставить общий доступ</translation>
<translation>Общий файл</translation>
</message>
<message>
<source>Display current TOTP value</source>
@ -3444,7 +3444,7 @@ This may cause the affected plugins to malfunction.</source>
<name>FdoSecretsPlugin</name>
<message>
<source>Fdo Secret Service: %1</source>
<translation type="unfinished"/>
<translation>FDO Secret Service: %1</translation>
</message>
</context>
<context>
@ -3470,7 +3470,7 @@ This may cause the affected plugins to malfunction.</source>
<name>IconDownloaderDialog</name>
<message>
<source>Download Favicons</source>
<translation>Загрузить значки</translation>
<translation>Получение значков сайтов</translation>
</message>
<message>
<source>Cancel</source>
@ -3563,7 +3563,8 @@ You can enable the DuckDuckGo website icon service in the security section of th
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Вероятно, для входа были указанные неверные данные.
Попробуйте ввести данные ещё раз, если ошибка повториться, возможно, файл базы данных повреждён.</translation>
</message>
</context>
<context>
@ -3698,11 +3699,12 @@ If this reoccurs, then your database file may be corrupt.</source>
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Вероятно, для входа были указанные неверные данные.
Попробуйте ввести данные ещё раз, если ошибка повториться, возможно, файл базы данных повреждён.</translation>
</message>
<message>
<source>(HMAC mismatch)</source>
<translation type="unfinished"/>
<translation>(несоответствие HMAC)</translation>
</message>
</context>
<context>
@ -3931,7 +3933,7 @@ Line %2, column %3</source>
</message>
<message>
<source>Import KeePass1 Database</source>
<translation type="unfinished"/>
<translation>Импортировать базу данных в формате KeePass1</translation>
</message>
</context>
<context>
@ -4088,18 +4090,19 @@ Line %2, column %3</source>
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Вероятно, для входа были указанные неверные данные.
Попробуйте ввести данные ещё раз, если ошибка повториться, возможно, файл базы данных повреждён.</translation>
</message>
</context>
<context>
<name>KeeShare</name>
<message>
<source>Invalid sharing reference</source>
<translation type="unfinished"/>
<translation>Неверная ссылка общего доступа</translation>
</message>
<message>
<source>Inactive share %1</source>
<translation type="unfinished"/>
<translation>Неактивная общая база %1</translation>
</message>
<message>
<source>Imported from %1</source>
@ -4107,35 +4110,35 @@ If this reoccurs, then your database file may be corrupt.</source>
</message>
<message>
<source>Exported to %1</source>
<translation type="unfinished"/>
<translation>Выполнен экспорт в «%1»</translation>
</message>
<message>
<source>Synchronized with %1</source>
<translation type="unfinished"/>
<translation>Синхронизировано с «%1»</translation>
</message>
<message>
<source>Import is disabled in settings</source>
<translation type="unfinished"/>
<translation>Возможность импортировать отключена в параметрах программы</translation>
</message>
<message>
<source>Export is disabled in settings</source>
<translation type="unfinished"/>
<translation>Возможность экспорировать отключена в параметрах программы</translation>
</message>
<message>
<source>Inactive share</source>
<translation type="unfinished"/>
<translation>Неактивная общая база</translation>
</message>
<message>
<source>Imported from</source>
<translation type="unfinished"/>
<translation>Импортировано из</translation>
</message>
<message>
<source>Exported to</source>
<translation type="unfinished"/>
<translation>Экспортировано в</translation>
</message>
<message>
<source>Synchronized with</source>
<translation type="unfinished"/>
<translation>Синхронизировано с</translation>
</message>
</context>
<context>
@ -4240,7 +4243,7 @@ Message: %2</source>
</message>
<message>
<source>Browse for key file</source>
<translation>Открытие диалога выбора ключевого файла </translation>
<translation>Открыть диалога выбора файла-ключа</translation>
</message>
<message>
<source>Browse...</source>
@ -4248,11 +4251,11 @@ Message: %2</source>
</message>
<message>
<source>Generate a new key file</source>
<translation type="unfinished"/>
<translation>Создать новый файл ключа</translation>
</message>
<message>
<source>Note: Do not use a file that may change as that will prevent you from unlocking your database!</source>
<translation type="unfinished"/>
<translation>Внимание: изменение файла приведёт к невозможности разблокировать базу данных!</translation>
</message>
<message>
<source>Invalid Key File</source>
@ -4260,7 +4263,7 @@ Message: %2</source>
</message>
<message>
<source>You cannot use the current database as its own keyfile. Please choose a different file or generate a new key file.</source>
<translation type="unfinished"/>
<translation>Файл базы данных не может быть использован в качестве файла-ключа. Выберите другой файл или создайте файл-ключ.</translation>
</message>
<message>
<source>Suspicious Key File</source>
@ -4269,7 +4272,8 @@ Message: %2</source>
<message>
<source>The chosen key file looks like a password database file. A key file must be a static file that never changes or you will lose access to your database forever.
Are you sure you want to continue with this file?</source>
<translation type="unfinished"/>
<translation>Выбранный файл-ключ, вероятно, является файлом базы данных паролей. Файл-ключ должен являться неизменяемым файлом, в противном случае доступ к базе данных будет безвозвратно утерян.
Продолжить использовать выбранный файл?</translation>
</message>
</context>
<context>
@ -4576,15 +4580,15 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Sort &amp;A-Z</source>
<translation>Сортировать &amp;A-Z</translation>
<translation>Сортировать &amp;А-Я</translation>
</message>
<message>
<source>Sort &amp;Z-A</source>
<translation>Сортировать &amp;Z-A</translation>
<translation>Сортировать &amp;Я-А</translation>
</message>
<message>
<source>&amp;Password Generator</source>
<translation>&amp;Генератор Паролей</translation>
<translation>&amp;Генератор паролей</translation>
</message>
<message>
<source>Download favicon</source>
@ -4620,11 +4624,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>&amp;User Guide</source>
<translation>&amp;Руководство Пользователя</translation>
<translation>&amp;Руководство пользователя</translation>
</message>
<message>
<source>Open User Guide PDF</source>
<translation>Открыть инструкцию пользователя в формате PDF</translation>
<translation>Открыть руководство пользователя в формате PDF</translation>
</message>
<message>
<source>&amp;Keyboard Shortcuts</source>
@ -4691,11 +4695,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Removed custom data %1 [%2]</source>
<translation type="unfinished"/>
<translation>Пользовательские данные %1 [%2] удалены </translation>
</message>
<message>
<source>Adding custom data %1 [%2]</source>
<translation type="unfinished"/>
<translation>Добавление пользовательских данные %1 [%2]</translation>
</message>
</context>
<context>
@ -4770,31 +4774,31 @@ Expect some bugs and minor issues, this version is not meant for production use.
<name>OpData01</name>
<message>
<source>Invalid OpData01, does not contain header</source>
<translation type="unfinished"/>
<translation>Данные OpData01 не содержат заголовка</translation>
</message>
<message>
<source>Unable to read all IV bytes, wanted 16 but got %1</source>
<translation type="unfinished"/>
<translation>Невозможно считать все данные вектора инициализации: ожидалось 16 байт, но получено %1 байт</translation>
</message>
<message>
<source>Unable to init cipher for opdata01: %1</source>
<translation type="unfinished"/>
<translation>Невозможно инициализировать шифрование для данных opdata01: %1</translation>
</message>
<message>
<source>Unable to read all HMAC signature bytes</source>
<translation type="unfinished"/>
<translation>Не удалось полностью считать данные подписи HMAC</translation>
</message>
<message>
<source>Malformed OpData01 due to a failed HMAC</source>
<translation type="unfinished"/>
<translation>Повреждённые данные OpData01 по причине неверного HMAC</translation>
</message>
<message>
<source>Unable to process clearText in place</source>
<translation type="unfinished"/>
<translation>Невозможно обработать простой текст</translation>
</message>
<message>
<source>Expected %1 bytes of clear-text, found %2</source>
<translation type="unfinished"/>
<translation>Ожидалось %1 байт простого текста, найдено %2</translation>
</message>
</context>
<context>
@ -4802,26 +4806,27 @@ Expect some bugs and minor issues, this version is not meant for production use.
<message>
<source>Read Database did not produce an instance
%1</source>
<translation type="unfinished"/>
<translation>Невозможно создать базу данных из прочитанного файла
%1</translation>
</message>
</context>
<context>
<name>OpVaultReader</name>
<message>
<source>Directory .opvault must exist</source>
<translation type="unfinished"/>
<translation>Не найден каталог «.opvault»</translation>
</message>
<message>
<source>Directory .opvault must be readable</source>
<translation type="unfinished"/>
<translation>Каталог «.opvault» не доступен для чтения</translation>
</message>
<message>
<source>Directory .opvault/default must exist</source>
<translation type="unfinished"/>
<translation>Не найден каталог «.opvault/default»</translation>
</message>
<message>
<source>Directory .opvault/default must be readable</source>
<translation type="unfinished"/>
<translation>Каталог «.opvault/default» не доступен для чтения</translation>
</message>
<message>
<source>Unable to decode masterKey: %1</source>
@ -4829,7 +4834,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Unable to derive master key: %1</source>
<translation type="unfinished"/>
<translation>Не удалось извлечь мастер-ключ: %1</translation>
</message>
</context>
<context>
@ -4939,7 +4944,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Passwords match so far</source>
<translation type="unfinished"/>
<translation>Количество совпавших паролей</translation>
</message>
</context>
<context>
@ -5026,7 +5031,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Pick characters from every group</source>
<translation>Подобрать символы из каждой группы</translation>
<translation>Использовать символы из каждой группы</translation>
</message>
<message>
<source>&amp;Length:</source>
@ -5363,7 +5368,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Key file of the database.</source>
<translation>Ключевой файл базы данных.</translation>
<translation>Файл-ключ базы данных.</translation>
</message>
<message>
<source>path</source>
@ -5403,7 +5408,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>Copy an entry&apos;s password to the clipboard.</source>
<translation>Скопировать пароль в буфер обмена.</translation>
<translation>Скопировать пароль записи в буфер обмена.</translation>
</message>
<message>
<source>Path of the entry to clip.</source>
@ -5971,7 +5976,7 @@ Available commands:
</message>
<message>
<source>Parent window handle</source>
<translation>Дескриптор родительского окна</translation>
<translation>Дескриптор родительского окна.</translation>
</message>
<message>
<source>Another instance of KeePassXC is already running.</source>
@ -6099,27 +6104,27 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Check if any passwords have been publicly leaked. FILENAME must be the path of a file listing SHA-1 hashes of leaked passwords in HIBP format, as available from https://haveibeenpwned.com/Passwords.</source>
<translation type="unfinished"/>
<translation>Проверка паролей на компрометацию. Параметр ИМЯАЙЛА должен быть путём к файлу данных списка SHA-1 хэшей паролей в формате HIBP, полученным, например, с сайта https://haveibeenpwned.com/Passwords.</translation>
</message>
<message>
<source>FILENAME</source>
<translation type="unfinished"/>
<translation>ИМЯАЙЛА</translation>
</message>
<message>
<source>Analyze passwords for weaknesses and problems.</source>
<translation type="unfinished"/>
<translation>Проверка надёжности и других характеристик паролей.</translation>
</message>
<message>
<source>Failed to open HIBP file %1: %2</source>
<translation type="unfinished"/>
<translation>Не удалось открыть файл в формате HIBP %1: %2</translation>
</message>
<message>
<source>Evaluating database entries against HIBP file, this will take a while...</source>
<translation type="unfinished"/>
<translation>Производится проверка записей базы данных относительно файла в формате HIBP</translation>
</message>
<message>
<source>Close the currently opened database.</source>
<translation type="unfinished"/>
<translation>Закрыть текущую базу данных.</translation>
</message>
<message>
<source>Display this help.</source>
@ -6127,7 +6132,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Yubikey slot used to encrypt the database.</source>
<translation type="unfinished"/>
<translation>Слот устройства Yubikey, использованный для шифрования базы данных.</translation>
</message>
<message>
<source>slot</source>
@ -6135,7 +6140,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Invalid word count %1</source>
<translation type="unfinished"/>
<translation>Количество неверных слов: %1</translation>
</message>
<message>
<source>The word list is too small (&lt; 1000 items)</source>
@ -6147,15 +6152,15 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Format to use when exporting. Available choices are xml or csv. Defaults to xml.</source>
<translation type="unfinished"/>
<translation>Выбор формата файла для экспорта. Возможные варианты: XML (по умолчанию) или CSV.</translation>
</message>
<message>
<source>Exports the content of a database to standard output in the specified format.</source>
<translation type="unfinished"/>
<translation>Экспорт базы данных в заданном формате на устройство стандартного вывода.</translation>
</message>
<message>
<source>Unable to export database to XML: %1</source>
<translation type="unfinished"/>
<translation>Ошибка экспортирования базы данных в формат XML: %1</translation>
</message>
<message>
<source>Unsupported format %1</source>
@ -6179,11 +6184,11 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Import the contents of an XML database.</source>
<translation type="unfinished"/>
<translation>Импортировать базу данных в формате XML.</translation>
</message>
<message>
<source>Path of the XML database export.</source>
<translation type="unfinished"/>
<translation>Путь для экспорта базы данных в формат XML</translation>
</message>
<message>
<source>Path of the new database.</source>
@ -6191,7 +6196,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Unable to import XML database export %1</source>
<translation type="unfinished"/>
<translation>Ошибка импорта базы данных %1 из формата XML</translation>
</message>
<message>
<source>Successfully imported database.</source>
@ -6207,7 +6212,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Only print the changes detected by the merge operation.</source>
<translation type="unfinished"/>
<translation>Не выполнять объединение, а только сообщать о найденных изменениях</translation>
</message>
<message>
<source>Yubikey slot for the second database.</source>
@ -6215,7 +6220,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Successfully merged %1 into %2.</source>
<translation type="unfinished"/>
<translation>%1 успешно объединён с %2.</translation>
</message>
<message>
<source>Database was not modified by merge operation.</source>
@ -6287,15 +6292,15 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Please touch the button on your YubiKey to unlock %1</source>
<translation type="unfinished"/>
<translation>Для разблокирования %1 нажмите кнопку на устройстве YubiKey</translation>
</message>
<message>
<source>Enter password to encrypt database (optional): </source>
<translation type="unfinished"/>
<translation>Введите пароль для шифрования базы данных (необязательно):</translation>
</message>
<message>
<source>HIBP file, line %1: parse error</source>
<translation type="unfinished"/>
<translation>Ошибка разбора строки %1 файла в формате HIBP</translation>
</message>
<message>
<source>Secret Service Integration</source>
@ -6307,15 +6312,15 @@ Kernel: %3 %4</source>
</message>
<message>
<source>%1[%2] Challenge Response - Slot %3 - %4</source>
<translation type="unfinished"/>
<translation>%1 [%2] Вызов-ответ слот %3 - %4</translation>
</message>
<message numerus="yes">
<source>Password for &apos;%1&apos; has been leaked %2 time(s)!</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Пароль к «%1» был замечен в утечках %2 раз.</numerusform><numerusform>Пароль к «%1» был замечен в утечках %2 раза.</numerusform><numerusform>Пароль к «%1» был замечен в утечках %2 раз.</numerusform><numerusform>Пароль к «%1» был замечен в утечках %2 раза.</numerusform></translation>
</message>
<message>
<source>Invalid password generator after applying all options</source>
<translation type="unfinished"/>
<translation>Невозможно создать пароль с заданными параметрами</translation>
</message>
<message>
<source>Show the protected attributes in clear text.</source>
@ -6482,7 +6487,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Enable KeepassXC Freedesktop.org Secret Service integration</source>
<translation type="unfinished"/>
<translation>Включить интеграцию KeepassXC со службой Freedesktop.org Secret Service</translation>
</message>
<message>
<source>General</source>
@ -6490,23 +6495,23 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Show notification when credentials are requested</source>
<translation type="unfinished"/>
<translation>Выводить уведомления при запросе записей</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If recycle bin is enabled for the database, entries will be moved to recycle bin directly. Otherwise, they will be deleted without confirmation.&lt;/p&gt;&lt;p&gt;You will still be prompted if any entries are referenced by others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Если в параметрах базы данных разрешено использование корзины, то при удалении записи будут перемещены в неё. В противном случае, будет выполнено необратимое удаление без подтверждения.&lt;/p&gt;&lt;p&gt;В случае, если на удаляемые записи имеются ссылки, потребуется подтверждение удаления.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Don&apos;t confirm when entries are deleted by clients.</source>
<translation type="unfinished"/>
<translation>Не подтверждать удаление записей приложениями-клиентами</translation>
</message>
<message>
<source>Exposed database groups:</source>
<translation type="unfinished"/>
<translation>Доступные группы из базы данных:</translation>
</message>
<message>
<source>File Name</source>
<translation type="unfinished"/>
<translation>Имя файла</translation>
</message>
<message>
<source>Group</source>
@ -6514,7 +6519,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Manage</source>
<translation type="unfinished"/>
<translation>Управление</translation>
</message>
<message>
<source>Authorization</source>
@ -6522,15 +6527,15 @@ Kernel: %3 %4</source>
</message>
<message>
<source>These applications are currently connected:</source>
<translation type="unfinished"/>
<translation>Подключены следующие приложения:</translation>
</message>
<message>
<source>Application</source>
<translation type="unfinished"/>
<translation>Приложение</translation>
</message>
<message>
<source>Disconnect</source>
<translation type="unfinished"/>
<translation>Отключить</translation>
</message>
<message>
<source>Database settings</source>
@ -6538,7 +6543,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Edit database settings</source>
<translation type="unfinished"/>
<translation>Редактирование параметров базы данных</translation>
</message>
<message>
<source>Unlock database</source>
@ -6662,7 +6667,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>KeeShare key file</source>
<translation>Ключевой файл KeeShare</translation>
<translation>Файл ключа KeeShare</translation>
</message>
<message>
<source>All files</source>
@ -6962,7 +6967,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Secret Key:</source>
<translation>Секретный ключ</translation>
<translation>Секретный ключ:</translation>
</message>
<message>
<source>Secret key must be in Base32 format</source>

View File

@ -743,7 +743,7 @@ Prosím, vyberte správnu databázu na uloženie prihlasovacích údajov.</trans
</message>
<message>
<source>&amp;Brave</source>
<translation type="unfinished"/>
<translation>&amp;Odvážny</translation>
</message>
<message>
<source>Returns expired credentials. String [expired] is added to the title.</source>
@ -767,11 +767,11 @@ Prosím, vyberte správnu databázu na uloženie prihlasovacích údajov.</trans
</message>
<message>
<source>Don&apos;t display the popup suggesting migration of legacy KeePassHTTP settings.</source>
<translation type="unfinished"/>
<translation>Nezobrazovať okno s návrhom na migráciu starých nastavení KeePassHTTP.</translation>
</message>
<message>
<source>&amp;Do not prompt for KeePassHTTP settings migration.</source>
<translation type="unfinished"/>
<translation>&amp;Nepýtať sa na migráciu nastavení KeePassHTTP.</translation>
</message>
<message>
<source>Custom proxy location field</source>
@ -878,7 +878,11 @@ Chcete teraz migrovať svoje nastavenia?</translation>
Give the connection a unique name or ID, for example:
chrome-laptop.</source>
<translation type="unfinished"/>
<translation>Obdržali ste požiadavku na priradenie nasledujúcej databázy.
%1
Zadajte mu jedinečný názov alebo identifikátor, napríklad:
chrome-laptop.</translation>
</message>
</context>
<context>
@ -2286,7 +2290,7 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>&lt;empty URL&gt;</source>
<translation type="unfinished"/>
<translation>&lt;prázdna URL&gt;</translation>
</message>
<message>
<source>Are you sure you want to remove this URL?</source>
@ -2451,15 +2455,15 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Skip Auto-Submit for this entry</source>
<translation type="unfinished"/>
<translation>Zapnúť Automatické vypĺňanie pre túto položku</translation>
</message>
<message>
<source>Hide this entry from the browser extension</source>
<translation type="unfinished"/>
<translation>Skryť túto položku v rozšírení prehliadača</translation>
</message>
<message>
<source>Additional URL&apos;s</source>
<translation type="unfinished"/>
<translation>Ďalšie URL</translation>
</message>
<message>
<source>Add</source>
@ -2494,19 +2498,19 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Entry history selection</source>
<translation type="unfinished"/>
<translation>Výber histórie položky</translation>
</message>
<message>
<source>Show entry at selected history state</source>
<translation type="unfinished"/>
<translation>Zobraziť stav položky v okamžiku zvolenej histórie</translation>
</message>
<message>
<source>Restore entry to selected history state</source>
<translation type="unfinished"/>
<translation>Obnoviť stav položky do okamžiku zvolenej histórie</translation>
</message>
<message>
<source>Delete selected history state</source>
<translation type="unfinished"/>
<translation>Vymazať zvolený stav histórie</translation>
</message>
<message>
<source>Delete all history</source>
@ -2557,7 +2561,7 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Download favicon for URL</source>
<translation type="unfinished"/>
<translation>Stiahnuť ikonu URL</translation>
</message>
<message>
<source>Repeat password field</source>
@ -2597,11 +2601,11 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Title field</source>
<translation type="unfinished"/>
<translation>Pole nadpisu</translation>
</message>
<message>
<source>Username field</source>
<translation type="unfinished"/>
<translation>Pole použ. mena</translation>
</message>
<message>
<source>Toggle expiration</source>
@ -2685,19 +2689,19 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Remove key from agent after specified seconds</source>
<translation type="unfinished"/>
<translation>Odstrániť kľúč z agenta po zadanom počte sekúnd</translation>
</message>
<message>
<source>Browser for key file</source>
<translation type="unfinished"/>
<translation>Vybrať súbor kľúča</translation>
</message>
<message>
<source>External key file</source>
<translation type="unfinished"/>
<translation>Súbor externého kľúča</translation>
</message>
<message>
<source>Select attachment file</source>
<translation type="unfinished"/>
<translation>Zvoľte súbor prílohy</translation>
</message>
</context>
<context>
@ -2799,20 +2803,21 @@ Vypnúť bezpečné ukladanie a skúsiť znova?</translation>
</message>
<message>
<source>Synchronize</source>
<translation type="unfinished"/>
<translation>Synchronizovať</translation>
</message>
<message>
<source>Your KeePassXC version does not support sharing this container type.
Supported extensions are: %1.</source>
<translation type="unfinished"/>
<translation>Táto verzia KeePassXC nepodporuje zdieľanie tohoto typu kontajnera.
Podporované rozšírenia : %1.</translation>
</message>
<message>
<source>%1 is already being exported by this database.</source>
<translation type="unfinished"/>
<translation>%1 bolo exportované touto databázou.</translation>
</message>
<message>
<source>%1 is already being imported by this database.</source>
<translation type="unfinished"/>
<translation>%1 bolo importované touto databázou.</translation>
</message>
<message>
<source>%1 is being imported and exported by different groups in this database.</source>
@ -2821,27 +2826,27 @@ Supported extensions are: %1.</source>
<message>
<source>KeeShare is currently disabled. You can enable import/export in the application settings.</source>
<comment>KeeShare is a proper noun</comment>
<translation type="unfinished"/>
<translation>KeeShare je momentálne vypnuté. Import/export môžete zapnúť v nastaveniach aplikácie.</translation>
</message>
<message>
<source>Database export is currently disabled by application settings.</source>
<translation type="unfinished"/>
<translation>Export databázy je momentálne vypnutý v nastaveniach aplikácie.</translation>
</message>
<message>
<source>Database import is currently disabled by application settings.</source>
<translation type="unfinished"/>
<translation>Import databázy je momentálne vypnutý v nastaveniach aplikácie.</translation>
</message>
<message>
<source>Sharing mode field</source>
<translation type="unfinished"/>
<translation>Pole režimu zdieľania</translation>
</message>
<message>
<source>Path to share file field</source>
<translation type="unfinished"/>
<translation>Pole cesty zdieľania súboru</translation>
</message>
<message>
<source>Browser for share file</source>
<translation type="unfinished"/>
<translation>Vybrať zdieľaný súbor</translation>
</message>
<message>
<source>Password field</source>
@ -2912,7 +2917,7 @@ Supported extensions are: %1.</source>
</message>
<message>
<source>Search toggle for this and sub groups</source>
<translation type="unfinished"/>
<translation>Prepnúť hľadanie tejto a podriadených skupín</translation>
</message>
<message>
<source>Default auto-type sequence field</source>
@ -2979,43 +2984,43 @@ Supported extensions are: %1.</source>
</message>
<message numerus="yes">
<source>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Táto ikona je použitá v %n položke a bude nahradená predvolenou ikonou. Naozaj ju chcete odstrániť?</numerusform><numerusform>Táto ikona je použitá v %n položkách a bude nahradená predvolenou ikonou. Naozaj ju chcete odstrániť?</numerusform><numerusform>Táto ikona je použitá v %n položkách a bude nahradená predvolenou ikonou. Naozaj ju chcete odstrániť?</numerusform><numerusform>Táto ikona je použitá v %n položkách a bude nahradená predvolenou ikonou. Naozaj ju chcete odstrániť?</numerusform></translation>
</message>
<message>
<source>You can enable the DuckDuckGo website icon service under Tools -&gt; Settings -&gt; Security</source>
<translation type="unfinished"/>
<translation>Môžete zapnúť webovú službu ikon DuckDuckGo v Nástroje -&gt; Nastavenie -&gt; Bezpečnosť</translation>
</message>
<message>
<source>Download favicon for URL</source>
<translation type="unfinished"/>
<translation>Stiahnuť ikonu URL</translation>
</message>
<message>
<source>Apply selected icon to subgroups and entries</source>
<translation type="unfinished"/>
<translation>Použiť zvolenú ikonu na podskupiny a položky</translation>
</message>
<message>
<source>Apply icon &amp;to ...</source>
<translation type="unfinished"/>
<translation>Použiť ikonu &amp;na </translation>
</message>
<message>
<source>Apply to this only</source>
<translation type="unfinished"/>
<translation>Použiť len na túto</translation>
</message>
<message>
<source>Also apply to child groups</source>
<translation type="unfinished"/>
<translation>Použiť aj na podriadené skupiny</translation>
</message>
<message>
<source>Also apply to child entries</source>
<translation type="unfinished"/>
<translation>Použiť aj na podriadené položky</translation>
</message>
<message>
<source>Also apply to all children</source>
<translation type="unfinished"/>
<translation>Použiť na všetkých potomkov</translation>
</message>
<message>
<source>Existing icon selected.</source>
<translation type="unfinished"/>
<translation>Zvolená existujúca ikona.</translation>
</message>
</context>
<context>
@ -3178,7 +3183,11 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
<message numerus="yes">
<source>Unable to open file(s):
%1</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Nemožno otvoriť súbor:
%1</numerusform><numerusform>Nemožno otvoriť súbory:
%1</numerusform><numerusform>Nemožno otvoriť súbory:
%1</numerusform><numerusform>Nemožno otvoriť súbory:
%1</numerusform></translation>
</message>
<message>
<source>Attachments</source>
@ -3186,19 +3195,19 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
</message>
<message>
<source>Add new attachment</source>
<translation type="unfinished"/>
<translation>Pridať novú prílohu</translation>
</message>
<message>
<source>Remove selected attachment</source>
<translation type="unfinished"/>
<translation>Odstrániť zvolenú prílohu</translation>
</message>
<message>
<source>Open selected attachment</source>
<translation type="unfinished"/>
<translation>Otvoriť zvolenú prílohu</translation>
</message>
<message>
<source>Save selected attachment to disk</source>
<translation type="unfinished"/>
<translation>Uložiť zvolenú prílohu na disk</translation>
</message>
</context>
<context>
@ -3380,7 +3389,7 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
</message>
<message>
<source>Display current TOTP value</source>
<translation type="unfinished"/>
<translation>Zobraziť aktuálnu hodnotu TOTP</translation>
</message>
<message>
<source>Advanced</source>
@ -3429,7 +3438,7 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
<name>FdoSecrets::Service</name>
<message>
<source>Failed to register DBus service at %1: another secret service is running.</source>
<translation type="unfinished"/>
<translation>Zlyhala registrácia služby DBus na %1: je spustená iná služba.</translation>
</message>
<message numerus="yes">
<source>%n Entry(s) was used by %1</source>
@ -3467,7 +3476,7 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
<name>IconDownloaderDialog</name>
<message>
<source>Download Favicons</source>
<translation type="unfinished"/>
<translation>Stiahnuť ikony</translation>
</message>
<message>
<source>Cancel</source>
@ -3476,7 +3485,8 @@ Môže to spôsobiť nefunkčnosť dotknutých zásuvných modulov.</translation
<message>
<source>Having trouble downloading icons?
You can enable the DuckDuckGo website icon service in the security section of the application settings.</source>
<translation type="unfinished"/>
<translation>Máte problémy so sťahovaním ikon?
V bezpečnostnej sekcii nastavení aplikácie môžete zapnúť webovú službu ikon.</translation>
</message>
<message>
<source>Close</source>
@ -3492,11 +3502,11 @@ You can enable the DuckDuckGo website icon service in the security section of th
</message>
<message>
<source>Please wait, processing entry list...</source>
<translation type="unfinished"/>
<translation>Prosím, počkajte, spracovanie zoznamu položiek</translation>
</message>
<message>
<source>Downloading...</source>
<translation type="unfinished"/>
<translation>Sťahovanie</translation>
</message>
<message>
<source>Ok</source>
@ -3512,7 +3522,7 @@ You can enable the DuckDuckGo website icon service in the security section of th
</message>
<message>
<source>Downloading favicons (%1/%2)...</source>
<translation type="unfinished"/>
<translation>Sťahovanie ikon (%1/%2)</translation>
</message>
</context>
<context>
@ -3559,7 +3569,8 @@ You can enable the DuckDuckGo website icon service in the security section of th
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Boli zadané neplatné prihlasovacie údaje, prosím skúste znova.
Ak sa to opakuje, potom môže byť súbor databázy poškodený.</translation>
</message>
</context>
<context>
@ -3694,11 +3705,12 @@ If this reoccurs, then your database file may be corrupt.</source>
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Boli zadané neplatné prihlasovacie údaje, prosím skúste znova.
Ak sa to opakuje, potom môže byť súbor databázy poškodený.</translation>
</message>
<message>
<source>(HMAC mismatch)</source>
<translation type="unfinished"/>
<translation>(nezhoda HMAC)</translation>
</message>
</context>
<context>
@ -4084,7 +4096,8 @@ Riadok %2, stĺpec %3</translation>
<message>
<source>Invalid credentials were provided, please try again.
If this reoccurs, then your database file may be corrupt.</source>
<translation type="unfinished"/>
<translation>Boli zadané neplatné prihlasovacie údaje, prosím skúste znova.
Ak sa to opakuje, potom môže byť súbor databázy poškodený.</translation>
</message>
</context>
<context>
@ -4613,7 +4626,7 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
</message>
<message>
<source>Go to online documentation (opens browser)</source>
<translation type="unfinished"/>
<translation>Prejsť na dokumentáciu on-line (otvorí prehliadač)</translation>
</message>
<message>
<source>&amp;User Guide</source>
@ -4771,7 +4784,7 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
</message>
<message>
<source>Unable to read all IV bytes, wanted 16 but got %1</source>
<translation type="unfinished"/>
<translation>Nemožno čítať všetky bajty IV, potrebných 16, ale získaných %1</translation>
</message>
<message>
<source>Unable to init cipher for opdata01: %1</source>
@ -4779,7 +4792,7 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
</message>
<message>
<source>Unable to read all HMAC signature bytes</source>
<translation type="unfinished"/>
<translation>Nemožno čítať všetky bajty podpisu HMAC</translation>
</message>
<message>
<source>Malformed OpData01 due to a failed HMAC</source>
@ -4806,27 +4819,27 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
<name>OpVaultReader</name>
<message>
<source>Directory .opvault must exist</source>
<translation type="unfinished"/>
<translation>Adresár .opvault musí existovať</translation>
</message>
<message>
<source>Directory .opvault must be readable</source>
<translation type="unfinished"/>
<translation>Adresár .opvault musí byť čitateľný</translation>
</message>
<message>
<source>Directory .opvault/default must exist</source>
<translation type="unfinished"/>
<translation>Adresár .opvault/default musí existovať</translation>
</message>
<message>
<source>Directory .opvault/default must be readable</source>
<translation type="unfinished"/>
<translation>Adresár .opvault/default musí byť čitateľný</translation>
</message>
<message>
<source>Unable to decode masterKey: %1</source>
<translation type="unfinished"/>
<translation>Nemožno dekódovať hlavný kľúč: %1</translation>
</message>
<message>
<source>Unable to derive master key: %1</source>
<translation type="unfinished"/>
<translation>Nemožno odvodiť hlavný kľúč: %1</translation>
</message>
</context>
<context>
@ -4932,11 +4945,11 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
<name>PasswordEdit</name>
<message>
<source>Passwords do not match</source>
<translation type="unfinished"/>
<translation>Heslá sa nezhodujú.</translation>
</message>
<message>
<source>Passwords match so far</source>
<translation type="unfinished"/>
<translation>Heslá sa zhodujú potiaľ</translation>
</message>
</context>
<context>
@ -6072,7 +6085,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Cannot generate a password and prompt at the same time!</source>
<translation type="unfinished"/>
<translation>Nemožno naraz generovať aj zadať heslo!</translation>
</message>
<message>
<source>Adds a new group to a database.</source>
@ -6096,11 +6109,11 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Check if any passwords have been publicly leaked. FILENAME must be the path of a file listing SHA-1 hashes of leaked passwords in HIBP format, as available from https://haveibeenpwned.com/Passwords.</source>
<translation type="unfinished"/>
<translation>Skontrolovať, či nejaké heslo neuniklo na verejnosť. MENOSÚBORU musí byť cesta k súboru so zoznamom odtlačkov SHA-1 uniknutých hesiel vo formáte HIBP, ako je dostupný z https://haveibeenpwned.com/Passwords.</translation>
</message>
<message>
<source>FILENAME</source>
<translation type="unfinished"/>
<translation>MENOSÚBORU</translation>
</message>
<message>
<source>Analyze passwords for weaknesses and problems.</source>
@ -6108,7 +6121,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Failed to open HIBP file %1: %2</source>
<translation type="unfinished"/>
<translation>Zlyhalo otvorenie súboru HIBP %1: %2</translation>
</message>
<message>
<source>Evaluating database entries against HIBP file, this will take a while...</source>
@ -6120,7 +6133,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Display this help.</source>
<translation type="unfinished"/>
<translation>Zobrazí tohoto pomocníka.</translation>
</message>
<message>
<source>Yubikey slot used to encrypt the database.</source>
@ -6128,11 +6141,11 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>slot</source>
<translation type="unfinished"/>
<translation>slot</translation>
</message>
<message>
<source>Invalid word count %1</source>
<translation type="unfinished"/>
<translation>Neplatný počet slov %1</translation>
</message>
<message>
<source>The word list is too small (&lt; 1000 items)</source>
@ -6140,7 +6153,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Exit interactive mode.</source>
<translation type="unfinished"/>
<translation>Ukončiť interaktívny režim.</translation>
</message>
<message>
<source>Format to use when exporting. Available choices are xml or csv. Defaults to xml.</source>
@ -6152,27 +6165,27 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Unable to export database to XML: %1</source>
<translation type="unfinished"/>
<translation>Nemožno exportovať databázu do XML: %1</translation>
</message>
<message>
<source>Unsupported format %1</source>
<translation type="unfinished"/>
<translation>Nepodporovaný formát: %1</translation>
</message>
<message>
<source>Use numbers</source>
<translation type="unfinished"/>
<translation>Použiť čísla</translation>
</message>
<message>
<source>Invalid password length %1</source>
<translation type="unfinished"/>
<translation>Neplatná dĺžka hesla %1</translation>
</message>
<message>
<source>Display command help.</source>
<translation type="unfinished"/>
<translation>Zobrazí pomocníka príkazu.</translation>
</message>
<message>
<source>Available commands:</source>
<translation type="unfinished"/>
<translation>Dostupné príkazy:</translation>
</message>
<message>
<source>Import the contents of an XML database.</source>
@ -6188,15 +6201,15 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Unable to import XML database export %1</source>
<translation type="unfinished"/>
<translation>Nemožno importovať databázu z exportu XML %1</translation>
</message>
<message>
<source>Successfully imported database.</source>
<translation type="unfinished"/>
<translation>Úspešne importovaná databáza.</translation>
</message>
<message>
<source>Unknown command %1</source>
<translation type="unfinished"/>
<translation>Neznáma príkaz %1</translation>
</message>
<message>
<source>Flattens the output to single lines.</source>
@ -6212,7 +6225,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Successfully merged %1 into %2.</source>
<translation type="unfinished"/>
<translation>Úspešne zlúčené %1 do %2.</translation>
</message>
<message>
<source>Database was not modified by merge operation.</source>
@ -6220,7 +6233,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Moves an entry to a new group.</source>
<translation type="unfinished"/>
<translation>Presunie položku do novej skupiny.</translation>
</message>
<message>
<source>Path of the entry to move.</source>
@ -6228,7 +6241,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Path of the destination group.</source>
<translation type="unfinished"/>
<translation>Cesta cieľovej skupiny.</translation>
</message>
<message>
<source>Could not find group with path %1.</source>
@ -6527,7 +6540,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Disconnect</source>
<translation type="unfinished"/>
<translation>Odpojiť</translation>
</message>
<message>
<source>Database settings</source>
@ -6535,7 +6548,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Edit database settings</source>
<translation type="unfinished"/>
<translation>Upraviť nastavenia databázy</translation>
</message>
<message>
<source>Unlock database</source>
@ -6543,7 +6556,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Unlock database to show more information</source>
<translation type="unfinished"/>
<translation>Odomknúť databázu na zobrazenie ďalších informácií</translation>
</message>
<message>
<source>Lock database</source>
@ -6551,7 +6564,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Unlock to show</source>
<translation type="unfinished"/>
<translation>Odomknúť na zobrazenie</translation>
</message>
<message>
<source>None</source>
@ -6683,15 +6696,15 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Allow KeeShare imports</source>
<translation type="unfinished"/>
<translation>Povoliť importy KeeShare</translation>
</message>
<message>
<source>Allow KeeShare exports</source>
<translation type="unfinished"/>
<translation>Povoliť exporty KeeShare</translation>
</message>
<message>
<source>Only show warnings and errors</source>
<translation type="unfinished"/>
<translation>Zobraziť len upozornenia a chyby</translation>
</message>
<message>
<source>Key</source>
@ -6703,35 +6716,35 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Generate new certificate</source>
<translation type="unfinished"/>
<translation>Generovať nový certifikát</translation>
</message>
<message>
<source>Import existing certificate</source>
<translation type="unfinished"/>
<translation>Importovať existujúci certifikát</translation>
</message>
<message>
<source>Export own certificate</source>
<translation type="unfinished"/>
<translation>Exportuje vlastný certifikát</translation>
</message>
<message>
<source>Known shares</source>
<translation type="unfinished"/>
<translation>Známe zdieľania</translation>
</message>
<message>
<source>Trust selected certificate</source>
<translation type="unfinished"/>
<translation>Dôverovať zvolenému certifikátu</translation>
</message>
<message>
<source>Ask whether to trust the selected certificate every time</source>
<translation type="unfinished"/>
<translation>Vždy sa spýtať, či dôverovať zvolenému certifikátu</translation>
</message>
<message>
<source>Untrust selected certificate</source>
<translation type="unfinished"/>
<translation>Zrušiť dôveru zvoleného certifikátu</translation>
</message>
<message>
<source>Remove selected certificate</source>
<translation type="unfinished"/>
<translation>Odstrániť zvolený certifikát</translation>
</message>
</context>
<context>
@ -6900,7 +6913,7 @@ Prepis podpísaných zdieľaných kontajnerov nie je podporovaný - export sa ne
</message>
<message numerus="yes">
<source>Expires in &lt;b&gt;%n&lt;/b&gt; second(s)</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
<translation><numerusform>Platnosť vyprší za % n sekundu</numerusform><numerusform>Platnosť vyprší za % n sekundy</numerusform><numerusform>Platnosť vyprší za % n sekúnd</numerusform><numerusform>Platnosť vyprší za &lt;b&gt;% n&lt;/b&gt; sekúnd</numerusform></translation>
</message>
</context>
<context>
@ -6960,19 +6973,19 @@ Prepis podpísaných zdieľaných kontajnerov nie je podporovaný - export sa ne
</message>
<message>
<source>Secret Key:</source>
<translation type="unfinished"/>
<translation>Tajný kľúč:</translation>
</message>
<message>
<source>Secret key must be in Base32 format</source>
<translation type="unfinished"/>
<translation>Tajný kľúč musí byť vo formáte Base32</translation>
</message>
<message>
<source>Secret key field</source>
<translation type="unfinished"/>
<translation>Pole tajného kľúča</translation>
</message>
<message>
<source>Algorithm:</source>
<translation type="unfinished"/>
<translation>Algoritmus:</translation>
</message>
<message>
<source>Time step field</source>
@ -6980,24 +6993,25 @@ Prepis podpísaných zdieľaných kontajnerov nie je podporovaný - export sa ne
</message>
<message>
<source> digits</source>
<translation type="unfinished"/>
<translation>číslice</translation>
</message>
<message>
<source>Invalid TOTP Secret</source>
<translation type="unfinished"/>
<translation>Neplatné tajomstvo TOTP</translation>
</message>
<message>
<source>You have entered an invalid secret key. The key must be in Base32 format.
Example: JBSWY3DPEHPK3PXP</source>
<translation type="unfinished"/>
<translation>Zadali ste neplatný tajný kľúč. Kľúč musí byť vo formáte Base32.
Napríklad: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>Confirm Remove TOTP Settings</source>
<translation type="unfinished"/>
<translation>Potvrďte odstránenie nastavení TOTP</translation>
</message>
<message>
<source>Are you sure you want to delete TOTP settings for this entry?</source>
<translation type="unfinished"/>
<translation>Naozaj chcete odstrániť nastavenia TOTP tejto položky?</translation>
</message>
</context>
<context>
@ -7087,7 +7101,7 @@ Example: JBSWY3DPEHPK3PXP</source>
</message>
<message>
<source>Open a recent database</source>
<translation type="unfinished"/>
<translation>Otvoriť nedávnu databázu</translation>
</message>
</context>
<context>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
name: keepassxc
version: 2.5.1
version: 2.5.2
grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe”
description: |

View File

@ -121,6 +121,7 @@ set(keepassx_SOURCES
gui/TotpDialog.cpp
gui/TotpExportSettingsDialog.cpp
gui/DatabaseOpenDialog.cpp
gui/URLEdit.cpp
gui/WelcomeWidget.cpp
gui/csvImport/CsvImportWidget.cpp
gui/csvImport/CsvImportWizard.cpp

View File

@ -42,7 +42,7 @@ QJsonObject BrowserAction::readResponse(const QJsonObject& json)
bool triggerUnlock = false;
const QString trigger = json.value("triggerUnlock").toString();
if (!trigger.isEmpty() && trigger.compare("true", Qt::CaseSensitive) == 0) {
if (!trigger.isEmpty() && trigger.compare(TRUE_STR, Qt::CaseSensitive) == 0) {
triggerUnlock = true;
}
@ -268,7 +268,7 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
const QString id = decrypted.value("id").toString();
const QString submit = decrypted.value("submitUrl").toString();
const QString auth = decrypted.value("httpAuth").toString();
const bool httpAuth = auth.compare("true", Qt::CaseSensitive) == 0 ? true : false;
const bool httpAuth = auth.compare(TRUE_STR, Qt::CaseSensitive) == 0 ? true : false;
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
if (users.isEmpty()) {
@ -469,7 +469,7 @@ QJsonObject BrowserAction::buildMessage(const QString& nonce) const
{
QJsonObject message;
message["version"] = KEEPASSXC_VERSION;
message["success"] = "true";
message["success"] = TRUE_STR;
message["nonce"] = nonce;
return message;
}

View File

@ -68,6 +68,10 @@ BrowserOptionDialog::BrowserOptionDialog(QWidget* parent)
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocationBrowseButton, SLOT(setEnabled(bool)));
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
#ifndef Q_OS_LINUX
m_ui->snapWarningLabel->setVisible(false);
#endif
#ifdef Q_OS_WIN
// Brave uses Chrome's registry settings
m_ui->braveSupport->setHidden(true);
@ -124,6 +128,9 @@ void BrowserOptionDialog::loadSettings()
m_ui->vivaldiSupport->setChecked(settings->vivaldiSupport());
m_ui->torBrowserSupport->setChecked(settings->torBrowserSupport());
#endif
#ifndef Q_OS_LINUX
m_ui->snapWarningLabel->setVisible(false);
#endif
#if defined(KEEPASSXC_DIST_APPIMAGE)
m_ui->supportBrowserProxy->setChecked(true);

View File

@ -60,7 +60,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="snapWarningLabel">
<property name="text">
<string>Browsers installed as snaps are currently not supported.</string>
</property>

View File

@ -54,6 +54,7 @@ static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwo
// Extra entry related options saved in custom data
const QString BrowserService::OPTION_SKIP_AUTO_SUBMIT = QStringLiteral("BrowserSkipAutoSubmit");
const QString BrowserService::OPTION_HIDE_ENTRY = QStringLiteral("BrowserHideEntry");
const QString BrowserService::OPTION_ONLY_HTTP_AUTH = QStringLiteral("BrowserOnlyHttpAuth");
// Multiple URL's
const QString BrowserService::ADDITIONAL_URL = QStringLiteral("KP2A_URL");
@ -380,9 +381,14 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
// Check entries for authorization
QList<Entry*> pwEntriesToConfirm;
QList<Entry*> pwEntries;
for (auto* entry : searchEntries(url, keyList)) {
for (auto* entry : searchEntries(url, submitUrl, keyList)) {
if (entry->customData()->contains(BrowserService::OPTION_HIDE_ENTRY)
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == "true") {
&& entry->customData()->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR) {
continue;
}
if (!httpAuth && entry->customData()->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)
&& entry->customData()->value(BrowserService::OPTION_ONLY_HTTP_AUTH) == TRUE_STR) {
continue;
}
@ -583,7 +589,7 @@ BrowserService::ReturnValue BrowserService::updateEntry(const QString& id,
}
QList<Entry*>
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& hostname, const QString& url)
BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl)
{
QList<Entry*> entries;
auto* rootGroup = db->rootGroup();
@ -601,20 +607,15 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
continue;
}
auto domain = baseDomain(hostname);
// Search for additional URL's starting with KP2A_URL
if (entry->attributes()->keys().contains(ADDITIONAL_URL)) {
for (const auto& key : entry->attributes()->keys()) {
if (key.startsWith(ADDITIONAL_URL)
&& handleURL(entry->attributes()->value(key), domain, url)) {
if (key.startsWith(ADDITIONAL_URL) && handleURL(entry->attributes()->value(key), url, submitUrl)) {
entries.append(entry);
continue;
}
}
}
if (!handleURL(entry->url(), domain, url)) {
if (!handleURL(entry->url(), url, submitUrl)) {
continue;
}
@ -625,7 +626,7 @@ BrowserService::searchEntries(const QSharedPointer<Database>& db, const QString&
return entries;
}
QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPairList& keyList)
QList<Entry*> BrowserService::searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList)
{
// Check if database is connected with KeePassXC-Browser
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
@ -662,7 +663,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
QList<Entry*> entries;
do {
for (const auto& db : databases) {
entries << searchEntries(db, hostname, url);
entries << searchEntries(db, url, submitUrl);
}
} while (entries.isEmpty() && removeFirstDomain(hostname));
@ -855,7 +856,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
}
if (entry->isExpired()) {
res["expired"] = "true";
res["expired"] = TRUE_STR;
}
if (entry->customData()->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
@ -1000,7 +1001,7 @@ bool BrowserService::removeFirstDomain(QString& hostname)
return false;
}
bool BrowserService::handleURL(const QString& entryUrl, const QString& hostname, const QString& url)
bool BrowserService::handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl)
{
if (entryUrl.isEmpty()) {
return false;
@ -1017,31 +1018,36 @@ bool BrowserService::handleURL(const QString& entryUrl, const QString& hostname,
}
}
// Make a direct compare if a local file is used
if (url.contains("file://")) {
return entryUrl == submitUrl;
}
// URL host validation fails
if (browserSettings()->matchUrlScheme() && entryQUrl.host().isEmpty()) {
if (entryQUrl.host().isEmpty()) {
return false;
}
// Match port, if used
QUrl qUrl(url);
if (entryQUrl.port() > 0 && entryQUrl.port() != qUrl.port()) {
QUrl siteQUrl(url);
if (entryQUrl.port() > 0 && entryQUrl.port() != siteQUrl.port()) {
return false;
}
// Match scheme
if (browserSettings()->matchUrlScheme() && !entryQUrl.scheme().isEmpty() && entryQUrl.scheme().compare(qUrl.scheme()) != 0) {
if (browserSettings()->matchUrlScheme() && !entryQUrl.scheme().isEmpty()
&& entryQUrl.scheme().compare(siteQUrl.scheme()) != 0) {
return false;
}
// Check for illegal characters
QRegularExpression re("[<>\\^`{|}]");
auto match = re.match(entryUrl);
if (match.hasMatch()) {
if (re.match(entryUrl).hasMatch()) {
return false;
}
// Filter to match hostname in URL field
if (entryQUrl.host().endsWith(hostname)) {
if (siteQUrl.host().endsWith(entryQUrl.host())) {
return true;
}

View File

@ -63,8 +63,8 @@ public:
const QString& group,
const QString& groupUuid,
const QSharedPointer<Database>& selectedDb = {});
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& hostname, const QString& url);
QList<Entry*> searchEntries(const QString& url, const StringPairList& keyList);
QList<Entry*> searchEntries(const QSharedPointer<Database>& db, const QString& url, const QString& submitUrl);
QList<Entry*> searchEntries(const QString& url, const QString& submitUrl, const StringPairList& keyList);
void convertAttributesToCustomData(const QSharedPointer<Database>& currentDb = {});
public:
@ -74,6 +74,7 @@ public:
static const QString LEGACY_ASSOCIATE_KEY_PREFIX;
static const QString OPTION_SKIP_AUTO_SUBMIT;
static const QString OPTION_HIDE_ENTRY;
static const QString OPTION_ONLY_HTTP_AUTH;
static const QString ADDITIONAL_URL;
public slots:
@ -130,7 +131,7 @@ private:
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
bool schemeFound(const QString& url);
bool removeFirstDomain(QString& hostname);
bool handleURL(const QString& entryUrl, const QString& hostname, const QString& url);
bool handleURL(const QString& entryUrl, const QString& url, const QString& submitUrl);
QString baseDomain(const QString& hostname) const;
QSharedPointer<Database> getDatabase();
QSharedPointer<Database> selectedDatabase();

View File

@ -55,7 +55,7 @@ int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPoint
return EXIT_FAILURE;
}
outputTextStream << QObject::tr("Evaluating database entries against HIBP file, this will take a while...");
outputTextStream << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl;
QList<QPair<const Entry*, int>> findings;
QString error;

View File

@ -30,8 +30,7 @@ const QCommandLineOption Merge::SameCredentialsOption =
QObject::tr("Use the same credentials for both database files."));
const QCommandLineOption Merge::KeyFileFromOption =
QCommandLineOption(QStringList() << "k"
<< "key-file-from",
QCommandLineOption(QStringList() << "key-file-from",
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));

View File

@ -149,7 +149,7 @@ void enterInteractiveMode(const QStringList& arguments)
prompt += "> ";
command = reader->readLine(prompt);
if (reader->isFinished()) {
return;
break;
}
QStringList args = Utils::splitCommandString(command);
@ -162,13 +162,17 @@ void enterInteractiveMode(const QStringList& arguments)
errorTextStream << QObject::tr("Unknown command %1").arg(args[0]) << "\n";
continue;
} else if (cmd->name == "quit" || cmd->name == "exit") {
return;
break;
}
cmd->currentDatabase = currentDatabase;
cmd->execute(args);
currentDatabase = cmd->currentDatabase;
}
if (currentDatabase) {
currentDatabase->releaseData();
}
}
int main(int argc, char** argv)

View File

@ -106,7 +106,11 @@ namespace Bootstrap
{
// start minimized if configured
if (config()->get("GUI/MinimizeOnStartup").toBool()) {
#ifdef Q_OS_WIN
mainWindow.showMinimized();
#else
mainWindow.hideWindow();
#endif
} else {
mainWindow.bringToFront();
}

View File

@ -42,7 +42,6 @@ Database::Database()
: m_metadata(new Metadata(this))
, m_data()
, m_rootGroup(nullptr)
, m_timer(new QTimer(this))
, m_fileWatcher(new FileWatcher(this))
, m_emitModified(false)
, m_uuid(QUuid::createUuid())
@ -50,12 +49,12 @@ Database::Database()
setRootGroup(new Group());
rootGroup()->setUuid(QUuid::createUuid());
rootGroup()->setName(tr("Root", "Root group name"));
m_timer->setSingleShot(true);
m_modifiedTimer.setSingleShot(true);
s_uuidMap.insert(m_uuid, this);
connect(m_metadata, SIGNAL(metadataModified()), SLOT(markAsModified()));
connect(m_timer, SIGNAL(timeout()), SIGNAL(databaseModified()));
connect(&m_modifiedTimer, SIGNAL(timeout()), SIGNAL(databaseModified()));
connect(this, SIGNAL(databaseOpened()), SLOT(updateCommonUsernames()));
connect(this, SIGNAL(databaseSaved()), SLOT(updateCommonUsernames()));
connect(m_fileWatcher, SIGNAL(fileChanged()), SIGNAL(databaseFileChanged()));
@ -229,6 +228,7 @@ bool Database::saveAs(const QString& filePath, QString* error, bool atomic, bool
auto& canonicalFilePath = QFileInfo::exists(filePath) ? QFileInfo(filePath).canonicalFilePath() : filePath;
bool ok = performSave(canonicalFilePath, error, atomic, backup);
if (ok) {
markAsClean();
setFilePath(filePath);
m_fileWatcher->start(canonicalFilePath, 30, 1);
} else {
@ -343,7 +343,6 @@ bool Database::writeDatabase(QIODevice* device, QString* error)
return false;
}
markAsClean();
return true;
}
@ -832,7 +831,7 @@ void Database::emptyRecycleBin()
void Database::setEmitModified(bool value)
{
if (m_emitModified && !value) {
m_timer->stop();
m_modifiedTimer.stop();
}
m_emitModified = value;
@ -846,8 +845,9 @@ bool Database::isModified() const
void Database::markAsModified()
{
m_modified = true;
if (m_emitModified) {
startModifiedTimer();
if (m_emitModified && !m_modifiedTimer.isActive()) {
// Small time delay prevents numerous consecutive saves due to repeated signals
m_modifiedTimer.start(150);
}
}
@ -855,6 +855,7 @@ void Database::markAsClean()
{
bool emitSignal = m_modified;
m_modified = false;
m_modifiedTimer.stop();
if (emitSignal) {
emit databaseSaved();
}
@ -869,18 +870,6 @@ Database* Database::databaseByUuid(const QUuid& uuid)
return s_uuidMap.value(uuid, nullptr);
}
void Database::startModifiedTimer()
{
if (!m_emitModified) {
return;
}
if (m_timer->isActive()) {
m_timer->stop();
}
m_timer->start(150);
}
QSharedPointer<const CompositeKey> Database::key() const
{
return m_data.key;

View File

@ -21,23 +21,22 @@
#include <QDateTime>
#include <QHash>
#include <QObject>
#include <QPointer>
#include <QScopedPointer>
#include <QTimer>
#include "config-keepassx.h"
#include "crypto/kdf/AesKdf.h"
#include "crypto/kdf/Kdf.h"
#include "format/KeePass2.h"
#include "keys/PasswordKey.h"
#include "keys/CompositeKey.h"
#include "keys/PasswordKey.h"
class Entry;
enum class EntryReferenceType;
class FileWatcher;
class Group;
class Metadata;
class QTimer;
class QIODevice;
struct DeletedObject
@ -155,9 +154,6 @@ signals:
void databaseDiscarded();
void databaseFileChanged();
private slots:
void startModifiedTimer();
private:
struct DatabaseData
{
@ -211,7 +207,7 @@ private:
DatabaseData m_data;
QPointer<Group> m_rootGroup;
QList<DeletedObject> m_deletedObjects;
QPointer<QTimer> m_timer;
QTimer m_modifiedTimer;
QPointer<FileWatcher> m_fileWatcher;
bool m_initialized = false;
bool m_modified = false;

View File

@ -35,11 +35,10 @@ namespace
FileWatcher::FileWatcher(QObject* parent)
: QObject(parent)
, m_ignoreFileChange(false)
{
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(onWatchedFileChanged()));
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(checkFileChanged()));
connect(&m_fileChecksumTimer, SIGNAL(timeout()), SLOT(checkFileChanged()));
connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged()));
connect(&m_fileChecksumTimer, SIGNAL(timeout()), SLOT(checkFileChecksum()));
m_fileChangeDelayTimer.setSingleShot(true);
m_fileIgnoreDelayTimer.setSingleShot(true);
}
@ -101,17 +100,6 @@ void FileWatcher::resume()
}
}
void FileWatcher::onWatchedFileChanged()
{
// Don't notify if we are ignoring events or already started a notification chain
if (shouldIgnoreChanges()) {
return;
}
m_fileChecksum = calculateChecksum();
m_fileChangeDelayTimer.start(0);
}
bool FileWatcher::shouldIgnoreChanges()
{
return m_filePath.isEmpty() || m_ignoreFileChange || m_fileIgnoreDelayTimer.isActive()
@ -123,15 +111,23 @@ bool FileWatcher::hasSameFileChecksum()
return calculateChecksum() == m_fileChecksum;
}
void FileWatcher::checkFileChecksum()
void FileWatcher::checkFileChanged()
{
if (shouldIgnoreChanges()) {
return;
}
if (!hasSameFileChecksum()) {
onWatchedFileChanged();
// Prevent reentrance
m_ignoreFileChange = true;
// Only trigger the change notice if there is a checksum mismatch
auto checksum = calculateChecksum();
if (checksum != m_fileChecksum) {
m_fileChecksum = checksum;
m_fileChangeDelayTimer.start(0);
}
m_ignoreFileChange = false;
}
QByteArray FileWatcher::calculateChecksum()

View File

@ -43,8 +43,7 @@ public slots:
void resume();
private slots:
void onWatchedFileChanged();
void checkFileChecksum();
void checkFileChanged();
private:
QByteArray calculateChecksum();
@ -56,8 +55,8 @@ private:
QTimer m_fileChangeDelayTimer;
QTimer m_fileIgnoreDelayTimer;
QTimer m_fileChecksumTimer;
int m_fileChecksumSizeBytes;
bool m_ignoreFileChange;
int m_fileChecksumSizeBytes = -1;
bool m_ignoreFileChange = false;
};
class BulkFileWatcher : public QObject

View File

@ -20,6 +20,7 @@
#ifndef KEEPASSX_GLOBAL_H
#define KEEPASSX_GLOBAL_H
#include <QString>
#include <QtGlobal>
#if defined(Q_OS_WIN)
@ -42,6 +43,9 @@
#define FILE_CASE_SENSITIVE Qt::CaseSensitive
#endif
static const auto TRUE_STR = QStringLiteral("true");
static const auto FALSE_STR = QStringLiteral("false");
template <typename T> struct AddConst
{
typedef const T Type;

View File

@ -32,6 +32,7 @@
#include <QRegularExpression>
#include <QStringList>
#include <QSysInfo>
#include <QUrl>
#include <QUuid>
#include <cctype>
@ -259,6 +260,33 @@ namespace Tools
}
}
bool checkUrlValid(const QString& urlField)
{
if (urlField.isEmpty()) {
return true;
}
QUrl url;
if (urlField.contains("://")) {
url = urlField;
} else {
url = QUrl::fromUserInput(urlField);
}
if (url.scheme() != "file" && url.host().isEmpty()) {
return false;
}
// Check for illegal characters. Adds also the wildcard * to the list
QRegularExpression re("[<>\\^`{|}\\*]");
auto match = re.match(urlField);
if (match.hasMatch()) {
return false;
}
return true;
}
// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");

View File

@ -41,6 +41,7 @@ namespace Tools
bool isBase64(const QByteArray& ba);
void sleep(int ms);
void wait(int ms);
bool checkUrlValid(const QString& urlField);
QString uuidToHex(const QUuid& uuid);
QUuid hexToUuid(const QString& uuid);
QRegularExpression convertToRegex(const QString& string,

View File

@ -4,6 +4,7 @@ if(WITH_XC_FDOSECRETS)
add_library(fdosecrets STATIC
# app settings page
FdoSecretsPlugin.cpp
widgets/SettingsModels.cpp
widgets/SettingsWidgetFdoSecrets.cpp
# per database settings page

View File

@ -60,11 +60,15 @@ void FdoSecretsPlugin::updateServiceState()
});
if (!m_secretService->initialize()) {
m_secretService.reset();
FdoSecrets::settings()->setEnabled(false);
return;
}
emit secretServiceStarted();
}
} else {
if (m_secretService) {
m_secretService.reset();
emit secretServiceStopped();
}
}
}
@ -74,6 +78,11 @@ Service* FdoSecretsPlugin::serviceInstance() const
return m_secretService.data();
}
DatabaseTabWidget* FdoSecretsPlugin::dbTabs() const
{
return m_dbTabs;
}
void FdoSecretsPlugin::emitRequestSwitchToDatabases()
{
emit requestSwitchToDatabases();

View File

@ -59,6 +59,11 @@ public:
*/
FdoSecrets::Service* serviceInstance() const;
/**
* @return The db tabs widget, containing opened databases. Can be nullptr.
*/
DatabaseTabWidget* dbTabs() const;
public slots:
void emitRequestSwitchToDatabases();
void emitRequestShowNotification(const QString& msg, const QString& title = {});
@ -67,6 +72,8 @@ signals:
void error(const QString& msg);
void requestSwitchToDatabases();
void requestShowNotification(const QString& msg, const QString& title, int msTimeoutHint);
void secretServiceStarted();
void secretServiceStopped();
private:
QPointer<DatabaseTabWidget> m_dbTabs;

View File

@ -21,6 +21,7 @@
#include "fdosecrets/objects/Item.h"
#include "fdosecrets/objects/Prompt.h"
#include "fdosecrets/objects/Service.h"
#include "fdosecrets/objects/Session.h"
#include "core/Config.h"
#include "core/Database.h"
@ -29,6 +30,7 @@
#include "gui/DatabaseWidget.h"
#include <QFileInfo>
#include <QRegularExpression>
namespace FdoSecrets
{
@ -47,6 +49,14 @@ namespace FdoSecrets
connect(backend, &DatabaseWidget::databaseUnlocked, this, &Collection::onDatabaseLockChanged);
connect(backend, &DatabaseWidget::databaseLocked, this, &Collection::onDatabaseLockChanged);
// get notified whenever unlock db dialog finishes
connect(parent, &Service::doneUnlockDatabaseInDialog, this, [this](bool accepted, DatabaseWidget* dbWidget) {
if (!dbWidget || dbWidget != m_backend) {
return;
}
emit doneUnlockCollection(accepted);
});
reloadBackend();
}
@ -241,6 +251,11 @@ namespace FdoSecrets
terms << attributeToTerm(it.key(), it.value());
}
// empty terms causes EntrySearcher returns everything
if (terms.isEmpty()) {
return QList<Item*>{};
}
QList<Item*> items;
const auto foundEntries = EntrySearcher().search(terms, m_exposedGroup);
items.reserve(foundEntries.size());
@ -267,7 +282,7 @@ namespace FdoSecrets
const auto useWildcards = false;
const auto exactMatch = true;
const auto caseSensitive = true;
term.regex = Tools::convertToRegex(value, useWildcards, exactMatch, caseSensitive);
term.regex = Tools::convertToRegex(QRegularExpression::escape(value), useWildcards, exactMatch, caseSensitive);
return term;
}
@ -284,8 +299,13 @@ namespace FdoSecrets
return ret;
}
if (!pathToObject<Session>(secret.session)) {
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SESSION));
}
prompt = nullptr;
bool newlyCreated = true;
Item* item = nullptr;
QString itemPath;
StringStringMap attributes;
@ -303,6 +323,7 @@ namespace FdoSecrets
}
if (!existings.value().isEmpty() && replace) {
item = existings.value().front();
newlyCreated = false;
}
}
@ -328,15 +349,25 @@ namespace FdoSecrets
// when creation finishes in backend, we will already have item
item = m_entryToItem.value(entry, nullptr);
Q_ASSERT(item);
if (!item) {
// may happen if entry somehow ends up in recycle bin
return DBusReturn<>::Error(QStringLiteral(DBUS_ERROR_SECRET_NO_SUCH_OBJECT));
}
}
ret = item->setProperties(properties);
if (ret.isError()) {
if (newlyCreated) {
item->doDelete();
}
return ret;
}
ret = item->setSecret(secret);
if (ret.isError()) {
if (newlyCreated) {
item->doDelete();
}
return ret;
}
@ -439,7 +470,7 @@ namespace FdoSecrets
auto newUuid = FdoSecrets::settings()->exposedGroup(m_backend->database());
auto newGroup = m_backend->database()->rootGroup()->findGroupByUuid(newUuid);
if (!newGroup) {
if (!newGroup || inRecycleBin(newGroup)) {
// no exposed group, delete self
doDelete();
return;
@ -451,14 +482,20 @@ namespace FdoSecrets
m_exposedGroup = newGroup;
// Attach signal to update exposed group settings if the group was removed.
// The lifetime of the connection is bound to the database object, because
// in Database::~Database, groups are also deleted, but we don't want to
// trigger this.
// This rely on the fact that QObject disconnects signals BEFORE deleting
// children.
//
// When the group object is normally deleted due to ~Database, the databaseReplaced
// signal should be first emitted, and we will clean up connection in reloadDatabase,
// so this handler won't be triggered.
QPointer<Database> db = m_backend->database().data();
connect(m_exposedGroup.data(), &Group::groupAboutToRemove, db, [db](Group* toBeRemoved) {
if (!db) {
connect(m_exposedGroup.data(), &Group::groupAboutToRemove, this, [this](Group* toBeRemoved) {
if (backendLocked()) {
return;
}
auto db = m_backend->database();
if (toBeRemoved->database() != db) {
// should not happen, but anyway.
// somehow our current database has been changed, and the old group is being deleted
// possibly logic changes in replaceDatabase.
return;
}
auto uuid = FdoSecrets::settings()->exposedGroup(db);
@ -468,10 +505,17 @@ namespace FdoSecrets
FdoSecrets::settings()->setExposedGroup(db, {});
}
});
// Another possibility is the group being moved to recycle bin.
connect(m_exposedGroup.data(), &Group::groupModified, this, [this]() {
if (inRecycleBin(m_exposedGroup->parentGroup())) {
// reset the exposed group to none
FdoSecrets::settings()->setExposedGroup(m_backend->database().data(), {});
}
});
// Monitor exposed group settings
connect(m_backend->database()->metadata()->customData(), &CustomData::customDataModified, this, [this]() {
if (!m_exposedGroup || !m_backend) {
if (!m_exposedGroup || backendLocked()) {
return;
}
if (m_exposedGroup->uuid() == FdoSecrets::settings()->exposedGroup(m_backend->database())) {
@ -590,9 +634,13 @@ namespace FdoSecrets
void Collection::cleanupConnections()
{
m_backend->database()->metadata()->customData()->disconnect(this);
if (m_exposedGroup) {
m_exposedGroup->disconnect(this);
for (const auto group : m_exposedGroup->groupsRecursive(true)) {
group->disconnect(this);
}
}
m_items.clear();
}
@ -646,19 +694,23 @@ namespace FdoSecrets
{
Q_ASSERT(m_backend);
if (!m_backend->database()->metadata()->recycleBin()) {
if (!group) {
// the root group's parent is nullptr, we treat it as not in recycle bin.
return false;
}
while (group) {
if (group->uuid() == m_backend->database()->metadata()->recycleBin()->uuid()) {
return true;
}
group = group->parentGroup();
}
if (!m_backend->database()->metadata()) {
return false;
}
auto recycleBin = m_backend->database()->metadata()->recycleBin();
if (!recycleBin) {
return false;
}
return group->uuid() == recycleBin->uuid() || group->isRecycled();
}
bool Collection::inRecycleBin(Entry* entry) const
{
Q_ASSERT(entry);

View File

@ -71,9 +71,16 @@ namespace FdoSecrets
void aliasAdded(const QString& alias);
void aliasRemoved(const QString& alias);
void doneUnlockCollection(bool accepted);
public:
DBusReturn<void> setProperties(const QVariantMap& properties);
bool isValid() const
{
return backend();
}
DBusReturn<void> removeAlias(QString alias);
DBusReturn<void> addAlias(QString alias);
const QSet<QString> aliases() const;
@ -106,6 +113,7 @@ namespace FdoSecrets
private slots:
void onDatabaseLockChanged();
void onDatabaseExposedGroupChanged();
// force reload info from backend, potentially delete self
void reloadBackend();
private:

View File

@ -158,6 +158,12 @@ namespace FdoSecrets
return std::move(m_value);
}
/**
* Get value or handle the error by the passed in dbus object
* @tparam P
* @param p
* @return
*/
template <typename P> T valueOrHandle(P* p) const&
{
if (isError()) {
@ -169,6 +175,12 @@ namespace FdoSecrets
return m_value;
}
/**
* Get value or handle the error by the passed in dbus object
* @tparam P
* @param p
* @return
*/
template <typename P> T&& valueOrHandle(P* p) &&
{
if (isError()) {

View File

@ -93,9 +93,9 @@ namespace FdoSecrets
MessageBox::OverrideParent override(findWindow(windowId));
// only need to delete in backend, collection will react itself.
service()->doCloseDatabase(m_collection->backend());
auto accepted = service()->doCloseDatabase(m_collection->backend());
emit completed(false, {});
emit completed(!accepted, {});
return {};
}
@ -189,19 +189,44 @@ namespace FdoSecrets
MessageBox::OverrideParent override(findWindow(windowId));
QList<QDBusObjectPath> unlocked;
for (const auto& c : asConst(m_collections)) {
if (c) {
connect(c, &Collection::doneUnlockCollection, this, &UnlockCollectionsPrompt::collectionUnlockFinished);
c->doUnlock();
unlocked << c->objectPath();
}
}
emit completed(false, QVariant::fromValue(unlocked));
return {};
}
void UnlockCollectionsPrompt::collectionUnlockFinished(bool accepted)
{
auto coll = qobject_cast<Collection*>(sender());
if (!coll) {
return;
}
if (!m_collections.contains(coll)) {
// should not happen
coll->disconnect(this);
return;
}
// one shot
coll->disconnect(this);
if (accepted) {
m_unlocked << coll->objectPath();
} else {
m_numRejected += 1;
}
// if we've get all
if (m_numRejected + m_unlocked.size() == m_collections.size()) {
emit completed(m_unlocked.isEmpty(), QVariant::fromValue(m_unlocked));
}
}
DeleteItemPrompt::DeleteItemPrompt(Service* parent, Item* item)
: PromptBase(parent)
, m_item(item)

View File

@ -98,8 +98,13 @@ namespace FdoSecrets
DBusReturn<void> prompt(const QString& windowId) override;
private slots:
void collectionUnlockFinished(bool accepted);
private:
QList<QPointer<Collection>> m_collections;
QList<QDBusObjectPath> m_unlocked;
int m_numRejected = 0;
};
class Item;

View File

@ -47,7 +47,8 @@ namespace FdoSecrets
, m_insdieEnsureDefaultAlias(false)
, m_serviceWatcher(nullptr)
{
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this));
connect(
m_databases, &DatabaseTabWidget::databaseUnlockDialogFinished, this, &Service::doneUnlockDatabaseInDialog);
}
Service::~Service()
@ -64,6 +65,8 @@ namespace FdoSecrets
return false;
}
registerWithPath(QStringLiteral(DBUS_PATH_SECRETS), new ServiceAdaptor(this));
// Connect to service unregistered signal
m_serviceWatcher.reset(new QDBusServiceWatcher());
connect(m_serviceWatcher.data(),
@ -93,7 +96,24 @@ namespace FdoSecrets
void Service::onDatabaseTabOpened(DatabaseWidget* dbWidget, bool emitSignal)
{
// The Collection will monitor the database's exposed group.
// When the Collection finds that no exposed group, it will delete itself.
// Thus the service also needs to monitor it and recreate the collection if the user changes
// from no exposed to exposed something.
if (!dbWidget->isLocked()) {
monitorDatabaseExposedGroup(dbWidget);
}
connect(dbWidget, &DatabaseWidget::databaseUnlocked, this, [this, dbWidget]() {
monitorDatabaseExposedGroup(dbWidget);
});
auto coll = new Collection(this, dbWidget);
// Creation may fail if the database is not exposed.
// This is okay, because we monitor the expose settings above
if (!coll->isValid()) {
coll->deleteLater();
return;
}
m_collections << coll;
m_dbToCollection[dbWidget] = coll;
@ -127,15 +147,6 @@ namespace FdoSecrets
emit collectionDeleted(coll);
});
// a special case: the database changed from no expose to expose something.
// in this case, there is no collection out there monitoring it, so create a new collection
if (!dbWidget->isLocked()) {
monitorDatabaseExposedGroup(dbWidget);
}
connect(dbWidget, &DatabaseWidget::databaseUnlocked, this, [this, dbWidget]() {
monitorDatabaseExposedGroup(dbWidget);
});
if (emitSignal) {
emit collectionCreated(coll);
}
@ -438,9 +449,9 @@ namespace FdoSecrets
return m_sessions;
}
void Service::doCloseDatabase(DatabaseWidget* dbWidget)
bool Service::doCloseDatabase(DatabaseWidget* dbWidget)
{
m_databases->closeDatabaseTab(dbWidget);
return m_databases->closeDatabaseTab(dbWidget);
}
Collection* Service::doNewDatabase()
@ -463,11 +474,10 @@ namespace FdoSecrets
void Service::doSwitchToChangeDatabaseSettings(DatabaseWidget* dbWidget)
{
// switch selected to current
// unlock if needed
if (dbWidget->isLocked()) {
m_databases->unlockDatabaseInDialog(dbWidget, DatabaseOpenDialog::Intent::None);
return;
}
// switch selected to current
m_databases->setCurrentWidget(dbWidget);
m_databases->changeDatabaseSettings();

View File

@ -88,6 +88,13 @@ namespace FdoSecrets
*/
void error(const QString& msg);
/**
* Finish signal for async action doUnlockDatabaseInDialog
* @param accepted If false, the action is canceled by the user
* @param dbWidget The unlocked the dbWidget if succeed
*/
void doneUnlockDatabaseInDialog(bool accepted, DatabaseWidget* dbWidget);
public:
/**
* List of sessions
@ -101,9 +108,14 @@ namespace FdoSecrets
}
public slots:
void doCloseDatabase(DatabaseWidget* dbWidget);
bool doCloseDatabase(DatabaseWidget* dbWidget);
Collection* doNewDatabase();
void doSwitchToChangeDatabaseSettings(DatabaseWidget* dbWidget);
/**
* Async, connect to signal doneUnlockDatabaseInDialog for finish notification
* @param dbWidget
*/
void doUnlockDatabaseInDialog(DatabaseWidget* dbWidget);
private slots:

View File

@ -85,7 +85,7 @@ protected:
// can not call mapFromSource, which internally calls filterAcceptsRow
auto group = groupFromSourceIndex(source_idx);
return group->uuid() != recycleBin->uuid();
return group && !group->isRecycled() && group->uuid() != recycleBin->uuid();
}
};
@ -118,8 +118,13 @@ void DatabaseSettingsWidgetFdoSecrets::loadSettings(QSharedPointer<Database> db)
m_model.reset(new GroupModelNoRecycle(m_db.data()));
m_ui->selectGroup->setModel(m_model.data());
Group* recycleBin = nullptr;
if (m_db->metadata() && m_db->metadata()->recycleBin()) {
recycleBin = m_db->metadata()->recycleBin();
}
auto group = m_db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(m_db));
if (!group) {
if (!group || group->isRecycled() || (recycleBin && group->uuid() == recycleBin->uuid())) {
m_ui->radioDonotExpose->setChecked(true);
} else {
auto idx = m_model->indexFromGroup(group);

View File

@ -0,0 +1,396 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SettingsModels.h"
#include "fdosecrets/FdoSecretsPlugin.h"
#include "fdosecrets/FdoSecretsSettings.h"
#include "fdosecrets/objects/Service.h"
#include "fdosecrets/objects/Session.h"
#include "core/Database.h"
#include "core/DatabaseIcons.h"
#include "core/FilePath.h"
#include "gui/DatabaseTabWidget.h"
#include "gui/DatabaseWidget.h"
#include <QFileInfo>
namespace FdoSecrets
{
SettingsDatabaseModel::SettingsDatabaseModel(DatabaseTabWidget* dbTabs, QObject* parent)
: QAbstractTableModel(parent)
, m_dbTabs(nullptr)
{
setTabWidget(dbTabs);
}
void SettingsDatabaseModel::setTabWidget(DatabaseTabWidget* dbTabs)
{
auto old = m_dbTabs;
m_dbTabs = dbTabs;
if (old != m_dbTabs) {
populateModel();
}
}
int SettingsDatabaseModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
return 0;
}
return m_dbs.size();
}
int SettingsDatabaseModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
return 0;
}
return 3;
}
QVariant SettingsDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal) {
return {};
}
if (role != Qt::DisplayRole) {
return {};
}
switch (section) {
case 0:
return tr("File Name");
case 1:
return tr("Group");
case 2:
return tr("Manage");
default:
return {};
}
}
QVariant SettingsDatabaseModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return {};
}
const auto& dbWidget = m_dbs[index.row()];
if (!dbWidget) {
return {};
}
switch (index.column()) {
case 0:
return dataForName(dbWidget, role);
case 1:
return dataForExposedGroup(dbWidget, role);
case 2:
return dataForManage(dbWidget, role);
default:
return {};
}
}
QVariant SettingsDatabaseModel::dataForName(DatabaseWidget* db, int role) const
{
switch (role) {
case Qt::DisplayRole: {
QFileInfo fi(db->database()->filePath());
return fi.fileName();
}
case Qt::ToolTipRole:
return db->database()->filePath();
default:
return {};
}
}
QVariant SettingsDatabaseModel::dataForExposedGroup(DatabaseWidget* dbWidget, int role)
{
if (dbWidget->isLocked()) {
switch (role) {
case Qt::DisplayRole:
return tr("Unlock to show");
case Qt::DecorationRole:
return filePath()->icon(QStringLiteral("apps"), QStringLiteral("object-locked"), true);
case Qt::FontRole: {
QFont font;
font.setItalic(true);
return font;
}
default:
return {};
}
}
auto db = dbWidget->database();
auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db));
if (group) {
switch (role) {
case Qt::DisplayRole:
return group->name();
case Qt::DecorationRole:
return group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
: group->iconScaledPixmap();
case Qt::FontRole:
if (group->isExpired()) {
QFont font;
font.setStrikeOut(true);
return font;
} else {
return {};
}
default:
return {};
}
} else {
switch (role) {
case Qt::DisplayRole:
return tr("None");
case Qt::DecorationRole:
return filePath()->icon(QStringLiteral("apps"), QStringLiteral("paint-none"), true);
default:
return {};
}
}
}
QVariant SettingsDatabaseModel::dataForManage(DatabaseWidget* db, int role) const
{
switch (role) {
case Qt::EditRole:
return QVariant::fromValue(db);
default:
return {};
}
}
void SettingsDatabaseModel::populateModel()
{
beginResetModel();
m_dbs.clear();
if (m_dbTabs) {
// Add existing database tabs
for (int idx = 0; idx != m_dbTabs->count(); ++idx) {
auto dbWidget = m_dbTabs->databaseWidgetFromIndex(idx);
databaseAdded(dbWidget, false);
}
// connect signals
connect(m_dbTabs, &DatabaseTabWidget::databaseOpened, this, [this](DatabaseWidget* db) {
databaseAdded(db, true);
});
connect(m_dbTabs, &DatabaseTabWidget::databaseClosed, this, &SettingsDatabaseModel::databaseRemoved);
}
endResetModel();
}
void SettingsDatabaseModel::databaseAdded(DatabaseWidget* db, bool emitSignals)
{
int row = m_dbs.size();
if (emitSignals) {
beginInsertRows({}, row, row);
}
m_dbs.append(db);
connect(db, &DatabaseWidget::databaseLocked, this, [row, this]() {
emit dataChanged(index(row, 1), index(row, 2));
});
connect(db, &DatabaseWidget::databaseUnlocked, this, [row, this]() {
emit dataChanged(index(row, 1), index(row, 2));
});
connect(db, &DatabaseWidget::databaseModified, this, [row, this]() {
emit dataChanged(index(row, 0), index(row, 2));
});
connect(db, &DatabaseWidget::databaseFilePathChanged, this, [row, this]() {
emit dataChanged(index(row, 0), index(row, 2));
});
if (emitSignals) {
endInsertRows();
}
}
void SettingsDatabaseModel::databaseRemoved(const QString& filePath)
{
for (int i = 0; i != m_dbs.size(); i++) {
if (m_dbs[i] && m_dbs[i]->database()->filePath() == filePath) {
beginRemoveRows({}, i, i);
m_dbs[i]->disconnect(this);
m_dbs.removeAt(i);
endRemoveRows();
break;
}
}
}
SettingsSessionModel::SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent)
: QAbstractTableModel(parent)
, m_service(nullptr)
{
setService(plugin->serviceInstance());
connect(plugin, &FdoSecretsPlugin::secretServiceStarted, this, [plugin, this]() {
setService(plugin->serviceInstance());
});
connect(plugin, &FdoSecretsPlugin::secretServiceStopped, this, [this]() { setService(nullptr); });
}
void SettingsSessionModel::setService(Service* service)
{
auto old = m_service;
m_service = service;
if (old != m_service) {
populateModel();
}
}
int SettingsSessionModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
return 0;
}
return m_sessions.size();
}
int SettingsSessionModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid()) {
return 0;
}
return 2;
}
QVariant SettingsSessionModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal) {
return {};
}
if (role != Qt::DisplayRole) {
return {};
}
switch (section) {
case 0:
return tr("Application");
case 1:
return tr("Manage");
default:
return {};
}
}
QVariant SettingsSessionModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return {};
}
const auto& sess = m_sessions[index.row()];
if (!sess) {
return {};
}
switch (index.column()) {
case 0:
return dataForApplication(sess, role);
case 1:
return dataForManage(sess, role);
default:
return {};
}
}
QVariant SettingsSessionModel::dataForApplication(Session* sess, int role) const
{
switch (role) {
case Qt::DisplayRole:
return sess->peer();
default:
return {};
}
}
QVariant SettingsSessionModel::dataForManage(Session* sess, int role) const
{
switch (role) {
case Qt::EditRole: {
auto v = QVariant::fromValue(sess);
qDebug() << v << v.type() << v.userType();
return v;
}
default:
return {};
}
}
void SettingsSessionModel::populateModel()
{
beginResetModel();
m_sessions.clear();
if (m_service) {
// Add existing database tabs
for (const auto& sess : m_service->sessions()) {
sessionAdded(sess, false);
}
// connect signals
connect(m_service, &Service::sessionOpened, this, [this](Session* sess) { sessionAdded(sess, true); });
connect(m_service, &Service::sessionClosed, this, &SettingsSessionModel::sessionRemoved);
}
endResetModel();
}
void SettingsSessionModel::sessionAdded(Session* sess, bool emitSignals)
{
int row = m_sessions.size();
if (emitSignals) {
beginInsertRows({}, row, row);
}
m_sessions.append(sess);
if (emitSignals) {
endInsertRows();
}
}
void SettingsSessionModel::sessionRemoved(Session* sess)
{
for (int i = 0; i != m_sessions.size(); i++) {
if (m_sessions[i] == sess) {
beginRemoveRows({}, i, i);
m_sessions[i]->disconnect(this);
m_sessions.removeAt(i);
endRemoveRows();
break;
}
}
}
} // namespace FdoSecrets

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
#define KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H
#include <QAbstractTableModel>
#include <QPointer>
class DatabaseTabWidget;
class DatabaseWidget;
class FdoSecretsPlugin;
namespace FdoSecrets
{
class SettingsDatabaseModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit SettingsDatabaseModel(DatabaseTabWidget* dbTabs, QObject* parent = nullptr);
void setTabWidget(DatabaseTabWidget* dbTabs);
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
private:
QVariant dataForName(DatabaseWidget* db, int role) const;
static QVariant dataForExposedGroup(DatabaseWidget* db, int role);
QVariant dataForManage(DatabaseWidget* db, int role) const;
private slots:
void populateModel();
void databaseAdded(DatabaseWidget* db, bool emitSignals);
void databaseRemoved(const QString& filePath);
private:
// source
QPointer<DatabaseTabWidget> m_dbTabs;
// internal store
QList<QPointer<DatabaseWidget>> m_dbs;
};
class Service;
class Session;
class SettingsSessionModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit SettingsSessionModel(FdoSecretsPlugin* plugin, QObject* parent = nullptr);
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
private:
void setService(Service* service);
QVariant dataForApplication(Session* sess, int role) const;
QVariant dataForManage(Session* sess, int role) const;
private slots:
void populateModel();
void sessionAdded(Session* sess, bool emitSignals);
void sessionRemoved(Session* sess);
private:
// source
QPointer<Service> m_service;
// internal copy, so we can emit with changed index
QList<Session*> m_sessions;
};
} // namespace FdoSecrets
#endif // KEEPASSXC_FDOSECRETS_SETTINGSMODELS_H

View File

@ -20,241 +20,273 @@
#include "fdosecrets/FdoSecretsPlugin.h"
#include "fdosecrets/FdoSecretsSettings.h"
#include "fdosecrets/objects/Collection.h"
#include "fdosecrets/objects/Prompt.h"
#include "fdosecrets/objects/Session.h"
#include "fdosecrets/widgets/SettingsModels.h"
#include "core/DatabaseIcons.h"
#include "core/FilePath.h"
#include "gui/DatabaseWidget.h"
#include <QAction>
#include <QDebug>
#include <QFileInfo>
#include <QHeaderView>
#include <QPushButton>
#include <QTableWidget>
#include <QItemEditorFactory>
#include <QStyledItemDelegate>
#include <QToolBar>
#include <QVariant>
using FdoSecrets::Collection;
using FdoSecrets::Service;
using FdoSecrets::Session;
using FdoSecrets::SettingsDatabaseModel;
using FdoSecrets::SettingsSessionModel;
namespace
{
class ManageDatabase : public QToolBar
{
Q_OBJECT
Q_PROPERTY(DatabaseWidget* dbWidget READ dbWidget WRITE setDbWidget USER true)
public:
explicit ManageDatabase(FdoSecretsPlugin* plugin, QWidget* parent = nullptr)
: QToolBar(parent)
, m_plugin(plugin)
{
setFloatable(false);
setMovable(false);
// use a dummy widget to center the buttons
auto spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->setVisible(true);
addWidget(spacer);
// db settings
m_dbSettingsAct = new QAction(tr("Database settings"), this);
m_dbSettingsAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("document-edit")));
m_dbSettingsAct->setToolTip(tr("Edit database settings"));
m_dbSettingsAct->setEnabled(false);
connect(m_dbSettingsAct, &QAction::triggered, this, [this]() {
if (!m_dbWidget) {
return;
}
auto db = m_dbWidget;
m_plugin->serviceInstance()->doSwitchToChangeDatabaseSettings(m_dbWidget);
});
addAction(m_dbSettingsAct);
// unlock/lock
m_lockAct = new QAction(tr("Unlock database"), this);
m_lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), false));
m_lockAct->setToolTip(tr("Unlock database to show more information"));
connect(m_lockAct, &QAction::triggered, this, [this]() {
if (!m_dbWidget) {
return;
}
if (m_dbWidget->isLocked()) {
m_plugin->serviceInstance()->doUnlockDatabaseInDialog(m_dbWidget);
} else {
m_dbWidget->lock();
}
});
addAction(m_lockAct);
// use a dummy widget to center the buttons
spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->setVisible(true);
addWidget(spacer);
}
DatabaseWidget* dbWidget() const
{
return m_dbWidget;
}
void setDbWidget(DatabaseWidget* dbWidget)
{
if (m_dbWidget == dbWidget) {
return;
}
if (m_dbWidget) {
disconnect();
}
m_dbWidget = dbWidget;
reconnect();
}
private:
void disconnect()
{
if (!m_dbWidget) {
return;
}
m_dbWidget->disconnect(this);
}
void reconnect()
{
if (!m_dbWidget) {
return;
}
connect(m_dbWidget, &DatabaseWidget::databaseLocked, this, [this]() {
m_lockAct->setText(tr("Unlock database"));
m_lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), false));
m_lockAct->setToolTip(tr("Unlock database to show more information"));
m_dbSettingsAct->setEnabled(false);
});
connect(m_dbWidget, &DatabaseWidget::databaseUnlocked, this, [this]() {
m_lockAct->setText(tr("Lock database"));
m_lockAct->setIcon(
filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-unlocked"), false));
m_lockAct->setToolTip(tr("Lock database"));
m_dbSettingsAct->setEnabled(true);
});
}
private:
FdoSecretsPlugin* m_plugin = nullptr;
QPointer<DatabaseWidget> m_dbWidget = nullptr;
QAction* m_dbSettingsAct = nullptr;
QAction* m_lockAct = nullptr;
};
class ManageSession : public QToolBar
{
Q_OBJECT
Q_PROPERTY(Session* session READ session WRITE setSession USER true)
public:
explicit ManageSession(FdoSecretsPlugin*, QWidget* parent = nullptr)
: QToolBar(parent)
{
setFloatable(false);
setMovable(false);
// use a dummy widget to center the buttons
auto spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->setVisible(true);
addWidget(spacer);
m_disconnectAct = new QAction(tr("Disconnect"), this);
m_disconnectAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("dialog-close")));
m_disconnectAct->setToolTip(tr("Disconnect this application"));
connect(m_disconnectAct, &QAction::triggered, this, [this]() {
if (m_session) {
m_session->close();
}
});
addAction(m_disconnectAct);
// use a dummy widget to center the buttons
spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->setVisible(true);
addWidget(spacer);
}
Session* session()
{
return m_session;
}
void setSession(Session* sess)
{
m_session = sess;
}
private:
Session* m_session = nullptr;
QAction* m_disconnectAct = nullptr;
};
template <typename T> class Creator : public QItemEditorCreatorBase
{
public:
inline explicit Creator(FdoSecretsPlugin* plugin)
: QItemEditorCreatorBase()
, m_plugin(plugin)
, m_propertyName(T::staticMetaObject.userProperty().name())
{
}
inline QWidget* createWidget(QWidget* parent) const override
{
return new T(m_plugin, parent);
}
inline QByteArray valuePropertyName() const override
{
return m_propertyName;
}
private:
FdoSecretsPlugin* m_plugin;
QByteArray m_propertyName;
};
} // namespace
SettingsWidgetFdoSecrets::SettingsWidgetFdoSecrets(FdoSecretsPlugin* plugin, QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::SettingsWidgetFdoSecrets())
, m_factory(new QItemEditorFactory)
, m_plugin(plugin)
{
m_ui->setupUi(this);
auto sessHeader = m_ui->tableSessions->horizontalHeader();
sessHeader->setSelectionMode(QAbstractItemView::NoSelection);
sessHeader->setSectionsClickable(false);
sessHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
sessHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
auto sessModel = new SettingsSessionModel(plugin, this);
m_ui->tableSessions->setModel(sessModel);
setupView(m_ui->tableSessions, 1, qMetaTypeId<Session*>(), new Creator<ManageSession>(m_plugin));
auto dbHeader = m_ui->tableDatabases->horizontalHeader();
dbHeader->setSelectionMode(QAbstractItemView::NoSelection);
dbHeader->setSectionsClickable(false);
dbHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name
dbHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group
dbHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button
// config header after setting model, otherwise the header doesn't have enough sections
auto sessViewHeader = m_ui->tableSessions->horizontalHeader();
sessViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
sessViewHeader->setSectionsClickable(false);
sessViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // application
sessViewHeader->setSectionResizeMode(1, QHeaderView::ResizeToContents); // disconnect button
auto dbModel = new SettingsDatabaseModel(plugin->dbTabs(), this);
m_ui->tableDatabases->setModel(dbModel);
setupView(m_ui->tableDatabases, 2, qMetaTypeId<DatabaseWidget*>(), new Creator<ManageDatabase>(m_plugin));
// config header after setting model, otherwise the header doesn't have enough sections
auto dbViewHeader = m_ui->tableDatabases->horizontalHeader();
dbViewHeader->setSelectionMode(QAbstractItemView::NoSelection);
dbViewHeader->setSectionsClickable(false);
dbViewHeader->setSectionResizeMode(0, QHeaderView::Stretch); // file name
dbViewHeader->setSectionResizeMode(1, QHeaderView::Stretch); // group
dbViewHeader->setSectionResizeMode(2, QHeaderView::ResizeToContents); // manage button
m_ui->tabWidget->setEnabled(m_ui->enableFdoSecretService->isChecked());
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, m_ui->tabWidget, &QTabWidget::setEnabled);
}
void SettingsWidgetFdoSecrets::setupView(QAbstractItemView* view,
int manageColumn,
int editorTypeId,
QItemEditorCreatorBase* creator)
{
auto manageButtonDelegate = new QStyledItemDelegate(this);
m_factory->registerEditor(editorTypeId, creator);
manageButtonDelegate->setItemEditorFactory(m_factory.data());
view->setItemDelegateForColumn(manageColumn, manageButtonDelegate);
connect(view->model(),
&QAbstractItemModel::rowsInserted,
this,
[view, manageColumn](const QModelIndex&, int first, int last) {
for (int i = first; i <= last; ++i) {
auto idx = view->model()->index(i, manageColumn);
view->openPersistentEditor(idx);
}
});
}
SettingsWidgetFdoSecrets::~SettingsWidgetFdoSecrets() = default;
void SettingsWidgetFdoSecrets::populateSessions(bool enabled)
{
m_ui->tableSessions->setRowCount(0);
auto service = m_plugin->serviceInstance();
if (!service || !enabled) {
return;
}
for (const auto& sess : service->sessions()) {
addSessionRow(sess);
}
}
void SettingsWidgetFdoSecrets::addSessionRow(Session* sess)
{
auto row = m_ui->tableSessions->rowCount();
m_ui->tableSessions->insertRow(row);
// column 0: application name
auto item = new QTableWidgetItem(sess->peer());
item->setData(Qt::UserRole, QVariant::fromValue(sess));
m_ui->tableSessions->setItem(row, 0, item);
// column 1: disconnect button
auto btn = new QPushButton(tr("Disconnect"));
connect(btn, &QPushButton::clicked, sess, &Session::close);
m_ui->tableSessions->setCellWidget(row, 1, btn);
// column 2: hidden uuid
m_ui->tableSessions->setItem(row, 2, new QTableWidgetItem(sess->id()));
}
void SettingsWidgetFdoSecrets::removeSessionRow(Session* sess)
{
int row = 0;
while (row != m_ui->tableSessions->rowCount()) {
auto item = m_ui->tableSessions->item(row, 0);
const auto itemSess = item->data(Qt::UserRole).value<Session*>();
if (itemSess == sess) {
break;
}
++row;
}
if (row == m_ui->tableSessions->rowCount()) {
qWarning() << "Unknown Fdo Secret Service session" << sess->id() << "while removing collection from table";
return;
}
m_ui->tableSessions->removeRow(row);
}
void SettingsWidgetFdoSecrets::populateDatabases(bool enabled)
{
m_ui->tableDatabases->setRowCount(0);
auto service = m_plugin->serviceInstance();
if (!service || !enabled) {
return;
}
auto ret = service->collections();
if (ret.isError()) {
return;
}
for (const auto& coll : ret.value()) {
addDatabaseRow(coll);
}
}
void SettingsWidgetFdoSecrets::addDatabaseRow(Collection* coll)
{
auto row = m_ui->tableDatabases->rowCount();
m_ui->tableDatabases->insertRow(row);
// column 0: File name
QFileInfo fi(coll->backend()->database()->filePath());
auto item = new QTableWidgetItem(fi.fileName());
item->setData(Qt::UserRole, QVariant::fromValue(coll));
m_ui->tableDatabases->setItem(row, 0, item);
// column 2: manage button: hboxlayout: unlock/lock settings
// create this first so we have a widget to bind connection to,
// which can then be auto deleted when the row is deleted.
auto widget = createManageButtons(coll);
m_ui->tableDatabases->setCellWidget(row, 2, widget);
// column 1: Group name
auto itemGroupName = new QTableWidgetItem();
updateExposedGroupItem(itemGroupName, coll);
connect(coll, &Collection::collectionLockChanged, widget, [this, itemGroupName, coll](bool) {
updateExposedGroupItem(itemGroupName, coll);
});
m_ui->tableDatabases->setItem(row, 1, itemGroupName);
}
QWidget* SettingsWidgetFdoSecrets::createManageButtons(Collection* coll)
{
auto toolbar = new QToolBar;
toolbar->setFloatable(false);
toolbar->setMovable(false);
// db settings
auto dbSettingsAct = new QAction(tr("Database settings"), toolbar);
dbSettingsAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("document-edit")));
dbSettingsAct->setToolTip(tr("Edit database settings"));
dbSettingsAct->setEnabled(!coll->locked().value());
connect(dbSettingsAct, &QAction::triggered, this, [this, coll]() {
auto db = coll->backend();
m_plugin->serviceInstance()->doSwitchToChangeDatabaseSettings(db);
});
toolbar->addAction(dbSettingsAct);
// unlock/lock
auto lockAct = new QAction(tr("Unlock database"), toolbar);
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
lockAct->setToolTip(tr("Unlock database to show more information"));
connect(coll, &Collection::collectionLockChanged, lockAct, [lockAct, dbSettingsAct](bool locked) {
if (locked) {
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-locked"), true));
lockAct->setToolTip(tr("Unlock database to show more information"));
} else {
lockAct->setIcon(filePath()->icon(QStringLiteral("actions"), QStringLiteral("object-unlocked"), true));
lockAct->setToolTip(tr("Lock database"));
}
dbSettingsAct->setEnabled(!locked);
});
connect(lockAct, &QAction::triggered, this, [coll]() {
if (coll->locked().value()) {
coll->doUnlock();
} else {
coll->doLock();
}
});
toolbar->addAction(lockAct);
return toolbar;
}
void SettingsWidgetFdoSecrets::updateExposedGroupItem(QTableWidgetItem* item, Collection* coll)
{
if (coll->locked().value()) {
item->setText(tr("Unlock to show"));
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("object-locked"), true));
QFont font;
font.setItalic(true);
item->setFont(font);
return;
}
auto db = coll->backend()->database();
auto group = db->rootGroup()->findGroupByUuid(FdoSecrets::settings()->exposedGroup(db));
if (group) {
item->setText(group->name());
item->setIcon(group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
: group->iconScaledPixmap());
if (group->isExpired()) {
QFont font;
font.setStrikeOut(true);
item->setFont(font);
}
} else {
item->setText(tr("None"));
item->setIcon(filePath()->icon(QStringLiteral("apps"), QStringLiteral("paint-none"), true));
}
}
void SettingsWidgetFdoSecrets::removeDatabaseRow(Collection* coll)
{
int row = 0;
while (row != m_ui->tableDatabases->rowCount()) {
auto item = m_ui->tableDatabases->item(row, 0);
const auto itemColl = item->data(Qt::UserRole).value<Collection*>();
if (itemColl == coll) {
break;
}
++row;
}
if (row == m_ui->tableDatabases->rowCount()) {
qWarning() << "Unknown Fdo Secret Service collection" << coll->name() << "while removing collection from table";
return;
}
m_ui->tableDatabases->removeRow(row);
}
void SettingsWidgetFdoSecrets::loadSettings()
{
m_ui->enableFdoSecretService->setChecked(FdoSecrets::settings()->isEnabled());
@ -269,52 +301,4 @@ void SettingsWidgetFdoSecrets::saveSettings()
FdoSecrets::settings()->setNoConfirmDeleteItem(m_ui->noConfirmDeleteItem->isChecked());
}
void SettingsWidgetFdoSecrets::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, true));
}
void SettingsWidgetFdoSecrets::hideEvent(QHideEvent* event)
{
QWidget::hideEvent(event);
QMetaObject::invokeMethod(this, "updateTables", Qt::QueuedConnection, Q_ARG(bool, false));
}
void SettingsWidgetFdoSecrets::updateTables(bool enabled)
{
if (enabled) {
// update the table
populateDatabases(m_ui->enableFdoSecretService->isChecked());
populateSessions(m_ui->enableFdoSecretService->isChecked());
// re-layout the widget to adjust the table cell size
adjustSize();
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
connect(m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
auto service = m_plugin->serviceInstance();
if (service) {
connect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
connect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
connect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
connect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
}
} else {
disconnect(
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateSessions);
disconnect(
m_ui->enableFdoSecretService, &QCheckBox::toggled, this, &SettingsWidgetFdoSecrets::populateDatabases);
auto service = m_plugin->serviceInstance();
if (service) {
disconnect(service, &Service::sessionOpened, this, &SettingsWidgetFdoSecrets::addSessionRow);
disconnect(service, &Service::sessionClosed, this, &SettingsWidgetFdoSecrets::removeSessionRow);
disconnect(service, &Service::collectionCreated, this, &SettingsWidgetFdoSecrets::addDatabaseRow);
disconnect(service, &Service::collectionDeleted, this, &SettingsWidgetFdoSecrets::removeDatabaseRow);
}
}
}
#include "SettingsWidgetFdoSecrets.moc"

View File

@ -21,7 +21,9 @@
#include <QScopedPointer>
#include <QWidget>
class QTableWidgetItem;
class QAbstractItemView;
class QItemEditorCreatorBase;
class QItemEditorFactory;
namespace FdoSecrets
{
@ -48,28 +50,12 @@ public slots:
void loadSettings();
void saveSettings();
private slots:
void populateSessions(bool enabled);
void populateDatabases(bool enabled);
void addSessionRow(FdoSecrets::Session* sess);
void removeSessionRow(FdoSecrets::Session* sess);
void addDatabaseRow(FdoSecrets::Collection* coll);
void removeDatabaseRow(FdoSecrets::Collection* coll);
void updateTables(bool enabled);
protected:
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
private:
QWidget* createManageButtons(FdoSecrets::Collection* coll);
void updateExposedGroupItem(QTableWidgetItem* item, FdoSecrets::Collection* coll);
void setupView(QAbstractItemView* view, int manageColumn, int editorTypeId, QItemEditorCreatorBase* creator);
private:
QScopedPointer<Ui::SettingsWidgetFdoSecrets> m_ui;
QScopedPointer<QItemEditorFactory> m_factory;
FdoSecretsPlugin* m_plugin;
};

View File

@ -75,7 +75,7 @@
</widget>
</item>
<item>
<widget class="QTableWidget" name="tableDatabases">
<widget class="QTableView" name="tableDatabases">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
@ -91,21 +91,6 @@
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>File Name</string>
</property>
</column>
<column>
<property name="text">
<string>Group</string>
</property>
</column>
<column>
<property name="text">
<string>Manage</string>
</property>
</column>
</widget>
</item>
</layout>
@ -123,7 +108,7 @@
</widget>
</item>
<item>
<widget class="QTableWidget" name="tableSessions">
<widget class="QTableView" name="tableSessions">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
@ -139,16 +124,6 @@
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Application</string>
</property>
</column>
<column>
<property name="text">
<string>Manage</string>
</property>
</column>
</widget>
</item>
</layout>

View File

@ -83,7 +83,7 @@ void OpVaultReader::fillFromSectionField(Entry* entry, const QString& sectionNam
}
attrValue = query.toString(QUrl::FullyEncoded);
}
entry->attributes()->set(Totp::ATTRIBUTE_SETTINGS, attrValue, true);
entry->setTotp(Totp::parseSettings(attrValue));
} else if (attrName.startsWith("expir", Qt::CaseInsensitive)) {
QDateTime expiry;
if (kind == "date") {

View File

@ -51,7 +51,7 @@ void DatabaseOpenDialog::setTargetDatabaseWidget(DatabaseWidget* dbWidget)
disconnect(this, nullptr, m_dbWidget, nullptr);
}
m_dbWidget = dbWidget;
connect(this, SIGNAL(dialogFinished(bool)), dbWidget, SLOT(unlockDatabase(bool)));
connect(this, &DatabaseOpenDialog::dialogFinished, dbWidget, &DatabaseWidget::unlockDatabase);
}
void DatabaseOpenDialog::setIntent(DatabaseOpenDialog::Intent intent)
@ -90,6 +90,6 @@ void DatabaseOpenDialog::complete(bool accepted)
} else {
reject();
}
emit dialogFinished(accepted);
emit dialogFinished(accepted, m_dbWidget);
clearForms();
}

View File

@ -50,7 +50,7 @@ public:
void clearForms();
signals:
void dialogFinished(bool);
void dialogFinished(bool accepted, DatabaseWidget* dbWidget);
public slots:
void complete(bool accepted);

View File

@ -204,9 +204,14 @@ void DatabaseOpenWidget::openDatabase()
m_db.reset(new Database());
QString error;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_ui->passwordFormFrame->setEnabled(false);
QCoreApplication::processEvents();
bool ok = m_db->open(m_filename, masterKey, &error, false);
QApplication::restoreOverrideCursor();
m_ui->passwordFormFrame->setEnabled(true);
if (!ok) {
if (m_ui->editPassword->text().isEmpty() && !m_retryUnlockWithEmptyPassword) {
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
@ -370,8 +375,10 @@ void DatabaseOpenWidget::browseKeyFile()
QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
if (QFileInfo(filename).canonicalFilePath() == QFileInfo(m_filename).canonicalFilePath()) {
MessageBox::warning(this, tr("Cannot use database file as key file"),
tr("You cannot use your database file as a key file.\nIf you do not have a key file, please leave the field empty."),
MessageBox::warning(this,
tr("Cannot use database file as key file"),
tr("You cannot use your database file as a key file.\nIf you do not have a key file, "
"please leave the field empty."),
MessageBox::Button::Ok);
filename = "";
}

View File

@ -132,7 +132,7 @@
<number>15</number>
</property>
<item>
<widget class="QFrame" name="verticalFrame">
<widget class="QFrame" name="passwordFormFrame">
<property name="minimumSize">
<size>
<width>400</width>

View File

@ -63,6 +63,8 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
connect(m_databaseOpenDialog.data(), &DatabaseOpenDialog::dialogFinished,
this, &DatabaseTabWidget::databaseUnlockDialogFinished);
// clang-format on
#ifdef Q_OS_MACOS
@ -166,7 +168,8 @@ void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
for (int i = 0, c = count(); i < c; ++i) {
auto* dbWidget = databaseWidgetFromIndex(i);
Q_ASSERT(dbWidget);
if (dbWidget && dbWidget->database()->canonicalFilePath().compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
if (dbWidget
&& dbWidget->database()->canonicalFilePath().compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
dbWidget->performUnlockDatabase(password, keyfile);
if (!inBackground) {
// switch to existing tab if file is already open

View File

@ -90,6 +90,7 @@ signals:
void tabNameChanged();
void messageGlobal(const QString&, MessageWidget::MessageType type);
void messageDismissGlobal();
void databaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
private slots:
void toggleTabbar();

View File

@ -275,6 +275,11 @@ bool DatabaseWidget::isEntryEditActive() const
return currentWidget() == m_editEntryWidget;
}
bool DatabaseWidget::isGroupEditActive() const
{
return currentWidget() == m_editGroupWidget;
}
bool DatabaseWidget::isEditWidgetModified() const
{
if (currentWidget() == m_editEntryWidget) {
@ -387,6 +392,8 @@ void DatabaseWidget::createEntry()
void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
{
Q_ASSERT(!isEntryEditActive() && !isGroupEditActive());
// Save off new parent UUID which will be valid when creating a new entry
QUuid newParentUuid;
if (m_newParent) {
@ -1370,7 +1377,7 @@ bool DatabaseWidget::lock()
if (m_db->isModified()) {
bool saved = false;
// Attempt to save on exit, but don't block locking if it fails
if (config()->get("AutoSaveOnExit").toBool()) {
if (config()->get("AutoSaveOnExit").toBool() || config()->get("AutoSaveAfterEveryChange").toBool()) {
saved = save();
}
@ -1421,7 +1428,8 @@ bool DatabaseWidget::lock()
void DatabaseWidget::reloadDatabaseFile()
{
if (!m_db || isLocked()) {
// Ignore reload if we are locked or currently editing an entry or group
if (!m_db || isLocked() || isEntryEditActive() || isGroupEditActive()) {
return;
}
@ -1441,6 +1449,11 @@ void DatabaseWidget::reloadDatabaseFile()
}
}
// Lock out interactions
m_entryView->setDisabled(true);
m_groupView->setDisabled(true);
QApplication::processEvents();
QString error;
auto db = QSharedPointer<Database>::create(m_db->filePath());
if (db->open(database()->key(), &error)) {
@ -1480,6 +1493,10 @@ void DatabaseWidget::reloadDatabaseFile()
// Mark db as modified since existing data may differ from file or file was deleted
m_db->markAsModified();
}
// Return control
m_entryView->setDisabled(false);
m_groupView->setDisabled(false);
}
int DatabaseWidget::numberOfSelectedEntries() const
@ -1620,11 +1637,20 @@ bool DatabaseWidget::save()
m_blockAutoSave = true;
++m_saveAttempts;
// TODO: Make this async, but lock out the database widget to prevent re-entrance
// TODO: Make this async
// Lock out interactions
m_entryView->setDisabled(true);
m_groupView->setDisabled(true);
QApplication::processEvents();
bool useAtomicSaves = config()->get("UseAtomicSaves", true).toBool();
QString errorMessage;
bool ok = m_db->save(&errorMessage, useAtomicSaves, config()->get("BackupBeforeSave").toBool());
// Return control
m_entryView->setDisabled(false);
m_groupView->setDisabled(false);
if (ok) {
m_saveAttempts = 0;
m_blockAutoSave = false;
@ -1744,7 +1770,7 @@ void DatabaseWidget::processAutoOpen()
}
for (const auto* entry : autoopenGroup->entries()) {
if (entry->url().isEmpty() || entry->password().isEmpty()) {
if (entry->url().isEmpty() || (entry->password().isEmpty() && entry->username().isEmpty())) {
continue;
}
QFileInfo filepath;

View File

@ -81,6 +81,7 @@ public:
bool isLocked() const;
bool isSearchActive() const;
bool isEntryEditActive() const;
bool isGroupEditActive() const;
QString getCurrentSearch();
void refreshSearch();

View File

@ -36,6 +36,11 @@ QFont Font::fixedFont()
fixedFont = consolasFont;
}
#endif
#ifdef Q_OS_MACOS
// Qt doesn't choose a monospace font correctly on macOS
const QFont defaultFont;
fixedFont = QFontDatabase().font("Menlo", defaultFont.styleName(), defaultFont.pointSize());
#endif
return fixedFont;
}

View File

@ -885,8 +885,8 @@ void MainWindow::switchToPasswordGen(bool enabled)
if (enabled) {
m_ui->passwordGeneratorWidget->loadSettings();
m_ui->passwordGeneratorWidget->regeneratePassword();
m_ui->passwordGeneratorWidget->setStandaloneMode(true);
m_ui->stackedWidget->setCurrentIndex(PasswordGeneratorScreen);
m_ui->passwordGeneratorWidget->setStandaloneMode(true);
} else {
m_ui->passwordGeneratorWidget->saveSettings();
switchToDatabases();

View File

@ -60,7 +60,7 @@ SearchWidget::SearchWidget(QWidget* parent)
.arg(QKeySequence(QKeySequence::Find).toString(QKeySequence::NativeText)));
m_ui->searchEdit->installEventFilter(this);
m_searchMenu = new QMenu();
m_searchMenu = new QMenu(this);
m_actionCaseSensitive = m_searchMenu->addAction(tr("Case sensitive"), this, SLOT(updateCaseSensitive()));
m_actionCaseSensitive->setObjectName("actionSearchCaseSensitive");
m_actionCaseSensitive->setCheckable(true);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
* Copyright (C) 2017 Weslly Honorato <weslly@protonmail.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
@ -42,7 +42,7 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry)
resetCounter();
updateProgressBar();
connect(parent, SIGNAL(lockedDatabase()), SLOT(close()));
connect(parent, SIGNAL(databaseLocked()), SLOT(close()));
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
m_totpUpdateTimer.start(m_step * 10);

59
src/gui/URLEdit.cpp Normal file
View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "URLEdit.h"
#include <QRegularExpression>
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/Tools.h"
#include "gui/Font.h"
const QColor URLEdit::ErrorColor = QColor(255, 125, 125);
URLEdit::URLEdit(QWidget* parent)
: QLineEdit(parent)
{
const QIcon errorIcon = filePath()->icon("status", "dialog-error");
m_errorAction = addAction(errorIcon, QLineEdit::TrailingPosition);
m_errorAction->setVisible(false);
m_errorAction->setToolTip(tr("Invalid URL"));
updateStylesheet();
}
void URLEdit::enableVerifyMode()
{
updateStylesheet();
connect(this, SIGNAL(textChanged(QString)), SLOT(updateStylesheet()));
}
void URLEdit::updateStylesheet()
{
const QString stylesheetTemplate("QLineEdit { background: %1; }");
if (!Tools::checkUrlValid(text())) {
setStyleSheet(stylesheetTemplate.arg(ErrorColor.name()));
m_errorAction->setVisible(true);
} else {
m_errorAction->setVisible(false);
setStyleSheet("");
}
}

43
src/gui/URLEdit.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_URLEDIT_H
#define KEEPASSX_URLEDIT_H
#include <QAction>
#include <QLineEdit>
#include <QPointer>
class URLEdit : public QLineEdit
{
Q_OBJECT
public:
static const QColor ErrorColor;
explicit URLEdit(QWidget* parent = nullptr);
void enableVerifyMode();
private slots:
void updateStylesheet();
private:
QPointer<QAction> m_errorAction;
};
#endif // KEEPASSX_URLEDIT_H

View File

@ -48,4 +48,3 @@ const QSharedPointer<Database> DatabaseSettingsWidget::getDatabase() const
{
return m_db;
}

View File

@ -167,6 +167,7 @@ void EditEntryWidget::setupMain()
#ifdef WITH_XC_NETWORKING
connect(m_mainUi->fetchFaviconButton, SIGNAL(clicked()), m_iconsWidget, SLOT(downloadFavicon()));
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
m_mainUi->urlEdit->enableVerifyMode();
#endif
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
connect(m_mainUi->notesEnabled, SIGNAL(toggled(bool)), this, SLOT(toggleHideNotes(bool)));
@ -201,7 +202,7 @@ void EditEntryWidget::setupAdvanced()
connect(m_advancedUi->editAttributeButton, SIGNAL(clicked()), SLOT(editCurrentAttribute()));
connect(m_advancedUi->removeAttributeButton, SIGNAL(clicked()), SLOT(removeCurrentAttribute()));
connect(m_advancedUi->protectAttributeButton, SIGNAL(toggled(bool)), SLOT(protectCurrentAttribute(bool)));
connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(revealCurrentAttribute()));
connect(m_advancedUi->revealAttributeButton, SIGNAL(clicked(bool)), SLOT(toggleCurrentAttributeVisibility()));
connect(m_advancedUi->attributesView->selectionModel(),
SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(updateCurrentAttribute()));
@ -271,9 +272,14 @@ void EditEntryWidget::setupBrowser()
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
// Use a custom item delegate to align the icon to the right side
auto iconDelegate = new URLModelIconDelegate(m_browserUi->additionalURLsView);
m_browserUi->additionalURLsView->setItemDelegate(iconDelegate);
// clang-format off
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(updateBrowserModified()));
connect(m_browserUi->addURLButton, SIGNAL(clicked()), SLOT(insertURL()));
connect(m_browserUi->removeURLButton, SIGNAL(clicked()), SLOT(removeCurrentURL()));
connect(m_browserUi->editURLButton, SIGNAL(clicked()), SLOT(editCurrentURL()));
@ -300,8 +306,10 @@ void EditEntryWidget::updateBrowser()
auto skip = m_browserUi->skipAutoSubmitCheckbox->isChecked();
auto hide = m_browserUi->hideEntryCheckbox->isChecked();
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? QString("true") : QString("false")));
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? QString("true") : QString("false")));
auto onlyHttpAuth = m_browserUi->onlyHttpAuthCheckbox->isChecked();
m_customData->set(BrowserService::OPTION_SKIP_AUTO_SUBMIT, (skip ? TRUE_STR : FALSE_STR));
m_customData->set(BrowserService::OPTION_HIDE_ENTRY, (hide ? TRUE_STR : FALSE_STR));
m_customData->set(BrowserService::OPTION_ONLY_HTTP_AUTH, (onlyHttpAuth ? TRUE_STR : FALSE_STR));
}
void EditEntryWidget::insertURL()
@ -465,6 +473,7 @@ void EditEntryWidget::setupEntryUpdate()
if (config()->get("Browser/Enabled", false).toBool()) {
connect(m_browserUi->skipAutoSubmitCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->hideEntryCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->onlyHttpAuthCheckbox, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
@ -959,18 +968,25 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
#ifdef WITH_XC_BROWSER
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
// clang-format off
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == "true");
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == TRUE_STR);
// clang-format on
} else {
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY) == "true");
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR);
} else {
m_browserUi->hideEntryCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)) {
m_browserUi->onlyHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_ONLY_HTTP_AUTH)
== TRUE_STR);
} else {
m_browserUi->onlyHttpAuthCheckbox->setChecked(false);
}
m_browserUi->addURLButton->setEnabled(!m_history);
m_browserUi->removeURLButton->setEnabled(false);
m_browserUi->editURLButton->setEnabled(false);
@ -1297,6 +1313,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
// Block signals to prevent modified being set
m_advancedUi->protectAttributeButton->blockSignals(true);
m_advancedUi->attributesEdit->blockSignals(true);
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
if (index.isValid()) {
QString key = m_attributesModel->keyByIndex(index);
@ -1348,7 +1365,7 @@ void EditEntryWidget::protectCurrentAttribute(bool state)
}
}
void EditEntryWidget::revealCurrentAttribute()
void EditEntryWidget::toggleCurrentAttributeVisibility()
{
if (!m_advancedUi->attributesEdit->isEnabled()) {
QModelIndex index = m_advancedUi->attributesView->currentIndex();
@ -1359,6 +1376,10 @@ void EditEntryWidget::revealCurrentAttribute()
m_advancedUi->attributesEdit->setEnabled(true);
m_advancedUi->attributesEdit->blockSignals(oldBlockSignals);
}
m_advancedUi->revealAttributeButton->setText(tr("Hide"));
} else {
protectCurrentAttribute(true);
m_advancedUi->revealAttributeButton->setText(tr("Reveal"));
}
}

View File

@ -93,7 +93,7 @@ private slots:
void removeCurrentAttribute();
void updateCurrentAttribute();
void protectCurrentAttribute(bool state);
void revealCurrentAttribute();
void toggleCurrentAttributeVisibility();
void updateAutoTypeEnabled();
void openAutotypeHelp();
void insertAutoTypeAssoc();

View File

@ -50,6 +50,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="onlyHttpAuthCheckbox">
<property name="toolTip">
<string>Only send this setting to the browser for HTTP Auth dialogs. If enabled, normal login forms will not show this entry for selection.</string>
</property>
<property name="text">
<string>Use this entry only with HTTP Basic Auth</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -130,6 +140,7 @@
<tabstops>
<tabstop>skipAutoSubmitCheckbox</tabstop>
<tabstop>hideEntryCheckbox</tabstop>
<tabstop>onlyHttpAuthCheckbox</tabstop>
<tabstop>additionalURLsView</tabstop>
<tabstop>addURLButton</tabstop>
<tabstop>removeURLButton</tabstop>

View File

@ -30,7 +30,7 @@
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLineEdit" name="urlEdit">
<widget class="URLEdit" name="urlEdit">
<property name="accessibleName">
<string>Url field</string>
</property>
@ -256,6 +256,12 @@
<header>gui/PasswordEdit.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>URLEdit</class>
<extends>QLineEdit</extends>
<header>gui/URLEdit.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>titleEdit</tabstop>

View File

@ -19,6 +19,7 @@
#include "EntryURLModel.h"
#include "core/Entry.h"
#include "core/FilePath.h"
#include "core/Tools.h"
#include <algorithm>
@ -26,6 +27,7 @@
EntryURLModel::EntryURLModel(QObject* parent)
: QStandardItemModel(parent)
, m_entryAttributes(nullptr)
, m_errorIcon(filePath()->icon("status", "dialog-error"))
{
}
@ -53,6 +55,33 @@ void EntryURLModel::setEntryAttributes(EntryAttributes* entryAttributes)
endResetModel();
}
QVariant EntryURLModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return {};
}
const auto key = keyByIndex(index);
if (key.isEmpty()) {
return {};
}
const auto value = m_entryAttributes->value(key);
const auto urlValid = Tools::checkUrlValid(value);
if (role == Qt::BackgroundRole && !urlValid) {
return QColor(255, 125, 125);
} else if (role == Qt::DecorationRole && !urlValid) {
return m_errorIcon;
} else if (role == Qt::DisplayRole || role == Qt::EditRole) {
return value;
} else if (role == Qt::ToolTipRole && !urlValid) {
return tr("Invalid URL");
}
return {};
}
bool EntryURLModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || role != Qt::EditRole || value.type() != QVariant::String || value.toString().isEmpty()) {

View File

@ -20,9 +20,23 @@
#define KEEPASSXC_ENTRYURLMODEL_H
#include <QStandardItemModel>
#include <QStyledItemDelegate>
class EntryAttributes;
class URLModelIconDelegate : public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
protected:
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
option->decorationPosition = QStyleOptionViewItem::Right;
}
};
class EntryURLModel : public QStandardItemModel
{
Q_OBJECT
@ -32,6 +46,7 @@ public:
void setEntryAttributes(EntryAttributes* entryAttributes);
void insertRow(const QString& key, const QString& value);
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
QVariant data(const QModelIndex& index, int role) const override;
QModelIndex indexByKey(const QString& key) const;
QString keyByIndex(const QModelIndex& index) const;
@ -41,6 +56,7 @@ private slots:
private:
QList<QPair<QString, QString>> m_urls;
EntryAttributes* m_entryAttributes;
QIcon m_errorIcon;
};
#endif // KEEPASSXC_ENTRYURLMODEL_H

View File

@ -20,11 +20,6 @@
#import <AppKit/NSWorkspace.h>
#import <CoreVideo/CVPixelBuffer.h>
#import <Availability.h>
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200
static const NSEventMask NSEventMaskKeyDown = NSKeyDownMask;
#endif
@implementation AppKitImpl

View File

@ -32,7 +32,6 @@ class KeyFileEditWidget : public KeyComponentWidget
{
Q_OBJECT
public:
explicit KeyFileEditWidget(DatabaseSettingsWidget* parent);
Q_DISABLE_COPY(KeyFileEditWidget);

View File

@ -95,8 +95,6 @@ void PasswordEditWidget::initComponentEditWidget(QWidget* widget)
void PasswordEditWidget::hideEvent(QHideEvent* event)
{
Q_ASSERT(m_compUi->enterPasswordEdit);
if (!isVisible() && m_compUi->enterPasswordEdit) {
m_compUi->enterPasswordEdit->setText("");
m_compUi->repeatPasswordEdit->setText("");

View File

@ -105,8 +105,10 @@ void ElidedLabel::updateElidedText()
const QFontMetrics metrix(font());
displayText = metrix.elidedText(m_rawText, m_elideMode, width() - 2);
}
setText(m_url.isEmpty() ? displayText : htmlLinkTemplate.arg(m_url, displayText));
setOpenExternalLinks(!m_url.isEmpty());
bool hasUrl = !m_url.isEmpty();
setText(hasUrl ? htmlLinkTemplate.arg(m_url.toHtmlEscaped(), displayText) : displayText);
setOpenExternalLinks(!hasUrl);
}
void ElidedLabel::resizeEvent(QResizeEvent* event)

View File

@ -18,6 +18,7 @@
#include "TestBrowser.h"
#include "TestGlobal.h"
#include "browser/BrowserSettings.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "sodium/crypto_box.h"
#include <QString>
@ -56,7 +57,7 @@ void TestBrowser::testChangePublicKeys()
auto response = m_browserAction->handleAction(json);
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
QCOMPARE(response["success"].toString(), QString("true"));
QCOMPARE(response["success"].toString(), TRUE_STR);
}
void TestBrowser::testEncryptMessage()
@ -179,29 +180,22 @@ void TestBrowser::testSearchEntries()
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QList<QString> urls;
urls.push_back("https://github.com/login_page");
urls.push_back("https://github.com/login");
urls.push_back("https://github.com/");
urls.push_back("github.com/login");
urls.push_back("http://github.com");
urls.push_back("http://github.com/login");
urls.push_back("github.com");
urls.push_back("github.com/login");
urls.push_back("https://github"); // Invalid URL
urls.push_back("github.com");
QStringList urls = {"https://github.com/login_page",
"https://github.com/login",
"https://github.com/",
"github.com/login",
"http://github.com",
"http://github.com/login",
"github.com",
"github.com/login",
"https://github", // Invalid URL
"github.com"};
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
}
createEntries(urls, root);
browserSettings()->setMatchUrlScheme(false);
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
auto result =
m_browserService->searchEntries(db, "https://github.com", "https://github.com/session"); // db, url, submitUrl
QCOMPARE(result.length(), 9);
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
@ -213,7 +207,7 @@ void TestBrowser::testSearchEntries()
// With matching there should be only 3 results + 4 without a scheme
browserSettings()->setMatchUrlScheme(true);
result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
QCOMPARE(result.length(), 7);
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
@ -226,22 +220,13 @@ void TestBrowser::testSearchEntriesWithPort()
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QList<QString> urls;
urls.push_back("http://127.0.0.1:443");
urls.push_back("http://127.0.0.1:80");
QStringList urls = {"http://127.0.0.1:443", "http://127.0.0.1:80"};
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
}
createEntries(urls, root);
auto result = m_browserService->searchEntries(db, "127.0.0.1", "http://127.0.0.1:443"); // db, hostname, url
auto result = m_browserService->searchEntries(db, "http://127.0.0.1:443", "http://127.0.0.1");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0]->url(), urls[0]);
QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
}
void TestBrowser::testSearchEntriesWithAdditionalURLs()
@ -249,70 +234,55 @@ void TestBrowser::testSearchEntriesWithAdditionalURLs()
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QList<Entry*> entries;
QList<QString> urls;
urls.push_back("https://github.com/");
urls.push_back("https://www.example.com");
urls.push_back("http://domain.com");
QStringList urls = {"https://github.com/", "https://www.example.com", "http://domain.com"};
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
entries.push_back(entry);
}
auto entries = createEntries(urls, root);
// Add an additional URL to the first entry
entries.first()->attributes()->set(BrowserService::ADDITIONAL_URL, "https://keepassxc.org");
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0]->url(), urls[0]);
QCOMPARE(result[0]->url(), QString("https://github.com/"));
// Search the additional URL. It should return the same entry
auto additionalResult = m_browserService->searchEntries(db, "keepassxc.org", "https://keepassxc.org");
auto additionalResult = m_browserService->searchEntries(db, "https://keepassxc.org", "https://keepassxc.org");
QCOMPARE(additionalResult.length(), 1);
QCOMPARE(additionalResult[0]->url(), urls[0]);
QCOMPARE(additionalResult[0]->url(), QString("https://github.com/"));
}
void TestBrowser::testInvalidEntries()
{
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
const QString url("https://github.com");
const QString submitUrl("https://github.com/session");
QList<QString> urls;
urls.push_back("https://github.com/login");
urls.push_back("https:///github.com/"); // Extra '/'
urls.push_back("http://github.com/**//*");
urls.push_back("http://*.github.com/login");
urls.push_back("//github.com"); // fromUserInput() corrects this one.
urls.push_back("github.com/{}<>");
QStringList urls = {
"https://github.com/login",
"https:///github.com/", // Extra '/'
"http://github.com/**//*",
"http://*.github.com/login",
"//github.com", // fromUserInput() corrects this one.
"github.com/{}<>",
"http:/example.com",
};
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
}
createEntries(urls, root);
browserSettings()->setMatchUrlScheme(true);
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
QCOMPARE(result.length(), 2);
QCOMPARE(result[0]->url(), QString("https://github.com/login"));
QCOMPARE(result[1]->url(), QString("//github.com"));
// Test the URL's directly
QCOMPARE(m_browserService->handleURL(urls[0], "github.com", "https://github.com"), true);
QCOMPARE(m_browserService->handleURL(urls[1], "github.com", "https://github.com"), false);
QCOMPARE(m_browserService->handleURL(urls[2], "github.com", "https://github.com"), false);
QCOMPARE(m_browserService->handleURL(urls[3], "github.com", "https://github.com"), false);
QCOMPARE(m_browserService->handleURL(urls[4], "github.com", "https://github.com"), true);
QCOMPARE(m_browserService->handleURL(urls[5], "github.com", "https://github.com"), false);
QCOMPARE(m_browserService->handleURL(urls[0], url, submitUrl), true);
QCOMPARE(m_browserService->handleURL(urls[1], url, submitUrl), false);
QCOMPARE(m_browserService->handleURL(urls[2], url, submitUrl), false);
QCOMPARE(m_browserService->handleURL(urls[3], url, submitUrl), false);
QCOMPARE(m_browserService->handleURL(urls[4], url, submitUrl), true);
QCOMPARE(m_browserService->handleURL(urls[5], url, submitUrl), false);
}
void TestBrowser::testSubdomainsAndPaths()
@ -320,44 +290,74 @@ void TestBrowser::testSubdomainsAndPaths()
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QList<QString> urls;
urls.push_back("https://www.github.com/login/page.xml");
urls.push_back("https://login.github.com/");
urls.push_back("https://github.com");
urls.push_back("http://www.github.com");
urls.push_back("http://login.github.com/pathtonowhere");
urls.push_back(".github.com"); // Invalid URL
urls.push_back("www.github.com/");
urls.push_back("https://github"); // Invalid URL
QStringList urls = {
"https://www.github.com/login/page.xml",
"https://login.github.com/",
"https://github.com",
"http://www.github.com",
"http://login.github.com/pathtonowhere",
".github.com", // Invalid URL
"www.github.com/",
"https://github" // Invalid URL
};
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
}
createEntries(urls, root);
browserSettings()->setMatchUrlScheme(false);
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
auto result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0]->url(), QString("https://github.com"));
QCOMPARE(result.length(), 6);
QCOMPARE(result[0]->url(), urls[0]);
QCOMPARE(result[1]->url(), urls[1]);
QCOMPARE(result[2]->url(), urls[2]);
QCOMPARE(result[3]->url(), urls[3]);
QCOMPARE(result[4]->url(), urls[4]);
QCOMPARE(result[5]->url(), urls[6]);
// With matching there should be only 3 results
browserSettings()->setMatchUrlScheme(true);
result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
// With www subdomain
result = m_browserService->searchEntries(db, "https://www.github.com", "https://www.github.com/session");
QCOMPARE(result.length(), 4);
QCOMPARE(result[0]->url(), urls[0]);
QCOMPARE(result[1]->url(), urls[1]);
QCOMPARE(result[2]->url(), urls[2]);
QCOMPARE(result[3]->url(), urls[6]);
QCOMPARE(result[0]->url(), QString("https://www.github.com/login/page.xml"));
QCOMPARE(result[1]->url(), QString("https://github.com")); // Accepts any subdomain
QCOMPARE(result[2]->url(), QString("http://www.github.com"));
QCOMPARE(result[3]->url(), QString("www.github.com/"));
// With scheme matching there should be only 1 result
browserSettings()->setMatchUrlScheme(true);
result = m_browserService->searchEntries(db, "https://github.com", "https://github.com/session");
QCOMPARE(result.length(), 1);
QCOMPARE(result[0]->url(), QString("https://github.com"));
// Test site with subdomain in the site URL
QStringList entryURLs = {
"https://accounts.example.com",
"https://accounts.example.com/path",
"https://subdomain.example.com/",
"https://another.accounts.example.com/",
"https://another.subdomain.example.com/",
"https://example.com/",
"https://example" // Invalid URL
};
createEntries(entryURLs, root);
result = m_browserService->searchEntries(db, "https://accounts.example.com", "https://accounts.example.com");
QCOMPARE(result.length(), 3);
QCOMPARE(result[0]->url(), QString("https://accounts.example.com"));
QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
QCOMPARE(result[2]->url(), QString("https://example.com/")); // Accepts any subdomain
result = m_browserService->searchEntries(
db, "https://another.accounts.example.com", "https://another.accounts.example.com");
QCOMPARE(result.length(), 4);
QCOMPARE(result[0]->url(),
QString("https://accounts.example.com")); // Accepts any subdomain under accounts.example.com
QCOMPARE(result[1]->url(), QString("https://accounts.example.com/path"));
QCOMPARE(result[2]->url(), QString("https://another.accounts.example.com/"));
QCOMPARE(result[3]->url(), QString("https://example.com/")); // Accepts one or more subdomains
// Test local files. It should be a direct match.
QStringList localFiles = {"file:///Users/testUser/tests/test.html"};
createEntries(localFiles, root);
// With local files, url is always set to the file scheme + ://. Submit URL holds the actual URL.
result = m_browserService->searchEntries(db, "file://", "file:///Users/testUser/tests/test.html");
QCOMPARE(result.length(), 1);
}
void TestBrowser::testSortEntries()
@ -365,28 +365,18 @@ void TestBrowser::testSortEntries()
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QList<QString> urls;
urls.push_back("https://github.com/login_page");
urls.push_back("https://github.com/login");
urls.push_back("https://github.com/");
urls.push_back("github.com/login");
urls.push_back("http://github.com");
urls.push_back("http://github.com/login");
urls.push_back("github.com");
urls.push_back("github.com/login");
urls.push_back("https://github"); // Invalid URL
urls.push_back("github.com");
QStringList urls = {"https://github.com/login_page",
"https://github.com/login",
"https://github.com/",
"github.com/login",
"http://github.com",
"http://github.com/login",
"github.com",
"github.com/login",
"https://github", // Invalid URL
"github.com"};
QList<Entry*> entries;
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
entries.push_back(entry);
}
auto entries = createEntries(urls, root);
browserSettings()->setBestMatchOnly(false);
auto result =
@ -400,7 +390,6 @@ void TestBrowser::testSortEntries()
QCOMPARE(result[2]->url(), QString("https://github.com/login"));
QCOMPARE(result[3]->username(), QString("User 3"));
QCOMPARE(result[3]->url(), QString("github.com/login"));
}
void TestBrowser::testGetDatabaseGroups()
@ -458,3 +447,38 @@ void TestBrowser::testGetDatabaseGroups()
auto lastChild = lastChildren.at(0);
QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1"));
}
QList<Entry*> TestBrowser::createEntries(QStringList& urls, Group* root) const
{
QList<Entry*> entries;
for (int i = 0; i < urls.length(); ++i) {
auto entry = new Entry();
entry->setGroup(root);
entry->beginUpdate();
entry->setUrl(urls[i]);
entry->setUsername(QString("User %1").arg(i));
entry->endUpdate();
entries.push_back(entry);
}
return entries;
}
void TestBrowser::testValidURLs()
{
QHash<QString, bool> urls;
urls["https://github.com/login"] = true;
urls["https:///github.com/"] = false;
urls["http://github.com/**//*"] = false;
urls["http://*.github.com/login"] = false;
urls["//github.com"] = true;
urls["github.com/{}<>"] = false;
urls["http:/example.com"] = false;
urls["cmd://C:/Toolchains/msys2/usr/bin/mintty \"ssh jon@192.168.0.1:22\""] = true;
urls["file:///Users/testUser/Code/test.html"] = true;
QHashIterator<QString, bool> i(urls);
while (i.hasNext()) {
i.next();
QCOMPARE(Tools::checkUrlValid(i.key()), i.value());
}
}

View File

@ -47,8 +47,11 @@ private slots:
void testSubdomainsAndPaths();
void testSortEntries();
void testGetDatabaseGroups();
void testValidURLs();
private:
QList<Entry*> createEntries(QStringList& urls, Group* root) const;
QScopedPointer<BrowserAction> m_browserAction;
QScopedPointer<BrowserService> m_browserService;
};

View File

@ -23,13 +23,13 @@
#include "core/Global.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "keys/drivers/YubiKey.h"
#include "format/Kdbx3Reader.h"
#include "format/Kdbx3Writer.h"
#include "format/Kdbx4Reader.h"
#include "format/Kdbx4Writer.h"
#include "format/KdbxXmlReader.h"
#include "format/KeePass2.h"
#include "keys/drivers/YubiKey.h"
#include "cli/Add.h"
#include "cli/AddGroup.h"
@ -1422,6 +1422,107 @@ void TestCli::testMerge()
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
}
void TestCli::testMergeWithKeys()
{
Create createCmd;
QVERIFY(!createCmd.name.isEmpty());
QVERIFY(createCmd.getDescriptionLine().contains(createCmd.name));
Merge mergeCmd;
QVERIFY(!mergeCmd.name.isEmpty());
QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name));
Kdbx4Writer writer;
Kdbx4Reader reader;
QScopedPointer<QTemporaryDir> testDir(new QTemporaryDir());
QString sourceDatabaseFilename = testDir->path() + "/testSourceDatabase.kdbx";
QString sourceKeyfilePath = testDir->path() + "/testSourceKeyfile.txt";
QString targetDatabaseFilename = testDir->path() + "/testTargetDatabase.kdbx";
QString targetKeyfilePath = testDir->path() + "/testTargetKeyfile.txt";
qint64 pos = m_stdoutFile->pos();
Utils::Test::setNextPassword("a");
createCmd.execute({"create", sourceDatabaseFilename, "-k", sourceKeyfilePath});
Utils::Test::setNextPassword("b");
createCmd.execute({"create", targetDatabaseFilename, "-k", targetKeyfilePath});
Utils::Test::setNextPassword("a");
auto sourceDatabase = QSharedPointer<Database>(
Utils::unlockDatabase(sourceDatabaseFilename, true, sourceKeyfilePath, "", Utils::STDOUT));
QVERIFY(sourceDatabase);
Utils::Test::setNextPassword("b");
auto targetDatabase = QSharedPointer<Database>(
Utils::unlockDatabase(targetDatabaseFilename, true, targetKeyfilePath, "", Utils::STDOUT));
QVERIFY(targetDatabase);
auto* rootGroup = new Group();
rootGroup->setName("root");
rootGroup->setUuid(QUuid::createUuid());
auto* group = new Group();
group->setUuid(QUuid::createUuid());
group->setParent(rootGroup);
group->setName("Internet");
auto* entry = new Entry();
entry->setUuid(QUuid::createUuid());
entry->setTitle("Some Website");
entry->setPassword("secretsecretsecret");
group->addEntry(entry);
sourceDatabase->setRootGroup(rootGroup);
auto* otherRootGroup = new Group();
otherRootGroup->setName("root");
otherRootGroup->setUuid(QUuid::createUuid());
auto* otherGroup = new Group();
otherGroup->setUuid(QUuid::createUuid());
otherGroup->setParent(otherRootGroup);
otherGroup->setName("Internet");
auto* otherEntry = new Entry();
otherEntry->setUuid(QUuid::createUuid());
otherEntry->setTitle("Some Website 2");
otherEntry->setPassword("secretsecretsecret 2");
otherGroup->addEntry(otherEntry);
targetDatabase->setRootGroup(otherRootGroup);
QFile sourceDatabaseFile(sourceDatabaseFilename);
sourceDatabaseFile.open(QIODevice::WriteOnly);
QVERIFY(writer.writeDatabase(&sourceDatabaseFile, sourceDatabase.data()));
sourceDatabaseFile.flush();
sourceDatabaseFile.close();
QFile targetDatabaseFile(targetDatabaseFilename);
targetDatabaseFile.open(QIODevice::WriteOnly);
QVERIFY(writer.writeDatabase(&targetDatabaseFile, targetDatabase.data()));
targetDatabaseFile.flush();
targetDatabaseFile.close();
pos = m_stdoutFile->pos();
Utils::Test::setNextPassword("b");
Utils::Test::setNextPassword("a");
mergeCmd.execute({"merge",
"-k",
targetKeyfilePath,
"--key-file-from",
sourceKeyfilePath,
targetDatabaseFile.fileName(),
sourceDatabaseFile.fileName()});
m_stdoutFile->seek(pos);
QList<QByteArray> lines = m_stdoutFile->readAll().split('\n');
QVERIFY(lines.contains(QString("Successfully merged %1 into %2.")
.arg(sourceDatabaseFile.fileName(), targetDatabaseFile.fileName())
.toUtf8()));
}
void TestCli::testMove()
{
Move moveCmd;

View File

@ -66,6 +66,7 @@ private slots:
void testList();
void testLocate();
void testMerge();
void testMergeWithKeys();
void testMove();
void testOpen();
void testRemove();

View File

@ -21,9 +21,9 @@
#include "core/EntrySearcher.h"
#include "fdosecrets/GcryptMPI.h"
#include "fdosecrets/objects/SessionCipher.h"
#include "fdosecrets/objects/Collection.h"
#include "fdosecrets/objects/Item.h"
#include "fdosecrets/objects/SessionCipher.h"
#include "crypto/Crypto.h"
@ -96,8 +96,8 @@ void TestFdoSecrets::testDhIetf1024Sha256Aes128CbcPkcs7()
void TestFdoSecrets::testCrazyAttributeKey()
{
using FdoSecrets::Item;
using FdoSecrets::Collection;
using FdoSecrets::Item;
const QScopedPointer<Group> root(new Group());
const QScopedPointer<Entry> e1(new Entry());
@ -112,3 +112,35 @@ void TestFdoSecrets::testCrazyAttributeKey()
const auto res = EntrySearcher().search({term}, root.data());
QCOMPARE(res.count(), 1);
}
void TestFdoSecrets::testSpecialCharsInAttributeValue()
{
using FdoSecrets::Collection;
using FdoSecrets::Item;
const QScopedPointer<Group> root(new Group());
QScopedPointer<Entry> e1(new Entry());
e1->setGroup(root.data());
e1->setTitle("titleA");
e1->attributes()->set("testAttribute", "OAuth::[test.name@gmail.com]");
QScopedPointer<Entry> e2(new Entry());
e2->setGroup(root.data());
e2->setTitle("titleB");
e2->attributes()->set("testAttribute", "Abc:*+.-");
// search for custom entries via programatic API
{
const auto term = Collection::attributeToTerm("testAttribute", "OAuth::[test.name@gmail.com]");
const auto res = EntrySearcher().search({term}, root.data());
QCOMPARE(res.count(), 1);
QCOMPARE(res[0]->title(), QStringLiteral("titleA"));
}
{
const auto term = Collection::attributeToTerm("testAttribute", "Abc:*+.-");
const auto res = EntrySearcher().search({term}, root.data());
QCOMPARE(res.count(), 1);
QCOMPARE(res[0]->title(), QStringLiteral("titleB"));
}
}

View File

@ -31,6 +31,7 @@ private slots:
void testGcryptMPI();
void testDhIetf1024Sha256Aes128CbcPkcs7();
void testCrazyAttributeKey();
void testSpecialCharsInAttributeValue();
};
#endif // KEEPASSXC_TESTFDOSECRETS_H

View File

@ -20,6 +20,7 @@
#include "TestGlobal.h"
#include "mock/MockClock.h"
#include <QScopedPointer>
#include <QSignalSpy>
#include "core/Metadata.h"
@ -798,16 +799,16 @@ void TestGroup::testAddEntryWithPath()
void TestGroup::testIsRecycled()
{
Database* db = new Database();
db->metadata()->setRecycleBinEnabled(true);
Database db;
db.metadata()->setRecycleBinEnabled(true);
Group* group1 = new Group();
group1->setName("group1");
group1->setParent(db->rootGroup());
group1->setParent(db.rootGroup());
Group* group2 = new Group();
group2->setName("group2");
group2->setParent(db->rootGroup());
group2->setParent(db.rootGroup());
Group* group3 = new Group();
group3->setName("group3");
@ -815,16 +816,16 @@ void TestGroup::testIsRecycled()
Group* group4 = new Group();
group4->setName("group4");
group4->setParent(db->rootGroup());
group4->setParent(db.rootGroup());
db->recycleGroup(group2);
db.recycleGroup(group2);
QVERIFY(!group1->isRecycled());
QVERIFY(group2->isRecycled());
QVERIFY(group3->isRecycled());
QVERIFY(!group4->isRecycled());
db->recycleGroup(group4);
db.recycleGroup(group4);
QVERIFY(group4->isRecycled());
}
@ -1052,12 +1053,12 @@ void TestGroup::testChildrenSort()
void TestGroup::testHierarchy()
{
Group* group1 = new Group();
group1->setName("group1");
Group group1;
group1.setName("group1");
Group* group2 = new Group();
group2->setName("group2");
group2->setParent(group1);
group2->setParent(&group1);
Group* group3 = new Group();
group3->setName("group3");
@ -1085,11 +1086,11 @@ void TestGroup::testHierarchy()
void TestGroup::testApplyGroupIconRecursively()
{
// Create a database with two nested groups with one entry each
Database* database = new Database();
Database database;
Group* subgroup = new Group();
subgroup->setName("Subgroup");
subgroup->setParent(database->rootGroup());
subgroup->setParent(database.rootGroup());
QVERIFY(subgroup);
Group* subsubgroup = new Group();
@ -1108,10 +1109,10 @@ void TestGroup::testApplyGroupIconRecursively()
// Set an icon per number to the root group and apply recursively
// -> all groups and entries have the same icon
const int rootIconNumber = 42;
database->rootGroup()->setIcon(rootIconNumber);
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
database->rootGroup()->applyGroupIconToChildGroups();
database->rootGroup()->applyGroupIconToChildEntries();
database.rootGroup()->setIcon(rootIconNumber);
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
database.rootGroup()->applyGroupIconToChildGroups();
database.rootGroup()->applyGroupIconToChildEntries();
QVERIFY(subgroup->iconNumber() == rootIconNumber);
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
@ -1124,7 +1125,7 @@ void TestGroup::testApplyGroupIconRecursively()
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
subsubgroup->applyGroupIconToChildGroups();
subsubgroup->applyGroupIconToChildEntries();
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
QVERIFY(subgroup->iconNumber() == rootIconNumber);
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
QVERIFY(subsubgroup->iconNumber() == subsubgroupIconNumber);
@ -1135,11 +1136,11 @@ void TestGroup::testApplyGroupIconRecursively()
const QUuid subgroupIconUuid = QUuid::createUuid();
QImage subgroupIcon(16, 16, QImage::Format_RGB32);
subgroupIcon.setPixel(0, 0, qRgb(255, 0, 0));
database->metadata()->addCustomIcon(subgroupIconUuid, subgroupIcon);
database.metadata()->addCustomIcon(subgroupIconUuid, subgroupIcon);
subgroup->setIcon(subgroupIconUuid);
subgroup->applyGroupIconToChildGroups();
subgroup->applyGroupIconToChildEntries();
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
QCOMPARE(subgroup->iconUuid(), subgroupIconUuid);
QCOMPARE(subgroup->icon(), subgroupIcon);
QCOMPARE(subgroupEntry->iconUuid(), subgroupIconUuid);
@ -1150,10 +1151,10 @@ void TestGroup::testApplyGroupIconRecursively()
QCOMPARE(subsubgroupEntry->icon(), subgroupIcon);
// Reset all icons to root icon
database->rootGroup()->setIcon(rootIconNumber);
QVERIFY(database->rootGroup()->iconNumber() == rootIconNumber);
database->rootGroup()->applyGroupIconToChildGroups();
database->rootGroup()->applyGroupIconToChildEntries();
database.rootGroup()->setIcon(rootIconNumber);
QVERIFY(database.rootGroup()->iconNumber() == rootIconNumber);
database.rootGroup()->applyGroupIconToChildGroups();
database.rootGroup()->applyGroupIconToChildEntries();
QVERIFY(subgroup->iconNumber() == rootIconNumber);
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
QVERIFY(subsubgroup->iconNumber() == rootIconNumber);
@ -1161,10 +1162,10 @@ void TestGroup::testApplyGroupIconRecursively()
// Apply only for child groups
const int iconForGroups = 10;
database->rootGroup()->setIcon(iconForGroups);
QVERIFY(database->rootGroup()->iconNumber() == iconForGroups);
database->rootGroup()->applyGroupIconToChildGroups();
QVERIFY(database->rootGroup()->iconNumber() == iconForGroups);
database.rootGroup()->setIcon(iconForGroups);
QVERIFY(database.rootGroup()->iconNumber() == iconForGroups);
database.rootGroup()->applyGroupIconToChildGroups();
QVERIFY(database.rootGroup()->iconNumber() == iconForGroups);
QVERIFY(subgroup->iconNumber() == iconForGroups);
QVERIFY(subgroupEntry->iconNumber() == rootIconNumber);
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
@ -1172,10 +1173,10 @@ void TestGroup::testApplyGroupIconRecursively()
// Apply only for child entries
const int iconForEntries = 20;
database->rootGroup()->setIcon(iconForEntries);
QVERIFY(database->rootGroup()->iconNumber() == iconForEntries);
database->rootGroup()->applyGroupIconToChildEntries();
QVERIFY(database->rootGroup()->iconNumber() == iconForEntries);
database.rootGroup()->setIcon(iconForEntries);
QVERIFY(database.rootGroup()->iconNumber() == iconForEntries);
database.rootGroup()->applyGroupIconToChildEntries();
QVERIFY(database.rootGroup()->iconNumber() == iconForEntries);
QVERIFY(subgroup->iconNumber() == iconForGroups);
QVERIFY(subgroupEntry->iconNumber() == iconForEntries);
QVERIFY(subsubgroup->iconNumber() == iconForGroups);
@ -1184,15 +1185,15 @@ void TestGroup::testApplyGroupIconRecursively()
void TestGroup::testUsernamesRecursive()
{
Database* database = new Database();
Database database;
// Create a subgroup
Group* subgroup = new Group();
subgroup->setName("Subgroup");
subgroup->setParent(database->rootGroup());
subgroup->setParent(database.rootGroup());
// Generate entries in the root group and the subgroup
Entry* rootGroupEntry = database->rootGroup()->addEntryWithPath("Root group entry");
Entry* rootGroupEntry = database.rootGroup()->addEntryWithPath("Root group entry");
rootGroupEntry->setUsername("Name1");
Entry* subgroupEntry = subgroup->addEntryWithPath("Subgroup entry");
@ -1201,7 +1202,7 @@ void TestGroup::testUsernamesRecursive()
Entry* subgroupEntryReusingUsername = subgroup->addEntryWithPath("Another subgroup entry");
subgroupEntryReusingUsername->setUsername("Name2");
QList<QString> usernames = database->rootGroup()->usernamesRecursive();
QList<QString> usernames = database.rootGroup()->usernamesRecursive();
QCOMPARE(usernames.size(), 2);
QVERIFY(usernames.contains("Name1"));
QVERIFY(usernames.contains("Name2"));

View File

@ -49,24 +49,24 @@ QPair<QString, QString>* split1PTextExportKV(QByteArray& line)
return new QPair<QString, QString>(k, v);
}
QJsonArray* read1PasswordTextExport(QFile& f)
QSharedPointer<QJsonArray> read1PasswordTextExport(QFile& f)
{
auto result = new QJsonArray;
auto current = new QJsonObject;
if (!f.open(QIODevice::ReadOnly)) {
qCritical("Unable to open your text export file for reading");
return nullptr;
return {};
}
auto result = QSharedPointer<QJsonArray>::create();
QJsonObject current;
while (!f.atEnd()) {
auto line = f.readLine(1024);
if (line.size() == 1 and line[0] == '\n') {
if (!current->isEmpty()) {
result->append(*current);
if (!current.isEmpty()) {
result->append(current);
}
current = new QJsonObject;
current = QJsonObject();
continue;
}
const auto kv = split1PTextExportKV(line);
@ -95,14 +95,14 @@ QJsonArray* read1PasswordTextExport(QFile& f)
}
}
auto v = lines.join("");
(*current)[k] = v;
current[k] = v;
} else {
(*current)[k] = kv->second;
current[k] = kv->second;
}
delete kv;
}
if (!current->isEmpty()) {
result->append(*current);
if (!current.isEmpty()) {
result->append(current);
}
f.close();
@ -120,10 +120,9 @@ void TestOpVaultReader::initTestCase()
m_password = "freddy";
QFile testData(m_opVaultTextExportPath);
QJsonArray* data = read1PasswordTextExport(testData);
auto data = read1PasswordTextExport(testData);
QVERIFY(data);
QCOMPARE(data->size(), 27);
delete data;
m_categoryMap.insert("001", "Login");
m_categoryMap.insert("002", "Credit Card");
@ -149,9 +148,9 @@ void TestOpVaultReader::testReadIntoDatabase()
{
QDir opVaultDir(m_opVaultPath);
auto reader = new OpVaultReader();
auto db = reader->readDatabase(opVaultDir, m_password);
QVERIFY2(!reader->hasError(), qPrintable(reader->errorString()));
OpVaultReader reader;
QScopedPointer<Database> db(reader.readDatabase(opVaultDir, m_password));
QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
QVERIFY(db);
QVERIFY(!db->children().isEmpty());
@ -179,7 +178,6 @@ void TestOpVaultReader::testReadIntoDatabase()
QUuid u = Tools::hexToUuid(value["uuid"].toString());
objectsByUuid[u] = value;
}
delete testData;
QCOMPARE(objectsByUuid.size(), 27);
for (QUuid u : objectsByUuid.keys()) {
@ -240,11 +238,11 @@ void TestOpVaultReader::testKeyDerivation()
void TestOpVaultReader::testBandEntry1()
{
auto reader = new OpVaultReader();
OpVaultReader reader;
QByteArray json(R"({"hello": "world"})");
QJsonDocument doc = QJsonDocument::fromJson(json);
QJsonObject data;
QByteArray entryKey;
QByteArray entryHmacKey;
QVERIFY(!reader->decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
QVERIFY(!reader.decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
}

View File

@ -79,6 +79,10 @@ static QString dbFileName = QStringLiteral(KEEPASSX_TEST_DATA_DIR).append("/NewD
void TestGui::initTestCase()
{
Application::setApplicationName("KeePassXC");
Application::setApplicationVersion(KEEPASSXC_VERSION);
QApplication::setQuitOnLastWindowClosed(false);
QVERIFY(Crypto::init());
Config::createTempFileInstance();
// Disable autosave so we can test the modified file indicator
@ -91,11 +95,12 @@ void TestGui::initTestCase()
// Disable the update check first time alert
config()->set("UpdateCheckMessageShown", true);
m_mainWindow.reset(new MainWindow());
Bootstrap::restoreMainWindowState(*m_mainWindow);
Bootstrap::bootstrapApplication();
m_mainWindow.reset(new MainWindow());
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
m_mainWindow->show();
m_mainWindow->resize(1024, 768);
}
// Every test starts with opening the temp database
@ -176,7 +181,7 @@ void TestGui::testSettingsDefaultTabOrder()
void TestGui::testCreateDatabase()
{
QTimer::singleShot(0, this, SLOT(createDatabaseCallback()));
QTimer::singleShot(50, this, SLOT(createDatabaseCallback()));
triggerAction("actionDatabaseNew");
// there is a new empty db
@ -1435,8 +1440,9 @@ int TestGui::addCannedEntries()
void TestGui::checkDatabase(QString dbFileName)
{
if (dbFileName.isEmpty())
if (dbFileName.isEmpty()) {
dbFileName = m_dbFilePath;
}
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("a"));