Release 2.4.2

- Improve resilience against memory attacks - overwrite memory before free [#3020]
- Prevent infinite save loop when location is unavailable [#3026]
- Attempt to fix quitting application when shutdown or logout issued [#3199]
- Support merging database custom data [#3002]
- Fix opening URL's with non-http schemes [#3153]
- Fix data loss due to not reading all database attachments if duplicates exist [#3180]
- Fix entry context menu disabling when using keyboard navigation [#3199]
- Fix behaviors when canceling an entry edit [#3199]
- Fix processing of tray icon click and doubleclick [#3112]
- Update group in preview widget when focused [#3199]
- Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996]
- Remove apply button in application settings [#3019]
- Use winqtdeploy on Windows to correct deployment issues [#3025]
- Don't mark entry edit as modified when attribute selection changes [#3041]
- Use console code page CP_UTF8 on Windows if supported [#3050]
- Snap: Fix locking database with session lock [#3046]
- Snap: Fix theming across Linux distributions [#3057]
- Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131]
- KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088]
- macOS: Fix toolbar text when in dark mode [#2998]
- macOS: Lock database on switching user [#3097]
- macOS: Fix global Auto-Type when the database is locked [#3138]
- Browser: Close popups when database is locked [#3093]
- Browser: Add tests [#3016]
- Browser: Don't create default group if custom group is enabled [#3127]
This commit is contained in:
Jonathan White 2019-05-31 16:03:47 -04:00
commit a775031fe9
No known key found for this signature in database
GPG key ID: 440FC65F2E0C6E01
105 changed files with 2299 additions and 1241 deletions

View file

@ -1,3 +1,32 @@
2.4.2 (2019-05-31)
=========================
- Improve resilience against memory attacks - overwrite memory before free [#3020]
- Prevent infinite save loop when location is unavailable [#3026]
- Attempt to fix quitting application when shutdown or logout issued [#3199]
- Support merging database custom data [#3002]
- Fix opening URL's with non-http schemes [#3153]
- Fix data loss due to not reading all database attachments if duplicates exist [#3180]
- Fix entry context menu disabling when using keyboard navigation [#3199]
- Fix behaviors when canceling an entry edit [#3199]
- Fix processing of tray icon click and doubleclick [#3112]
- Update group in preview widget when focused [#3199]
- Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996]
- Remove apply button in application settings [#3019]
- Use winqtdeploy on Windows to correct deployment issues [#3025]
- Don't mark entry edit as modified when attribute selection changes [#3041]
- Use console code page CP_UTF8 on Windows if supported [#3050]
- Snap: Fix locking database with session lock [#3046]
- Snap: Fix theming across Linux distributions [#3057]
- Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131]
- KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088]
- macOS: Fix toolbar text when in dark mode [#2998]
- macOS: Lock database on switching user [#3097]
- macOS: Fix global Auto-Type when the database is locked [#3138]
- Browser: Close popups when database is locked [#3093]
- Browser: Add tests [#3016]
- Browser: Don't create default group if custom group is enabled [#3127]
2.4.1 (2019-04-12) 2.4.1 (2019-04-12)
========================= =========================

View file

@ -20,9 +20,10 @@ project(KeePassXC)
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel." "Choose the type of build, options are: Debug Release RelWithDebInfo Profile"
FORCE) FORCE)
endif() endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
@ -40,22 +41,21 @@ option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF) option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON) option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins") set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins")
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF) option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website icons)." OFF)
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF) option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF) option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF) option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON) option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
if(APPLE) if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif() endif()
if(WITH_XC_ALL) if(WITH_XC_ALL)
# Enable all options # Enable all options (except update check)
set(WITH_XC_AUTOTYPE ON) set(WITH_XC_AUTOTYPE ON)
set(WITH_XC_NETWORKING ON) set(WITH_XC_NETWORKING ON)
set(WITH_XC_BROWSER ON) set(WITH_XC_BROWSER ON)
@ -67,23 +67,21 @@ if(WITH_XC_ALL)
endif() endif()
endif() endif()
if(WITH_XC_KEESHARE_SECURE)
set(WITH_XC_KEESHARE ON)
endif()
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE) if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON) set(WITH_XC_CRYPTO_SSH ON)
else() else()
set(WITH_XC_CRYPTO_SSH OFF) set(WITH_XC_CRYPTO_SSH OFF)
endif() endif()
if(WITH_XC_UPDATECHECK) # Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
set(WITH_XC_NETWORKING ON) if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
set(WITH_XC_UPDATECHECK OFF)
endif() endif()
set(KEEPASSXC_VERSION_MAJOR "2") set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "4") set(KEEPASSXC_VERSION_MINOR "4")
set(KEEPASSXC_VERSION_PATCH "1") set(KEEPASSXC_VERSION_PATCH "2")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}") set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds") set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
@ -162,11 +160,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
set(IS_32BIT TRUE) set(IS_32BIT TRUE)
endif() endif()
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_C_COMPILER}" MATCHES "clang$"
OR "${CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANG 1) set(CMAKE_COMPILER_IS_CLANG 1)
endif() endif()
if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$"
OR "${CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS}" MATCHES "__clang__"
OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANGXX 1) set(CMAKE_COMPILER_IS_CLANGXX 1)
endif() endif()
@ -199,7 +201,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_flags("-fvisibility=hidden")
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden") add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
add_gcc_compiler_flags("-Werror") add_gcc_compiler_flags("-Werror")
endif() endif()
@ -230,7 +232,6 @@ if(WITH_ASAN)
endif() endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2") add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
endif() endif()
@ -264,6 +265,11 @@ endif()
add_gcc_compiler_cflags("-std=c99") add_gcc_compiler_cflags("-std=c99")
add_gcc_compiler_cxxflags("-std=c++11") add_gcc_compiler_cxxflags("-std=c++11")
if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99) OR
(CMAKE_COMPILER_IS_CLANGXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.6.99))
add_gcc_compiler_cxxflags("-fsized-deallocation")
endif()
if(APPLE) if(APPLE)
add_gcc_compiler_cxxflags("-stdlib=libc++") add_gcc_compiler_cxxflags("-stdlib=libc++")
endif() endif()
@ -276,7 +282,7 @@ if(MINGW)
set(CMAKE_RC_COMPILER_INIT windres) set(CMAKE_RC_COMPILER_INIT windres)
enable_language(RC) enable_language(RC)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>") set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) if(NOT (CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo"))
# Enable DEP and ASLR # Enable DEP and ASLR
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
@ -365,10 +371,17 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH TRUE) set(CMAKE_MACOSX_RPATH TRUE)
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH) find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT MACDEPLOYQT_EXE) if(NOT MACDEPLOYQT_EXE)
message(FATAL_ERROR "macdeployqt is required to build in macOS") message(FATAL_ERROR "macdeployqt is required to build on macOS")
else() else()
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}") message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
endif() endif()
elseif(MINGW)
find_program(WINDEPLOYQT_EXE windeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
if(NOT WINDEPLOYQT_EXE)
message(FATAL_ERROR "windeployqt is required to build on Windows")
else()
message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}")
endif()
endif() endif()
# Debian sets the the build type to None for package builds. # Debian sets the the build type to None for package builds.
@ -380,6 +393,7 @@ find_package(Gcrypt 1.7.0 REQUIRED)
find_package(Argon2 REQUIRED) find_package(Argon2 REQUIRED)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(QREncode REQUIRED) find_package(QREncode REQUIRED)
find_package(sodium 1.0.12 REQUIRED)
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
@ -387,20 +401,7 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif() endif()
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
# Optional
if(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE ON)
if(WITH_XC_KEESHARE_SECURE)
# ZLIB is needed and already required
find_package(QuaZip REQUIRED)
include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})
endif()
else()
set(WITH_XC_KEESHARE_INSECURE OFF)
set(WITH_XC_KEESHARE_SECURE OFF)
endif()
# Optional # Optional
if(WITH_XC_YUBIKEY) if(WITH_XC_YUBIKEY)

View file

@ -25,7 +25,7 @@ The following libraries are required:
* zlib * zlib
* libmicrohttpd * libmicrohttpd
* libxi, libxtst, qtx11extras (optional for auto-type on X11) * libxi, libxtst, qtx11extras (optional for auto-type on X11)
* libsodium (>= 1.0.12, optional for KeePassXC-Browser support) * libsodium (>= 1.0.12)
* libargon2 * libargon2
Prepare the Building Environment Prepare the Building Environment
@ -97,18 +97,26 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON) -DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF) -DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF) -DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (favicon download) (default: OFF) -DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF) -DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group syncronization extension (default: OFF)
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF) -DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare signed containers, requires libquazip5 (default: OFF)
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF) -DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare secure containers, requires libquazip5 (default: OFF)
-DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON)
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON) -DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF) -DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF) -DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF) -DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF) -DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON) -DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
-DKEEPASSXC_BUILD_TYPE=[Snapshot|PreRelease|Release] Set the build type to show/hide stability warnings (default: "Snapshot")
-DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Other] Specify the distribution method (default: "Other")
-DOVERRIDE_VERSION=[X.X.X] Specify a version number when building. Used with snapshot builds (default: "")
-DGIT_HEAD_OVERRIDE=[XXXXXXX] Specify the 7 digit git commit ref for this build. Used with distribution builds (default: "")
``` ```
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/` * If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`

View file

@ -112,7 +112,7 @@ mark_as_advanced(
CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"

View file

@ -1,41 +1,24 @@
# QUAZIP_FOUND - QuaZip library was found # QUAZIP_FOUND - QuaZip library was found
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir # QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR) # QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
# QUAZIP_LIBRARIES - List of QuaZip libraries # QUAZIP_LIBRARIES - List of QuaZip libraries
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers # QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) if(MINGW)
# in cache already find_library(QUAZIP_LIBRARIES libquazip5)
SET(QUAZIP_FOUND TRUE) find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES) find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
IF(Qt5Core_FOUND) else()
set(QUAZIP_LIB_VERSION_SUFFIX 5) find_library(QUAZIP_LIBRARIES
ENDIF() NAMES quazip5 quazip
IF(WIN32) PATHS /usr/lib /usr/lib64 /usr/local/lib
FIND_PATH(QUAZIP_LIBRARY_DIR
WIN32_DEBUG_POSTFIX d
NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll
HINTS "C:/Programme/" "C:/Program Files"
PATH_SUFFIXES QuaZip/lib
) )
FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR}) find_path(QUAZIP_INCLUDE_DIR quazip.h
FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5) PATHS /usr/include /usr/local/include
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h) PATH_SUFFIXES quazip5 quazip
ELSE(WIN32)
FIND_PACKAGE(PkgConfig)
pkg_check_modules(PC_QUAZIP quazip)
FIND_LIBRARY(QUAZIP_LIBRARIES
WIN32_DEBUG_POSTFIX d
NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX}
HINTS /usr/lib /usr/lib64
) )
FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
HINTS /usr/include /usr/local/include endif()
PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX} include(FindPackageHandleStandardArgs)
) set(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include) find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
ENDIF(WIN32)
INCLUDE(FindPackageHandleStandardArgs)
SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
ENDIF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)

View file

@ -50,6 +50,37 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.4.2" date="2019-05-31">
<description>
<ul>
<li>Improve resilience against memory attacks - overwrite memory before free [#3020]</li>
<li>Prevent infinite save loop when location is unavailable [#3026]</li>
<li>Attempt to fix quitting application when shutdown or logout issued [#3199]</li>
<li>Support merging database custom data [#3002]</li>
<li>Fix opening URL's with non-http schemes [#3153]</li>
<li>Fix data loss due to not reading all database attachments if duplicates exist [#3180]</li>
<li>Fix entry context menu disabling when using keyboard navigation [#3199]</li>
<li>Fix behaviors when canceling an entry edit [#3199]</li>
<li>Fix processing of tray icon click and doubleclick [#3112]</li>
<li>Update group in preview widget when focused [#3199]</li>
<li>Prefer DuckDuckGo service over direct icon download (increases resolution) [#2996]</li>
<li>Remove apply button in application settings [#3019]</li>
<li>Use winqtdeploy on Windows to correct deployment issues [#3025]</li>
<li>Don't mark entry edit as modified when attribute selection changes [#3041]</li>
<li>Use console code page CP_UTF8 on Windows if supported [#3050]</li>
<li>Snap: Fix locking database with session lock [#3046]</li>
<li>Snap: Fix theming across Linux distributions [#3057]</li>
<li>Snap: Use SNAP_USER_COMMON and SNAP_USER_DATA directories [#3131]</li>
<li>KeeShare: Automatically enable WITH_XC_KEESHARE_SECURE if quazip is found [#3088]</li>
<li>macOS: Fix toolbar text when in dark mode [#2998]</li>
<li>macOS: Lock database on switching user [#3097]</li>
<li>macOS: Fix global Auto-Type when the database is locked [#3138]</li>
<li>Browser: Close popups when database is locked [#3093]</li>
<li>Browser: Add tests [#3016]</li>
<li>Browser: Don't create default group if custom group is enabled [#3127]</li>
</ul>
</description>
</release>
<release version="2.4.1" date="2019-04-12"> <release version="2.4.1" date="2019-04-12">
<description> <description>
<ul> <ul>

View file

@ -1531,7 +1531,7 @@ Möchten Sie Ihre Änderungen zusammenführen?</translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Do you really want to delete %n entry(s) for good?</source> <source>Do you really want to delete %n entry(s) for good?</source>
<translation><numerusform>Sollen tatsächlich %1 Einträge gelöscht werden?</numerusform><numerusform>Sollen tatsächlich %1 Einträge gelöscht werden?</numerusform></translation> <translation><numerusform>Sollen tatsächlich %n Einträge gelöscht werden?</numerusform><numerusform>Sollen tatsächlich %n Einträge gelöscht werden?</numerusform></translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Delete entry(s)?</source> <source>Delete entry(s)?</source>

View file

@ -326,8 +326,8 @@
<translation>Privacy</translation> <translation>Privacy</translation>
</message> </message>
<message> <message>
<source>Use DuckDuckGo as fallback for downloading website icons</source> <source>Use DuckDuckGo service to download website icons</source>
<translation>Use DuckDuckGo as fallback for downloading website icons</translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>
@ -631,6 +631,14 @@ Please select the correct database for saving credentials.</translation>
<source>&amp;Brave</source> <source>&amp;Brave</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Returns expired credentials. String [expired] is added to the title.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Allow returning expired credentials.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>BrowserService</name> <name>BrowserService</name>
@ -2244,10 +2252,6 @@ Supported extensions are: %1.</source>
<source>Custom icon successfully downloaded</source> <source>Custom icon successfully downloaded</source>
<translation>Custom icon successfully downloaded</translation> <translation>Custom icon successfully downloaded</translation>
</message> </message>
<message>
<source>Hint: You can enable DuckDuckGo as a fallback under Tools&gt;Settings&gt;Security</source>
<translation>Hint: You can enable DuckDuckGo as a fallback under Tools&gt;Settings&gt;Security</translation>
</message>
<message> <message>
<source>Select Image(s)</source> <source>Select Image(s)</source>
<translation>Select Image(s)</translation> <translation>Select Image(s)</translation>
@ -2284,6 +2288,10 @@ Supported extensions are: %1.</source>
<numerusform>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</numerusform> <numerusform>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</numerusform>
</translation> </translation>
</message> </message>
<message>
<source>You can enable the DuckDuckGo website icon service under Tools -&gt; Settings -&gt; Security</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>EditWidgetProperties</name> <name>EditWidgetProperties</name>
@ -3750,6 +3758,14 @@ Expect some bugs and minor issues, this version is not meant for production use.
<source>Adding missing icon %1</source> <source>Adding missing icon %1</source>
<translation>Adding missing icon %1</translation> <translation>Adding missing icon %1</translation>
</message> </message>
<message>
<source>Removed custom data %1 [%2]</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Adding custom data %1 [%2]</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>NewDatabaseWizard</name> <name>NewDatabaseWizard</name>

View file

@ -200,27 +200,27 @@
</message> </message>
<message> <message>
<source>Auto-Type</source> <source>Auto-Type</source>
<translation>Auto-Escritura</translation> <translation>Autoescritura</translation>
</message> </message>
<message> <message>
<source>Use entry title to match windows for global Auto-Type</source> <source>Use entry title to match windows for global Auto-Type</source>
<translation>Use título de entrada para acertar ventanas en Auto-Escritura global.</translation> <translation>Usar título de la entrada para emparejar ventanas en autoescritura global</translation>
</message> </message>
<message> <message>
<source>Use entry URL to match windows for global Auto-Type</source> <source>Use entry URL to match windows for global Auto-Type</source>
<translation>Use URL para acertar ventanas en Auto-Escritura global</translation> <translation>Usar URL de la entrada para emparejar ventanas en autoescritura global</translation>
</message> </message>
<message> <message>
<source>Always ask before performing Auto-Type</source> <source>Always ask before performing Auto-Type</source>
<translation>Siempre preguntar antes de hacer Auto-Escritura</translation> <translation>Siempre preguntar antes de hacer autoescritura</translation>
</message> </message>
<message> <message>
<source>Global Auto-Type shortcut</source> <source>Global Auto-Type shortcut</source>
<translation>Atajo global de Auto-Escritura</translation> <translation>Atajo global de autoescritura</translation>
</message> </message>
<message> <message>
<source>Auto-Type typing delay</source> <source>Auto-Type typing delay</source>
<translation>Escribiendo retardo de la Auto-Escritura</translation> <translation>Escribiendo retardo de la autoescritura</translation>
</message> </message>
<message> <message>
<source> ms</source> <source> ms</source>
@ -229,7 +229,7 @@
</message> </message>
<message> <message>
<source>Auto-Type start delay</source> <source>Auto-Type start delay</source>
<translation>Iniciar retardo de Auto-Escritura</translation> <translation>Iniciar retardo de autoescritura</translation>
</message> </message>
<message> <message>
<source>Check for updates at application startup</source> <source>Check for updates at application startup</source>
@ -293,7 +293,7 @@
</message> </message>
<message> <message>
<source>Re-lock previously locked database after performing Auto-Type</source> <source>Re-lock previously locked database after performing Auto-Type</source>
<translation>Volver a bloquear la base de datos tras realizar una Auto-Escritura </translation> <translation>Volver a bloquear la base de datos tras realizar una autoescritura</translation>
</message> </message>
<message> <message>
<source>Don&apos;t require password repeat when it is visible</source> <source>Don&apos;t require password repeat when it is visible</source>
@ -332,27 +332,27 @@
</message> </message>
<message> <message>
<source>Auto-Type - KeePassXC</source> <source>Auto-Type - KeePassXC</source>
<translation>Auto-Escritura - KeePassXC</translation> <translation>Autoescritura - KeePassXC</translation>
</message> </message>
<message> <message>
<source>Auto-Type</source> <source>Auto-Type</source>
<translation>Auto-Escritura</translation> <translation>Autoescritura</translation>
</message> </message>
<message> <message>
<source>The Syntax of your Auto-Type statement is incorrect!</source> <source>The Syntax of your Auto-Type statement is incorrect!</source>
<translation>¡La sintaxis de la declaración de su Auto-Escritura es incorrecta!</translation> <translation>¡La sintaxis de la declaración de su autoescritura es incorrecta!</translation>
</message> </message>
<message> <message>
<source>This Auto-Type command contains a very long delay. Do you really want to proceed?</source> <source>This Auto-Type command contains a very long delay. Do you really want to proceed?</source>
<translation>Este comando de Auto-Escritura contiene un retraso muy largo. ¿Realmente desea continuar?</translation> <translation>Este comando de autoescritura contiene un retraso muy largo. ¿Realmente desea continuar?</translation>
</message> </message>
<message> <message>
<source>This Auto-Type command contains very slow key presses. Do you really want to proceed?</source> <source>This Auto-Type command contains very slow key presses. Do you really want to proceed?</source>
<translation>Este comando de Auto-Escritura contiene pulsaciones de teclas muy lentas. ¿Realmente desea continuar?</translation> <translation>Este comando de autoescritura contiene pulsaciones de teclas muy lentas. ¿Realmente desea continuar?</translation>
</message> </message>
<message> <message>
<source>This Auto-Type command contains arguments which are repeated very often. Do you really want to proceed?</source> <source>This Auto-Type command contains arguments which are repeated very often. Do you really want to proceed?</source>
<translation>Este comando de Auto-Escritura contiene argumentos que se repiten muy a menudo. ¿Realmente desea continuar?</translation> <translation>Este comando de autoescritura contiene argumentos que se repiten muy a menudo. ¿Realmente desea continuar?</translation>
</message> </message>
</context> </context>
<context> <context>
@ -393,11 +393,11 @@
<name>AutoTypeSelectDialog</name> <name>AutoTypeSelectDialog</name>
<message> <message>
<source>Auto-Type - KeePassXC</source> <source>Auto-Type - KeePassXC</source>
<translation>Auto-Escritura - KeePassXC</translation> <translation>Autoescritura - KeePassXC</translation>
</message> </message>
<message> <message>
<source>Select entry to Auto-Type:</source> <source>Select entry to Auto-Type:</source>
<translation>Seleccionar entrada para Auto-Escritura:</translation> <translation>Seleccionar entrada para autoescritura:</translation>
</message> </message>
</context> </context>
<context> <context>
@ -884,7 +884,7 @@ Es necesario para mantener sus conexiones presentes del navegador.
<name>DatabaseOpenDialog</name> <name>DatabaseOpenDialog</name>
<message> <message>
<source>Unlock Database - KeePassXC</source> <source>Unlock Database - KeePassXC</source>
<translation>Desbloquear Base de Datos - KeePassXC</translation> <translation>Desbloquear base de datos - KeePassXC</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1259,7 +1259,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
<name>DatabaseSettingsWidgetGeneral</name> <name>DatabaseSettingsWidgetGeneral</name>
<message> <message>
<source>Database Meta Data</source> <source>Database Meta Data</source>
<translation>Metadatos de la Base de Datos</translation> <translation>Metadatos de la base de datos</translation>
</message> </message>
<message> <message>
<source>Database name:</source> <source>Database name:</source>
@ -1295,7 +1295,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
</message> </message>
<message> <message>
<source>Additional Database Settings</source> <source>Additional Database Settings</source>
<translation>Configuraciones Adicionales de la Base de Datos</translation> <translation>Configuraciones adicionales de la base de datos</translation>
</message> </message>
<message> <message>
<source>Enable &amp;compression (recommended)</source> <source>Enable &amp;compression (recommended)</source>
@ -1438,12 +1438,12 @@ Esto es definitivamente un error, por favor repórtelo a los desarrolladores.</t
</message> </message>
<message> <message>
<source>New Database</source> <source>New Database</source>
<translation>Nueva Base de datos</translation> <translation>Nueva base de datos</translation>
</message> </message>
<message> <message>
<source>%1 [New Database]</source> <source>%1 [New Database]</source>
<comment>Database tab name modifier</comment> <comment>Database tab name modifier</comment>
<translation>%1 [Nueva Base de Datos]</translation> <translation>%1 [Nueva base de datos]</translation>
</message> </message>
<message> <message>
<source>%1 [Locked]</source> <source>%1 [Locked]</source>
@ -1549,7 +1549,7 @@ Do you want to merge your changes?</source>
</message> </message>
<message> <message>
<source>Lock Database?</source> <source>Lock Database?</source>
<translation>¿Bloquear la Base de datos?</translation> <translation>¿Bloquear la base de datos?</translation>
</message> </message>
<message> <message>
<source>You are editing an entry. Discard changes and lock anyway?</source> <source>You are editing an entry. Discard changes and lock anyway?</source>
@ -1654,7 +1654,7 @@ Disable safe saves and try again?</source>
</message> </message>
<message> <message>
<source>Auto-Type</source> <source>Auto-Type</source>
<translation>Auto-Escritura</translation> <translation>Autoescritura</translation>
</message> </message>
<message> <message>
<source>Properties</source> <source>Properties</source>
@ -1800,15 +1800,15 @@ Disable safe saves and try again?</source>
<name>EditEntryWidgetAutoType</name> <name>EditEntryWidgetAutoType</name>
<message> <message>
<source>Enable Auto-Type for this entry</source> <source>Enable Auto-Type for this entry</source>
<translation>Activar Auto-Escritura para esta entrada</translation> <translation>Activar autoescritura para esta entrada</translation>
</message> </message>
<message> <message>
<source>Inherit default Auto-Type sequence from the &amp;group</source> <source>Inherit default Auto-Type sequence from the &amp;group</source>
<translation>Heredar la secuencia de Auto-Escritura por defecto del &amp;grupo</translation> <translation>Heredar la secuencia de autoescritura por defecto del &amp;grupo</translation>
</message> </message>
<message> <message>
<source>&amp;Use custom Auto-Type sequence:</source> <source>&amp;Use custom Auto-Type sequence:</source>
<translation>&amp;Usar secuencia de Auto-Escritura personalizada:</translation> <translation>&amp;Usar secuencia de autoescritura personalizada:</translation>
</message> </message>
<message> <message>
<source>Window Associations</source> <source>Window Associations</source>
@ -2111,15 +2111,15 @@ Disable safe saves and try again?</source>
</message> </message>
<message> <message>
<source>Auto-Type</source> <source>Auto-Type</source>
<translation>Auto-Escritura</translation> <translation>Autoescritura</translation>
</message> </message>
<message> <message>
<source>&amp;Use default Auto-Type sequence of parent group</source> <source>&amp;Use default Auto-Type sequence of parent group</source>
<translation>&amp;Usar por defecto la secuencia de Auto-Escritura del grupo padre</translation> <translation>&amp;Usar por defecto la secuencia de autoescritura del grupo padre</translation>
</message> </message>
<message> <message>
<source>Set default Auto-Type se&amp;quence</source> <source>Set default Auto-Type se&amp;quence</source>
<translation>Seleccionar se&amp;cuencia de Auto-Escritura por defecto</translation> <translation>Seleccionar se&amp;cuencia de autoescritura por defecto</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2932,7 +2932,7 @@ Esta migración es en único sentido. No podrá abrir la base de datos importada
</message> </message>
<message> <message>
<source>Auto-type association window or sequence missing</source> <source>Auto-type association window or sequence missing</source>
<translation>Falta de secuencia o ventana de asociación de Auto-Escritura</translation> <translation>Falta de secuencia o ventana de asociación de autoescritura</translation>
</message> </message>
<message> <message>
<source>Invalid bool value</source> <source>Invalid bool value</source>
@ -3522,7 +3522,7 @@ Le recomendamos que utilice la AppImage disponible en nuestra página de descarg
</message> </message>
<message> <message>
<source>Perform &amp;Auto-Type</source> <source>Perform &amp;Auto-Type</source>
<translation>Realizar &amp;Auto-Escritura</translation> <translation>Realizar &amp;autoescritura</translation>
</message> </message>
<message> <message>
<source>Open &amp;URL</source> <source>Open &amp;URL</source>
@ -4857,7 +4857,7 @@ Comandos disponibles:
</message> </message>
<message> <message>
<source>Database password: </source> <source>Database password: </source>
<translation>Contraseña de la Base de Datos:</translation> <translation>Contraseña de la base de datos:</translation>
</message> </message>
<message> <message>
<source>Cannot create new group</source> <source>Cannot create new group</source>

File diff suppressed because it is too large Load diff

View file

@ -611,7 +611,7 @@ Veuillez sélectionner la base de donnée souhaitée pour enregistrer les identi
</message> </message>
<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> <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>
<translation type="unfinished"/> <translation>À cause du mécanisme de sandboxing Snap, vous devez lancer un script pour activer l&apos;intégration du navigateur.&lt;br /&gt;Vous pouvez obtenir ce script depuis %1</translation>
</message> </message>
<message> <message>
<source>Please see special instructions for browser extension use below</source> <source>Please see special instructions for browser extension use below</source>
@ -710,7 +710,7 @@ Voulez-vous créer ce groupe ?
<source>Your KeePassXC-Browser settings need to be moved into the database settings. <source>Your KeePassXC-Browser settings need to be moved into the database settings.
This is necessary to maintain your current browser connections. This is necessary to maintain your current browser connections.
Would you like to migrate your existing settings now?</source> Would you like to migrate your existing settings now?</source>
<translation type="unfinished"/> <translation>Vos réglages pour KeePassXC-Browser doivent être intégrés dans les réglages de la base de données. Ceci est nécessaire pour maintenir vos connexions actuelles avec le navigateur ouvertes. Souhaitez-vous effectuer la migration de vos réglages maintenant ?</translation>
</message> </message>
</context> </context>
<context> <context>
@ -874,7 +874,7 @@ Would you like to migrate your existing settings now?</source>
</message> </message>
<message> <message>
<source>Key not transformed. This is a bug, please report it to the developers!</source> <source>Key not transformed. This is a bug, please report it to the developers!</source>
<translation type="unfinished"/> <translation>La clé n&apos;a pas é transformée. Ceci est un bogue, pouvez-vous s&apos;il vous plaît le signaler aux développeurs ?</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1071,7 +1071,7 @@ Cela peut empêcher la connexion avec l&apos;extension de navigateur.</translati
<message> <message>
<source>Do you really want forget all site-specific settings on every entry? <source>Do you really want forget all site-specific settings on every entry?
Permissions to access entries will be revoked.</source> Permissions to access entries will be revoked.</source>
<translation type="unfinished"/> <translation>Êtes-vous sûr de vouloir effacer les préférences de site pour toutes les entrées ? Les permissions d&apos;accès aux entrées seront révoquées.</translation>
</message> </message>
<message> <message>
<source>Removing stored permissions</source> <source>Removing stored permissions</source>
@ -1099,12 +1099,13 @@ Permissions to access entries will be revoked.</source>
</message> </message>
<message> <message>
<source>Move KeePassHTTP attributes to custom data</source> <source>Move KeePassHTTP attributes to custom data</source>
<translation type="unfinished"/> <translation>Déplacer les attributs KeePassHTTP vers les données personnalisées</translation>
</message> </message>
<message> <message>
<source>Do you really want to move all legacy browser integration data to the latest standard? <source>Do you really want to move all legacy browser integration data to the latest standard?
This is necessary to maintain compatibility with the browser plugin.</source> This is necessary to maintain compatibility with the browser plugin.</source>
<translation type="unfinished"/> <translation>Voulez-vous convertir toutes les anciennes données d&apos;intégration au navigateur en version plus récente ?
Ceci est nécessaire pour assurer la compatibilité de l&apos;extension.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1528,7 +1529,7 @@ Voulez-vous fusionner vos changements?</translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Do you really want to delete %n entry(s) for good?</source> <source>Do you really want to delete %n entry(s) for good?</source>
<translation><numerusform>Voulez-vous vraiment supprimer définitivement%1 entrée?</numerusform><numerusform>Voulez-vous vraiment supprimer définitivement%1 entrées?</numerusform></translation> <translation><numerusform>Voulez-vous vraiment supprimer définitivement%n entrée?</numerusform><numerusform>Voulez-vous vraiment supprimer définitivement%n entrées?</numerusform></translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Delete entry(s)?</source> <source>Delete entry(s)?</source>
@ -3463,7 +3464,7 @@ Nous recommandons l&apos;utilisation de l&apos;AppImage disponible sur notre pag
</message> </message>
<message> <message>
<source>&amp;New database...</source> <source>&amp;New database...</source>
<translation>&amp;Ńouvelle base de données...</translation> <translation>&amp;Nouvelle base de données...</translation>
</message> </message>
<message> <message>
<source>Create a new database</source> <source>Create a new database</source>
@ -4513,7 +4514,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Multi-word extra bits %1</source> <source>Multi-word extra bits %1</source>
<translation type="unfinished"/> <translation>Octets additionnels mots multiples %1</translation>
</message> </message>
<message> <message>
<source>Type: Bruteforce</source> <source>Type: Bruteforce</source>
@ -4561,7 +4562,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Type: Dict+Leet(Rep)</source> <source>Type: Dict+Leet(Rep)</source>
<translation type="unfinished"/> <translation>Type : Dictionnaire + Leet (rep)</translation>
</message> </message>
<message> <message>
<source>Type: User Words(Rep)</source> <source>Type: User Words(Rep)</source>
@ -4569,7 +4570,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Type: User+Leet(Rep)</source> <source>Type: User+Leet(Rep)</source>
<translation type="unfinished"/> <translation>Type : Utilisateur + Leet (rep)</translation>
</message> </message>
<message> <message>
<source>Type: Repeated(Rep)</source> <source>Type: Repeated(Rep)</source>
@ -4597,7 +4598,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>*** Password length (%1) != sum of length of parts (%2) ***</source> <source>*** Password length (%1) != sum of length of parts (%2) ***</source>
<translation type="unfinished"/> <translation>*** Longueur du mot de passe (%1) != longueurs additionnées des morceaux (%2) ***</translation>
</message> </message>
<message> <message>
<source>Failed to load key file %1: %2</source> <source>Failed to load key file %1: %2</source>
@ -4933,11 +4934,11 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Search terms are as follows: [modifiers][field:][&quot;]term[&quot;]</source> <source>Search terms are as follows: [modifiers][field:][&quot;]term[&quot;]</source>
<translation type="unfinished"/> <translation>Les termes de recherche sont construits comme suit : [modificateurs][champ:][&quot;]terme[&quot;]</translation>
</message> </message>
<message> <message>
<source>Every search term must match (ie, logical AND)</source> <source>Every search term must match (ie, logical AND)</source>
<translation type="unfinished"/> <translation>Tous les termes doivent correspondre (ET logique)</translation>
</message> </message>
<message> <message>
<source>Modifiers</source> <source>Modifiers</source>
@ -4961,15 +4962,15 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Term Wildcards</source> <source>Term Wildcards</source>
<translation type="unfinished"/> <translation>Caractères spéciaux</translation>
</message> </message>
<message> <message>
<source>match anything</source> <source>match anything</source>
<translation type="unfinished"/> <translation>correspond à n&apos;importe quel caractère</translation>
</message> </message>
<message> <message>
<source>match one</source> <source>match one</source>
<translation type="unfinished"/> <translation>correspond à un seul caractère</translation>
</message> </message>
<message> <message>
<source>logical OR</source> <source>logical OR</source>
@ -5105,7 +5106,7 @@ Commandes disponibles :
<message> <message>
<source>key.share</source> <source>key.share</source>
<comment>Filetype for KeeShare key</comment> <comment>Filetype for KeeShare key</comment>
<translation type="unfinished"/> <translation>cle.share</translation>
</message> </message>
<message> <message>
<source>KeeShare key file</source> <source>KeeShare key file</source>
@ -5121,15 +5122,15 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Exporting changed certificate</source> <source>Exporting changed certificate</source>
<translation type="unfinished"/> <translation>Exportation des certificats modifiés</translation>
</message> </message>
<message> <message>
<source>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</source> <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>Le certificat exporté est différent de celui en cours d&apos;utilisation. Voulez-vous exporter le certificat actuel ?</translation>
</message> </message>
<message> <message>
<source>Signer:</source> <source>Signer:</source>
<translation type="unfinished"/> <translation>Signataire :</translation>
</message> </message>
</context> </context>
<context> <context>
@ -5140,7 +5141,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?</source> <source>We cannot verify the source of the shared container because it is not signed. Do you really want to import from %1?</source>
<translation type="unfinished"/> <translation>Nous ne pouvons vérifier la source du conteneur partagé car celui-ci n&apos;est pas signé. Êtes-vous sûr de vouloir importer depuis %1 ?</translation>
</message> </message>
<message> <message>
<source>Import from container with certificate</source> <source>Import from container with certificate</source>
@ -5176,7 +5177,7 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Signed share container are not supported - import prevented</source> <source>Signed share container are not supported - import prevented</source>
<translation type="unfinished"/> <translation>Conteneur de partage signé non pris en charge - importation annulée</translation>
</message> </message>
<message> <message>
<source>File is not readable</source> <source>File is not readable</source>
@ -5184,15 +5185,15 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Invalid sharing container</source> <source>Invalid sharing container</source>
<translation type="unfinished"/> <translation>Conteneur de partage invalide</translation>
</message> </message>
<message> <message>
<source>Untrusted import prevented</source> <source>Untrusted import prevented</source>
<translation type="unfinished"/> <translation>Importation non sécurisée annulée</translation>
</message> </message>
<message> <message>
<source>Successful signed import</source> <source>Successful signed import</source>
<translation type="unfinished"/> <translation>Importation signée réussie</translation>
</message> </message>
<message> <message>
<source>Unexpected error</source> <source>Unexpected error</source>
@ -5200,11 +5201,11 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Unsigned share container are not supported - import prevented</source> <source>Unsigned share container are not supported - import prevented</source>
<translation type="unfinished"/> <translation>Conteneur de partage non signé non pris en charge - importation annulée</translation>
</message> </message>
<message> <message>
<source>Successful unsigned import</source> <source>Successful unsigned import</source>
<translation type="unfinished"/> <translation>Importation non signée réussie</translation>
</message> </message>
<message> <message>
<source>File does not exist</source> <source>File does not exist</source>
@ -5212,27 +5213,27 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Unknown share container type</source> <source>Unknown share container type</source>
<translation type="unfinished"/> <translation>Type de conteneur de partage non reconnu</translation>
</message> </message>
<message> <message>
<source>Overwriting signed share container is not supported - export prevented</source> <source>Overwriting signed share container is not supported - export prevented</source>
<translation type="unfinished"/> <translation>Remplacement de conteneur de partage signé non pris en charge - exportation annulée</translation>
</message> </message>
<message> <message>
<source>Could not write export container (%1)</source> <source>Could not write export container (%1)</source>
<translation type="unfinished"/> <translation>Impossible d&apos;exporter le conteneur (%1)</translation>
</message> </message>
<message> <message>
<source>Overwriting unsigned share container is not supported - export prevented</source> <source>Overwriting unsigned share container is not supported - export prevented</source>
<translation type="unfinished"/> <translation>Remplacement de conteneur non signé non pris en charge - exportation annulée</translation>
</message> </message>
<message> <message>
<source>Could not write export container</source> <source>Could not write export container</source>
<translation type="unfinished"/> <translation>Impossible d&apos;exporter le conteneur</translation>
</message> </message>
<message> <message>
<source>Unexpected export error occurred</source> <source>Unexpected export error occurred</source>
<translation type="unfinished"/> <translation>Une erreur inattendue est survenue lors de l&apos;exportation</translation>
</message> </message>
<message> <message>
<source>Export to %1 failed (%2)</source> <source>Export to %1 failed (%2)</source>
@ -5248,31 +5249,31 @@ Commandes disponibles :
</message> </message>
<message> <message>
<source>Do you want to trust %1 with the fingerprint of %2 from %3?</source> <source>Do you want to trust %1 with the fingerprint of %2 from %3?</source>
<translation type="unfinished"/> <translation>Voulez-vous autoriser %1 avec l&apos;empreinte de %2 à %3 ? {1 ?} {2 ?}</translation>
</message> </message>
<message> <message>
<source>Multiple import source path to %1 in %2</source> <source>Multiple import source path to %1 in %2</source>
<translation type="unfinished"/> <translation>Chemin source d&apos;importation multiple de %1 dans %2</translation>
</message> </message>
<message> <message>
<source>Conflicting export target path %1 in %2</source> <source>Conflicting export target path %1 in %2</source>
<translation type="unfinished"/> <translation>Conflit du chemin cible d&apos;exportation %1 dans %2</translation>
</message> </message>
<message> <message>
<source>Could not embed signature: Could not open file to write (%1)</source> <source>Could not embed signature: Could not open file to write (%1)</source>
<translation type="unfinished"/> <translation>Impossible d&apos;intégrer la signature : le fichier (%1) n&apos;a pas pu être ouvert en écriture</translation>
</message> </message>
<message> <message>
<source>Could not embed signature: Could not write file (%1)</source> <source>Could not embed signature: Could not write file (%1)</source>
<translation type="unfinished"/> <translation>Impossible d&apos;intégrer la signature : problème d&apos;écriture dans le fichier (%1)</translation>
</message> </message>
<message> <message>
<source>Could not embed database: Could not open file to write (%1)</source> <source>Could not embed database: Could not open file to write (%1)</source>
<translation type="unfinished"/> <translation>Impossible d&apos;intégrer la base de données : le fichier (%1) n&apos;a pas pu être ouvert en écriture</translation>
</message> </message>
<message> <message>
<source>Could not embed database: Could not write file (%1)</source> <source>Could not embed database: Could not write file (%1)</source>
<translation type="unfinished"/> <translation>Impossible d&apos;intégrer la base de données : problème d&apos;écriture dans le fichier (%1)</translation>
</message> </message>
</context> </context>
<context> <context>

View file

@ -54,7 +54,7 @@
</message> </message>
<message> <message>
<source>Use OpenSSH for Windows instead of Pageant</source> <source>Use OpenSSH for Windows instead of Pageant</source>
<translation type="unfinished"/> <translation>Usa OpenSSH per Windows al posto di Pageant</translation>
</message> </message>
</context> </context>
<context> <context>
@ -442,7 +442,8 @@ Seleziona se vuoi consentire l&apos;accesso.</translation>
<message> <message>
<source>You have multiple databases open. <source>You have multiple databases open.
Please select the correct database for saving credentials.</source> Please select the correct database for saving credentials.</source>
<translation type="unfinished"/> <translation>C&apos;è più di un database aperto
Selezionare il database corretto dove salvare le credenziali</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1341,7 +1342,9 @@ Se continui con questo numero, il tuo database potrebbe essere decifrato molto f
<source>WARNING! You have not set a password. Using a database without a password is strongly discouraged! <source>WARNING! You have not set a password. Using a database without a password is strongly discouraged!
Are you sure you want to continue without a password?</source> Are you sure you want to continue without a password?</source>
<translation type="unfinished"/> <translation>ATTENZIONE! Non è stata impostata una password. Utilizzare un database senza password è fortemente sconsigliato!
Siete sicuri di voler continuare senza password?</translation>
</message> </message>
<message> <message>
<source>Unknown error</source> <source>Unknown error</source>
@ -1547,7 +1550,8 @@ Vuoi salvare le modifiche?</translation>
<message> <message>
<source>Database was modified. <source>Database was modified.
Save changes?</source> Save changes?</source>
<translation type="unfinished"/> <translation>Il database è stato modificato.
Salvare le modifiche?</translation>
</message> </message>
<message> <message>
<source>Save changes?</source> <source>Save changes?</source>
@ -3532,7 +3536,7 @@ Si consiglia di utilizzare l&apos;AppImage disponibile sulla nostra pagina di do
</message> </message>
<message> <message>
<source>Check for Updates...</source> <source>Check for Updates...</source>
<translation type="unfinished"/> <translation>Controllo aggiornamenti...</translation>
</message> </message>
<message> <message>
<source>Share entry</source> <source>Share entry</source>
@ -3545,15 +3549,15 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Check for updates on startup?</source> <source>Check for updates on startup?</source>
<translation type="unfinished"/> <translation>Controllare gli aggiornamenti all&apos;avvio?</translation>
</message> </message>
<message> <message>
<source>Would you like KeePassXC to check for updates on startup?</source> <source>Would you like KeePassXC to check for updates on startup?</source>
<translation type="unfinished"/> <translation>Volete che KeePassXC controlli eventuali aggiornamenti all&apos;avvio?</translation>
</message> </message>
<message> <message>
<source>You can always check for updates manually from the application menu.</source> <source>You can always check for updates manually from the application menu.</source>
<translation type="unfinished"/> <translation>È sempre possibile controllare gli aggiornamenti manualmente tramite i menu dell&apos;applicazione.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3619,7 +3623,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
<name>NewDatabaseWizard</name> <name>NewDatabaseWizard</name>
<message> <message>
<source>Create a new KeePassXC database...</source> <source>Create a new KeePassXC database...</source>
<translation type="unfinished"/> <translation>Creazione di un nuovo database KeePassXC...</translation>
</message> </message>
<message> <message>
<source>Root</source> <source>Root</source>
@ -3639,7 +3643,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source> <source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source>
<translation type="unfinished"/> <translation>Qui è possibile modificare le impostazioni di crittaggio del database. È sempre possibile modificarli dopo nelle impostazioni del database.</translation>
</message> </message>
<message> <message>
<source>Advanced Settings</source> <source>Advanced Settings</source>
@ -3658,7 +3662,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source> <source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source>
<translation type="unfinished"/> <translation>Qui è possibile modificare le impostazioni di crittaggio del database. È sempre possibile modificarli dopo nelle impostazioni del database.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3669,7 +3673,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>A master key known only to you protects your database.</source> <source>A master key known only to you protects your database.</source>
<translation type="unfinished"/> <translation>Una password principale segreta protegge il vostro database.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3798,7 +3802,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>&lt;p&gt;A password is the primary method for securing your database.&lt;/p&gt;&lt;p&gt;Good passwords are long and unique. KeePassXC can generate one for you.&lt;/p&gt;</source> <source>&lt;p&gt;A password is the primary method for securing your database.&lt;/p&gt;&lt;p&gt;Good passwords are long and unique. KeePassXC can generate one for you.&lt;/p&gt;</source>
<translation type="unfinished"/> <translation>&lt;p&gt;Una password è il metodo principale per mantenere sicuro il vostro database.&lt;/p&gt;&lt;p&gt;Una buona password dev&apos;essere lunga ed unica. KeePassXC può generarne una per voi.&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>Passwords do not match.</source> <source>Passwords do not match.</source>
@ -3958,7 +3962,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>{[(</source> <source>{[(</source>
<translation type="unfinished"/> <translation>{[(</translation>
</message> </message>
<message> <message>
<source>Punctuation</source> <source>Punctuation</source>
@ -3966,7 +3970,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>.,:;</source> <source>.,:;</source>
<translation type="unfinished"/> <translation>.,:;</translation>
</message> </message>
<message> <message>
<source>Quotes</source> <source>Quotes</source>
@ -3974,7 +3978,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>&quot; &apos;</source> <source>&quot; &apos;</source>
<translation type="unfinished"/> <translation>&quot; &apos;</translation>
</message> </message>
<message> <message>
<source>Math</source> <source>Math</source>
@ -3982,7 +3986,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>&lt;*+!?=</source> <source>&lt;*+!?=</source>
<translation type="unfinished"/> <translation>&lt;*+!?=</translation>
</message> </message>
<message> <message>
<source>Dashes</source> <source>Dashes</source>
@ -3990,7 +3994,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>\_|-/</source> <source>\_|-/</source>
<translation type="unfinished"/> <translation>\_|-/</translation>
</message> </message>
<message> <message>
<source>Logograms</source> <source>Logograms</source>
@ -3998,7 +4002,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>#$%&amp;&amp;@^`~</source> <source>#$%&amp;&amp;@^`~</source>
<translation type="unfinished"/> <translation>#$%&amp;&amp;@^`~</translation>
</message> </message>
<message> <message>
<source>Switch to simple mode</source> <source>Switch to simple mode</source>
@ -4041,21 +4045,21 @@ Expect some bugs and minor issues, this version is not meant for production use.
<name>QApplication</name> <name>QApplication</name>
<message> <message>
<source>KeeShare</source> <source>KeeShare</source>
<translation type="unfinished"/> <translation>KeeShare</translation>
</message> </message>
</context> </context>
<context> <context>
<name>QFileDialog</name> <name>QFileDialog</name>
<message> <message>
<source>Select</source> <source>Select</source>
<translation type="unfinished"/> <translation>Seleziona</translation>
</message> </message>
</context> </context>
<context> <context>
<name>QMessageBox</name> <name>QMessageBox</name>
<message> <message>
<source>Overwrite</source> <source>Overwrite</source>
<translation type="unfinished"/> <translation>Sovrascrivi</translation>
</message> </message>
<message> <message>
<source>Delete</source> <source>Delete</source>
@ -4063,11 +4067,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Move</source> <source>Move</source>
<translation type="unfinished"/> <translation>Sposta</translation>
</message> </message>
<message> <message>
<source>Empty</source> <source>Empty</source>
<translation type="unfinished"/> <translation>Vuoto</translation>
</message> </message>
<message> <message>
<source>Remove</source> <source>Remove</source>
@ -4075,7 +4079,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Skip</source> <source>Skip</source>
<translation type="unfinished"/> <translation>Salta</translation>
</message> </message>
<message> <message>
<source>Disable</source> <source>Disable</source>
@ -4083,7 +4087,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Merge</source> <source>Merge</source>
<translation type="unfinished"/> <translation>Incorpora</translation>
</message> </message>
</context> </context>
<context> <context>
@ -4743,19 +4747,19 @@ Comandi disponibili:
</message> </message>
<message> <message>
<source>Create a new database.</source> <source>Create a new database.</source>
<translation type="unfinished"/> <translation>Crea un nuovo database.</translation>
</message> </message>
<message> <message>
<source>File %1 already exists.</source> <source>File %1 already exists.</source>
<translation type="unfinished"/> <translation>Il file %1 esiste già.</translation>
</message> </message>
<message> <message>
<source>Loading the key file failed</source> <source>Loading the key file failed</source>
<translation type="unfinished"/> <translation>Caricamento del key-file fallito.</translation>
</message> </message>
<message> <message>
<source>No key is set. Aborting database creation.</source> <source>No key is set. Aborting database creation.</source>
<translation type="unfinished"/> <translation>Chiave non impostata. Annullamento creazione database.</translation>
</message> </message>
<message> <message>
<source>Failed to save the database: %1.</source> <source>Failed to save the database: %1.</source>
@ -4763,7 +4767,7 @@ Comandi disponibili:
</message> </message>
<message> <message>
<source>Successfully created new database.</source> <source>Successfully created new database.</source>
<translation type="unfinished"/> <translation>Nuovo database creato con successo.</translation>
</message> </message>
<message> <message>
<source>Insert password to encrypt database (Press enter to leave blank): </source> <source>Insert password to encrypt database (Press enter to leave blank): </source>

View file

@ -491,7 +491,7 @@ Por favor, selecione o banco de dados correto para salvar as credenciais.</trans
</message> </message>
<message> <message>
<source>Re&amp;quest to unlock the database if it is locked</source> <source>Re&amp;quest to unlock the database if it is locked</source>
<translation>Pe&amp;dir para desbloquear a base de dados se estiver bloqueada</translation> <translation>Pe&amp;dir para desbloquear a banco de dados se estiver bloqueado</translation>
</message> </message>
<message> <message>
<source>Only entries with the same scheme (http://, https://, ...) are returned.</source> <source>Only entries with the same scheme (http://, https://, ...) are returned.</source>
@ -1063,7 +1063,7 @@ Isso pode impedir a conexão com o plugin do navegador.</translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Successfully removed %n encryption key(s) from KeePassXC settings.</source> <source>Successfully removed %n encryption key(s) from KeePassXC settings.</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> <translation><numerusform>Removido com sucesso% n chave (s) criptográficas das configurações do KeePassXC.</numerusform><numerusform>Removido com sucesso% n chave (s) criptográficas das configurações do KeePassXC.</numerusform></translation>
</message> </message>
<message> <message>
<source>Forget all site-specific settings on entries</source> <source>Forget all site-specific settings on entries</source>
@ -1097,7 +1097,7 @@ Permissões para acessar entradas serão revogadas.</translation>
</message> </message>
<message> <message>
<source>The active database does not contain an entry with permissions.</source> <source>The active database does not contain an entry with permissions.</source>
<translation>A base de dados ativa não contém uma entrada com permissões.</translation> <translation>O banco de dados ativo não contém uma entrada com permissões.</translation>
</message> </message>
<message> <message>
<source>Move KeePassHTTP attributes to custom data</source> <source>Move KeePassHTTP attributes to custom data</source>
@ -1239,7 +1239,7 @@ Se você manter este número, seu banco de dados pode ser facilmente crackeado!<
<message numerus="yes"> <message numerus="yes">
<source> thread(s)</source> <source> thread(s)</source>
<comment>Threads for parallel execution (KDF settings)</comment> <comment>Threads for parallel execution (KDF settings)</comment>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> <translation><numerusform>processo(s)</numerusform><numerusform>processo(s)</numerusform></translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>%1 ms</source> <source>%1 ms</source>
@ -1489,11 +1489,11 @@ Este é definitivamente um bug, por favor denuncie para os desenvolvedores.</tra
</message> </message>
<message> <message>
<source>No current database.</source> <source>No current database.</source>
<translation>Nenhuma base de dados atual.</translation> <translation>Nenhuma banco de dados atual.</translation>
</message> </message>
<message> <message>
<source>No source database, nothing to do.</source> <source>No source database, nothing to do.</source>
<translation>Nenhuma base de dados de origem, nada a fazer.</translation> <translation>Nenhuma banco de dados de origem, nada a fazer.</translation>
</message> </message>
<message> <message>
<source>Search Results (%1)</source> <source>Search Results (%1)</source>
@ -1509,7 +1509,7 @@ Este é definitivamente um bug, por favor denuncie para os desenvolvedores.</tra
</message> </message>
<message> <message>
<source>The database file has changed. Do you want to load the changes?</source> <source>The database file has changed. Do you want to load the changes?</source>
<translation>A base de dados foi alterada. Deseja carregar as alterações?</translation> <translation>O banco de dados foi alterado. Deseja carregar as alterações?</translation>
</message> </message>
<message> <message>
<source>Merge Request</source> <source>Merge Request</source>
@ -1531,7 +1531,7 @@ Você deseja combinar suas alterações?</translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Do you really want to delete %n entry(s) for good?</source> <source>Do you really want to delete %n entry(s) for good?</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> <translation><numerusform>Você realmente quer apagar %n entrada(s) para o bem?</numerusform><numerusform>Você realmente quer apagar %n entrada(s) para o bem?</numerusform></translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>Delete entry(s)?</source> <source>Delete entry(s)?</source>
@ -1609,7 +1609,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
</message> </message>
<message numerus="yes"> <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> <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>A entrada &quot;%1&quot; tem %2 referência(s). Deseja substituir referências por valores, ignorar essa entrada ou excluir mesmo assim?</numerusform><numerusform>A entrada &quot;%1&quot; tem %2 referência(s). Deseja substituir referências por valores, ignorar essa entrada ou excluir mesmo assim?</numerusform></translation>
</message> </message>
<message> <message>
<source>Delete group</source> <source>Delete group</source>
@ -2078,15 +2078,15 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
</message> </message>
<message> <message>
<source>The export container %1 is already referenced.</source> <source>The export container %1 is already referenced.</source>
<translation type="unfinished"/> <translation>O contêiner de exportado %1 é referenciado.</translation>
</message> </message>
<message> <message>
<source>The import container %1 is already imported.</source> <source>The import container %1 is already imported.</source>
<translation type="unfinished"/> <translation>O contêiner de importado %1 foi importado.</translation>
</message> </message>
<message> <message>
<source>The container %1 imported and export by different groups.</source> <source>The container %1 imported and export by different groups.</source>
<translation type="unfinished"/> <translation>O contêiner %1 importado e exportado por diferentes grupos.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2168,7 +2168,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
</message> </message>
<message> <message>
<source>Hint: You can enable DuckDuckGo as a fallback under Tools&gt;Settings&gt;Security</source> <source>Hint: You can enable DuckDuckGo as a fallback under Tools&gt;Settings&gt;Security</source>
<translation type="unfinished"/> <translation>Dica: você pode habilitar o DuckDuckGo como um reserva em Ferramentas&gt; Configurações&gt; Segurança</translation>
</message> </message>
<message> <message>
<source>Select Image(s)</source> <source>Select Image(s)</source>
@ -2184,7 +2184,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>%n icon(s) already exist in the database</source> <source>%n icon(s) already exist in the database</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> <translation><numerusform>%n ícone(s) existe no banco de dados</numerusform><numerusform>%n ícone(s) existe no banco de dados</numerusform></translation>
</message> </message>
<message numerus="yes"> <message numerus="yes">
<source>The following icon(s) failed:</source> <source>The following icon(s) failed:</source>
@ -2192,7 +2192,7 @@ Deseja desabilitar salvamento seguro e tentar novamente?</translation>
</message> </message>
<message numerus="yes"> <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> <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>Este ícone é usado por %n entrada(s) e será substituído pelo ícone padrão. Tem certeza de que deseja excluí-lo?</numerusform><numerusform>Este ícone é usado por %n entrada(s) e será substituído pelo ícone padrão. Tem certeza de que deseja excluí-lo?</numerusform></translation>
</message> </message>
</context> </context>
<context> <context>
@ -2593,7 +2593,7 @@ Isto pode causar mal funcionamento dos plugins afetados.</translation>
</message> </message>
<message> <message>
<source>Wrong key or database file is corrupt.</source> <source>Wrong key or database file is corrupt.</source>
<translation>Chave errada ou base de dados corrompida.</translation> <translation>Chave errada ou banco de dados corrompido.</translation>
</message> </message>
<message> <message>
<source>missing database headers</source> <source>missing database headers</source>
@ -2601,7 +2601,7 @@ Isto pode causar mal funcionamento dos plugins afetados.</translation>
</message> </message>
<message> <message>
<source>Header doesn&apos;t match hash</source> <source>Header doesn&apos;t match hash</source>
<translation type="unfinished"/> <translation>Cabeçalho não corresponde ao hash</translation>
</message> </message>
<message> <message>
<source>Invalid header id size</source> <source>Invalid header id size</source>
@ -2833,7 +2833,7 @@ Isto é uma migração de caminho único. Você não poderá abrir o banco de da
</message> </message>
<message> <message>
<source>Unable to parse UUID: %1</source> <source>Unable to parse UUID: %1</source>
<translation type="unfinished"/> <translation>Não é possível analisar o UUID: %1</translation>
</message> </message>
<message> <message>
<source>Failed to read database file.</source> <source>Failed to read database file.</source>
@ -3036,7 +3036,7 @@ Linha %2, coluna %3</translation>
</message> </message>
<message> <message>
<source>Wrong key or database file is corrupt.</source> <source>Wrong key or database file is corrupt.</source>
<translation>Chave errada ou base de dados corrompida.</translation> <translation>Chave errada ou banco de dados corrompido.</translation>
</message> </message>
<message> <message>
<source>Key transformation failed</source> <source>Key transformation failed</source>
@ -3225,7 +3225,7 @@ Linha %2, coluna %3</translation>
</message> </message>
<message> <message>
<source>&lt;p&gt;You can add a key file containing random bytes for additional security.&lt;/p&gt;&lt;p&gt;You must keep it secret and never lose it or you will be locked out!&lt;/p&gt;</source> <source>&lt;p&gt;You can add a key file containing random bytes for additional security.&lt;/p&gt;&lt;p&gt;You must keep it secret and never lose it or you will be locked out!&lt;/p&gt;</source>
<translation type="unfinished"/> <translation>&lt;p&gt;Você pode adicionar um arquivo de chave contendo bytes aleatórios para segurança adicional.&lt;/p&gt;&lt;p&gt;Você deve mantê-lo em segredo e nunca perdê-lo ou você será bloqueado!&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<source>Legacy key file format</source> <source>Legacy key file format</source>
@ -3236,12 +3236,16 @@ Linha %2, coluna %3</translation>
unsupported in the future. unsupported in the future.
Please go to the master key settings and generate a new key file.</source> Please go to the master key settings and generate a new key file.</source>
<translation type="unfinished"/> <translation>Você está usando um formato de arquivo de chave antigo que pode ficar
sem suporte no futuro.
Por favor, para as configurações da chave mestra e gere um novo arquivo de chave.</translation>
</message> </message>
<message> <message>
<source>Error loading the key file '%1' <source>Error loading the key file '%1'
Message: %2</source> Message: %2</source>
<translation type="unfinished"/> <translation>Erro ao carregar o arquivo de chave &apos;%1&apos;
Mensagem: %2</translation>
</message> </message>
<message> <message>
<source>Key files</source> <source>Key files</source>
@ -3261,7 +3265,7 @@ Message: %2</source>
</message> </message>
<message> <message>
<source>Unable to create key file: %1</source> <source>Unable to create key file: %1</source>
<translation type="unfinished"/> <translation>Não foi possível criar arquivo de chave: %1</translation>
</message> </message>
<message> <message>
<source>Select a key file</source> <source>Select a key file</source>
@ -3308,11 +3312,11 @@ Message: %2</source>
</message> </message>
<message> <message>
<source>&amp;Save database</source> <source>&amp;Save database</source>
<translation>&amp;Salvar base de dados</translation> <translation>&amp;Salvar banco de dados</translation>
</message> </message>
<message> <message>
<source>&amp;Close database</source> <source>&amp;Close database</source>
<translation>&amp;Fechar base de dados</translation> <translation>&amp;Fechar banco de dados</translation>
</message> </message>
<message> <message>
<source>&amp;Delete entry</source> <source>&amp;Delete entry</source>
@ -3360,7 +3364,7 @@ Message: %2</source>
</message> </message>
<message> <message>
<source>&amp;Lock databases</source> <source>&amp;Lock databases</source>
<translation>&amp;Trancar base de dados</translation> <translation>&amp;Trancar banco de dados</translation>
</message> </message>
<message> <message>
<source>&amp;Title</source> <source>&amp;Title</source>
@ -3591,11 +3595,11 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Adding backup for older target %1 [%2]</source> <source>Adding backup for older target %1 [%2]</source>
<translation type="unfinished"/> <translation>Adicionando backup para o alvo mais antigo %1 [%2]</translation>
</message> </message>
<message> <message>
<source>Adding backup for older source %1 [%2]</source> <source>Adding backup for older source %1 [%2]</source>
<translation type="unfinished"/> <translation>Adicionando backup para fonte mais antiga %1 [%2]</translation>
</message> </message>
<message> <message>
<source>Reapplying older target entry on top of newer source %1 [%2]</source> <source>Reapplying older target entry on top of newer source %1 [%2]</source>
@ -3607,7 +3611,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Synchronizing from newer source %1 [%2]</source> <source>Synchronizing from newer source %1 [%2]</source>
<translation type="unfinished"/> <translation>Sincronizando de uma fonte mais nova %1 [%2]</translation>
</message> </message>
<message> <message>
<source>Synchronizing from older source %1 [%2]</source> <source>Synchronizing from older source %1 [%2]</source>
@ -3619,7 +3623,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Deleting orphan %1 [%2]</source> <source>Deleting orphan %1 [%2]</source>
<translation type="unfinished"/> <translation>Excluindo órfã %1 [%2]</translation>
</message> </message>
<message> <message>
<source>Changed deleted objects</source> <source>Changed deleted objects</source>
@ -3937,7 +3941,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>ExtendedASCII</source> <source>ExtendedASCII</source>
<translation type="unfinished"/> <translation>ASCIIEstendido</translation>
</message> </message>
<message> <message>
<source>Switch to advanced mode</source> <source>Switch to advanced mode</source>
@ -3957,7 +3961,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Lower Case Letters A to F</source> <source>Lower Case Letters A to F</source>
<translation type="unfinished"/> <translation>Letras minúsculas de A a F</translation>
</message> </message>
<message> <message>
<source>a-z</source> <source>a-z</source>
@ -4033,7 +4037,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Add non-hex letters to &quot;do not include&quot; list</source> <source>Add non-hex letters to &quot;do not include&quot; list</source>
<translation type="unfinished"/> <translation>Adicionar letras não hexadecimais à lista &quot;não incluir&quot;</translation>
</message> </message>
<message> <message>
<source>Hex</source> <source>Hex</source>
@ -4041,7 +4045,7 @@ Espere alguns bugs e problemas menores, esta versão não é para uso em produç
</message> </message>
<message> <message>
<source>Excluded characters: &quot;0&quot;, &quot;1&quot;, &quot;l&quot;, &quot;I&quot;, &quot;O&quot;, &quot;|&quot;, &quot;&quot;</source> <source>Excluded characters: &quot;0&quot;, &quot;1&quot;, &quot;l&quot;, &quot;I&quot;, &quot;O&quot;, &quot;|&quot;, &quot;&quot;</source>
<translation type="unfinished"/> <translation>Caracteres excluídos: &quot;0&quot;, &quot;1&quot;, &quot;l&quot;, &quot;I&quot;, &quot;O&quot;, &quot;|&quot;, &quot;&quot;</translation>
</message> </message>
<message> <message>
<source>Word Co&amp;unt:</source> <source>Word Co&amp;unt:</source>
@ -4415,11 +4419,11 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Invalid value for password length %1.</source> <source>Invalid value for password length %1.</source>
<translation type="unfinished"/> <translation>Valor inválido para o tamanho da senha %1.</translation>
</message> </message>
<message> <message>
<source>Could not create entry with path %1.</source> <source>Could not create entry with path %1.</source>
<translation type="unfinished"/> <translation>Não foi possível criar uma entrada com o caminho %1.</translation>
</message> </message>
<message> <message>
<source>Enter password for new entry: </source> <source>Enter password for new entry: </source>
@ -4427,15 +4431,15 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Writing the database failed %1.</source> <source>Writing the database failed %1.</source>
<translation type="unfinished"/> <translation>Gravação do banco de dados falhou %1.</translation>
</message> </message>
<message> <message>
<source>Successfully added entry %1.</source> <source>Successfully added entry %1.</source>
<translation type="unfinished"/> <translation>Entrada adicionada com sucesso %1.</translation>
</message> </message>
<message> <message>
<source>Copy the current TOTP to the clipboard.</source> <source>Copy the current TOTP to the clipboard.</source>
<translation type="unfinished"/> <translation>Copie o TOTP atual para a área de transferência.</translation>
</message> </message>
<message> <message>
<source>Invalid timeout value %1.</source> <source>Invalid timeout value %1.</source>
@ -4516,11 +4520,11 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Type: Bruteforce</source> <source>Type: Bruteforce</source>
<translation type="unfinished"/> <translation>Tipo: Força Bruta</translation>
</message> </message>
<message> <message>
<source>Type: Dictionary</source> <source>Type: Dictionary</source>
<translation type="unfinished"/> <translation>Tipo: Dicionário</translation>
</message> </message>
<message> <message>
<source>Type: Dict+Leet</source> <source>Type: Dict+Leet</source>
@ -4540,15 +4544,15 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Type: Sequence</source> <source>Type: Sequence</source>
<translation type="unfinished"/> <translation>Tipo: Sequência</translation>
</message> </message>
<message> <message>
<source>Type: Spatial</source> <source>Type: Spatial</source>
<translation type="unfinished"/> <translation>Tipo: Espacial</translation>
</message> </message>
<message> <message>
<source>Type: Date</source> <source>Type: Date</source>
<translation type="unfinished"/> <translation>Tipo: Data</translation>
</message> </message>
<message> <message>
<source>Type: Bruteforce(Rep)</source> <source>Type: Bruteforce(Rep)</source>
@ -4596,11 +4600,11 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>*** Password length (%1) != sum of length of parts (%2) ***</source> <source>*** Password length (%1) != sum of length of parts (%2) ***</source>
<translation type="unfinished"/> <translation>*** Comprimento da senha (%1) != soma do comprimento das partes (%2) ***</translation>
</message> </message>
<message> <message>
<source>Failed to load key file %1: %2</source> <source>Failed to load key file %1: %2</source>
<translation type="unfinished"/> <translation>Falha ao carregar o arquivo de chave %1: %2</translation>
</message> </message>
<message> <message>
<source>File %1 does not exist.</source> <source>File %1 does not exist.</source>
@ -4613,16 +4617,18 @@ Comandos disponíveis:
<message> <message>
<source>Error while reading the database: <source>Error while reading the database:
%1</source> %1</source>
<translation type="unfinished"/> <translation>Erro ao ler o banco de dados:
%1</translation>
</message> </message>
<message> <message>
<source>Error while parsing the database: <source>Error while parsing the database:
%1</source> %1</source>
<translation type="unfinished"/> <translation>Erro ao analisar o banco de dados:
%1</translation>
</message> </message>
<message> <message>
<source>Length of the generated password</source> <source>Length of the generated password</source>
<translation type="unfinished"/> <translation>Comprimento da senha gerada</translation>
</message> </message>
<message> <message>
<source>Use lowercase characters</source> <source>Use lowercase characters</source>
@ -4646,11 +4652,11 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Exclude character set</source> <source>Exclude character set</source>
<translation type="unfinished"/> <translation>Excluir conjunto de caracteres</translation>
</message> </message>
<message> <message>
<source>chars</source> <source>chars</source>
<translation type="unfinished"/> <translation>caracteres</translation>
</message> </message>
<message> <message>
<source>Exclude similar looking characters</source> <source>Exclude similar looking characters</source>
@ -4666,7 +4672,7 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Cannot find group %1.</source> <source>Cannot find group %1.</source>
<translation type="unfinished"/> <translation>Não foi possível encontrar o grupo %1.</translation>
</message> </message>
<message> <message>
<source>Error reading merge file: <source>Error reading merge file:
@ -4691,19 +4697,19 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Show the entry&apos;s current TOTP.</source> <source>Show the entry&apos;s current TOTP.</source>
<translation type="unfinished"/> <translation>Mostrar o TOTP atual da entrada.</translation>
</message> </message>
<message> <message>
<source>ERROR: unknown attribute %1.</source> <source>ERROR: unknown attribute %1.</source>
<translation type="unfinished"/> <translation>ERRO: atributo desconhecido %1.</translation>
</message> </message>
<message> <message>
<source>No program defined for clipboard manipulation</source> <source>No program defined for clipboard manipulation</source>
<translation type="unfinished"/> <translation>Nenhum programa definido para manipulação da área de transferência</translation>
</message> </message>
<message> <message>
<source>Unable to start program %1</source> <source>Unable to start program %1</source>
<translation type="unfinished"/> <translation>Não é possível iniciar o programa %1</translation>
</message> </message>
<message> <message>
<source>file empty</source> <source>file empty</source>
@ -5268,7 +5274,7 @@ Comandos disponíveis:
</message> </message>
<message> <message>
<source>Could not embed database: Could not write file (%1)</source> <source>Could not embed database: Could not write file (%1)</source>
<translation type="unfinished"/> <translation>Não foi possível incorporar o banco de dados: não foi possível gravar o arquivo (%1)</translation>
</message> </message>
</context> </context>
<context> <context>

File diff suppressed because it is too large Load diff

View file

@ -39,7 +39,7 @@
</message> </message>
<message> <message>
<source>Project Maintainers:</source> <source>Project Maintainers:</source>
<translation>Projekt ansvariga:</translation> <translation>Projektansvariga:</translation>
</message> </message>
<message> <message>
<source>Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.</source> <source>Special thanks from the KeePassXC team go to debfx for creating the original KeePassX.</source>
@ -220,7 +220,7 @@
</message> </message>
<message> <message>
<source>Auto-Type typing delay</source> <source>Auto-Type typing delay</source>
<translation>Auto-skriv fördröjning</translation> <translation>Fördröjning för auto-skriv</translation>
</message> </message>
<message> <message>
<source> ms</source> <source> ms</source>
@ -765,7 +765,7 @@ Would you like to migrate your existing settings now?</source>
</message> </message>
<message> <message>
<source>Number of headers line to discard</source> <source>Number of headers line to discard</source>
<translation>Antal av rubrik rader att kasta bort</translation> <translation>Antal rubrikrader att kasta bort</translation>
</message> </message>
<message> <message>
<source>Consider &apos;\&apos; an escape character</source> <source>Consider &apos;\&apos; an escape character</source>
@ -1258,7 +1258,7 @@ If you keep this number, your database may be too easy to crack!</source>
</message> </message>
<message> <message>
<source>Max. history items:</source> <source>Max. history items:</source>
<translation>Maxantal historik poster:</translation> <translation>Maxantal historikposter:</translation>
</message> </message>
<message> <message>
<source>Max. history size:</source> <source>Max. history size:</source>
@ -3856,7 +3856,7 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Word Separator:</source> <source>Word Separator:</source>
<translation>Ord separerare:</translation> <translation>Ordseparerare:</translation>
</message> </message>
<message> <message>
<source>Copy</source> <source>Copy</source>
@ -4008,11 +4008,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Word Co&amp;unt:</source> <source>Word Co&amp;unt:</source>
<translation type="unfinished"/> <translation>&amp;Antal ord:</translation>
</message> </message>
<message> <message>
<source>Regenerate</source> <source>Regenerate</source>
<translation type="unfinished"/> <translation>Regenerera</translation>
</message> </message>
</context> </context>
<context> <context>

View file

@ -73,7 +73,7 @@
</message> </message>
<message> <message>
<source>Access error for config file %1</source> <source>Access error for config file %1</source>
<translation>%1 yapılandırma dosyası için erişim hatası</translation> <translation>Yapılandırma dosyası erişim hatası %1</translation>
</message> </message>
<message> <message>
<source>Icon only</source> <source>Icon only</source>

View file

@ -27,11 +27,11 @@
</message> </message>
<message> <message>
<source>Debug Info</source> <source>Debug Info</source>
<translation>Інформація щодо зневадження</translation> <translation>Зневаджувальна інформація</translation>
</message> </message>
<message> <message>
<source>Include the following information whenever you report a bug:</source> <source>Include the following information whenever you report a bug:</source>
<translation>Коли Ви повідомляєте про ваду, завжди долучайте таку інформацію:</translation> <translation>Повідомляючи про проблему, завжди долучайте наступну інформацію:</translation>
</message> </message>
<message> <message>
<source>Copy to clipboard</source> <source>Copy to clipboard</source>
@ -61,7 +61,7 @@
<name>ApplicationSettingsWidget</name> <name>ApplicationSettingsWidget</name>
<message> <message>
<source>Application Settings</source> <source>Application Settings</source>
<translation>Налаштування застосунку</translation> <translation>Налаштування програми</translation>
</message> </message>
<message> <message>
<source>General</source> <source>General</source>
@ -100,7 +100,7 @@
<name>ApplicationSettingsWidgetGeneral</name> <name>ApplicationSettingsWidgetGeneral</name>
<message> <message>
<source>Basic Settings</source> <source>Basic Settings</source>
<translation>Базове налаштування</translation> <translation>Основні налаштування</translation>
</message> </message>
<message> <message>
<source>Startup</source> <source>Startup</source>
@ -116,7 +116,7 @@
</message> </message>
<message> <message>
<source>Remember last key files and security dongles</source> <source>Remember last key files and security dongles</source>
<translation>Пам&apos;ятати останні файли ключів і механізми захисту</translation> <translation>Пам&apos;ятати останні файли ключів та апаратні ключі</translation>
</message> </message>
<message> <message>
<source>Load previous databases on startup</source> <source>Load previous databases on startup</source>
@ -172,7 +172,7 @@
</message> </message>
<message> <message>
<source>General</source> <source>General</source>
<translation>Загальне</translation> <translation>Загальні</translation>
</message> </message>
<message> <message>
<source>Hide toolbar (icons)</source> <source>Hide toolbar (icons)</source>
@ -1425,7 +1425,7 @@ Are you sure you want to continue without a password?</source>
<source>The created database has no key or KDF, refusing to save it. <source>The created database has no key or KDF, refusing to save it.
This is definitely a bug, please report it to the developers.</source> This is definitely a bug, please report it to the developers.</source>
<translation>Створене сховище не має ані ключа, ані ФОК, і тому не може бути збереженим. <translation>Створене сховище не має ані ключа, ані ФОК, і тому не може бути збереженим.
Це напевно вада у програмі. Будь ласка, повідомте про це розробникам.</translation> Це певно є вадою програми, будь ласка, повідомте про це розробникам.</translation>
</message> </message>
<message> <message>
<source>The database file does not exist or is not accessible.</source> <source>The database file does not exist or is not accessible.</source>
@ -3564,8 +3564,8 @@ We recommend you use the AppImage available on our downloads page.</source>
<message> <message>
<source>NOTE: You are using a pre-release version of KeePassXC! <source>NOTE: You are using a pre-release version of KeePassXC!
Expect some bugs and minor issues, this version is not meant for production use.</source> Expect some bugs and minor issues, this version is not meant for production use.</source>
<translation>&lt;b&gt;Примітка&lt;/b&gt;: Ви використовуєте попередню версію KeePassXC! <translation>&lt;b&gt;Примітка&lt;/b&gt;: Ви використовуєте попередній випуск KeePassXC!
Зважайте на можливість деяких вади та незначних проблем, ця версія не призначена для повсякденного користування.</translation> Зважайте на ймовірні помилки та незначні проблеми, ця версія не призначена для повсякденного користування.</translation>
</message> </message>
<message> <message>
<source>Check for updates on startup?</source> <source>Check for updates on startup?</source>

View file

@ -4672,7 +4672,7 @@ Available commands:
</message> </message>
<message> <message>
<source>Cannot find group %1.</source> <source>Cannot find group %1.</source>
<translation>%1</translation> <translation>%1</translation>
</message> </message>
<message> <message>
<source>Error reading merge file: <source>Error reading merge file:
@ -4760,7 +4760,7 @@ Available commands:
</message> </message>
<message> <message>
<source>No groups found</source> <source>No groups found</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Create a new database.</source> <source>Create a new database.</source>

View file

@ -54,7 +54,7 @@
</message> </message>
<message> <message>
<source>Use OpenSSH for Windows instead of Pageant</source> <source>Use OpenSSH for Windows instead of Pageant</source>
<translation type="unfinished"/> <translation>使 OpenSSH for Windows Pageant</translation>
</message> </message>
</context> </context>
<context> <context>
@ -93,7 +93,7 @@
</message> </message>
<message> <message>
<source>Follow style</source> <source>Follow style</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -132,11 +132,11 @@
</message> </message>
<message> <message>
<source>Safely save database files (may be incompatible with Dropbox, etc)</source> <source>Safely save database files (may be incompatible with Dropbox, etc)</source>
<translation type="unfinished"/> <translation> ( Dropbox )</translation>
</message> </message>
<message> <message>
<source>Backup database file before saving</source> <source>Backup database file before saving</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Automatically save after every change</source> <source>Automatically save after every change</source>
@ -168,7 +168,7 @@
</message> </message>
<message> <message>
<source>Hide the entry preview panel</source> <source>Hide the entry preview panel</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>General</source> <source>General</source>
@ -176,11 +176,11 @@
</message> </message>
<message> <message>
<source>Hide toolbar (icons)</source> <source>Hide toolbar (icons)</source>
<translation type="unfinished"/> <translation> ()</translation>
</message> </message>
<message> <message>
<source>Minimize instead of app exit</source> <source>Minimize instead of app exit</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Show a system tray icon</source> <source>Show a system tray icon</source>
@ -220,7 +220,7 @@
</message> </message>
<message> <message>
<source>Auto-Type typing delay</source> <source>Auto-Type typing delay</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source> ms</source> <source> ms</source>
@ -229,23 +229,23 @@
</message> </message>
<message> <message>
<source>Auto-Type start delay</source> <source>Auto-Type start delay</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Check for updates at application startup</source> <source>Check for updates at application startup</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Include pre-releases when checking for updates</source> <source>Include pre-releases when checking for updates</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Movable toolbar</source> <source>Movable toolbar</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Button style</source> <source>Button style</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -269,11 +269,11 @@
</message> </message>
<message> <message>
<source> min</source> <source> min</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Forget TouchID after inactivity of</source> <source>Forget TouchID after inactivity of</source>
<translation type="unfinished"/> <translation> TouchID </translation>
</message> </message>
<message> <message>
<source>Convenience</source> <source>Convenience</source>
@ -285,7 +285,7 @@
</message> </message>
<message> <message>
<source>Forget TouchID when session is locked or lid is closed</source> <source>Forget TouchID when session is locked or lid is closed</source>
<translation type="unfinished"/> <translation> TouchID</translation>
</message> </message>
<message> <message>
<source>Lock databases after minimizing the window</source> <source>Lock databases after minimizing the window</source>
@ -293,7 +293,7 @@
</message> </message>
<message> <message>
<source>Re-lock previously locked database after performing Auto-Type</source> <source>Re-lock previously locked database after performing Auto-Type</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Don&apos;t require password repeat when it is visible</source> <source>Don&apos;t require password repeat when it is visible</source>
@ -301,15 +301,15 @@
</message> </message>
<message> <message>
<source>Don&apos;t hide passwords when editing them</source> <source>Don&apos;t hide passwords when editing them</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Don&apos;t use placeholder for empty password fields</source> <source>Don&apos;t use placeholder for empty password fields</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Hide passwords in the entry preview panel</source> <source>Hide passwords in the entry preview panel</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Hide entry notes by default</source> <source>Hide entry notes by default</source>
@ -429,11 +429,11 @@ Please select whether you want to allow access.</source>
<name>BrowserEntrySaveDialog</name> <name>BrowserEntrySaveDialog</name>
<message> <message>
<source>KeePassXC-Browser Save Entry</source> <source>KeePassXC-Browser Save Entry</source>
<translation type="unfinished"/> <translation>KeePassXC-Browser </translation>
</message> </message>
<message> <message>
<source>Ok</source> <source>Ok</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Cancel</source> <source>Cancel</source>
@ -442,7 +442,8 @@ Please select whether you want to allow access.</source>
<message> <message>
<source>You have multiple databases open. <source>You have multiple databases open.
Please select the correct database for saving credentials.</source> Please select the correct database for saving credentials.</source>
<translation type="unfinished"/> <translation>
</translation>
</message> </message>
</context> </context>
<context> <context>
@ -589,11 +590,11 @@ Please select the correct database for saving credentials.</source>
</message> </message>
<message> <message>
<source>&amp;Tor Browser</source> <source>&amp;Tor Browser</source>
<translation type="unfinished"/> <translation>&amp;Tor </translation>
</message> </message>
<message> <message>
<source>&lt;b&gt;Warning&lt;/b&gt;, the keepassxc-proxy application was not found!&lt;br /&gt;Please check the KeePassXC installation directory or confirm the custom path in advanced options.&lt;br /&gt;Browser integration WILL NOT WORK without the proxy application.&lt;br /&gt;Expected Path: </source> <source>&lt;b&gt;Warning&lt;/b&gt;, the keepassxc-proxy application was not found!&lt;br /&gt;Please check the KeePassXC installation directory or confirm the custom path in advanced options.&lt;br /&gt;Browser integration WILL NOT WORK without the proxy application.&lt;br /&gt;Expected Path: </source>
<translation type="unfinished"/> <translation>&lt;b&gt;&lt;/b&gt;,找不到 keepassxc-proxy 應用程式!&lt;br /&gt; KeePassXC &lt;br /&gt;&lt;br /&gt; </translation>
</message> </message>
<message> <message>
<source>Executable Files</source> <source>Executable Files</source>
@ -606,7 +607,7 @@ Please select the correct database for saving credentials.</source>
<message> <message>
<source>Do not ask permission for HTTP &amp;Basic Auth</source> <source>Do not ask permission for HTTP &amp;Basic Auth</source>
<extracomment>An extra HTTP Basic Auth setting</extracomment> <extracomment>An extra HTTP Basic Auth setting</extracomment>
<translation type="unfinished"/> <translation> HTTP </translation>
</message> </message>
<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> <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>
@ -618,7 +619,7 @@ Please select the correct database for saving credentials.</source>
</message> </message>
<message> <message>
<source>KeePassXC-Browser is needed for the browser integration to work. &lt;br /&gt;Download it for %1 and %2. %3</source> <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> KeePassXC-Browser 使 %1 %2 %3</translation>
</message> </message>
</context> </context>
<context> <context>
@ -883,7 +884,7 @@ Would you like to migrate your existing settings now?</source>
<name>DatabaseOpenWidget</name> <name>DatabaseOpenWidget</name>
<message> <message>
<source>Enter master key</source> <source>Enter master key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Key File:</source> <source>Key File:</source>
@ -972,7 +973,7 @@ Please consider generating a new key file.</source>
</message> </message>
<message> <message>
<source>Master Key</source> <source>Master Key</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Encryption Settings</source> <source>Encryption Settings</source>
@ -987,7 +988,7 @@ Please consider generating a new key file.</source>
<name>DatabaseSettingsWidgetBrowser</name> <name>DatabaseSettingsWidgetBrowser</name>
<message> <message>
<source>KeePassXC-Browser settings</source> <source>KeePassXC-Browser settings</source>
<translation type="unfinished"/> <translation>KeePassXC-Browser </translation>
</message> </message>
<message> <message>
<source>&amp;Disconnect all browsers</source> <source>&amp;Disconnect all browsers</source>
@ -995,7 +996,7 @@ Please consider generating a new key file.</source>
</message> </message>
<message> <message>
<source>Forg&amp;et all site-specific settings on entries</source> <source>Forg&amp;et all site-specific settings on entries</source>
<translation type="unfinished"/> <translation> (&amp;e)</translation>
</message> </message>
<message> <message>
<source>Move KeePassHTTP attributes to KeePassXC-Browser &amp;custom data</source> <source>Move KeePassHTTP attributes to KeePassXC-Browser &amp;custom data</source>
@ -1032,7 +1033,7 @@ This may prevent connection to the browser plugin.</source>
</message> </message>
<message> <message>
<source>Disconnect all browsers</source> <source>Disconnect all browsers</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Do you really want to disconnect all browsers? <source>Do you really want to disconnect all browsers?
@ -1291,7 +1292,7 @@ If you keep this number, your database may be too easy to crack!</source>
<name>DatabaseSettingsWidgetKeeShare</name> <name>DatabaseSettingsWidgetKeeShare</name>
<message> <message>
<source>Sharing</source> <source>Sharing</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Breadcrumb</source> <source>Breadcrumb</source>
@ -1299,11 +1300,11 @@ If you keep this number, your database may be too easy to crack!</source>
</message> </message>
<message> <message>
<source>Type</source> <source>Type</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Path</source> <source>Path</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Last Signer</source> <source>Last Signer</source>
@ -1311,12 +1312,12 @@ If you keep this number, your database may be too easy to crack!</source>
</message> </message>
<message> <message>
<source>Certificates</source> <source>Certificates</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source> &gt; </source> <source> &gt; </source>
<comment>Breadcrumb separator</comment> <comment>Breadcrumb separator</comment>
<translation type="unfinished"/> <translation>&gt;</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1335,13 +1336,15 @@ If you keep this number, your database may be too easy to crack!</source>
</message> </message>
<message> <message>
<source>No password set</source> <source>No password set</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>WARNING! You have not set a password. Using a database without a password is strongly discouraged! <source>WARNING! You have not set a password. Using a database without a password is strongly discouraged!
Are you sure you want to continue without a password?</source> Are you sure you want to continue without a password?</source>
<translation type="unfinished"/> <translation>使
</translation>
</message> </message>
<message> <message>
<source>Unknown error</source> <source>Unknown error</source>
@ -1349,7 +1352,7 @@ Are you sure you want to continue without a password?</source>
</message> </message>
<message> <message>
<source>Failed to change master key</source> <source>Failed to change master key</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -1403,7 +1406,7 @@ Are you sure you want to continue without a password?</source>
</message> </message>
<message> <message>
<source>Database creation error</source> <source>Database creation error</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>The created database has no key or KDF, refusing to save it. <source>The created database has no key or KDF, refusing to save it.
@ -1425,17 +1428,17 @@ This is definitely a bug, please report it to the developers.</source>
<message> <message>
<source>%1 [New Database]</source> <source>%1 [New Database]</source>
<comment>Database tab name modifier</comment> <comment>Database tab name modifier</comment>
<translation type="unfinished"/> <translation>%1 []</translation>
</message> </message>
<message> <message>
<source>%1 [Locked]</source> <source>%1 [Locked]</source>
<comment>Database tab name modifier</comment> <comment>Database tab name modifier</comment>
<translation type="unfinished"/> <translation>%1 []</translation>
</message> </message>
<message> <message>
<source>%1 [Read-only]</source> <source>%1 [Read-only]</source>
<comment>Database tab name modifier</comment> <comment>Database tab name modifier</comment>
<translation type="unfinished"/> <translation>%1 []</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1532,7 +1535,7 @@ Do you want to merge your changes?</source>
</message> </message>
<message> <message>
<source>Lock Database?</source> <source>Lock Database?</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>You are editing an entry. Discard changes and lock anyway?</source> <source>You are editing an entry. Discard changes and lock anyway?</source>
@ -1547,7 +1550,8 @@ Save changes?</source>
<message> <message>
<source>Database was modified. <source>Database was modified.
Save changes?</source> Save changes?</source>
<translation type="unfinished"/> <translation>
</translation>
</message> </message>
<message> <message>
<source>Save changes?</source> <source>Save changes?</source>
@ -1560,7 +1564,7 @@ Error: %1</source>
</message> </message>
<message> <message>
<source>Disable safe saves?</source> <source>Disable safe saves?</source>
<translation>?</translation> <translation></translation>
</message> </message>
<message> <message>
<source>KeePassXC has failed to save the database multiple times. This is likely caused by file sync services holding a lock on the save file. <source>KeePassXC has failed to save the database multiple times. This is likely caused by file sync services holding a lock on the save file.
@ -2565,7 +2569,7 @@ This may cause the affected plugins to malfunction.</source>
<name>Kdbx3Reader</name> <name>Kdbx3Reader</name>
<message> <message>
<source>Unable to calculate master key</source> <source>Unable to calculate master key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Unable to issue challenge-response.</source> <source>Unable to issue challenge-response.</source>
@ -2604,7 +2608,7 @@ This may cause the affected plugins to malfunction.</source>
</message> </message>
<message> <message>
<source>Unable to calculate master key</source> <source>Unable to calculate master key</source>
<translation></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -2615,7 +2619,7 @@ This may cause the affected plugins to malfunction.</source>
</message> </message>
<message> <message>
<source>Unable to calculate master key</source> <source>Unable to calculate master key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Invalid header checksum size</source> <source>Invalid header checksum size</source>
@ -2743,7 +2747,7 @@ This may cause the affected plugins to malfunction.</source>
</message> </message>
<message> <message>
<source>Unable to calculate master key</source> <source>Unable to calculate master key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Failed to serialize KDF parameters variant map</source> <source>Failed to serialize KDF parameters variant map</source>
@ -3010,7 +3014,7 @@ Line %2, column %3</source>
</message> </message>
<message> <message>
<source>Unable to calculate master key</source> <source>Unable to calculate master key</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Wrong key or database file is corrupt.</source> <source>Wrong key or database file is corrupt.</source>
@ -3174,17 +3178,17 @@ Line %2, column %3</source>
<message> <message>
<source>Change %1</source> <source>Change %1</source>
<comment>Change a key component</comment> <comment>Change a key component</comment>
<translation type="unfinished"/> <translation>%1</translation>
</message> </message>
<message> <message>
<source>Remove %1</source> <source>Remove %1</source>
<comment>Remove a key component</comment> <comment>Remove a key component</comment>
<translation type="unfinished"/> <translation>%1</translation>
</message> </message>
<message> <message>
<source>%1 set, click to change or remove</source> <source>%1 set, click to change or remove</source>
<comment>Change or remove a key component</comment> <comment>Change or remove a key component</comment>
<translation type="unfinished"/> <translation>%1</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3214,7 +3218,9 @@ Line %2, column %3</source>
unsupported in the future. unsupported in the future.
Please go to the master key settings and generate a new key file.</source> Please go to the master key settings and generate a new key file.</source>
<translation type="unfinished"/> <translation>使
</translation>
</message> </message>
<message> <message>
<source>Error loading the key file '%1' <source>Error loading the key file '%1'
@ -3414,11 +3420,11 @@ This version is not meant for production use.</source>
</message> </message>
<message> <message>
<source>&amp;Donate</source> <source>&amp;Donate</source>
<translation type="unfinished"/> <translation> (&amp;D)</translation>
</message> </message>
<message> <message>
<source>Report a &amp;bug</source> <source>Report a &amp;bug</source>
<translation type="unfinished"/> <translation> (&amp;b)</translation>
</message> </message>
<message> <message>
<source>WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard! <source>WARNING: Your Qt version may cause KeePassXC to crash with an On-Screen Keyboard!
@ -3427,75 +3433,75 @@ We recommend you use the AppImage available on our downloads page.</source>
</message> </message>
<message> <message>
<source>&amp;Import</source> <source>&amp;Import</source>
<translation type="unfinished"/> <translation> (&amp;I)</translation>
</message> </message>
<message> <message>
<source>Copy att&amp;ribute...</source> <source>Copy att&amp;ribute...</source>
<translation type="unfinished"/> <translation> (&amp;r)</translation>
</message> </message>
<message> <message>
<source>TOTP...</source> <source>TOTP...</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>&amp;New database...</source> <source>&amp;New database...</source>
<translation type="unfinished"/> <translation>(&amp;N)</translation>
</message> </message>
<message> <message>
<source>Create a new database</source> <source>Create a new database</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>&amp;Merge from database...</source> <source>&amp;Merge from database...</source>
<translation type="unfinished"/> <translation>(&amp;M)</translation>
</message> </message>
<message> <message>
<source>Merge from another KDBX database</source> <source>Merge from another KDBX database</source>
<translation type="unfinished"/> <translation> KDBX </translation>
</message> </message>
<message> <message>
<source>&amp;New entry</source> <source>&amp;New entry</source>
<translation type="unfinished"/> <translation>(&amp;N)</translation>
</message> </message>
<message> <message>
<source>Add a new entry</source> <source>Add a new entry</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>&amp;Edit entry</source> <source>&amp;Edit entry</source>
<translation type="unfinished"/> <translation>(&amp;E)</translation>
</message> </message>
<message> <message>
<source>View or edit entry</source> <source>View or edit entry</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>&amp;New group</source> <source>&amp;New group</source>
<translation type="unfinished"/> <translation> (&amp;N)</translation>
</message> </message>
<message> <message>
<source>Add a new group</source> <source>Add a new group</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Change master &amp;key...</source> <source>Change master &amp;key...</source>
<translation type="unfinished"/> <translation>(&amp;k)</translation>
</message> </message>
<message> <message>
<source>&amp;Database settings...</source> <source>&amp;Database settings...</source>
<translation type="unfinished"/> <translation>(&amp;D)</translation>
</message> </message>
<message> <message>
<source>Copy &amp;password</source> <source>Copy &amp;password</source>
<translation type="unfinished"/> <translation>(&amp;p)</translation>
</message> </message>
<message> <message>
<source>Perform &amp;Auto-Type</source> <source>Perform &amp;Auto-Type</source>
<translation type="unfinished"/> <translation> (&amp;A)</translation>
</message> </message>
<message> <message>
<source>Open &amp;URL</source> <source>Open &amp;URL</source>
<translation type="unfinished"/> <translation>(&amp;U)</translation>
</message> </message>
<message> <message>
<source>KeePass 1 database...</source> <source>KeePass 1 database...</source>
@ -3515,7 +3521,7 @@ We recommend you use the AppImage available on our downloads page.</source>
</message> </message>
<message> <message>
<source>Show TOTP...</source> <source>Show TOTP...</source>
<translation type="unfinished"/> <translation> TOTP</translation>
</message> </message>
<message> <message>
<source>Show TOTP QR Code...</source> <source>Show TOTP QR Code...</source>
@ -3626,11 +3632,11 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>En&amp;cryption Settings</source> <source>En&amp;cryption Settings</source>
<translation type="unfinished"/> <translation> (&amp;c)</translation>
</message> </message>
<message> <message>
<source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source> <source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source>
<translation type="unfinished"/> <translation>調</translation>
</message> </message>
<message> <message>
<source>Advanced Settings</source> <source>Advanced Settings</source>
@ -3649,18 +3655,18 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message> </message>
<message> <message>
<source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source> <source>Here you can adjust the database encryption settings. Don&apos;t worry, you can change them later in the database settings.</source>
<translation type="unfinished"/> <translation>調</translation>
</message> </message>
</context> </context>
<context> <context>
<name>NewDatabaseWizardPageMasterKey</name> <name>NewDatabaseWizardPageMasterKey</name>
<message> <message>
<source>Database Master Key</source> <source>Database Master Key</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>A master key known only to you protects your database.</source> <source>A master key known only to you protects your database.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -5047,7 +5053,7 @@ Available commands:
</message> </message>
<message> <message>
<source>Path</source> <source>Path</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Status</source> <source>Status</source>
@ -5361,11 +5367,11 @@ Available commands:
</message> </message>
<message> <message>
<source>Please try again later.</source> <source>Please try again later.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>Software Update</source> <source>Software Update</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<source>A new version of KeePassXC is available!</source> <source>A new version of KeePassXC is available!</source>
@ -5377,7 +5383,7 @@ Available commands:
</message> </message>
<message> <message>
<source>Download it at keepassxc.org</source> <source>Download it at keepassxc.org</source>
<translation type="unfinished"/> <translation> keepassxc.org </translation>
</message> </message>
<message> <message>
<source>You&apos;re up-to-date!</source> <source>You&apos;re up-to-date!</source>

View file

@ -0,0 +1,11 @@
# /snap/local/launchers
Here are the launchers, or wrapper programs to deal with some runtime-fixable problems for the snapped applications, like setting proper environmental variables in snap.
In convention launchers are named _something_-launch, for dealing certain problem with _something_, and usually can be called in a stacked manner to consolidate their modifications.
```yaml
apps:
_app_name_:
command: foo-launch bar-launch _app_command_
```

View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# This is the maintainence launcher for the snap, make necessary runtime environment changes to make the snap work here. You may also insert security confinement/deprecation/obsoletion notice of the snap here.
set \
-o errexit \
-o errtrace \
-o nounset \
-o pipefail
# gtk-common-themes support
export QT_QPA_PLATFORMTHEME=gtk3
# Finally run the next part of the command chain
exec "${@}"

View file

@ -1,5 +1,5 @@
name: keepassxc name: keepassxc
version: 2.4.1 version: 2.4.2
grade: stable grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe” summary: Community-driven port of the Windows application “KeePass Password Safe”
description: | description: |
@ -9,16 +9,28 @@ description: |
confinement: strict confinement: strict
base: core18 base: core18
plugs: plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
icon-themes: # fix mouse cursor theme gtk-3-themes:
interface: content
target: $SNAP/data-dir/themes
default-provider: gtk-common-themes:gtk-3-themes
icon-themes:
interface: content interface: content
target: $SNAP/data-dir/icons target: $SNAP/data-dir/icons
default-provider: gtk-common-themes default-provider: gtk-common-themes:icon-themes
sound-themes:
interface: content
target: $SNAP/data-dir/sounds
default-provider: gtk-common-themes:sounds-themes
apps: apps:
keepassxc: keepassxc:
command: desktop-launch keepassxc adapter: full
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy] command: usr/bin/keepassxc -style fusion
command-chain:
- bin/desktop-launch
- bin/gtk3-env-launch
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy, desktop]
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
environment: environment:
DISABLE_WAYLAND: 1 DISABLE_WAYLAND: 1
@ -68,12 +80,12 @@ parts:
- libxtst6 - libxtst6
- libqt5x11extras5 - libqt5x11extras5
- libqt5svg5 - libqt5svg5
- libqrencode3 - try: [libqrencode3, libqrencode4]
- libqt5concurrent5 - libqt5concurrent5
- libquazip5-1 - libquazip5-1
- libusb-1.0-0 - libusb-1.0-0
- qtwayland5 - qtwayland5
- qt5-style-plugins # for mouse cursor theme fix - qt5-gtk-platformtheme # for theming, font settings, cursor and to use gtk3 file chooser
override-build: | override-build: |
snapcraftctl build snapcraftctl build
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
@ -82,7 +94,15 @@ parts:
stage: stage:
- -opt - -opt
after: [desktop-qt5] after: [desktop-qt5]
launchers: # custom launcher to set QT_QPA_PLATFORMTHEME=gtk3 correctly
source: snap/local/launchers
plugin: dump
organize:
'*': bin/
stage:
- -bin/README.*
desktop-qt5: desktop-qt5:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
source-subdir: qt source-subdir: qt

View file

@ -16,9 +16,6 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
find_library(ZXCVBN_LIBRARIES zxcvbn) find_library(ZXCVBN_LIBRARIES zxcvbn)
if(NOT ZXCVBN_LIBRARIES) if(NOT ZXCVBN_LIBRARIES)
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c) add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
@ -27,6 +24,7 @@ if(NOT ZXCVBN_LIBRARIES)
endif(NOT ZXCVBN_LIBRARIES) endif(NOT ZXCVBN_LIBRARIES)
set(keepassx_SOURCES set(keepassx_SOURCES
core/Alloc.cpp
core/AutoTypeAssociations.cpp core/AutoTypeAssociations.cpp
core/AutoTypeMatch.cpp core/AutoTypeMatch.cpp
core/Compare.cpp core/Compare.cpp
@ -167,7 +165,8 @@ if(APPLE)
core/ScreenLockListenerMac.cpp core/ScreenLockListenerMac.cpp
core/MacPasteboard.cpp core/MacPasteboard.cpp
gui/macutils/MacUtils.cpp gui/macutils/MacUtils.cpp
gui/macutils/AppKitImpl.mm) gui/macutils/AppKitImpl.mm
gui/macutils/AppKit.h)
endif() endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
set(keepassx_SOURCES set(keepassx_SOURCES
@ -192,8 +191,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)") add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser") add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent") add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare") add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response") add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking") add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
if(APPLE) if(APPLE)
@ -254,8 +252,13 @@ endif()
if(WITH_XC_TOUCHID) if(WITH_XC_TOUCHID)
list(APPEND keepassx_SOURCES touchid/TouchID.mm) list(APPEND keepassx_SOURCES touchid/TouchID.mm)
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error")
endif() endif()
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
add_library(autotype STATIC ${autotype_SOURCES}) add_library(autotype STATIC ${autotype_SOURCES})
target_link_libraries(autotype Qt5::Core Qt5::Widgets) target_link_libraries(autotype Qt5::Core Qt5::Widgets)
@ -270,6 +273,7 @@ target_link_libraries(keepassx_core
Qt5::Concurrent Qt5::Concurrent
Qt5::Network Qt5::Network
Qt5::Widgets Qt5::Widgets
${sodium_LIBRARY_RELEASE}
${YUBIKEY_LIBRARIES} ${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES} ${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES} ${ARGON2_LIBRARIES}
@ -410,25 +414,19 @@ if(MINGW)
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime) install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
include(DeployQt4) # Deploy all 3rd party library dependencies first
install_qt4_executable(${PROGNAME}.exe) install(CODE "include(BundleUtilities)
fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")"
COMPONENT Runtime)
# install Qt5 plugins # Use windeployqt.exe to setup Qt dependencies
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins) set(WINDEPLOYQT_MODE "--release")
install(FILES if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll set(WINDEPLOYQT_MODE "--debug")
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll endif()
DESTINATION "platforms")
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles") install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)"
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts") COMPONENT Runtime)
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
install(FILES
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
DESTINATION "imageformats")
# install CA cert chains # install CA cert chains
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs") install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")

View file

@ -47,7 +47,7 @@ AutoType::AutoType(QObject* parent, bool test)
, m_pluginLoader(new QPluginLoader(this)) , m_pluginLoader(new QPluginLoader(this))
, m_plugin(nullptr) , m_plugin(nullptr)
, m_executor(nullptr) , m_executor(nullptr)
, m_windowFromGlobal(0) , m_windowForGlobal(0)
{ {
// prevent crash when the plugin has unresolved symbols // prevent crash when the plugin has unresolved symbols
m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint); m_pluginLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
@ -90,7 +90,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
if (m_plugin) { if (m_plugin) {
if (m_plugin->isAvailable()) { if (m_plugin->isAvailable()) {
m_executor = m_plugin->createExecutor(); m_executor = m_plugin->createExecutor();
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered())); connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SLOT(startGlobalAutoType()));
} else { } else {
unloadPlugin(); unloadPlugin();
} }
@ -222,6 +222,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt())); Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt()));
// Used only for selected entry auto-type
if (!window) { if (!window) {
window = m_plugin->activeWindow(); window = m_plugin->activeWindow();
} }
@ -240,6 +241,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
QCoreApplication::processEvents(QEventLoop::AllEvents, 10); QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} }
m_windowForGlobal = 0;
m_windowTitleForGlobal.clear();
// emit signal only if autotype performed correctly // emit signal only if autotype performed correctly
emit autotypePerformed(); emit autotypePerformed();
@ -264,6 +268,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
executeAutoTypeActions(entry, hideWindow, sequences.first()); executeAutoTypeActions(entry, hideWindow, sequences.first());
} }
void AutoType::startGlobalAutoType()
{
m_windowForGlobal = m_plugin->activeWindow();
m_windowTitleForGlobal = m_plugin->activeWindowTitle();
emit globalAutoTypeTriggered();
}
/** /**
* Global Autotype entry-point function * Global Autotype entry-point function
* Perform global Auto-Type on the active window * Perform global Auto-Type on the active window
@ -278,9 +289,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
return; return;
} }
QString windowTitle = m_plugin->activeWindowTitle(); if (m_windowTitleForGlobal.isEmpty()) {
if (windowTitle.isEmpty()) {
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
return; return;
} }
@ -290,7 +299,7 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
for (const auto& db : dbList) { for (const auto& db : dbList) {
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive(); const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
for (Entry* entry : dbEntries) { for (Entry* entry : dbEntries) {
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet(); const QSet<QString> sequences = autoTypeSequences(entry, m_windowTitleForGlobal).toSet();
for (const QString& sequence : sequences) { for (const QString& sequence : sequences) {
if (!sequence.isEmpty()) { if (!sequence.isEmpty()) {
matchList << AutoTypeMatch(entry, sequence); matchList << AutoTypeMatch(entry, sequence);
@ -304,8 +313,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
auto* msgBox = new QMessageBox(); auto* msgBox = new QMessageBox();
msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC")); msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
msgBox->setText( msgBox->setText(tr("Couldn't find an entry that matches the window title:")
tr("Couldn't find an entry that matches the window title:").append("\n\n").append(windowTitle)); .append("\n\n")
.append(m_windowTitleForGlobal));
msgBox->setIcon(QMessageBox::Information); msgBox->setIcon(QMessageBox::Information);
msgBox->setStandardButtons(QMessageBox::Ok); msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->show(); msgBox->show();
@ -316,10 +326,9 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
emit autotypeRejected(); emit autotypeRejected();
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence, m_windowForGlobal);
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
} else { } else {
m_windowFromGlobal = m_plugin->activeWindow();
auto* selectDialog = new AutoTypeSelectDialog(); auto* selectDialog = new AutoTypeSelectDialog();
// connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex // connect slots, both of which must unlock the m_inGlobalAutoTypeDialog mutex
@ -327,11 +336,12 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal())); connect(selectDialog, SIGNAL(rejected()), SLOT(autoTypeRejectedFromGlobal()));
selectDialog->setMatchList(matchList); selectDialog->setMatchList(matchList);
#if defined(Q_OS_MACOS) #ifdef Q_OS_MACOS
m_plugin->raiseOwnWindow(); m_plugin->raiseOwnWindow();
Tools::wait(500); Tools::wait(200);
#endif #endif
selectDialog->show(); selectDialog->show();
selectDialog->raise();
// necessary when the main window is minimized // necessary when the main window is minimized
selectDialog->activateWindow(); selectDialog->activateWindow();
} }
@ -339,8 +349,8 @@ void AutoType::performGlobalAutoType(const QList<QSharedPointer<Database>>& dbLi
void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match)
{ {
m_plugin->raiseWindow(m_windowFromGlobal); m_plugin->raiseWindow(m_windowForGlobal);
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowForGlobal);
// make sure the mutex is definitely locked before we unlock it // make sure the mutex is definitely locked before we unlock it
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
@ -353,6 +363,8 @@ void AutoType::autoTypeRejectedFromGlobal()
// so make sure the mutex is locked before we try unlocking it // so make sure the mutex is locked before we try unlocking it
Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock()); Q_UNUSED(m_inGlobalAutoTypeDialog.tryLock());
m_inGlobalAutoTypeDialog.unlock(); m_inGlobalAutoTypeDialog.unlock();
m_windowForGlobal = 0;
m_windowTitleForGlobal.clear();
emit autotypeRejected(); emit autotypeRejected();
} }

View file

@ -62,18 +62,19 @@ public slots:
void raiseWindow(); void raiseWindow();
signals: signals:
void globalShortcutTriggered(); void globalAutoTypeTriggered();
void autotypePerformed(); void autotypePerformed();
void autotypeRejected(); void autotypeRejected();
private slots: private slots:
void startGlobalAutoType();
void performAutoTypeFromGlobal(AutoTypeMatch match); void performAutoTypeFromGlobal(AutoTypeMatch match);
void autoTypeRejectedFromGlobal(); void autoTypeRejectedFromGlobal();
void unloadPlugin(); void unloadPlugin();
private: private:
explicit AutoType(QObject* parent = nullptr, bool test = false); explicit AutoType(QObject* parent = nullptr, bool test = false);
~AutoType(); ~AutoType() override;
void loadPlugin(const QString& pluginPath); void loadPlugin(const QString& pluginPath);
void executeAutoTypeActions(const Entry* entry, void executeAutoTypeActions(const Entry* entry,
QWidget* hideWindow = nullptr, QWidget* hideWindow = nullptr,
@ -94,9 +95,11 @@ private:
QPluginLoader* m_pluginLoader; QPluginLoader* m_pluginLoader;
AutoTypePlatformInterface* m_plugin; AutoTypePlatformInterface* m_plugin;
AutoTypeExecutor* m_executor; AutoTypeExecutor* m_executor;
WId m_windowFromGlobal;
static AutoType* m_instance; static AutoType* m_instance;
QString m_windowTitleForGlobal;
WId m_windowForGlobal;
Q_DISABLE_COPY(AutoType) Q_DISABLE_COPY(AutoType)
}; };

View file

@ -1,10 +1,6 @@
set(autotype_mac_SOURCES AutoTypeMac.cpp) set(autotype_mac_SOURCES AutoTypeMac.cpp)
set(autotype_mac_mm_SOURCES add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES})
${CMAKE_SOURCE_DIR}/src/gui/macutils/AppKitImpl.mm
${CMAKE_SOURCE_DIR}/src/gui/macutils/MacUtils.cpp)
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES} ${autotype_mac_mm_SOURCES})
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon") set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)

View file

@ -68,6 +68,11 @@ AutoTypeExecutor* AutoTypePlatformTest::createExecutor()
return new AutoTypeExecutorTest(this); return new AutoTypeExecutorTest(this);
} }
void AutoTypePlatformTest::triggerGlobalAutoType()
{
emit globalShortcutTriggered();
}
void AutoTypePlatformTest::setActiveWindowTitle(const QString& title) void AutoTypePlatformTest::setActiveWindowTitle(const QString& title)
{ {
m_activeWindowTitle = title; m_activeWindowTitle = title;

View file

@ -48,6 +48,7 @@ public:
bool raiseOwnWindow() override; bool raiseOwnWindow() override;
#endif #endif
void triggerGlobalAutoType() override;
void setActiveWindowTitle(const QString& title) override; void setActiveWindowTitle(const QString& title) override;
QString actionChars() override; QString actionChars() override;

View file

@ -26,6 +26,7 @@ public:
virtual ~AutoTypeTestInterface() virtual ~AutoTypeTestInterface()
{ {
} }
virtual void triggerGlobalAutoType() = 0;
virtual void setActiveWindowTitle(const QString& title) = 0; virtual void setActiveWindowTitle(const QString& title) = 0;
virtual QString actionChars() = 0; virtual QString actionChars() = 0;

View file

@ -94,6 +94,8 @@ private:
QString m_publicKey; QString m_publicKey;
QString m_secretKey; QString m_secretKey;
bool m_associated; bool m_associated;
friend class TestBrowser;
}; };
#endif // BROWSERACTION_H #endif // BROWSERACTION_H

View file

@ -120,6 +120,7 @@ void BrowserOptionDialog::loadSettings()
m_ui->useCustomProxy->setChecked(settings->useCustomProxy()); m_ui->useCustomProxy->setChecked(settings->useCustomProxy());
m_ui->customProxyLocation->setText(settings->customProxyLocation()); m_ui->customProxyLocation->setText(settings->customProxyLocation());
m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath()); m_ui->updateBinaryPath->setChecked(settings->updateBinaryPath());
m_ui->allowExpiredCredentials->setChecked(settings->allowExpiredCredentials());
m_ui->chromeSupport->setChecked(settings->chromeSupport()); m_ui->chromeSupport->setChecked(settings->chromeSupport());
m_ui->chromiumSupport->setChecked(settings->chromiumSupport()); m_ui->chromiumSupport->setChecked(settings->chromiumSupport());
m_ui->firefoxSupport->setChecked(settings->firefoxSupport()); m_ui->firefoxSupport->setChecked(settings->firefoxSupport());
@ -176,6 +177,7 @@ void BrowserOptionDialog::saveSettings()
settings->setCustomProxyLocation(m_ui->customProxyLocation->text()); settings->setCustomProxyLocation(m_ui->customProxyLocation->text());
settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked()); settings->setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
settings->setAllowExpiredCredentials(m_ui->allowExpiredCredentials->isChecked());
settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked()); settings->setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked()); settings->setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked()); settings->setHttpAuthPermission(m_ui->httpAuthPermission->isChecked());

View file

@ -219,6 +219,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="allowExpiredCredentials">
<property name="toolTip">
<string>Returns expired credentials. String [expired] is added to the title.</string>
</property>
<property name="text">
<string>&amp;Allow returning expired credentials.</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QRadioButton" name="sortByTitle"> <widget class="QRadioButton" name="sortByTitle">
<property name="text"> <property name="text">

View file

@ -172,9 +172,9 @@ QJsonArray BrowserService::getChildrenFromGroup(Group* group)
return groupList; return groupList;
} }
QJsonObject BrowserService::getDatabaseGroups() QJsonObject BrowserService::getDatabaseGroups(const QSharedPointer<Database>& selectedDb)
{ {
auto db = getDatabase(); auto db = selectedDb ? selectedDb : getDatabase();
if (!db) { if (!db) {
return {}; return {};
} }
@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key)
do { do {
QInputDialog keyDialog; QInputDialog keyDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request")); keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n" keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
"If you would like to allow it access to your KeePassXC database,\n" "If you would like to allow it access to your KeePassXC database,\n"
@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key)
id = keyDialog.textValue(); id = keyDialog.textValue();
if (ok != QDialog::Accepted || id.isEmpty()) { if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
hideWindow(); hideWindow();
return {}; return {};
} }
@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return QJsonArray(); return QJsonArray();
} }
// Ensure that database is not locked when the popup was visible
if (!isDatabaseOpened()) {
return QJsonArray();
}
// Sort results // Sort results
pwEntries = sortEntries(pwEntries, host, submitUrl); pwEntries = sortEntries(pwEntries, host, submitUrl);
@ -447,11 +453,6 @@ void BrowserService::addEntry(const QString& id,
return; return;
} }
auto* addEntryGroup = findCreateAddEntryGroup(db);
if (!addEntryGroup) {
return;
}
auto* entry = new Entry(); auto* entry = new Entry();
entry->setUuid(QUuid::createUuid()); entry->setUuid(QUuid::createUuid());
entry->setTitle(QUrl(url).host()); entry->setTitle(QUrl(url).host());
@ -459,16 +460,19 @@ void BrowserService::addEntry(const QString& id,
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON); entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
entry->setUsername(login); entry->setUsername(login);
entry->setPassword(password); entry->setPassword(password);
entry->setGroup(addEntryGroup);
// Select a group for the entry // Select a group for the entry
if (!group.isEmpty()) { if (!group.isEmpty()) {
if (db->rootGroup()) { if (db->rootGroup()) {
auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid)); auto selectedGroup = db->rootGroup()->findGroupByUuid(Tools::hexToUuid(groupUuid));
if (selectedGroup && selectedGroup->name() == group) { if (selectedGroup) {
entry->setGroup(selectedGroup); entry->setGroup(selectedGroup);
} else {
entry->setGroup(getDefaultEntryGroup(db));
} }
} }
} else {
entry->setGroup(getDefaultEntryGroup(db));
} }
const QString host = QUrl(url).host(); const QString host = QUrl(url).host();
@ -760,6 +764,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
m_dialogActive = true; m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog; BrowserAccessControlDialog accessControlDialog;
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
accessControlDialog.setUrl(url); accessControlDialog.setUrl(url);
accessControlDialog.setItems(pwEntriesToConfirm); accessControlDialog.setItems(pwEntriesToConfirm);
@ -811,6 +816,10 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
res["totp"] = entry->totp(); res["totp"] = entry->totp();
} }
if (entry->isExpired()) {
res["expired"] = "true";
}
if (browserSettings()->supportKphFields()) { if (browserSettings()->supportKphFields()) {
const EntryAttributes* attr = entry->attributes(); const EntryAttributes* attr = entry->attributes();
QJsonArray stringFields; QJsonArray stringFields;
@ -834,7 +843,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown; return Unknown;
} }
if (entry->isExpired()) { if (entry->isExpired()) {
return Denied; return browserSettings()->allowExpiredCredentials() ? Allowed : Denied;
} }
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) { if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
return Allowed; return Allowed;
@ -848,7 +857,7 @@ BrowserService::checkAccess(const Entry* entry, const QString& host, const QStri
return Unknown; return Unknown;
} }
Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb) Group* BrowserService::getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb)
{ {
auto db = selectedDb ? selectedDb : getDatabase(); auto db = selectedDb ? selectedDb : getDatabase();
if (!db) { if (!db) {
@ -861,7 +870,7 @@ Group* BrowserService::findCreateAddEntryGroup(const QSharedPointer<Database>& s
} }
const QString groupName = const QString groupName =
QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); // TODO: setting to decide where new keys are created QLatin1String(KEEPASSXCBROWSER_GROUP_NAME);
for (auto* g : rootGroup->groupsRecursive(true)) { for (auto* g : rootGroup->groupsRecursive(true)) {
if (g->name() == groupName && !g->isRecycled()) { if (g->name() == groupName && !g->isRecycled()) {

View file

@ -44,7 +44,7 @@ public:
bool openDatabase(bool triggerUnlock); bool openDatabase(bool triggerUnlock);
QString getDatabaseRootUuid(); QString getDatabaseRootUuid();
QString getDatabaseRecycleBinUuid(); QString getDatabaseRecycleBinUuid();
QJsonObject getDatabaseGroups(); QJsonObject getDatabaseGroups(const QSharedPointer<Database>& selectedDb = {});
QJsonObject createNewGroup(const QString& groupName); QJsonObject createNewGroup(const QString& groupName);
QString getKey(const QString& id); QString getKey(const QString& id);
void addEntry(const QString& id, void addEntry(const QString& id,
@ -114,7 +114,7 @@ private:
const QString& realm); const QString& realm);
QJsonObject prepareEntry(const Entry* entry); QJsonObject prepareEntry(const Entry* entry);
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm); Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
Group* findCreateAddEntryGroup(const QSharedPointer<Database>& selectedDb = {}); Group* getDefaultEntryGroup(const QSharedPointer<Database>& selectedDb = {});
int int
sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const; sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const;
bool matchUrlScheme(const QString& url); bool matchUrlScheme(const QString& url);
@ -135,6 +135,8 @@ private:
bool m_bringToFrontRequested; bool m_bringToFrontRequested;
WindowState m_prevWindowState; WindowState m_prevWindowState;
QUuid m_keepassBrowserUUID; QUuid m_keepassBrowserUUID;
friend class TestBrowser;
}; };
#endif // BROWSERSERVICE_H #endif // BROWSERSERVICE_H

View file

@ -194,6 +194,16 @@ void BrowserSettings::setUpdateBinaryPath(bool enabled)
config()->set("Browser/UpdateBinaryPath", enabled); config()->set("Browser/UpdateBinaryPath", enabled);
} }
bool BrowserSettings::allowExpiredCredentials()
{
return config()->get("Browser/AllowExpiredCredentials", false).toBool();
}
void BrowserSettings::setAllowExpiredCredentials(bool enabled)
{
config()->set("Browser/AllowExpiredCredentials", enabled);
}
bool BrowserSettings::chromeSupport() bool BrowserSettings::chromeSupport()
{ {
return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME); return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);

View file

@ -64,6 +64,8 @@ public:
void setCustomProxyLocation(const QString& location); void setCustomProxyLocation(const QString& location);
bool updateBinaryPath(); bool updateBinaryPath();
void setUpdateBinaryPath(bool enabled); void setUpdateBinaryPath(bool enabled);
bool allowExpiredCredentials();
void setAllowExpiredCredentials(bool enabled);
bool chromeSupport(); bool chromeSupport();
void setChromeSupport(bool enabled); void setChromeSupport(bool enabled);
bool chromiumSupport(); bool chromiumSupport();

View file

@ -16,7 +16,6 @@
if(WITH_XC_BROWSER) if(WITH_XC_BROWSER)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
find_package(sodium 1.0.12 REQUIRED)
set(keepassxcbrowser_SOURCES set(keepassxcbrowser_SOURCES
BrowserAccessControlDialog.cpp BrowserAccessControlDialog.cpp
@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp) Variant.cpp)
add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium) target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
endif() endif()

View file

@ -19,6 +19,8 @@
#include "NativeMessagingBase.h" #include "NativeMessagingBase.h"
#include <QStandardPaths> #include <QStandardPaths>
#include "config-keepassx.h"
#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) #if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
#include <sys/event.h> #include <sys/event.h>
#include <sys/time.h> #include <sys/time.h>
@ -138,7 +140,7 @@ QString NativeMessagingBase::getLocalServerPath() const
{ {
const QString serverPath = "/kpxc_server"; const QString serverPath = "/kpxc_server";
#if defined(KEEPASSXC_DIST_SNAP) #if defined(KEEPASSXC_DIST_SNAP)
return QProcessEnvironment::systemEnvironment().value("SNAP_COMMON") + serverPath; return QProcessEnvironment::systemEnvironment().value("SNAP_USER_COMMON") + serverPath;
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
// Use XDG_RUNTIME_DIR instead of /tmp if it's available // Use XDG_RUNTIME_DIR instead of /tmp if it's available
QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);

View file

@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add"); errorTextStream << parser.helpText().replace("[options]", "add [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
keepassx_core keepassx_core
Qt5::Core Qt5::Core
${GCRYPT_LIBRARIES} ${GCRYPT_LIBRARIES}
${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES} ${ARGON2_LIBRARIES}
${GPGERROR_LIBRARIES} ${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES} ${ZLIB_LIBRARIES}

View file

@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2 && args.size() != 3) { if (args.size() != 2 && args.size() != 3) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli clip"); errorTextStream << parser.helpText().replace("[options]", "clip [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() < 1) { if (args.size() < 1) {
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create"); out << parser.helpText().replace("[options]", "create [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) { if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments)
} }
if (!dicewareGenerator.isValid()) { if (!dicewareGenerator.isValid()) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); errorTextStream << parser.helpText().replace("[options]", "edit [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() > 1) { if (args.size() > 1) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -57,7 +57,7 @@ int Extract::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 1) { if (args.size() != 1) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract"); errorTextStream << parser.helpText().replace("[options]", "extract [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) { if (!args.isEmpty()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments)
passwordGenerator.setExcludedChars(parser.value(exclude)); passwordGenerator.setExcludedChars(parser.value(exclude));
if (!passwordGenerator.isValid()) { if (!passwordGenerator.isValid()) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 1 && args.size() != 2) { if (args.size() != 1 && args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls"); errorTextStream << parser.helpText().replace("[options]", "ls [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate"); errorTextStream << parser.helpText().replace("[options]", "locate [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge"); errorTextStream << parser.helpText().replace("[options]", "merge [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); errorTextStream << parser.helpText().replace("[options]", "rm [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show"); errorTextStream << parser.helpText().replace("[options]", "show [options]");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View file

@ -19,6 +19,9 @@
#include <QProcessEnvironment> #include <QProcessEnvironment>
#include <QTextCodec> #include <QTextCodec>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
TextStream::TextStream() TextStream::TextStream()
{ {
@ -59,12 +62,26 @@ void TextStream::detectCodec()
{ {
QString codecName = "UTF-8"; QString codecName = "UTF-8";
auto env = QProcessEnvironment::systemEnvironment(); auto env = QProcessEnvironment::systemEnvironment();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (!env.contains("SHELL")) { WINBOOL success = false;
// native shell (no Msys or cygwin) #ifdef CP_UTF8
success = SetConsoleOutputCP(CP_UTF8);
#endif
if (!success && !env.contains("SHELL")) {
// Fall back to cp850 if this is Windows without CP_UTF8 and we
// are running in a native shell (i.e., no Msys or Cygwin).
codecName = "Windows-850"; codecName = "Windows-850";
} }
#else
if (env.contains("LANG") && !env.value("LANG").isEmpty() && env.value("LANG") != "C") {
// Only override codec if LANG is set, otherwise Qt will assume
// US-ASCII, which is almost always wrong and results in
// Unicode passwords being displayed as question marks.
codecName = QTextCodec::codecForLocale()->name();
}
#endif #endif
codecName = env.value("ENCODING_OVERRIDE", codecName); codecName = env.value("ENCODING_OVERRIDE", codecName);
auto* codec = QTextCodec::codecForName(codecName.toLatin1()); auto* codec = QTextCodec::codecForName(codecName.toLatin1());
if (codec) { if (codec) {

89
src/core/Alloc.cpp Normal file
View file

@ -0,0 +1,89 @@
/*
* 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 <QtGlobal>
#include <cstdint>
#include <sodium.h>
#ifdef Q_OS_MACOS
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#if defined(NDEBUG) && !defined(__cpp_sized_deallocation)
#warning "KeePassXC is being compiled without sized deallocation support. Deletes may be slow."
#endif
/**
* Custom sized delete operator which securely zeroes out allocated
* memory before freeing it (requires C++14 sized deallocation support).
*/
void operator delete(void* ptr, std::size_t size) noexcept
{
if (!ptr) {
return;
}
sodium_memzero(ptr, size);
std::free(ptr);
}
void operator delete[](void* ptr, std::size_t size) noexcept
{
::operator delete(ptr, size);
}
/**
* Custom delete operator which securely zeroes out
* allocated memory before freeing it.
*/
void operator delete(void* ptr) noexcept
{
if (!ptr) {
return;
}
#if defined(Q_OS_WIN)
::operator delete(ptr, _msize(ptr));
#elif defined(Q_OS_MACOS)
::operator delete(ptr, malloc_size(ptr));
#elif defined(Q_OS_UNIX)
::operator delete(ptr, malloc_usable_size(ptr));
#else
// whatever OS this is, give up and simply free stuff
std::free(ptr);
#endif
}
void operator delete[](void* ptr) noexcept
{
::operator delete(ptr);
}
/**
* Custom insecure delete operator that does not zero out memory before
* freeing a buffer. Can be used for better performance.
*/
void operator delete(void* ptr, bool) noexcept
{
std::free(ptr);
}
void operator delete[](void* ptr, bool) noexcept
{
::operator delete(ptr, false);
}

View file

@ -85,6 +85,12 @@ namespace Bootstrap
bootstrap(); bootstrap();
MessageBox::initializeButtonDefs(); MessageBox::initializeButtonDefs();
#ifdef KEEPASSXC_DIST_SNAP
// snap: force fallback theme to avoid using system theme (gtk integration)
// with missing actions just like on Windows and macOS
QIcon::setThemeSearchPaths(QStringList() << ":/icons");
#endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
// Don't show menu icons on OSX // Don't show menu icons on OSX
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);

View file

@ -16,9 +16,12 @@
*/ */
#include "CustomData.h" #include "CustomData.h"
#include "Clock.h"
#include "core/Global.h" #include "core/Global.h"
const QString CustomData::LastModified = "_LAST_MODIFIED";
CustomData::CustomData(QObject* parent) CustomData::CustomData(QObject* parent)
: QObject(parent) : QObject(parent)
{ {
@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value)
if (addAttribute || changeValue) { if (addAttribute || changeValue) {
m_data.insert(key, value); m_data.insert(key, value);
updateLastModified();
emit customDataModified(); emit customDataModified();
} }
@ -74,6 +78,7 @@ void CustomData::remove(const QString& key)
m_data.remove(key); m_data.remove(key);
updateLastModified();
emit removed(key); emit removed(key);
emit customDataModified(); emit customDataModified();
} }
@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
m_data.remove(oldKey); m_data.remove(oldKey);
m_data.insert(newKey, data); m_data.insert(newKey, data);
updateLastModified();
emit customDataModified(); emit customDataModified();
emit renamed(oldKey, newKey); emit renamed(oldKey, newKey);
} }
@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other)
m_data = other->m_data; m_data = other->m_data;
updateLastModified();
emit reset(); emit reset();
emit customDataModified(); emit customDataModified();
} }
QDateTime CustomData::getLastModified() const
{
if (m_data.contains(LastModified)) {
return Clock::parse(m_data.value(LastModified));
}
return {};
}
bool CustomData::operator==(const CustomData& other) const bool CustomData::operator==(const CustomData& other) const
{ {
return (m_data == other.m_data); return (m_data == other.m_data);
@ -152,3 +168,13 @@ int CustomData::dataSize() const
} }
return size; return size;
} }
void CustomData::updateLastModified()
{
if (m_data.size() == 1 && m_data.contains(LastModified)) {
m_data.remove(LastModified);
return;
}
m_data.insert(LastModified, Clock::currentDateTimeUtc().toString());
}

View file

@ -42,9 +42,12 @@ public:
int size() const; int size() const;
int dataSize() const; int dataSize() const;
void copyDataFrom(const CustomData* other); void copyDataFrom(const CustomData* other);
QDateTime getLastModified() const;
bool operator==(const CustomData& other) const; bool operator==(const CustomData& other) const;
bool operator!=(const CustomData& other) const; bool operator!=(const CustomData& other) const;
static const QString LastModified;
signals: signals:
void customDataModified(); void customDataModified();
void aboutToBeAdded(const QString& key); void aboutToBeAdded(const QString& key);
@ -55,6 +58,10 @@ signals:
void renamed(const QString& oldKey, const QString& newKey); void renamed(const QString& oldKey, const QString& newKey);
void aboutToBeReset(); void aboutToBeReset();
void reset(); void reset();
void lastModified();
private slots:
void updateLastModified();
private: private:
QHash<QString, QString> m_data; QHash<QString, QString> m_data;

View file

@ -609,9 +609,6 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
// TODO HNH: missing handling of recycle bin, names, templates for groups and entries, // TODO HNH: missing handling of recycle bin, names, templates for groups and entries,
// public data (entries of newer dict override keys of older dict - ignoring // public data (entries of newer dict override keys of older dict - ignoring
// their own age - it is enough if one entry of the whole dict is newer) => possible lost update // their own age - it is enough if one entry of the whole dict is newer) => possible lost update
// TODO HNH: CustomData is merged with entries of the new customData overwrite entries
// of the older CustomData - the dict with the newest entry is considered
// newer regardless of the age of the other entries => possible lost update
ChangeList changes; ChangeList changes;
auto* sourceMetadata = context.m_sourceDb->metadata(); auto* sourceMetadata = context.m_sourceDb->metadata();
auto* targetMetadata = context.m_targetDb->metadata(); auto* targetMetadata = context.m_targetDb->metadata();
@ -624,5 +621,32 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex())); changes << tr("Adding missing icon %1").arg(QString::fromLatin1(customIconId.toRfc4122().toHex()));
} }
} }
// Merge Custom Data if source is newer
const auto targetCustomDataModificationTime = sourceMetadata->customData()->getLastModified();
const auto sourceCustomDataModificationTime = targetMetadata->customData()->getLastModified();
if (!targetMetadata->customData()->contains(CustomData::LastModified) ||
(targetCustomDataModificationTime.isValid() && sourceCustomDataModificationTime.isValid() &&
targetCustomDataModificationTime > sourceCustomDataModificationTime)) {
const auto sourceCustomDataKeys = sourceMetadata->customData()->keys();
const auto targetCustomDataKeys = targetMetadata->customData()->keys();
// Check missing keys from source. Remove those from target
for (const auto& key : targetCustomDataKeys) {
if (!sourceMetadata->customData()->contains(key)) {
auto value = targetMetadata->customData()->value(key);
targetMetadata->customData()->remove(key);
changes << tr("Removed custom data %1 [%2]").arg(key, value);
}
}
// Transfer new/existing keys
for (const auto& key : sourceCustomDataKeys) {
auto value = sourceMetadata->customData()->value(key);
targetMetadata->customData()->set(key, value);
changes << tr("Adding custom data %1 [%2]").arg(key, value);
}
}
return changes; return changes;
} }

View file

@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid]; QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
if (!QPixmapCache::find(cacheKey, &pixmap)) { if (!QPixmapCache::find(cacheKey, &pixmap)) {
QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::KeepAspectRatio, Qt::SmoothTransformation); QImage image = m_customIcons.value(uuid).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pixmap = QPixmap::fromImage(image); pixmap = QPixmap::fromImage(image);
cacheKey = QPixmapCache::insert(pixmap); cacheKey = QPixmapCache::insert(pixmap);
} }

View file

@ -34,13 +34,14 @@
*/ */
void Translator::installTranslators() void Translator::installTranslators()
{ {
QLocale locale;
QString language = config()->get("GUI/Language").toString(); QString language = config()->get("GUI/Language").toString();
if (language == "system" || language.isEmpty()) { if (!language.isEmpty() && language != "system") {
language = QLocale::system().name();
}
if (language == "en") {
// use actual English translation instead of the English locale source language // use actual English translation instead of the English locale source language
language = "en_US"; if (language == "en") {
language = "en_US";
}
locale = QLocale(language);
} }
const QStringList paths = { const QStringList paths = {
@ -51,11 +52,12 @@ void Translator::installTranslators()
bool translationsLoaded = false; bool translationsLoaded = false;
for (const QString& path : paths) { for (const QString& path : paths) {
translationsLoaded |= installTranslator(language, path) || installTranslator("en_US", path); translationsLoaded |= installTranslator(locale, path) || installTranslator(QLocale("en_US"), path);
if (!installQtTranslator(language, path)) { if (!installQtTranslator(language, path)) {
installQtTranslator("en", path); installQtTranslator(QLocale("en"), path);
} }
} }
if (!translationsLoaded) { if (!translationsLoaded) {
// couldn't load configured language or fallback // couldn't load configured language or fallback
qWarning("Couldn't load translations."); qWarning("Couldn't load translations.");
@ -114,10 +116,10 @@ QList<QPair<QString, QString>> Translator::availableLanguages()
* @param path local search path * @param path local search path
* @return true on success * @return true on success
*/ */
bool Translator::installTranslator(const QString& language, const QString& path) bool Translator::installTranslator(const QLocale& locale, const QString& path)
{ {
QScopedPointer<QTranslator> translator(new QTranslator(qApp)); QScopedPointer<QTranslator> translator(new QTranslator(qApp));
if (translator->load(QString("keepassx_%1").arg(language), path)) { if (translator->load(locale, "keepassx_", "", path)) {
return QCoreApplication::installTranslator(translator.take()); return QCoreApplication::installTranslator(translator.take());
} }
return false; return false;
@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path)
* @param path local search path * @param path local search path
* @return true on success * @return true on success
*/ */
bool Translator::installQtTranslator(const QString& language, const QString& path) bool Translator::installQtTranslator(const QLocale& locale, const QString& path)
{ {
QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp)); QScopedPointer<QTranslator> qtTranslator(new QTranslator(qApp));
if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) { if (qtTranslator->load(locale, "qtbase_", "", path)) {
return QCoreApplication::installTranslator(qtTranslator.take()); return QCoreApplication::installTranslator(qtTranslator.take());
} else if (qtTranslator->load(QString("qtbase_%1").arg(language), } else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
return QCoreApplication::installTranslator(qtTranslator.take()); return QCoreApplication::installTranslator(qtTranslator.take());
} }
return false; return false;

View file

@ -20,6 +20,7 @@
#include <QPair> #include <QPair>
#include <QString> #include <QString>
#include <QLocale>
class Translator class Translator
{ {
@ -28,8 +29,8 @@ public:
static QList<QPair<QString, QString>> availableLanguages(); static QList<QPair<QString, QString>> availableLanguages();
private: private:
static bool installTranslator(const QString& language, const QString& path); static bool installTranslator(const QLocale& locale, const QString& path);
static bool installQtTranslator(const QString& language, const QString& path); static bool installQtTranslator(const QLocale& locale, const QString& path);
}; };
#endif // KEEPASSX_TRANSLATOR_H #endif // KEEPASSX_TRANSLATOR_H

View file

@ -35,7 +35,7 @@ Argon2Kdf::Argon2Kdf()
, m_memory(1 << 16) , m_memory(1 << 16)
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount())) , m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
{ {
m_rounds = 1; m_rounds = 10;
} }
quint32 Argon2Kdf::version() const quint32 Argon2Kdf::version() const

View file

@ -35,7 +35,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
{ {
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4); Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
m_binaryPoolInverse.clear(); m_binaryPool.clear();
if (hasError()) { if (hasError()) {
return false; return false;
@ -273,11 +273,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
return false; return false;
} }
auto data = fieldData.mid(1); auto data = fieldData.mid(1);
if (m_binaryPoolInverse.contains(data)) { m_binaryPool.insert(QString::number(m_binaryPool.size()), data);
qWarning("Skipping duplicate binary record");
break;
}
m_binaryPoolInverse.insert(data, QString::number(m_binaryPoolInverse.size()));
break; break;
} }
} }
@ -422,17 +418,5 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
*/ */
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
{ {
QHash<QString, QByteArray> binaryPool; return m_binaryPool;
for (auto it = m_binaryPoolInverse.cbegin(); it != m_binaryPoolInverse.cend(); ++it) {
binaryPool.insert(it.value(), it.key());
}
return binaryPool;
}
/**
* @return mapping from binary data to attachment keys
*/
QHash<QByteArray, QString> Kdbx4Reader::binaryPoolInverse() const
{
return m_binaryPoolInverse;
} }

View file

@ -34,7 +34,6 @@ public:
const QByteArray& headerData, const QByteArray& headerData,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
Database* db) override; Database* db) override;
QHash<QByteArray, QString> binaryPoolInverse() const;
QHash<QString, QByteArray> binaryPool() const; QHash<QString, QByteArray> binaryPool() const;
protected: protected:
@ -44,7 +43,7 @@ private:
bool readInnerHeaderField(QIODevice* device); bool readInnerHeaderField(QIODevice* device);
QVariantMap readVariantMap(QIODevice* device); QVariantMap readVariantMap(QIODevice* device);
QHash<QByteArray, QString> m_binaryPoolInverse; QHash<QString, QByteArray> m_binaryPool;
}; };
#endif // KEEPASSX_KDBX4READER_H #endif // KEEPASSX_KDBX4READER_H

View file

@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
, m_globalAutoTypeModifiers(Qt::NoModifier) , m_globalAutoTypeModifiers(Qt::NoModifier)
{ {
setHeadline(tr("Application Settings")); setHeadline(tr("Application Settings"));
showApplyButton(false);
m_secUi->setupUi(m_secWidget); m_secUi->setupUi(m_secWidget);
m_generalUi->setupUi(m_generalWidget); m_generalUi->setupUi(m_generalWidget);
@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
} }
connect(this, SIGNAL(accepted()), SLOT(saveSettings())); connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
connect(this, SIGNAL(rejected()), SLOT(reject())); connect(this, SIGNAL(rejected()), SLOT(reject()));
// clang-format off // clang-format off

View file

@ -212,7 +212,7 @@
<item> <item>
<widget class="QCheckBox" name="fallbackToSearch"> <widget class="QCheckBox" name="fallbackToSearch">
<property name="text"> <property name="text">
<string>Use DuckDuckGo as fallback for downloading website icons</string> <string>Use DuckDuckGo service to download website icons</string>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -59,10 +59,14 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)),
m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*)));
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType())); connect(autoType(), SIGNAL(globalAutoTypeTriggered()), SLOT(performGlobalAutoType()));
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase())); connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
// clang-format on // clang-format on
#ifdef Q_OS_MACOS
connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
#endif
} }
DatabaseTabWidget::~DatabaseTabWidget() DatabaseTabWidget::~DatabaseTabWidget()
@ -558,7 +562,7 @@ void DatabaseTabWidget::unlockDatabaseInDialog(DatabaseWidget* dbWidget,
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) { if (intent == DatabaseOpenDialog::Intent::AutoType || intent == DatabaseOpenDialog::Intent::Browser) {
macUtils()->raiseOwnWindow(); macUtils()->raiseOwnWindow();
Tools::wait(500); Tools::wait(200);
} }
#endif #endif

View file

@ -174,7 +174,8 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged())); connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged())); connect(m_previewSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(previewSplitterSizesChanged()));
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode))); connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), m_previewView, SLOT(setDatabaseMode(DatabaseWidget::Mode)));
connect(m_previewView, SIGNAL(errorOccurred(QString)), this, SLOT(showErrorMessage(QString))); connect(m_previewView, SIGNAL(errorOccurred(QString)), SLOT(showErrorMessage(QString)));
connect(m_previewView, SIGNAL(entryUrlActivated(Entry*)), SLOT(openUrlForEntry(Entry*)));
connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged())); connect(m_entryView, SIGNAL(viewStateChanged()), SIGNAL(entryViewStateChanged()));
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*))); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged())); connect(m_groupView, SIGNAL(groupSelectionChanged(Group*)), SIGNAL(groupChanged()));
@ -190,7 +191,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool))); connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool))); connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile())); connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile()));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged())); connect(this, SIGNAL(currentChanged(int)), SLOT(emitCurrentModeChanged()));
// clang-format on // clang-format on
connectDatabaseSignals(); connectDatabaseSignals();
@ -652,6 +653,10 @@ void DatabaseWidget::openUrl()
void DatabaseWidget::openUrlForEntry(Entry* entry) void DatabaseWidget::openUrlForEntry(Entry* entry)
{ {
Q_ASSERT(entry); Q_ASSERT(entry);
if (!entry) {
return;
}
QString cmdString = entry->resolveMultiplePlaceholders(entry->url()); QString cmdString = entry->resolveMultiplePlaceholders(entry->url());
if (cmdString.startsWith("cmd://")) { if (cmdString.startsWith("cmd://")) {
// check if decision to execute command was stored // check if decision to execute command was stored
@ -695,9 +700,9 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
} }
} }
} else { } else {
QString urlString = entry->webUrl(); QUrl url = QUrl(entry->url());
if (!urlString.isEmpty()) { if (!url.isEmpty()) {
QDesktopServices::openUrl(urlString); QDesktopServices::openUrl(url);
} }
} }
} }
@ -782,6 +787,9 @@ void DatabaseWidget::switchToMainView(bool previousDialogAccepted)
} }
m_newParent = nullptr; m_newParent = nullptr;
} else {
// Workaround: ensure entries are focused so search doesn't reset
m_entryView->setFocus();
} }
setCurrentWidget(m_mainWidget); setCurrentWidget(m_mainWidget);
@ -1158,9 +1166,10 @@ void DatabaseWidget::onDatabaseModified()
{ {
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) { if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
save(); save();
} else {
// Only block once, then reset
m_blockAutoSave = false;
} }
m_blockAutoSave = false;
} }
QString DatabaseWidget::getCurrentSearch() QString DatabaseWidget::getCurrentSearch()
@ -1258,11 +1267,13 @@ bool DatabaseWidget::lock()
} }
if (m_db->isModified()) { 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()) {
if (!save()) { saved = save();
return false; }
}
} else { if (!saved) {
QString msg; QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) { if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped()); msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
@ -1521,11 +1532,14 @@ bool DatabaseWidget::save()
return true; return true;
} }
// Read-only and new databases ask for filename
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) { if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
return saveAs(); return saveAs();
} }
// Prevent recursions and infinite save loops
blockAutoReload(true); blockAutoReload(true);
m_blockAutoSave = true;
++m_saveAttempts; ++m_saveAttempts;
// TODO: Make this async, but lock out the database widget to prevent re-entrance // TODO: Make this async, but lock out the database widget to prevent re-entrance
@ -1536,6 +1550,7 @@ bool DatabaseWidget::save()
if (ok) { if (ok) {
m_saveAttempts = 0; m_saveAttempts = 0;
m_blockAutoSave = false;
return true; return true;
} }

View file

@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e)
} }
} else } else
#endif #endif
if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { if (!e->modifiers() || e->modifiers() == Qt::ControlModifier
|| (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) {
switch (e->key()) { switch (e->key()) {
case Qt::Key_Enter: case Qt::Key_Enter:
case Qt::Key_Return: case Qt::Key_Return:

View file

@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon()
QString fullyQualifiedDomain = m_url.host(); QString fullyQualifiedDomain = m_url.host();
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
// Determine if host portion of URL is an IP address by resolving it and // Determine if host portion of URL is an IP address by resolving it and
// searching for a match with the returned address(es). // searching for a match with the returned address(es).
bool hostIsIp = false; bool hostIsIp = false;
@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon()
} }
} }
// Determine the second-level domain, if available
QString secondLevelDomain;
if (!hostIsIp) { if (!hostIsIp) {
QString secondLevelDomain = getSecondLevelDomain(m_url); secondLevelDomain = getSecondLevelDomain(m_url);
// Attempt to simply load the favicon.ico file
if (fullyQualifiedDomain != secondLevelDomain) {
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
}
} }
// Try to use alternative fallback URL, if enabled // Start with the "fallback" url (if enabled) to try to get the best favicon
if (config()->get("security/IconDownloadFallback", false).toBool()) { if (config()->get("security/IconDownloadFallback", false).toBool()) {
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com"); QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico"); fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
m_urlsToTry.append(fallbackUrl); m_urlsToTry.append(fallbackUrl);
if (!hostIsIp) { // Also try a direct pull of the second-level domain (if possible)
QString secondLevelDomain = getSecondLevelDomain(m_url); if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
if (fullyQualifiedDomain != secondLevelDomain) { m_urlsToTry.append(fallbackUrl);
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
m_urlsToTry.append(fallbackUrl);
}
} }
} }
// Add a direct pull of the website's own favicon.ico file
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
// Also try a direct pull of the second-level domain (if possible)
if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
}
// Use the first URL to start the download process
// If a favicon is not found, the next URL will be tried
startFetchFavicon(m_urlsToTry.takeFirst()); startFetchFavicon(m_urlsToTry.takeFirst());
#endif #endif
} }
@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished()
if (!image.isNull()) { if (!image.isNull()) {
if (!addCustomIcon(image)) { if (!addCustomIcon(image)) {
emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
} else if (!this->isVisible()) { } else if (!isVisible()) {
// Show confirmation message if triggered from Entry tab download button // Show confirmation message if triggered from Entry tab download button
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive); emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
} }
@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished()
if (!fallbackEnabled) { if (!fallbackEnabled) {
emit messageEditEntry( emit messageEditEntry(
tr("Unable to fetch favicon.") + "\n" tr("Unable to fetch favicon.") + "\n"
+ tr("Hint: You can enable DuckDuckGo as a fallback under Tools>Settings>Security"), + tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security"),
MessageWidget::Error); MessageWidget::Error);
} else { } else {
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);

View file

@ -54,11 +54,13 @@ EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
m_ui->entryAttachmentsWidget->setReadOnly(true); m_ui->entryAttachmentsWidget->setReadOnly(true);
m_ui->entryAttachmentsWidget->setButtonsVisible(false); m_ui->entryAttachmentsWidget->setButtonsVisible(false);
connect(m_ui->entryUrlLabel, SIGNAL(linkActivated(QString)), SLOT(openEntryUrl()));
connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool))); connect(m_ui->entryTotpButton, SIGNAL(toggled(bool)), m_ui->entryTotpWidget, SLOT(setVisible(bool)));
connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide())); connect(m_ui->entryCloseButton, SIGNAL(clicked()), SLOT(hide()));
connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool))); connect(m_ui->togglePasswordButton, SIGNAL(clicked(bool)), SLOT(setPasswordVisible(bool)));
connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection); connect(m_ui->entryTabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndexes()), Qt::QueuedConnection);
connect(&m_totpTimer, SIGNAL(timeout()), this, SLOT(updateTotpLabel())); connect(&m_totpTimer, SIGNAL(timeout()), SLOT(updateTotpLabel()));
// Group // Group
m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close")); m_ui->groupCloseButton->setIcon(filePath()->icon("actions", "dialog-close"));
@ -197,11 +199,12 @@ void EntryPreviewWidget::updateEntryGeneralTab()
} }
m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl()); m_ui->entryUrlLabel->setRawText(m_currentEntry->displayUrl());
const QString url = m_currentEntry->webUrl(); const QString url = m_currentEntry->url();
if (!url.isEmpty()) { if (!url.isEmpty()) {
// URL is well formed and can be opened in a browser // URL is well formed and can be opened in a browser
m_ui->entryUrlLabel->setUrl(url); m_ui->entryUrlLabel->setUrl(url);
m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor); m_ui->entryUrlLabel->setCursor(Qt::PointingHandCursor);
m_ui->entryUrlLabel->setOpenExternalLinks(false);
} else { } else {
m_ui->entryUrlLabel->setUrl({}); m_ui->entryUrlLabel->setUrl({});
m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor); m_ui->entryUrlLabel->setCursor(Qt::ArrowCursor);
@ -327,6 +330,13 @@ void EntryPreviewWidget::updateTabIndexes()
m_selectedTabGroup = m_ui->groupTabWidget->currentIndex(); m_selectedTabGroup = m_ui->groupTabWidget->currentIndex();
} }
void EntryPreviewWidget::openEntryUrl()
{
if (m_currentEntry) {
emit entryUrlActivated(m_currentEntry);
}
}
void EntryPreviewWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled) void EntryPreviewWidget::setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled)
{ {
const int tabIndex = tabWidget->indexOf(widget); const int tabIndex = tabWidget->indexOf(widget);

View file

@ -43,6 +43,7 @@ public slots:
signals: signals:
void errorOccurred(const QString& error); void errorOccurred(const QString& error);
void entryUrlActivated(Entry* entry);
private slots: private slots:
void updateEntryHeaderLine(); void updateEntryHeaderLine();
@ -63,6 +64,7 @@ private slots:
void updateTotpLabel(); void updateTotpLabel();
void updateTabIndexes(); void updateTabIndexes();
void openEntryUrl();
private: private:
void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled); void setTabEnabled(QTabWidget* tabWidget, QWidget* widget, bool enabled);

View file

@ -41,6 +41,10 @@
#include "keys/FileKey.h" #include "keys/FileKey.h"
#include "keys/PasswordKey.h" #include "keys/PasswordKey.h"
#ifdef Q_OS_MACOS
#include "macutils/MacUtils.h"
#endif
#ifdef WITH_XC_UPDATECHECK #ifdef WITH_XC_UPDATECHECK
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "gui/UpdateCheckDialog.h" #include "gui/UpdateCheckDialog.h"
@ -135,6 +139,7 @@ MainWindow::MainWindow()
, m_trayIcon(nullptr) , m_trayIcon(nullptr)
, m_appExitCalled(false) , m_appExitCalled(false)
, m_appExiting(false) , m_appExiting(false)
, m_lastFocusOutTime(0)
{ {
g_MainWindow = this; g_MainWindow = this;
@ -248,6 +253,9 @@ MainWindow::MainWindow()
m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true); m_ui->actionEntryCopyURL->setShortcutVisibleInContextMenu(true);
#endif #endif
connect(m_ui->menuEntries, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
connect(m_ui->menuGroups, SIGNAL(aboutToHide()), SLOT(releaseContextFocusLock()));
// Control window state // Control window state
new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized())); new QShortcut(Qt::CTRL + Qt::Key_M, this, SLOT(showMinimized()));
new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow())); new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M, this, SLOT(hideWindow()));
@ -370,6 +378,9 @@ MainWindow::MainWindow()
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true); setUnifiedTitleAndToolBarOnMac(true);
if (macUtils()->isDarkMode()) {
setStyleSheet("QToolButton {color:white;}");
}
#endif #endif
#ifdef WITH_XC_UPDATECHECK #ifdef WITH_XC_UPDATECHECK
@ -396,6 +407,12 @@ MainWindow::MainWindow()
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock())); connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
#endif #endif
// Tray Icon setup
connect(Application::instance(), SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*)));
m_trayIconTriggerReason = QSystemTrayIcon::Unknown;
m_trayIconTriggerTimer.setSingleShot(true);
connect(&m_trayIconTriggerTimer, SIGNAL(timeout()), SLOT(processTrayIconTrigger()));
updateTrayIcon(); updateTrayIcon();
if (config()->hasAccessError()) { if (config()->hasAccessError()) {
@ -529,8 +546,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
switch (mode) { switch (mode) {
case DatabaseWidget::Mode::ViewMode: { case DatabaseWidget::Mode::ViewMode: {
// bool inSearch = dbWidget->isInSearchMode(); // bool inSearch = dbWidget->isInSearchMode();
bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && dbWidget->currentEntryHasFocus(); bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1 && (m_contextMenuFocusLock || dbWidget->currentEntryHasFocus());
bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && dbWidget->currentEntryHasFocus(); bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0 && (m_contextMenuFocusLock || dbWidget->currentEntryHasFocus());
bool groupSelected = dbWidget->isGroupSelected(); bool groupSelected = dbWidget->isGroupSelected();
bool recycleBinSelected = dbWidget->isRecycleBinSelected(); bool recycleBinSelected = dbWidget->isRecycleBinSelected();
@ -857,7 +874,9 @@ void MainWindow::closeEvent(QCloseEvent* event)
return; return;
} }
if (config()->get("GUI/MinimizeOnClose").toBool() && !m_appExitCalled) { // Don't ignore close event when the app is hidden to tray.
// This can occur when the OS issues close events on shutdown.
if (config()->get("GUI/MinimizeOnClose").toBool() && !isHidden() && !m_appExitCalled) {
event->ignore(); event->ignore();
hideWindow(); hideWindow();
return; return;
@ -912,7 +931,7 @@ bool MainWindow::saveLastDatabases()
} }
QStringList openDatabases; QStringList openDatabases;
for (int i=0; i < m_ui->tabWidget->count(); ++i) { for (int i = 0; i < m_ui->tabWidget->count(); ++i) {
auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i); auto dbWidget = m_ui->tabWidget->databaseWidgetFromIndex(i);
openDatabases.append(dbWidget->database()->filePath()); openDatabases.append(dbWidget->database()->filePath());
} }
@ -936,6 +955,8 @@ void MainWindow::updateTrayIcon()
QAction* actionToggle = new QAction(tr("Toggle window"), menu); QAction* actionToggle = new QAction(tr("Toggle window"), menu);
menu->addAction(actionToggle); menu->addAction(actionToggle);
menu->addAction(m_ui->actionLockDatabases);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu); QAction* actionQuit = new QAction(tr("Quit KeePassXC"), menu);
menu->addAction(actionQuit); menu->addAction(actionQuit);
@ -969,13 +990,20 @@ void MainWindow::updateTrayIcon()
} }
} }
void MainWindow::releaseContextFocusLock()
{
m_contextMenuFocusLock = false;
}
void MainWindow::showEntryContextMenu(const QPoint& globalPos) void MainWindow::showEntryContextMenu(const QPoint& globalPos)
{ {
m_contextMenuFocusLock = true;
m_ui->menuEntries->popup(globalPos); m_ui->menuEntries->popup(globalPos);
} }
void MainWindow::showGroupContextMenu(const QPoint& globalPos) void MainWindow::showGroupContextMenu(const QPoint& globalPos)
{ {
m_contextMenuFocusLock = true;
m_ui->menuGroups->popup(globalPos); m_ui->menuGroups->popup(globalPos);
} }
@ -1029,10 +1057,38 @@ void MainWindow::applySettingsChanges()
updateTrayIcon(); updateTrayIcon();
} }
void MainWindow::focusWindowChanged(QWindow* focusWindow)
{
if (focusWindow != windowHandle()) {
m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
}
}
void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason) void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
{ {
if (reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::MiddleClick) { if (!m_trayIconTriggerTimer.isActive()) {
m_trayIconTriggerTimer.start(150);
}
// Overcome Qt bug https://bugreports.qt.io/browse/QTBUG-69698
// Store last issued tray icon activation reason to properly
// capture doubleclick events
m_trayIconTriggerReason = reason;
}
void MainWindow::processTrayIconTrigger()
{
if (m_trayIconTriggerReason == QSystemTrayIcon::DoubleClick) {
// Always toggle window on double click
toggleWindow(); toggleWindow();
} else if (m_trayIconTriggerReason == QSystemTrayIcon::Trigger
|| m_trayIconTriggerReason == QSystemTrayIcon::MiddleClick) {
// On single/middle click focus the window if it is not hidden
// and did not have focus less than a second ago, otherwise toggle
if (isHidden() || (Clock::currentSecondsSinceEpoch() - m_lastFocusOutTime) <= 1) {
toggleWindow();
} else {
bringToFront();
}
} }
} }

View file

@ -85,6 +85,7 @@ private slots:
void showAboutDialog(); void showAboutDialog();
void showUpdateCheckStartup(); void showUpdateCheckStartup();
void showUpdateCheckDialog(); void showUpdateCheckDialog();
void focusWindowChanged(QWindow* focusWindow);
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested); void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
void openDonateUrl(); void openDonateUrl();
void openBugReportUrl(); void openBugReportUrl();
@ -107,6 +108,7 @@ private slots:
void showGroupContextMenu(const QPoint& globalPos); void showGroupContextMenu(const QPoint& globalPos);
void applySettingsChanges(); void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason); void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
void processTrayIconTrigger();
void lockDatabasesAfterInactivity(); void lockDatabasesAfterInactivity();
void forgetTouchIDAfterInactivity(); void forgetTouchIDAfterInactivity();
void handleScreenLock(); void handleScreenLock();
@ -115,6 +117,7 @@ private slots:
void selectPreviousDatabaseTab(); void selectPreviousDatabaseTab();
void togglePasswordsHidden(); void togglePasswordsHidden();
void toggleUsernamesHidden(); void toggleUsernamesHidden();
void releaseContextFocusLock();
private: private:
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0); static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
@ -146,6 +149,10 @@ private:
bool m_appExitCalled; bool m_appExitCalled;
bool m_appExiting; bool m_appExiting;
bool m_contextMenuFocusLock;
uint m_lastFocusOutTime;
QTimer m_trayIconTriggerTimer;
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
}; };
/** /**

View file

@ -16,9 +16,6 @@
<property name="autoFillBackground"> <property name="autoFillBackground">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="styleSheet">
<string notr="true">#SearchHelpWidget { background-color: #ffffff }</string>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::Box</enum> <enum>QFrame::Box</enum>
</property> </property>

View file

@ -152,11 +152,6 @@ void EditEntryWidget::setupMain()
m_mainUi->expirePresets->setMenu(createPresetsMenu()); m_mainUi->expirePresets->setMenu(createPresetsMenu());
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*))); connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
QAction* action = new QAction(this);
action->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(action, SIGNAL(triggered()), this, SLOT(commitEntry()));
this->addAction(action);
m_mainUi->passwordGenerator->hide(); m_mainUi->passwordGenerator->hide();
m_mainUi->passwordGenerator->reset(); m_mainUi->passwordGenerator->reset();
} }
@ -285,7 +280,6 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString))); connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
#endif #endif
connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified())); connect(m_mainUi->expireCheck, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->notesEnabled, SIGNAL(stateChanged(int)), this, SLOT(setModified()));
connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified())); connect(m_mainUi->expireDatePicker, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(setModified()));
connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified())); connect(m_mainUi->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
@ -968,6 +962,7 @@ void EditEntryWidget::cancel()
m_entry->setIcon(Entry::DefaultIconNumber); m_entry->setIcon(Entry::DefaultIconNumber);
} }
bool accepted = false;
if (isModified()) { if (isModified()) {
auto result = MessageBox::question(this, auto result = MessageBox::question(this,
QString(), QString(),
@ -975,18 +970,17 @@ void EditEntryWidget::cancel()
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard, MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel); MessageBox::Cancel);
if (result == MessageBox::Cancel) { if (result == MessageBox::Cancel) {
m_mainUi->passwordGenerator->reset();
return; return;
} } else if (result == MessageBox::Save) {
if (result == MessageBox::Save) { accepted = true;
commitEntry(); if (!commitEntry()) {
setModified(false); return;
}
} }
} }
clear(); clear();
emit editFinished(accepted);
emit editFinished(!isModified());
} }
void EditEntryWidget::clear() void EditEntryWidget::clear()
@ -1111,8 +1105,9 @@ void EditEntryWidget::updateCurrentAttribute()
void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected) void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
{ {
// Block signals to prevent extra calls // Block signals to prevent modified being set
m_advancedUi->protectAttributeButton->blockSignals(true); m_advancedUi->protectAttributeButton->blockSignals(true);
m_advancedUi->attributesEdit->blockSignals(true);
if (index.isValid()) { if (index.isValid()) {
QString key = m_attributesModel->keyByIndex(index); QString key = m_attributesModel->keyByIndex(index);
@ -1143,6 +1138,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
} }
m_advancedUi->protectAttributeButton->blockSignals(false); m_advancedUi->protectAttributeButton->blockSignals(false);
m_advancedUi->attributesEdit->blockSignals(false);
} }
void EditEntryWidget::protectCurrentAttribute(bool state) void EditEntryWidget::protectCurrentAttribute(bool state)

View file

@ -7,9 +7,11 @@
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QMimeData> #include <QMimeData>
#include <QProcessEnvironment>
#include <QTemporaryFile> #include <QTemporaryFile>
#include "EntryAttachmentsModel.h" #include "EntryAttachmentsModel.h"
#include "config-keepassx.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/EntryAttachments.h" #include "core/EntryAttachments.h"
#include "core/Tools.h" #include "core/Tools.h"
@ -324,7 +326,12 @@ bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& e
const QByteArray attachmentData = m_entryAttachments->value(filename); const QByteArray attachmentData = m_entryAttachments->value(filename);
// tmp file will be removed once the database (or the application) has been closed // tmp file will be removed once the database (or the application) has been closed
#ifdef KEEPASSXC_DIST_SNAP
const QString tmpFileTemplate =
QString("%1/XXXXXX.%2").arg(QProcessEnvironment::systemEnvironment().value("SNAP_USER_DATA"), filename);
#else
const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename));
#endif
QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this)); QScopedPointer<QTemporaryFile> tmpFile(new QTemporaryFile(tmpFileTemplate, this));

View file

@ -139,17 +139,18 @@ void EntryView::keyPressEvent(QKeyEvent* event)
} }
int last = m_model->rowCount() - 1; int last = m_model->rowCount() - 1;
if (last > 0) {
if (event->key() == Qt::Key_Up && currentIndex().row() == 0) {
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(last, 0));
setCurrentEntry(m_model->entryFromIndex(index));
return;
}
if (event->key() == Qt::Key_Up && currentIndex().row() == 0) { if (event->key() == Qt::Key_Down && currentIndex().row() == last) {
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(last, 0)); QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
setCurrentEntry(m_model->entryFromIndex(index)); setCurrentEntry(m_model->entryFromIndex(index));
return; return;
} }
if (event->key() == Qt::Key_Down && currentIndex().row() == last) {
QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0));
setCurrentEntry(m_model->entryFromIndex(index));
return;
} }
QTreeView::keyPressEvent(event); QTreeView::keyPressEvent(event);

View file

@ -227,6 +227,9 @@ void EditGroupWidget::cancel()
tr("Entry has unsaved changes"), tr("Entry has unsaved changes"),
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard, MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel); MessageBox::Cancel);
if (result == MessageBox::Cancel) {
return;
}
if (result == MessageBox::Save) { if (result == MessageBox::Save) {
apply(); apply();
setModified(false); setModified(false);

View file

@ -72,6 +72,12 @@ void GroupView::dragMoveEvent(QDragMoveEvent* event)
} }
} }
void GroupView::focusInEvent(QFocusEvent* event)
{
emitGroupChanged();
QTreeView::focusInEvent(event);
}
Group* GroupView::currentGroup() Group* GroupView::currentGroup()
{ {
if (currentIndex() == QModelIndex()) { if (currentIndex() == QModelIndex()) {

View file

@ -47,6 +47,7 @@ private slots:
protected: protected:
void dragMoveEvent(QDragMoveEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
private: private:
void recInitExpanded(Group* group); void recInitExpanded(Group* group);

View file

@ -19,14 +19,15 @@
#ifndef KEEPASSX_APPKIT_H #ifndef KEEPASSX_APPKIT_H
#define KEEPASSX_APPKIT_H #define KEEPASSX_APPKIT_H
#include <QObject>
#include <unistd.h> #include <unistd.h>
extern "C" { class AppKit : public QObject
class AppKit
{ {
Q_OBJECT
public: public:
AppKit(); AppKit(QObject* parent = nullptr);
~AppKit(); ~AppKit();
pid_t lastActiveProcessId(); pid_t lastActiveProcessId();
@ -37,10 +38,11 @@ public:
bool isHidden(pid_t pid); bool isHidden(pid_t pid);
bool isDarkMode(); bool isDarkMode();
signals:
void lockDatabases();
private: private:
void *self; void *self;
}; };
} // extern "C"
#endif // KEEPASSX_APPKIT_H #endif // KEEPASSX_APPKIT_H

View file

@ -22,6 +22,10 @@
#import <AppKit/NSRunningApplication.h> #import <AppKit/NSRunningApplication.h>
@interface AppKitImpl : NSObject @interface AppKitImpl : NSObject
{
AppKit *m_appkit;
}
- (id) initWithObject:(AppKit *)appkit;
@property (strong) NSRunningApplication *lastActiveApplication; @property (strong) NSRunningApplication *lastActiveApplication;
@ -31,5 +35,6 @@
- (bool) hideProcess:(pid_t) pid; - (bool) hideProcess:(pid_t) pid;
- (bool) isHidden:(pid_t) pid; - (bool) isHidden:(pid_t) pid;
- (bool) isDarkMode; - (bool) isDarkMode;
- (void) userSwitchHandler:(NSNotification*) notification;
@end @end

View file

@ -22,19 +22,22 @@
@implementation AppKitImpl @implementation AppKitImpl
AppKit::AppKit() - (id) initWithObject:(AppKit *)appkit
{ {
self = [[AppKitImpl alloc] init]; self = [super init];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self) if (self) {
m_appkit = appkit;
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
selector:@selector(didDeactivateApplicationObserver:) selector:@selector(didDeactivateApplicationObserver:)
name:NSWorkspaceDidDeactivateApplicationNotification name:NSWorkspaceDidDeactivateApplicationNotification
object:nil]; object:nil];
}
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
AppKit::~AppKit() selector:@selector(userSwitchHandler:)
{ name:NSWorkspaceSessionDidResignActiveNotification
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)]; object:nil];
[static_cast<id>(self) dealloc]; }
return self;
} }
// //
@ -104,10 +107,34 @@ AppKit::~AppKit()
&& NSOrderedSame == [style caseInsensitiveCompare:@"dark"] ); && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
} }
//
// Notification for user switch
//
- (void) userSwitchHandler:(NSNotification*) notification
{
if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit)
{
emit m_appkit->lockDatabases();
}
}
@end
// //
// ------------------------- C++ Trampolines ------------------------- // ------------------------- C++ Trampolines -------------------------
// //
AppKit::AppKit(QObject* parent) : QObject(parent)
{
self = [[AppKitImpl alloc] initWithObject:this];
}
AppKit::~AppKit()
{
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
[static_cast<id>(self) dealloc];
}
pid_t AppKit::lastActiveProcessId() pid_t AppKit::lastActiveProcessId()
{ {
return [static_cast<id>(self) lastActiveApplication].processIdentifier; return [static_cast<id>(self) lastActiveApplication].processIdentifier;
@ -142,5 +169,3 @@ bool AppKit::isDarkMode()
{ {
return [static_cast<id>(self) isDarkMode]; return [static_cast<id>(self) isDarkMode];
} }
@end

View file

@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) : QObject(parent) MacUtils::MacUtils(QObject* parent) : QObject(parent)
, m_appkit(new AppKit()) , m_appkit(new AppKit())
{ {
connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));
} }
MacUtils::~MacUtils() MacUtils::~MacUtils()

View file

@ -39,14 +39,16 @@ public:
bool isHidden(); bool isHidden();
bool isDarkMode(); bool isDarkMode();
signals:
void lockDatabases();
private: private:
explicit MacUtils(QObject* parent = nullptr); explicit MacUtils(QObject* parent = nullptr);
~MacUtils(); ~MacUtils();
private: private:
std::unique_ptr<AppKit> m_appkit; QScopedPointer<AppKit> m_appkit;
static MacUtils* m_instance; static MacUtils* m_instance;
void* self;
Q_DISABLE_COPY(MacUtils) Q_DISABLE_COPY(MacUtils)
}; };

View file

@ -1,4 +1,6 @@
if(WITH_XC_KEESHARE) if(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(keeshare_SOURCES set(keeshare_SOURCES
@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE)
) )
add_library(keeshare STATIC ${keeshare_SOURCES}) add_library(keeshare STATIC ${keeshare_SOURCES})
if(WITH_XC_KEESHARE_SECURE) target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
# Try to find libquazip5, if found, enable secure sharing
find_package(QuaZip)
if(QUAZIP_FOUND)
set(WITH_XC_KEESHARE_SECURE ON PARENT_SCOPE)
target_include_directories(keeshare SYSTEM PRIVATE ${QUAZIP_INCLUDE_DIR})
target_link_libraries(keeshare PRIVATE ${QUAZIP_LIBRARIES})
else() else()
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
message(STATUS "KeeShare: Secure container support is DISABLED; quazip library not found")
endif() endif()
endif() else(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE)
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
endif(WITH_XC_KEESHARE)

View file

@ -46,8 +46,8 @@
#include <QStringBuilder> #include <QStringBuilder>
#if defined(WITH_XC_KEESHARE_SECURE) #if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip5/quazip.h> #include <quazip.h>
#include <quazip5/quazipfile.h> #include <quazipfile.h>
#endif #endif
namespace namespace

View file

@ -18,19 +18,35 @@
#include "FileKey.h" #include "FileKey.h"
#include <QFile>
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/Random.h" #include "crypto/Random.h"
#include <QFile>
#include <sodium.h>
#include <gcrypt.h>
#include <algorithm>
#include <cstring>
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273"); QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
constexpr int FileKey::SHA256_SIZE;
FileKey::FileKey() FileKey::FileKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
} }
FileKey::~FileKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
/** /**
* Read key file from device while trying to detect its file format. * Read key file from device while trying to detect its file format.
* *
@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/ */
QByteArray FileKey::rawKey() const QByteArray FileKey::rawKey() const
{ {
return m_key; if (!m_key) {
return {};
}
return QByteArray::fromRawData(m_key, SHA256_SIZE);
} }
/** /**
@ -223,12 +242,15 @@ bool FileKey::loadXml(QIODevice* device)
} }
} }
bool ok = false;
if (!xmlReader.error() && correctMeta && !data.isEmpty()) { if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
m_key = data; std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
return true; ok = true;
} }
return false; sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return ok;
} }
/** /**
@ -293,7 +315,8 @@ bool FileKey::loadBinary(QIODevice* device)
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) { if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
return false; return false;
} else { } else {
m_key = data; std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return true; return true;
} }
} }
@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
} }
QByteArray key = QByteArray::fromHex(data); QByteArray key = QByteArray::fromHex(data);
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
if (key.size() != 32) { if (key.size() != 32) {
return false; return false;
} }
m_key = key; std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
return true; return true;
} }
@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer); cryptoHash.addData(buffer);
} while (!buffer.isEmpty()); } while (!buffer.isEmpty());
m_key = cryptoHash.result(); auto result = cryptoHash.result();
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
return true; return true;
} }

View file

@ -40,6 +40,7 @@ public:
}; };
FileKey(); FileKey();
~FileKey() override;
bool load(QIODevice* device); bool load(QIODevice* device);
bool load(const QString& fileName, QString* errorMsg = nullptr); bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override; QByteArray rawKey() const override;
@ -48,6 +49,8 @@ public:
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128); static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
private: private:
static constexpr int SHA256_SIZE = 32;
bool loadXml(QIODevice* device); bool loadXml(QIODevice* device);
bool loadXmlMeta(QXmlStreamReader& xmlReader); bool loadXmlMeta(QXmlStreamReader& xmlReader);
QByteArray loadXmlKey(QXmlStreamReader& xmlReader); QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
@ -55,7 +58,7 @@ private:
bool loadHex(QIODevice* device); bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device); bool loadHashed(QIODevice* device);
QByteArray m_key; char* m_key = nullptr;
Type m_type = None; Type m_type = None;
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 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 * 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 * it under the terms of the GNU General Public License as published by
@ -16,35 +16,51 @@
*/ */
#include "PasswordKey.h" #include "PasswordKey.h"
#include "core/Tools.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include <gcrypt.h>
#include <algorithm>
#include <cstring>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead"); QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
constexpr int PasswordKey::SHA256_SIZE;
PasswordKey::PasswordKey() PasswordKey::PasswordKey()
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
} }
PasswordKey::PasswordKey(const QString& password) PasswordKey::PasswordKey(const QString& password)
: Key(UUID) : Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{ {
setPassword(password); setPassword(password);
} }
PasswordKey::~PasswordKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey) QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{ {
auto result = QSharedPointer<PasswordKey>::create(); auto result = QSharedPointer<PasswordKey>::create();
result->m_key = rawKey; std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
return result; return result;
} }
QByteArray PasswordKey::rawKey() const QByteArray PasswordKey::rawKey() const
{ {
return m_key; return QByteArray::fromRawData(m_key, SHA256_SIZE);
} }
void PasswordKey::setPassword(const QString& password) void PasswordKey::setPassword(const QString& password)
{ {
m_key = CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256); std::memcpy(m_key, CryptoHash::hash(password.toUtf8(), CryptoHash::Sha256).data(), SHA256_SIZE);
} }

View file

@ -30,13 +30,16 @@ public:
PasswordKey(); PasswordKey();
explicit PasswordKey(const QString& password); explicit PasswordKey(const QString& password);
~PasswordKey() override;
QByteArray rawKey() const override; QByteArray rawKey() const override;
void setPassword(const QString& password); void setPassword(const QString& password);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey); static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
private: private:
QByteArray m_key; static constexpr int SHA256_SIZE = 32;
char* m_key = nullptr;
}; };
#endif // KEEPASSX_PASSWORDKEY_H #endif // KEEPASSX_PASSWORDKEY_H

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com> * Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -32,6 +32,10 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QtConcurrent> #include <QtConcurrent>
#include <gcrypt.h>
#include <sodium.h>
#include <cstring>
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508"); QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking) YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
@ -45,9 +49,18 @@ YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
} }
} }
YkChallengeResponseKey::~YkChallengeResponseKey()
{
if (m_key) {
gcry_free(m_key);
m_keySize = 0;
m_key = nullptr;
}
}
QByteArray YkChallengeResponseKey::rawKey() const QByteArray YkChallengeResponseKey::rawKey() const
{ {
return m_key; return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
} }
/** /**
@ -67,14 +80,22 @@ bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int
emit userInteractionRequired(); emit userInteractionRequired();
} }
QByteArray key;
auto result = AsyncTask::runAndWaitForFuture( auto result = AsyncTask::runAndWaitForFuture(
[this, challenge]() { return YubiKey::instance()->challenge(m_slot, true, challenge, m_key); }); [this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); });
if (m_blocking) { if (m_blocking) {
emit userConfirmed(); emit userConfirmed();
} }
if (result == YubiKey::SUCCESS) { if (result == YubiKey::SUCCESS) {
if (m_key) {
gcry_free(m_key);
}
m_keySize = static_cast<std::size_t>(key.size());
m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
std::memcpy(m_key, key.data(), m_keySize);
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
return true; return true;
} }
} while (retries > 0); } while (retries > 0);

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -32,6 +32,7 @@ public:
static QUuid UUID; static QUuid UUID;
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false); explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
~YkChallengeResponseKey() override;
QByteArray rawKey() const override; QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override; bool challenge(const QByteArray& challenge) override;
@ -52,7 +53,8 @@ signals:
void userConfirmed(); void userConfirmed();
private: private:
QByteArray m_key; char* m_key = nullptr;
std::size_t m_keySize = 0;
int m_slot; int m_slot;
bool m_blocking; bool m_blocking;
}; };

View file

@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR}) include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES set(proxy_SOURCES
../core/Alloc.cpp
keepassxc-proxy.cpp keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp ${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp) NativeMessagingHost.cpp)
add_library(proxy STATIC ${proxy_SOURCES}) add_library(proxy STATIC ${proxy_SOURCES})
target_link_libraries(proxy Qt5::Core Qt5::Network) target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy keepassxc-proxy.cpp) add_executable(keepassxc-proxy keepassxc-proxy.cpp)
target_link_libraries(keepassxc-proxy proxy) target_link_libraries(keepassxc-proxy proxy)

View file

@ -128,9 +128,9 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1") auto urlstring = QString("otpauth://totp/%1:%2?secret=%3&period=%4&digits=%5&issuer=%1")
.arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)), .arg(title.isEmpty() ? "KeePassXC" : QString(QUrl::toPercentEncoding(title)),
username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)), username.isEmpty() ? "none" : QString(QUrl::toPercentEncoding(username)),
QString(Base32::sanitizeInput(settings->key.toLatin1()))) QString(Base32::sanitizeInput(settings->key.toLatin1())),
.arg(settings->step) QString::number(settings->step),
.arg(settings->digits); QString::number(settings->digits));
if (!settings->encoder.name.isEmpty()) { if (!settings->encoder.name.isEmpty()) {
urlstring.append("&encoder=").append(settings->encoder.name); urlstring.append("&encoder=").append(settings->encoder.name);

View file

@ -15,6 +15,7 @@
inline void debug(const char* message, ...) inline void debug(const char* message, ...)
{ {
Q_UNUSED(message);
// qWarning(...); // qWarning(...);
} }
@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
NSString* authMessage = msg.toNSString(); // autoreleased NSString* authMessage = msg.toNSString(); // autoreleased
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:authMessage reply:^(BOOL success, NSError* error) { localizedReason:authMessage reply:^(BOOL success, NSError* error) {
Q_UNUSED(error);
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed; result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
CFRunLoopWakeUp(CFRunLoopGetCurrent()); CFRunLoopWakeUp(CFRunLoopGetCurrent());
}]; }];

View file

@ -220,6 +220,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
add_unit_test(NAME testtools SOURCES TestTools.cpp add_unit_test(NAME testtools SOURCES TestTools.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})
if(WITH_XC_BROWSER)
add_unit_test(NAME testbrowser SOURCES TestBrowser.cpp
LIBS ${TEST_LIBRARIES})
endif()
if(WITH_GUI_TESTS) if(WITH_GUI_TESTS)
# CLI clip tests need X environment on Linux # CLI clip tests need X environment on Linux

View file

@ -157,6 +157,7 @@ void TestAutoType::testGlobalAutoTypeWithNoMatch()
void TestAutoType::testGlobalAutoTypeWithOneMatch() void TestAutoType::testGlobalAutoTypeWithOneMatch()
{ {
m_test->setActiveWindowTitle("custom window"); m_test->setActiveWindowTitle("custom window");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password())); QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password()));
@ -167,6 +168,7 @@ void TestAutoType::testGlobalAutoTypeTitleMatch()
config()->set("AutoTypeEntryTitleMatch", true); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter))); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter)));
@ -177,6 +179,7 @@ void TestAutoType::testGlobalAutoTypeUrlMatch()
config()->set("AutoTypeEntryTitleMatch", true); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
@ -187,6 +190,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
config()->set("AutoTypeEntryTitleMatch", true); config()->set("AutoTypeEntryTitleMatch", true);
m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>"); m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter))); QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
@ -195,6 +199,7 @@ void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled() void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
{ {
m_test->setActiveWindowTitle("An Entry Title!"); m_test->setActiveWindowTitle("An Entry Title!");
m_test->triggerGlobalAutoType();
MessageBox::setNextAnswer(MessageBox::Ok); MessageBox::setNextAnswer(MessageBox::Ok);
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
@ -205,58 +210,68 @@ void TestAutoType::testGlobalAutoTypeRegExp()
{ {
// substring matches are ok // substring matches are ok
m_test->setActiveWindowTitle("lorem REGEX1 ipsum"); m_test->setActiveWindowTitle("lorem REGEX1 ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// should be case-insensitive // should be case-insensitive
m_test->setActiveWindowTitle("lorem regex1 ipsum"); m_test->setActiveWindowTitle("lorem regex1 ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex1")); QCOMPARE(m_test->actionChars(), QString("regex1"));
m_test->clearActions(); m_test->clearActions();
// exact match // exact match
m_test->setActiveWindowTitle("REGEX2"); m_test->setActiveWindowTitle("REGEX2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex2")); QCOMPARE(m_test->actionChars(), QString("regex2"));
m_test->clearActions(); m_test->clearActions();
// a bit more complicated regex // a bit more complicated regex
m_test->setActiveWindowTitle("REGEX3-R2D2"); m_test->setActiveWindowTitle("REGEX3-R2D2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("regex3")); QCOMPARE(m_test->actionChars(), QString("regex3"));
m_test->clearActions(); m_test->clearActions();
// with custom attributes // with custom attributes
m_test->setActiveWindowTitle("CustomAttr1"); m_test->setActiveWindowTitle("CustomAttr1");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute")); QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute"));
m_test->clearActions(); m_test->clearActions();
// with (non uppercase) undefined custom attributes // with (non uppercase) undefined custom attributes
m_test->setActiveWindowTitle("CustomAttr2"); m_test->setActiveWindowTitle("CustomAttr2");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("")); QCOMPARE(m_test->actionChars(), QString(""));
m_test->clearActions(); m_test->clearActions();
// with mixedcase default attributes // with mixedcase default attributes
m_test->setActiveWindowTitle("CustomAttr3"); m_test->setActiveWindowTitle("CustomAttr3");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr")); QCOMPARE(m_test->actionChars(), QString("custom_attr"));
m_test->clearActions(); m_test->clearActions();
// with resolve placeholders in window association title // with resolve placeholders in window association title
m_test->setActiveWindowTitle("AttrValueFirst"); m_test->setActiveWindowTitle("AttrValueFirst");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum"); m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second")); QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second"));
m_test->clearActions(); m_test->clearActions();
m_test->setActiveWindowTitle("lorem AttrValueThird ipsum"); m_test->setActiveWindowTitle("lorem AttrValueThird ipsum");
m_test->triggerGlobalAutoType();
m_autoType->performGlobalAutoType(m_dbList); m_autoType->performGlobalAutoType(m_dbList);
QCOMPARE(m_test->actionChars(), QString("custom_attr_third")); QCOMPARE(m_test->actionChars(), QString("custom_attr_third"));
m_test->clearActions(); m_test->clearActions();

344
tests/TestBrowser.cpp Normal file
View file

@ -0,0 +1,344 @@
/*
* 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 "TestBrowser.h"
#include "TestGlobal.h"
#include "crypto/Crypto.h"
#include "sodium/crypto_box.h"
#include "browser/BrowserSettings.h"
#include <QString>
QTEST_GUILESS_MAIN(TestBrowser)
const QString PUBLICKEY = "UIIPObeoya1G8g1M5omgyoPR/j1mR1HlYHu0wHCgMhA=";
const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI=";
const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ=";
const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q=";
const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
const QString CLIENTID = "testClient";
void TestBrowser::initTestCase()
{
QVERIFY(Crypto::init());
m_browserService.reset(new BrowserService(nullptr));
m_browserAction.reset(new BrowserAction(*m_browserService.data()));
}
void TestBrowser::cleanupTestCase()
{
}
/**
* Tests for BrowserAction
*/
void TestBrowser::testChangePublicKeys()
{
QJsonObject json;
json["action"] = "change-public-keys";
json["publicKey"] = PUBLICKEY;
json["nonce"] = NONCE;
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"));
}
void TestBrowser::testEncryptMessage()
{
QJsonObject message;
message["action"] = "test-action";
m_browserAction->m_publicKey = SERVERPUBLICKEY;
m_browserAction->m_secretKey = SERVERSECRETKEY;
m_browserAction->m_clientPublicKey = PUBLICKEY;
auto encrypted = m_browserAction->encryptMessage(message, NONCE);
QCOMPARE(encrypted, QString("+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP"));
}
void TestBrowser::testDecryptMessage()
{
QString message = "+zjtntnk4rGWSl/Ph7Vqip/swvgeupk4lNgHEm2OO3ujNr0OMz6eQtGwjtsj+/rP";
m_browserAction->m_publicKey = SERVERPUBLICKEY;
m_browserAction->m_secretKey = SERVERSECRETKEY;
m_browserAction->m_clientPublicKey = PUBLICKEY;
auto decrypted = m_browserAction->decryptMessage(message, NONCE);
QCOMPARE(decrypted["action"].toString(), QString("test-action"));
}
void TestBrowser::testGetBase64FromKey()
{
unsigned char pk[crypto_box_PUBLICKEYBYTES];
for (unsigned int i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
pk[i] = i;
}
auto response = m_browserAction->getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
QCOMPARE(response, QString("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="));
}
void TestBrowser::testIncrementNonce()
{
auto result = m_browserAction->incrementNonce(NONCE);
QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"));
}
/**
* Tests for BrowserService
*/
void TestBrowser::testBaseDomain()
{
QString url1 = "https://another.example.co.uk";
QString url2 = "https://www.example.com";
QString url3 = "http://test.net";
QString url4 = "http://so.many.subdomains.co.jp";
QString res1 = m_browserService->baseDomain(url1);
QString res2 = m_browserService->baseDomain(url2);
QString res3 = m_browserService->baseDomain(url3);
QString res4 = m_browserService->baseDomain(url4);
QCOMPARE(res1, QString("example.co.uk"));
QCOMPARE(res2, QString("example.com"));
QCOMPARE(res3, QString("test.net"));
QCOMPARE(res4, QString("subdomains.co.jp"));
}
void TestBrowser::testSortPriority()
{
QString host = "github.com";
QString submitUrl = "https://github.com/session";
QString baseSubmitUrl = "https://github.com";
QScopedPointer<Entry> entry1(new Entry());
QScopedPointer<Entry> entry2(new Entry());
QScopedPointer<Entry> entry3(new Entry());
QScopedPointer<Entry> entry4(new Entry());
QScopedPointer<Entry> entry5(new Entry());
QScopedPointer<Entry> entry6(new Entry());
QScopedPointer<Entry> entry7(new Entry());
QScopedPointer<Entry> entry8(new Entry());
QScopedPointer<Entry> entry9(new Entry());
QScopedPointer<Entry> entry10(new Entry());
entry1->setUrl("https://github.com/login");
entry2->setUrl("https://github.com/login");
entry3->setUrl("https://github.com/");
entry4->setUrl("github.com/login");
entry5->setUrl("http://github.com");
entry6->setUrl("http://github.com/login");
entry7->setUrl("github.com");
entry8->setUrl("github.com/login");
entry9->setUrl("https://github");
entry10->setUrl("github.com");
// The extension uses the submitUrl as default for comparison
auto res1 = m_browserService->sortPriority(entry1.data(), host, "https://github.com/login", baseSubmitUrl);
auto res2 = m_browserService->sortPriority(entry2.data(), host, submitUrl, baseSubmitUrl);
auto res3 = m_browserService->sortPriority(entry3.data(), host, submitUrl, baseSubmitUrl);
auto res4 = m_browserService->sortPriority(entry4.data(), host, submitUrl, baseSubmitUrl);
auto res5 = m_browserService->sortPriority(entry5.data(), host, submitUrl, baseSubmitUrl);
auto res6 = m_browserService->sortPriority(entry6.data(), host, submitUrl, baseSubmitUrl);
auto res7 = m_browserService->sortPriority(entry7.data(), host, submitUrl, baseSubmitUrl);
auto res8 = m_browserService->sortPriority(entry8.data(), host, submitUrl, baseSubmitUrl);
auto res9 = m_browserService->sortPriority(entry9.data(), host, submitUrl, baseSubmitUrl);
auto res10 = m_browserService->sortPriority(entry10.data(), host, submitUrl, baseSubmitUrl);
QCOMPARE(res1, 100);
QCOMPARE(res2, 40);
QCOMPARE(res3, 90);
QCOMPARE(res4, 0);
QCOMPARE(res5, 0);
QCOMPARE(res6, 0);
QCOMPARE(res7, 0);
QCOMPARE(res8, 0);
QCOMPARE(res9, 90);
QCOMPARE(res10, 0);
}
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");
urls.push_back("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();
}
browserSettings()->setMatchUrlScheme(false);
auto result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
QCOMPARE(result.length(), 7);
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
QCOMPARE(result[2]->url(), QString("https://github.com/"));
QCOMPARE(result[3]->url(), QString("http://github.com"));
QCOMPARE(result[4]->url(), QString("http://github.com/login"));
QCOMPARE(result[5]->url(), QString("github.com"));
QCOMPARE(result[6]->url(), QString("github.com")) ;
// With matching there should be only 5 results
browserSettings()->setMatchUrlScheme(true);
result = m_browserService->searchEntries(db, "github.com", "https://github.com"); // db, hostname, url
QCOMPARE(result.length(), 5);
QCOMPARE(result[0]->url(), QString("https://github.com/login_page"));
QCOMPARE(result[1]->url(), QString("https://github.com/login"));
QCOMPARE(result[2]->url(), QString("https://github.com/"));
QCOMPARE(result[3]->url(), QString("github.com"));
QCOMPARE(result[4]->url(), QString("github.com"));
}
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");
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();
}
auto result = m_browserService->searchEntries(db, "127.0.0.1", "http://127.0.0.1:443"); // db, hostname, url
QCOMPARE(result.length(), 1);
QCOMPARE(result[0]->url(), QString("http://127.0.0.1:443"));
}
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");
urls.push_back("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);
}
browserSettings()->setBestMatchOnly(false);
auto result = m_browserService->sortEntries(entries, "github.com", "https://github.com/session"); // entries, host, submitUrl
QCOMPARE(result.size(), 10);
QCOMPARE(result[0]->username(), QString("User 2"));
QCOMPARE(result[0]->url(), QString("https://github.com/"));
QCOMPARE(result[1]->username(), QString("User 8"));
QCOMPARE(result[1]->url(), QString("https://github"));
QCOMPARE(result[2]->username(), QString("User 0"));
QCOMPARE(result[2]->url(), QString("https://github.com/login_page"));
QCOMPARE(result[3]->username(), QString("User 1"));
QCOMPARE(result[3]->url(), QString("https://github.com/login"));
}
void TestBrowser::testGetDatabaseGroups()
{
auto db = QSharedPointer<Database>::create();
auto* root = db->rootGroup();
QScopedPointer<Group> group1(new Group());
group1->setParent(root);
group1->setName("group1");
QScopedPointer<Group> group2(new Group());
group2->setParent(root);
group2->setName("group2");
QScopedPointer<Group> group3(new Group());
group3->setParent(root);
group3->setName("group3");
QScopedPointer<Group> group2_1(new Group());
group2_1->setParent(group2.data());
group2_1->setName("group2_1");
QScopedPointer<Group> group2_2(new Group());
group2_2->setParent(group2.data());
group2_2->setName("group2_2");
QScopedPointer<Group> group2_1_1(new Group());
group2_1_1->setParent(group2_1.data());
group2_1_1->setName("group2_1_1");
auto result = m_browserService->getDatabaseGroups(db);
QCOMPARE(result.length(), 1);
auto groups = result["groups"].toArray();
auto first = groups.at(0);
auto children = first.toObject()["children"].toArray();
QCOMPARE(first.toObject()["name"].toString(), QString("Root"));
QCOMPARE(children.size(), 3);
auto firstChild = children.at(0);
auto secondChild = children.at(1);
auto thirdChild = children.at(2);
QCOMPARE(firstChild.toObject()["name"].toString(), QString("group1"));
QCOMPARE(secondChild.toObject()["name"].toString(), QString("group2"));
QCOMPARE(thirdChild.toObject()["name"].toString(), QString("group3"));
auto childrenOfSecond = secondChild.toObject()["children"].toArray();
auto firstOfCOS = childrenOfSecond.at(0);
auto secondOfCOS = childrenOfSecond.at(1);
QCOMPARE(firstOfCOS.toObject()["name"].toString(), QString("group2_1"));
QCOMPARE(secondOfCOS.toObject()["name"].toString(), QString("group2_2"));
auto lastChildren = firstOfCOS.toObject()["children"].toArray();
auto lastChild = lastChildren.at(0);
QCOMPARE(lastChild.toObject()["name"].toString(), QString("group2_1_1"));
}

Some files were not shown because too many files have changed in this diff Show more