Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt65
-rw-r--r--INSTALL.md18
-rw-r--r--cmake/CodeCoverage.cmake2
-rw-r--r--cmake/FindQuaZip.cmake59
-rw-r--r--snap/local/launchers/README.md11
-rwxr-xr-xsnap/local/launchers/gtk3-env-launch14
-rw-r--r--snap/snapcraft.yaml (renamed from snapcraft.yaml)34
-rw-r--r--src/CMakeLists.txt48
-rw-r--r--src/browser/BrowserService.cpp9
-rwxr-xr-xsrc/browser/CMakeLists.txt3
-rw-r--r--src/cli/Add.cpp2
-rw-r--r--src/cli/CMakeLists.txt1
-rw-r--r--src/cli/Clip.cpp2
-rw-r--r--src/cli/Create.cpp2
-rw-r--r--src/cli/Diceware.cpp4
-rw-r--r--src/cli/Edit.cpp2
-rw-r--r--src/cli/Estimate.cpp2
-rw-r--r--src/cli/Extract.cpp2
-rw-r--r--src/cli/Generate.cpp4
-rw-r--r--src/cli/List.cpp2
-rw-r--r--src/cli/Locate.cpp2
-rw-r--r--src/cli/Merge.cpp2
-rw-r--r--src/cli/Remove.cpp2
-rw-r--r--src/cli/Show.cpp2
-rw-r--r--src/cli/TextStream.cpp21
-rw-r--r--src/core/Alloc.cpp89
-rw-r--r--src/core/Bootstrap.cpp6
-rw-r--r--src/core/CustomData.cpp26
-rw-r--r--src/core/CustomData.h7
-rw-r--r--src/core/Merger.cpp30
-rw-r--r--src/core/Metadata.cpp2
-rw-r--r--src/core/Translator.cpp27
-rw-r--r--src/core/Translator.h5
-rw-r--r--src/gui/ApplicationSettingsWidget.cpp2
-rw-r--r--src/gui/ApplicationSettingsWidgetSecurity.ui2
-rw-r--r--src/gui/DatabaseTabWidget.cpp4
-rw-r--r--src/gui/DatabaseWidget.cpp19
-rw-r--r--src/gui/DialogyWidget.cpp3
-rw-r--r--src/gui/EditWidgetIcons.cpp39
-rw-r--r--src/gui/MainWindow.cpp46
-rw-r--r--src/gui/MainWindow.h5
-rw-r--r--src/gui/entry/EditEntryWidget.cpp10
-rw-r--r--src/gui/group/EditGroupWidget.cpp3
-rw-r--r--src/gui/macutils/AppKit.h14
-rw-r--r--src/gui/macutils/AppKitImpl.h5
-rw-r--r--src/gui/macutils/AppKitImpl.mm47
-rw-r--r--src/gui/macutils/MacUtils.cpp2
-rw-r--r--src/gui/macutils/MacUtils.h6
-rw-r--r--src/keeshare/CMakeLists.txt20
-rw-r--r--src/keeshare/ShareObserver.cpp4
-rw-r--r--src/keys/FileKey.cpp46
-rw-r--r--src/keys/FileKey.h5
-rw-r--r--src/keys/PasswordKey.cpp24
-rw-r--r--src/keys/PasswordKey.h5
-rw-r--r--src/keys/YkChallengeResponseKey.cpp27
-rw-r--r--src/keys/YkChallengeResponseKey.h6
-rwxr-xr-xsrc/proxy/CMakeLists.txt3
-rw-r--r--src/touchid/TouchID.mm2
-rw-r--r--tests/TestKdbx4.cpp20
-rw-r--r--tests/TestMerge.cpp59
-rw-r--r--tests/TestMerge.h1
-rw-r--r--tests/gui/TestGui.cpp17
62 files changed, 710 insertions, 243 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0fc8db81b..abac73c94 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,9 +20,10 @@ project(KeePassXC)
if(NOT CMAKE_BUILD_TYPE)
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)
endif()
+string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
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_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_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_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
-option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
-option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
+option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif()
if(WITH_XC_ALL)
- # Enable all options
+ # Enable all options (except update check)
set(WITH_XC_AUTOTYPE ON)
set(WITH_XC_NETWORKING ON)
set(WITH_XC_BROWSER ON)
@@ -67,18 +67,16 @@ if(WITH_XC_ALL)
endif()
endif()
-if(WITH_XC_KEESHARE_SECURE)
- set(WITH_XC_KEESHARE ON)
-endif()
-
if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON)
else()
set(WITH_XC_CRYPTO_SSH OFF)
endif()
-if(WITH_XC_UPDATECHECK)
- set(WITH_XC_NETWORKING ON)
+# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
+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()
set(KEEPASSXC_VERSION_MAJOR "2")
@@ -162,11 +160,15 @@ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
set(IS_32BIT TRUE)
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)
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)
endif()
@@ -199,7 +201,7 @@ add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
add_gcc_compiler_flags("-fvisibility=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")
endif()
@@ -230,7 +232,6 @@ if(WITH_ASAN)
endif()
-string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
endif()
@@ -264,6 +265,11 @@ endif()
add_gcc_compiler_cflags("-std=c99")
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)
add_gcc_compiler_cxxflags("-stdlib=libc++")
endif()
@@ -276,7 +282,7 @@ if(MINGW)
set(CMAKE_RC_COMPILER_INIT windres)
enable_language(RC)
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
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")
@@ -365,10 +371,17 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH TRUE)
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
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()
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
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()
# 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(ZLIB REQUIRED)
find_package(QREncode REQUIRED)
+find_package(sodium 1.0.12 REQUIRED)
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")
endif()
-include_directories(SYSTEM ${ARGON2_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()
+include_directories(SYSTEM ${ARGON2_INCLUDE_DIR} ${sodium_INCLUDE_DIR})
# Optional
if(WITH_XC_YUBIKEY)
diff --git a/INSTALL.md b/INSTALL.md
index 9ecaf83f7..c0c3a7a08 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -25,8 +25,8 @@ The following libraries are required:
* zlib
* libmicrohttpd
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
-* libsodium (>= 1.0.12, optional for KeePassXC-Browser support)
-* argon2
+* libsodium (>= 1.0.12)
+* libargon2
* qrencode
* yubikey ykpers (optional to support YubiKey)
@@ -99,18 +99,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_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_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_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_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_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_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_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_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/`
diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake
index d10791745..f5287b75b 100644
--- a/cmake/CodeCoverage.cmake
+++ b/cmake/CodeCoverage.cmake
@@ -112,7 +112,7 @@ mark_as_advanced(
CMAKE_EXE_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")
endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
diff --git a/cmake/FindQuaZip.cmake b/cmake/FindQuaZip.cmake
index 8d3091810..a387e2f81 100644
--- a/cmake/FindQuaZip.cmake
+++ b/cmake/FindQuaZip.cmake
@@ -1,41 +1,24 @@
-# QUAZIP_FOUND - QuaZip library was found
-# 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_LIBRARIES - List of QuaZip libraries
-# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
+# QUAZIP_FOUND - QuaZip library was found
+# 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_LIBRARIES - List of QuaZip libraries
+# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
-IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
- # in cache already
- SET(QUAZIP_FOUND TRUE)
-ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
- IF(Qt5Core_FOUND)
- set(QUAZIP_LIB_VERSION_SUFFIX 5)
- ENDIF()
- IF(WIN32)
- 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
+if(MINGW)
+ find_library(QUAZIP_LIBRARIES libquazip5)
+ find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
+ find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
+else()
+ find_library(QUAZIP_LIBRARIES
+ NAMES quazip5 quazip
+ PATHS /usr/lib /usr/lib64 /usr/local/lib
)
- FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR})
- FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5)
- FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h)
- 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
+ PATHS /usr/include /usr/local/include
+ PATH_SUFFIXES quazip5 quazip
)
- FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h
- HINTS /usr/include /usr/local/include
- PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX}
- )
- FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include)
- 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)
+ find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
+endif()
+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)
diff --git a/snap/local/launchers/README.md b/snap/local/launchers/README.md
new file mode 100644
index 000000000..334fbbdcc
--- /dev/null
+++ b/snap/local/launchers/README.md
@@ -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_
+```
+
diff --git a/snap/local/launchers/gtk3-env-launch b/snap/local/launchers/gtk3-env-launch
new file mode 100755
index 000000000..f017e8611
--- /dev/null
+++ b/snap/local/launchers/gtk3-env-launch
@@ -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 "${@}"
diff --git a/snapcraft.yaml b/snap/snapcraft.yaml
index e249c6cb3..9ba5d8f53 100644
--- a/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -9,16 +9,28 @@ description: |
confinement: strict
base: core18
-plugs:
- icon-themes: # fix mouse cursor theme
+plugs: # plugs for theming, font settings, cursor and to use gtk3 file chooser
+ gtk-3-themes:
+ interface: content
+ target: $SNAP/data-dir/themes
+ default-provider: gtk-common-themes:gtk-3-themes
+ icon-themes:
interface: content
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:
keepassxc:
- command: desktop-launch keepassxc
- plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb, wayland, desktop-legacy]
+ adapter: full
+ 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
environment:
DISABLE_WAYLAND: 1
@@ -73,7 +85,7 @@ parts:
- libquazip5-1
- libusb-1.0-0
- 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: |
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
@@ -82,7 +94,15 @@ parts:
stage:
- -opt
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:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
source-subdir: qt
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ef12fadb2..5ddcb76ea 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -16,9 +16,6 @@
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)
if(NOT ZXCVBN_LIBRARIES)
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
@@ -27,6 +24,7 @@ if(NOT ZXCVBN_LIBRARIES)
endif(NOT ZXCVBN_LIBRARIES)
set(keepassx_SOURCES
+ core/Alloc.cpp
core/AutoTypeAssociations.cpp
core/AutoTypeMatch.cpp
core/Compare.cpp
@@ -167,7 +165,8 @@ if(APPLE)
core/ScreenLockListenerMac.cpp
core/MacPasteboard.cpp
gui/macutils/MacUtils.cpp
- gui/macutils/AppKitImpl.mm)
+ gui/macutils/AppKitImpl.mm
+ gui/macutils/AppKit.h)
endif()
if(UNIX AND NOT APPLE)
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(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(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
-add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
+add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
if(APPLE)
@@ -255,8 +253,13 @@ endif()
if(WITH_XC_TOUCHID)
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()
+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})
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
@@ -271,6 +274,7 @@ target_link_libraries(keepassx_core
Qt5::Concurrent
Qt5::Network
Qt5::Widgets
+ ${sodium_LIBRARY_RELEASE}
${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES}
${ARGON2_LIBRARIES}
@@ -411,25 +415,19 @@ if(MINGW)
install(CODE "set(gp_tool \"objdump\")" COMPONENT Runtime)
- include(DeployQt4)
- install_qt4_executable(${PROGNAME}.exe)
-
- # install Qt5 plugins
- set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
- install(FILES
- ${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
- ${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
- DESTINATION "platforms")
- install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
- install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
- 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")
+ # Deploy all 3rd party library dependencies first
+ install(CODE "include(BundleUtilities)
+ fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${PROGNAME}.exe\" \"\" \"\")"
+ COMPONENT Runtime)
+
+ # Use windeployqt.exe to setup Qt dependencies
+ set(WINDEPLOYQT_MODE "--release")
+ if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
+ set(WINDEPLOYQT_MODE "--debug")
+ endif()
+
+ install(CODE "execute_process(COMMAND ${WINDEPLOYQT_EXE} ${PROGNAME}.exe ${WINDEPLOYQT_MODE} WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX} OUTPUT_QUIET)"
+ COMPONENT Runtime)
# install CA cert chains
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
index 9c06c2487..112a7cda9 100644
--- a/src/browser/BrowserService.cpp
+++ b/src/browser/BrowserService.cpp
@@ -296,6 +296,7 @@ QString BrowserService::storeKey(const QString& key)
do {
QInputDialog keyDialog;
+ connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
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"
@@ -310,7 +311,7 @@ QString BrowserService::storeKey(const QString& key)
id = keyDialog.textValue();
- if (ok != QDialog::Accepted || id.isEmpty()) {
+ if (ok != QDialog::Accepted || id.isEmpty() || !isDatabaseOpened()) {
hideWindow();
return {};
}
@@ -406,6 +407,11 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
return QJsonArray();
}
+ // Ensure that database is not locked when the popup was visible
+ if (!isDatabaseOpened()) {
+ return QJsonArray();
+ }
+
// Sort results
pwEntries = sortEntries(pwEntries, host, submitUrl);
@@ -760,6 +766,7 @@ bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
m_dialogActive = true;
BrowserAccessControlDialog accessControlDialog;
+ connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), &accessControlDialog, SLOT(reject()));
accessControlDialog.setUrl(url);
accessControlDialog.setItems(pwEntriesToConfirm);
diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt
index 10189d931..7e813eb5b 100755
--- a/src/browser/CMakeLists.txt
+++ b/src/browser/CMakeLists.txt
@@ -16,7 +16,6 @@
if(WITH_XC_BROWSER)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
- find_package(sodium 1.0.12 REQUIRED)
set(keepassxcbrowser_SOURCES
BrowserAccessControlDialog.cpp
@@ -33,5 +32,5 @@ if(WITH_XC_BROWSER)
Variant.cpp)
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()
diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp
index 395b84919..975d549e5 100644
--- a/src/cli/Add.cpp
+++ b/src/cli/Add.cpp
@@ -84,7 +84,7 @@ int Add::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli add");
+ errorTextStream << parser.helpText().replace("[options]", "add [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index c3f97a2cd..2f4a7275e 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -38,6 +38,7 @@ target_link_libraries(keepassxc-cli
keepassx_core
Qt5::Core
${GCRYPT_LIBRARIES}
+ ${sodium_LIBRARY_RELEASE}
${ARGON2_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES}
diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp
index 31b421de6..e1e74c682 100644
--- a/src/cli/Clip.cpp
+++ b/src/cli/Clip.cpp
@@ -63,7 +63,7 @@ int Clip::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
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;
}
diff --git a/src/cli/Create.cpp b/src/cli/Create.cpp
index b8c094f90..80dcb5691 100644
--- a/src/cli/Create.cpp
+++ b/src/cli/Create.cpp
@@ -70,7 +70,7 @@ int Create::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() < 1) {
- out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli create");
+ out << parser.helpText().replace("[options]", "create [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp
index f11347344..c663cfc39 100644
--- a/src/cli/Diceware.cpp
+++ b/src/cli/Diceware.cpp
@@ -58,7 +58,7 @@ int Diceware::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
+ errorTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE;
}
@@ -78,7 +78,7 @@ int Diceware::execute(const QStringList& arguments)
}
if (!dicewareGenerator.isValid()) {
- outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
+ outputTextStream << parser.helpText().replace("[options]", "diceware [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp
index 76e996c98..59cedd7c9 100644
--- a/src/cli/Edit.cpp
+++ b/src/cli/Edit.cpp
@@ -88,7 +88,7 @@ int Edit::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit");
+ errorTextStream << parser.helpText().replace("[options]", "edit [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp
index 7064963f4..c278b50f3 100644
--- a/src/cli/Estimate.cpp
+++ b/src/cli/Estimate.cpp
@@ -171,7 +171,7 @@ int Estimate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() > 1) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate");
+ errorTextStream << parser.helpText().replace("[options]", "estimate [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp
index 45c961332..054a391bd 100644
--- a/src/cli/Extract.cpp
+++ b/src/cli/Extract.cpp
@@ -53,7 +53,7 @@ int Extract::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 1) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract");
+ errorTextStream << parser.helpText().replace("[options]", "extract [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp
index 5f0ad98ac..e8ca90275 100644
--- a/src/cli/Generate.cpp
+++ b/src/cli/Generate.cpp
@@ -84,7 +84,7 @@ int Generate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (!args.isEmpty()) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
+ errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE;
}
@@ -128,7 +128,7 @@ int Generate::execute(const QStringList& arguments)
passwordGenerator.setExcludedChars(parser.value(exclude));
if (!passwordGenerator.isValid()) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
+ errorTextStream << parser.helpText().replace("[options]", "generate [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/List.cpp b/src/cli/List.cpp
index ebf7bfda1..52797470c 100644
--- a/src/cli/List.cpp
+++ b/src/cli/List.cpp
@@ -59,7 +59,7 @@ int List::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
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;
}
diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp
index 81bbdd55d..af5f24196 100644
--- a/src/cli/Locate.cpp
+++ b/src/cli/Locate.cpp
@@ -56,7 +56,7 @@ int Locate::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate");
+ errorTextStream << parser.helpText().replace("[options]", "locate [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp
index a7357394f..2356f5d3a 100644
--- a/src/cli/Merge.cpp
+++ b/src/cli/Merge.cpp
@@ -69,7 +69,7 @@ int Merge::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge");
+ errorTextStream << parser.helpText().replace("[options]", "merge [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp
index 07da23b7b..bb2374e9a 100644
--- a/src/cli/Remove.cpp
+++ b/src/cli/Remove.cpp
@@ -58,7 +58,7 @@ int Remove::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm");
+ errorTextStream << parser.helpText().replace("[options]", "rm [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp
index d16fbfe3c..3abccd79c 100644
--- a/src/cli/Show.cpp
+++ b/src/cli/Show.cpp
@@ -69,7 +69,7 @@ int Show::execute(const QStringList& arguments)
const QStringList args = parser.positionalArguments();
if (args.size() != 2) {
- errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli show");
+ errorTextStream << parser.helpText().replace("[options]", "show [options]");
return EXIT_FAILURE;
}
diff --git a/src/cli/TextStream.cpp b/src/cli/TextStream.cpp
index d75cb74a9..938fd6292 100644
--- a/src/cli/TextStream.cpp
+++ b/src/cli/TextStream.cpp
@@ -19,6 +19,9 @@
#include <QProcessEnvironment>
#include <QTextCodec>
+#ifdef Q_OS_WIN
+#include <windows.h>
+#endif
TextStream::TextStream()
{
@@ -59,12 +62,26 @@ void TextStream::detectCodec()
{
QString codecName = "UTF-8";
auto env = QProcessEnvironment::systemEnvironment();
+
#ifdef Q_OS_WIN
- if (!env.contains("SHELL")) {
- // native shell (no Msys or cygwin)
+ WINBOOL success = false;
+#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";
}
+#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
+
codecName = env.value("ENCODING_OVERRIDE", codecName);
auto* codec = QTextCodec::codecForName(codecName.toLatin1());
if (codec) {
diff --git a/src/core/Alloc.cpp b/src/core/Alloc.cpp
new file mode 100644
index 000000000..a33b56196
--- /dev/null
+++ b/src/core/Alloc.cpp
@@ -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);
+}
diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp
index a06bf74c1..2d1a3e087 100644
--- a/src/core/Bootstrap.cpp
+++ b/src/core/Bootstrap.cpp
@@ -85,6 +85,12 @@ namespace Bootstrap
bootstrap();
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
// Don't show menu icons on OSX
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
diff --git a/src/core/CustomData.cpp b/src/core/CustomData.cpp
index 86adae158..f009176a0 100644
--- a/src/core/CustomData.cpp
+++ b/src/core/CustomData.cpp
@@ -16,9 +16,12 @@
*/
#include "CustomData.h"
+#include "Clock.h"
#include "core/Global.h"
+const QString CustomData::LastModified = "_LAST_MODIFIED";
+
CustomData::CustomData(QObject* parent)
: QObject(parent)
{
@@ -60,6 +63,7 @@ void CustomData::set(const QString& key, const QString& value)
if (addAttribute || changeValue) {
m_data.insert(key, value);
+ updateLastModified();
emit customDataModified();
}
@@ -74,6 +78,7 @@ void CustomData::remove(const QString& key)
m_data.remove(key);
+ updateLastModified();
emit removed(key);
emit customDataModified();
}
@@ -94,6 +99,7 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
m_data.remove(oldKey);
m_data.insert(newKey, data);
+ updateLastModified();
emit customDataModified();
emit renamed(oldKey, newKey);
}
@@ -108,9 +114,19 @@ void CustomData::copyDataFrom(const CustomData* other)
m_data = other->m_data;
+ updateLastModified();
emit reset();
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
{
return (m_data == other.m_data);
@@ -152,3 +168,13 @@ int CustomData::dataSize() const
}
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());
+}
diff --git a/src/core/CustomData.h b/src/core/CustomData.h
index d085c9409..126d4d84e 100644
--- a/src/core/CustomData.h
+++ b/src/core/CustomData.h
@@ -42,9 +42,12 @@ public:
int size() const;
int dataSize() const;
void copyDataFrom(const CustomData* other);
+ QDateTime getLastModified() const;
bool operator==(const CustomData& other) const;
bool operator!=(const CustomData& other) const;
+ static const QString LastModified;
+
signals:
void customDataModified();
void aboutToBeAdded(const QString& key);
@@ -55,6 +58,10 @@ signals:
void renamed(const QString& oldKey, const QString& newKey);
void aboutToBeReset();
void reset();
+ void lastModified();
+
+private slots:
+ void updateLastModified();
private:
QHash<QString, QString> m_data;
diff --git a/src/core/Merger.cpp b/src/core/Merger.cpp
index c73248388..dcbed250f 100644
--- a/src/core/Merger.cpp
+++ b/src/core/Merger.cpp
@@ -609,9 +609,6 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
// 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
// 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;
auto* sourceMetadata = context.m_sourceDb->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()));
}
}
+
+ // 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;
}
diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp
index 6448c391a..ff1ee71e7 100644
--- a/src/core/Metadata.cpp
+++ b/src/core/Metadata.cpp
@@ -195,7 +195,7 @@ QPixmap Metadata::customIconScaledPixmap(const QUuid& uuid) const
QPixmapCache::Key& cacheKey = m_customIconScaledCacheKeys[uuid];
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);
cacheKey = QPixmapCache::insert(pixmap);
}
diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp
index 595dadfa1..95de3ce91 100644
--- a/src/core/Translator.cpp
+++ b/src/core/Translator.cpp
@@ -34,13 +34,14 @@
*/
void Translator::installTranslators()
{
+ QLocale locale;
QString language = config()->get("GUI/Language").toString();
- if (language == "system" || language.isEmpty()) {
- language = QLocale::system().name();
- }
- if (language == "en") {
+ if (!language.isEmpty() && language != "system") {
// 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 = {
@@ -51,11 +52,12 @@ void Translator::installTranslators()
bool translationsLoaded = false;
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)) {
- installQtTranslator("en", path);
+ installQtTranslator(QLocale("en"), path);
}
}
+
if (!translationsLoaded) {
// couldn't load configured language or fallback
qWarning("Couldn't load translations.");
@@ -114,10 +116,10 @@ QList<QPair<QString, QString>> Translator::availableLanguages()
* @param path local search path
* @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));
- if (translator->load(QString("keepassx_%1").arg(language), path)) {
+ if (translator->load(locale, "keepassx_", "", path)) {
return QCoreApplication::installTranslator(translator.take());
}
return false;
@@ -131,13 +133,12 @@ bool Translator::installTranslator(const QString& language, const QString& path)
* @param path local search path
* @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));
- if (qtTranslator->load(QString("qtbase_%1").arg(language), path)) {
+ if (qtTranslator->load(locale, "qtbase_", "", path)) {
return QCoreApplication::installTranslator(qtTranslator.take());
- } else if (qtTranslator->load(QString("qtbase_%1").arg(language),
- QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
+ } else if (qtTranslator->load(locale, "qtbase_", "", QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
return QCoreApplication::installTranslator(qtTranslator.take());
}
return false;
diff --git a/src/core/Translator.h b/src/core/Translator.h
index cf62f48e4..cfc49d710 100644
--- a/src/core/Translator.h
+++ b/src/core/Translator.h
@@ -20,6 +20,7 @@
#include <QPair>
#include <QString>
+#include <QLocale>
class Translator
{
@@ -28,8 +29,8 @@ public:
static QList<QPair<QString, QString>> availableLanguages();
private:
- static bool installTranslator(const QString& language, const QString& path);
- static bool installQtTranslator(const QString& language, const QString& path);
+ static bool installTranslator(const QLocale& locale, const QString& path);
+ static bool installQtTranslator(const QLocale& locale, const QString& path);
};
#endif // KEEPASSX_TRANSLATOR_H
diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp
index 22a49dece..2461230c8 100644
--- a/src/gui/ApplicationSettingsWidget.cpp
+++ b/src/gui/ApplicationSettingsWidget.cpp
@@ -64,6 +64,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
, m_globalAutoTypeModifiers(Qt::NoModifier)
{
setHeadline(tr("Application Settings"));
+ showApplyButton(false);
m_secUi->setupUi(m_secWidget);
m_generalUi->setupUi(m_generalWidget);
@@ -75,7 +76,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
}
connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
- connect(this, SIGNAL(apply()), SLOT(saveSettings()));
connect(this, SIGNAL(rejected()), SLOT(reject()));
// clang-format off
diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui
index 344c2b81c..bf5cce2d3 100644
--- a/src/gui/ApplicationSettingsWidgetSecurity.ui
+++ b/src/gui/ApplicationSettingsWidgetSecurity.ui
@@ -212,7 +212,7 @@
<item>
<widget class="QCheckBox" name="fallbackToSearch">
<property name="text">
- <string>Use DuckDuckGo as fallback for downloading website icons</string>
+ <string>Use DuckDuckGo service to download website icons</string>
</property>
</widget>
</item>
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index 313bfabb1..fb234795c 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -63,6 +63,10 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase()));
connect(autoType(), SIGNAL(autotypeRejected()), SLOT(relockPendingDatabase()));
// clang-format on
+
+#ifdef Q_OS_MACOS
+ connect(macUtils(), SIGNAL(lockDatabases()), SLOT(lockDatabases()));
+#endif
}
DatabaseTabWidget::~DatabaseTabWidget()
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 8cfc40815..e4f175bf2 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -1158,9 +1158,10 @@ void DatabaseWidget::onDatabaseModified()
{
if (!m_blockAutoSave && config()->get("AutoSaveAfterEveryChange").toBool()) {
save();
+ } else {
+ // Only block once, then reset
+ m_blockAutoSave = false;
}
-
- m_blockAutoSave = false;
}
QString DatabaseWidget::getCurrentSearch()
@@ -1258,11 +1259,13 @@ bool DatabaseWidget::lock()
}
if (m_db->isModified()) {
+ bool saved = false;
+ // Attempt to save on exit, but don't block locking if it fails
if (config()->get("AutoSaveOnExit").toBool()) {
- if (!save()) {
- return false;
- }
- } else {
+ saved = save();
+ }
+
+ if (!saved) {
QString msg;
if (!m_db->metadata()->name().toHtmlEscaped().isEmpty()) {
msg = tr("\"%1\" was modified.\nSave changes?").arg(m_db->metadata()->name().toHtmlEscaped());
@@ -1521,11 +1524,14 @@ bool DatabaseWidget::save()
return true;
}
+ // Read-only and new databases ask for filename
if (m_db->isReadOnly() || m_db->filePath().isEmpty()) {
return saveAs();
}
+ // Prevent recursions and infinite save loops
blockAutoReload(true);
+ m_blockAutoSave = true;
++m_saveAttempts;
// TODO: Make this async, but lock out the database widget to prevent re-entrance
@@ -1536,6 +1542,7 @@ bool DatabaseWidget::save()
if (ok) {
m_saveAttempts = 0;
+ m_blockAutoSave = false;
return true;
}
diff --git a/src/gui/DialogyWidget.cpp b/src/gui/DialogyWidget.cpp
index 858d2949b..597bcc59d 100644
--- a/src/gui/DialogyWidget.cpp
+++ b/src/gui/DialogyWidget.cpp
@@ -35,7 +35,8 @@ void DialogyWidget::keyPressEvent(QKeyEvent* e)
}
} else
#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()) {
case Qt::Key_Enter:
case Qt::Key_Return:
diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp
index 242ae4542..dcc5160a3 100644
--- a/src/gui/EditWidgetIcons.cpp
+++ b/src/gui/EditWidgetIcons.cpp
@@ -197,8 +197,6 @@ void EditWidgetIcons::downloadFavicon()
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
// searching for a match with the returned address(es).
bool hostIsIp = false;
@@ -209,32 +207,35 @@ void EditWidgetIcons::downloadFavicon()
}
}
+ // Determine the second-level domain, if available
+ QString secondLevelDomain;
if (!hostIsIp) {
- QString 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"));
- }
+ secondLevelDomain = getSecondLevelDomain(m_url);
}
- // 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()) {
QUrl fallbackUrl = QUrl("https://icons.duckduckgo.com");
fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(fullyQualifiedDomain) + ".ico");
-
m_urlsToTry.append(fallbackUrl);
- if (!hostIsIp) {
- QString secondLevelDomain = getSecondLevelDomain(m_url);
-
- if (fullyQualifiedDomain != secondLevelDomain) {
- fallbackUrl.setPath("/ip3/" + QUrl::toPercentEncoding(secondLevelDomain) + ".ico");
- m_urlsToTry.append(fallbackUrl);
- }
+ // Also try a direct pull of the second-level domain (if possible)
+ if (!hostIsIp && fullyQualifiedDomain != secondLevelDomain) {
+ 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());
#endif
}
@@ -277,7 +278,7 @@ void EditWidgetIcons::fetchFinished()
if (!image.isNull()) {
if (!addCustomIcon(image)) {
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
emit messageEditEntry(tr("Custom icon successfully downloaded"), MessageWidget::Positive);
}
@@ -289,7 +290,7 @@ void EditWidgetIcons::fetchFinished()
if (!fallbackEnabled) {
emit messageEditEntry(
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);
} else {
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 0d95f6021..e5f5ea613 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -41,6 +41,10 @@
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
+#ifdef Q_OS_MACOS
+#include "macutils/MacUtils.h"
+#endif
+
#ifdef WITH_XC_UPDATECHECK
#include "gui/MessageBox.h"
#include "gui/UpdateCheckDialog.h"
@@ -135,6 +139,7 @@ MainWindow::MainWindow()
, m_trayIcon(nullptr)
, m_appExitCalled(false)
, m_appExiting(false)
+ , m_lastFocusOutTime(0)
{
g_MainWindow = this;
@@ -370,6 +375,9 @@ MainWindow::MainWindow()
#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
+ if (macUtils()->isDarkMode()) {
+ setStyleSheet("QToolButton {color:white;}");
+ }
#endif
#ifdef WITH_XC_UPDATECHECK
@@ -396,6 +404,12 @@ MainWindow::MainWindow()
connect(m_screenLockListener, SIGNAL(screenLocked()), SLOT(handleScreenLock()));
#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();
if (config()->hasAccessError()) {
@@ -912,7 +926,7 @@ bool MainWindow::saveLastDatabases()
}
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);
openDatabases.append(dbWidget->database()->filePath());
}
@@ -1031,10 +1045,38 @@ void MainWindow::applySettingsChanges()
updateTrayIcon();
}
+void MainWindow::focusWindowChanged(QWindow* focusWindow)
+{
+ if (focusWindow != windowHandle()) {
+ m_lastFocusOutTime = Clock::currentSecondsSinceEpoch();
+ }
+}
+
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();
+ } 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();
+ }
}
}
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
index 5a72d6f02..f1e543468 100644
--- a/src/gui/MainWindow.h
+++ b/src/gui/MainWindow.h
@@ -85,6 +85,7 @@ private slots:
void showAboutDialog();
void showUpdateCheckStartup();
void showUpdateCheckDialog();
+ void focusWindowChanged(QWindow* focusWindow);
void hasUpdateAvailable(bool hasUpdate, const QString& version, bool isManuallyRequested);
void openDonateUrl();
void openBugReportUrl();
@@ -107,6 +108,7 @@ private slots:
void showGroupContextMenu(const QPoint& globalPos);
void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
+ void processTrayIconTrigger();
void lockDatabasesAfterInactivity();
void forgetTouchIDAfterInactivity();
void handleScreenLock();
@@ -146,6 +148,9 @@ private:
bool m_appExitCalled;
bool m_appExiting;
+ uint m_lastFocusOutTime;
+ QTimer m_trayIconTriggerTimer;
+ QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
};
/**
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index 063f8da2c..8d0f6f556 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -152,11 +152,6 @@ void EditEntryWidget::setupMain()
m_mainUi->expirePresets->setMenu(createPresetsMenu());
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->reset();
}
@@ -285,7 +280,6 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFaviconButtonEnable(QString)));
#endif
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->notesEdit, SIGNAL(textChanged()), this, SLOT(setModified()));
@@ -1111,8 +1105,9 @@ void EditEntryWidget::updateCurrentAttribute()
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->attributesEdit->blockSignals(true);
if (index.isValid()) {
QString key = m_attributesModel->keyByIndex(index);
@@ -1143,6 +1138,7 @@ void EditEntryWidget::displayAttribute(QModelIndex index, bool showProtected)
}
m_advancedUi->protectAttributeButton->blockSignals(false);
+ m_advancedUi->attributesEdit->blockSignals(false);
}
void EditEntryWidget::protectCurrentAttribute(bool state)
diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp
index fe83a943e..051f23d4b 100644
--- a/src/gui/group/EditGroupWidget.cpp
+++ b/src/gui/group/EditGroupWidget.cpp
@@ -227,6 +227,9 @@ void EditGroupWidget::cancel()
tr("Entry has unsaved changes"),
MessageBox::Cancel | MessageBox::Save | MessageBox::Discard,
MessageBox::Cancel);
+ if (result == MessageBox::Cancel) {
+ return;
+ }
if (result == MessageBox::Save) {
apply();
setModified(false);
diff --git a/src/gui/macutils/AppKit.h b/src/gui/macutils/AppKit.h
index cdb822ffc..da81f6913 100644
--- a/src/gui/macutils/AppKit.h
+++ b/src/gui/macutils/AppKit.h
@@ -19,14 +19,15 @@
#ifndef KEEPASSX_APPKIT_H
#define KEEPASSX_APPKIT_H
+#include <QObject>
#include <unistd.h>
-extern "C" {
-
-class AppKit
+class AppKit : public QObject
{
+ Q_OBJECT
+
public:
- AppKit();
+ AppKit(QObject* parent = nullptr);
~AppKit();
pid_t lastActiveProcessId();
@@ -37,10 +38,11 @@ public:
bool isHidden(pid_t pid);
bool isDarkMode();
+signals:
+ void lockDatabases();
+
private:
void *self;
};
-} // extern "C"
-
#endif // KEEPASSX_APPKIT_H
diff --git a/src/gui/macutils/AppKitImpl.h b/src/gui/macutils/AppKitImpl.h
index 3bf2d20ef..ca2506794 100644
--- a/src/gui/macutils/AppKitImpl.h
+++ b/src/gui/macutils/AppKitImpl.h
@@ -22,6 +22,10 @@
#import <AppKit/NSRunningApplication.h>
@interface AppKitImpl : NSObject
+{
+ AppKit *m_appkit;
+}
+- (id) initWithObject:(AppKit *)appkit;
@property (strong) NSRunningApplication *lastActiveApplication;
@@ -31,5 +35,6 @@
- (bool) hideProcess:(pid_t) pid;
- (bool) isHidden:(pid_t) pid;
- (bool) isDarkMode;
+- (void) userSwitchHandler:(NSNotification*) notification;
@end
diff --git a/src/gui/macutils/AppKitImpl.mm b/src/gui/macutils/AppKitImpl.mm
index cd709df27..4165e0d5e 100644
--- a/src/gui/macutils/AppKitImpl.mm
+++ b/src/gui/macutils/AppKitImpl.mm
@@ -22,19 +22,22 @@
@implementation AppKitImpl
-AppKit::AppKit()
+- (id) initWithObject:(AppKit *)appkit
{
- self = [[AppKitImpl alloc] init];
- [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
+ self = [super init];
+ if (self) {
+ m_appkit = appkit;
+ [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
selector:@selector(didDeactivateApplicationObserver:)
name:NSWorkspaceDidDeactivateApplicationNotification
object:nil];
-}
-
-AppKit::~AppKit()
-{
- [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:static_cast<id>(self)];
- [static_cast<id>(self) dealloc];
+
+ [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:static_cast<id>(self)
+ selector:@selector(userSwitchHandler:)
+ name:NSWorkspaceSessionDidResignActiveNotification
+ object:nil];
+ }
+ return self;
}
//
@@ -105,9 +108,33 @@ AppKit::~AppKit()
}
//
+// Notification for user switch
+//
+- (void) userSwitchHandler:(NSNotification*) notification
+{
+ if ([[notification name] isEqualToString:NSWorkspaceSessionDidResignActiveNotification] && m_appkit)
+ {
+ emit m_appkit->lockDatabases();
+ }
+}
+
+@end
+
+//
// ------------------------- 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()
{
return [static_cast<id>(self) lastActiveApplication].processIdentifier;
@@ -142,5 +169,3 @@ bool AppKit::isDarkMode()
{
return [static_cast<id>(self) isDarkMode];
}
-
-@end
diff --git a/src/gui/macutils/MacUtils.cpp b/src/gui/macutils/MacUtils.cpp
index c362fe1bd..654923c31 100644
--- a/src/gui/macutils/MacUtils.cpp
+++ b/src/gui/macutils/MacUtils.cpp
@@ -24,7 +24,7 @@ MacUtils* MacUtils::m_instance = nullptr;
MacUtils::MacUtils(QObject* parent) : QObject(parent)
, m_appkit(new AppKit())
{
-
+ connect(m_appkit.data(), SIGNAL(lockDatabases()), SIGNAL(lockDatabases()));
}
MacUtils::~MacUtils()
diff --git a/src/gui/macutils/MacUtils.h b/src/gui/macutils/MacUtils.h
index 39a06bd84..49644795e 100644
--- a/src/gui/macutils/MacUtils.h
+++ b/src/gui/macutils/MacUtils.h
@@ -39,14 +39,16 @@ public:
bool isHidden();
bool isDarkMode();
+signals:
+ void lockDatabases();
+
private:
explicit MacUtils(QObject* parent = nullptr);
~MacUtils();
private:
- std::unique_ptr<AppKit> m_appkit;
+ QScopedPointer<AppKit> m_appkit;
static MacUtils* m_instance;
- void* self;
Q_DISABLE_COPY(MacUtils)
};
diff --git a/src/keeshare/CMakeLists.txt b/src/keeshare/CMakeLists.txt
index 14aa17b99..d791d3be6 100644
--- a/src/keeshare/CMakeLists.txt
+++ b/src/keeshare/CMakeLists.txt
@@ -1,4 +1,6 @@
if(WITH_XC_KEESHARE)
+ set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
+
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(keeshare_SOURCES
@@ -15,9 +17,19 @@ if(WITH_XC_KEESHARE)
)
add_library(keeshare STATIC ${keeshare_SOURCES})
- if(WITH_XC_KEESHARE_SECURE)
- target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
+ target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${GCRYPT_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()
- 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()
+else(WITH_XC_KEESHARE)
+ set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE)
+ set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
+endif(WITH_XC_KEESHARE)
diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp
index 33f5ed1f6..644f1c157 100644
--- a/src/keeshare/ShareObserver.cpp
+++ b/src/keeshare/ShareObserver.cpp
@@ -46,8 +46,8 @@
#include <QStringBuilder>
#if defined(WITH_XC_KEESHARE_SECURE)
-#include <quazip5/quazip.h>
-#include <quazip5/quazipfile.h>
+#include <quazip.h>
+#include <quazipfile.h>
#endif
namespace
diff --git a/src/keys/FileKey.cpp b/src/keys/FileKey.cpp
index 9d1e8f50f..da25ef4ae 100644
--- a/src/keys/FileKey.cpp
+++ b/src/keys/FileKey.cpp
@@ -18,19 +18,35 @@
#include "FileKey.h"
-#include <QFile>
-
#include "core/Tools.h"
#include "crypto/CryptoHash.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");
+constexpr int FileKey::SHA256_SIZE;
+
FileKey::FileKey()
: 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.
*
@@ -148,7 +164,10 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/
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()) {
- m_key = data;
- return true;
+ std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
+ 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) {
return false;
} 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;
}
}
@@ -321,12 +344,15 @@ bool FileKey::loadHex(QIODevice* device)
}
QByteArray key = QByteArray::fromHex(data);
+ sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
if (key.size() != 32) {
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;
}
@@ -348,7 +374,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer);
} 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;
}
diff --git a/src/keys/FileKey.h b/src/keys/FileKey.h
index d7486467b..290a04af0 100644
--- a/src/keys/FileKey.h
+++ b/src/keys/FileKey.h
@@ -40,6 +40,7 @@ public:
};
FileKey();
+ ~FileKey() override;
bool load(QIODevice* device);
bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override;
@@ -48,6 +49,8 @@ public:
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
private:
+ static constexpr int SHA256_SIZE = 32;
+
bool loadXml(QIODevice* device);
bool loadXmlMeta(QXmlStreamReader& xmlReader);
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
@@ -55,7 +58,7 @@ private:
bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device);
- QByteArray m_key;
+ char* m_key = nullptr;
Type m_type = None;
};
diff --git a/src/keys/PasswordKey.cpp b/src/keys/PasswordKey.cpp
index 35ecb9989..2d0416af8 100644
--- a/src/keys/PasswordKey.cpp
+++ b/src/keys/PasswordKey.cpp
@@ -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
* it under the terms of the GNU General Public License as published by
@@ -16,35 +16,51 @@
*/
#include "PasswordKey.h"
+#include "core/Tools.h"
#include "crypto/CryptoHash.h"
+#include <gcrypt.h>
+#include <algorithm>
+#include <cstring>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
+constexpr int PasswordKey::SHA256_SIZE;
+
PasswordKey::PasswordKey()
: Key(UUID)
+ , m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{
}
PasswordKey::PasswordKey(const QString& password)
: Key(UUID)
+ , m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
{
setPassword(password);
}
+PasswordKey::~PasswordKey()
+{
+ if (m_key) {
+ gcry_free(m_key);
+ m_key = nullptr;
+ }
+}
+
QSharedPointer<PasswordKey> PasswordKey::fromRawKey(const QByteArray& rawKey)
{
auto result = QSharedPointer<PasswordKey>::create();
- result->m_key = rawKey;
+ std::memcpy(result->m_key, rawKey.data(), std::min(SHA256_SIZE, rawKey.size()));
return result;
}
QByteArray PasswordKey::rawKey() const
{
- return m_key;
+ return QByteArray::fromRawData(m_key, SHA256_SIZE);
}
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);
}
diff --git a/src/keys/PasswordKey.h b/src/keys/PasswordKey.h
index 68ab79895..4408cabcf 100644
--- a/src/keys/PasswordKey.h
+++ b/src/keys/PasswordKey.h
@@ -30,13 +30,16 @@ public:
PasswordKey();
explicit PasswordKey(const QString& password);
+ ~PasswordKey() override;
QByteArray rawKey() const override;
void setPassword(const QString& password);
static QSharedPointer<PasswordKey> fromRawKey(const QByteArray& rawKey);
private:
- QByteArray m_key;
+ static constexpr int SHA256_SIZE = 32;
+
+ char* m_key = nullptr;
};
#endif // KEEPASSX_PASSWORDKEY_H
diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp
index f9cbe3174..759d8d1bc 100644
--- a/src/keys/YkChallengeResponseKey.cpp
+++ b/src/keys/YkChallengeResponseKey.cpp
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
* 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
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,10 @@
#include <QXmlStreamReader>
#include <QtConcurrent>
+#include <gcrypt.h>
+#include <sodium.h>
+#include <cstring>
+
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
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
{
- 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();
}
+ QByteArray key;
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) {
emit userConfirmed();
}
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;
}
} while (retries > 0);
diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h
index b8467e7a6..5f7c40e72 100644
--- a/src/keys/YkChallengeResponseKey.h
+++ b/src/keys/YkChallengeResponseKey.h
@@ -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
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ public:
static QUuid UUID;
explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
+ ~YkChallengeResponseKey() override;
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
@@ -52,7 +53,8 @@ signals:
void userConfirmed();
private:
- QByteArray m_key;
+ char* m_key = nullptr;
+ std::size_t m_keySize = 0;
int m_slot;
bool m_blocking;
};
diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt
index ff645dadb..bdbfa3b74 100755
--- a/src/proxy/CMakeLists.txt
+++ b/src/proxy/CMakeLists.txt
@@ -18,12 +18,13 @@ if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES
+ ../core/Alloc.cpp
keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp)
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)
target_link_libraries(keepassxc-proxy proxy)
diff --git a/src/touchid/TouchID.mm b/src/touchid/TouchID.mm
index 9ef72189b..7df5ad556 100644
--- a/src/touchid/TouchID.mm
+++ b/src/touchid/TouchID.mm
@@ -15,6 +15,7 @@
inline void debug(const char* message, ...)
{
+ Q_UNUSED(message);
// qWarning(...);
}
@@ -258,6 +259,7 @@ bool TouchID::authenticate(const QString& message) const
NSString* authMessage = msg.toNSString(); // autoreleased
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:authMessage reply:^(BOOL success, NSError* error) {
+ Q_UNUSED(error);
result = success ? kTouchIDResultAllowed : kTouchIDResultFailed;
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}];
diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp
index fe4679da5..88352d825 100644
--- a/tests/TestKdbx4.cpp
+++ b/tests/TestKdbx4.cpp
@@ -186,8 +186,10 @@ void TestKdbx4::testFormat400Upgrade()
QCOMPARE(reader.version(), expectedVersion);
QCOMPARE(targetDb->cipher(), cipherUuid);
- QCOMPARE(*targetDb->metadata()->customData(), *sourceDb->metadata()->customData());
- QCOMPARE(*targetDb->rootGroup()->customData(), *sourceDb->rootGroup()->customData());
+ QCOMPARE(targetDb->metadata()->customData()->value("CustomPublicData"),
+ sourceDb->metadata()->customData()->value("CustomPublicData"));
+ QCOMPARE(targetDb->rootGroup()->customData()->value("CustomGroupData"),
+ sourceDb->rootGroup()->customData()->value("CustomGroupData"));
}
// clang-format off
@@ -346,20 +348,22 @@ void TestKdbx4::testCustomData()
const QString customDataKey2 = "CD2";
const QString customData1 = "abcäöü";
const QString customData2 = "Hello World";
- const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
- + customData2.toUtf8().size();
// test custom database data
db.metadata()->customData()->set(customDataKey1, customData1);
db.metadata()->customData()->set(customDataKey2, customData2);
- QCOMPARE(db.metadata()->customData()->size(), 2);
+ auto lastModified = db.metadata()->customData()->value(CustomData::LastModified);
+ const int dataSize = customDataKey1.toUtf8().size() + customDataKey1.toUtf8().size() + customData1.toUtf8().size()
+ + customData2.toUtf8().size() + lastModified.toUtf8().size()
+ + CustomData::LastModified.toUtf8().size();
+ QCOMPARE(db.metadata()->customData()->size(), 3);
QCOMPARE(db.metadata()->customData()->dataSize(), dataSize);
// test custom root group data
Group* root = db.rootGroup();
root->customData()->set(customDataKey1, customData1);
root->customData()->set(customDataKey2, customData2);
- QCOMPARE(root->customData()->size(), 2);
+ QCOMPARE(root->customData()->size(), 3);
QCOMPARE(root->customData()->dataSize(), dataSize);
// test copied custom group data
@@ -378,9 +382,9 @@ void TestKdbx4::testCustomData()
// test custom data deletion
entry->customData()->set("additional item", "foobar");
- QCOMPARE(entry->customData()->size(), 3);
+ QCOMPARE(entry->customData()->size(), 4);
entry->customData()->remove("additional item");
- QCOMPARE(entry->customData()->size(), 2);
+ QCOMPARE(entry->customData()->size(), 3);
QCOMPARE(entry->customData()->dataSize(), dataSize);
// test custom data on cloned groups
diff --git a/tests/TestMerge.cpp b/tests/TestMerge.cpp
index 03eae32ef..4d9aef211 100644
--- a/tests/TestMerge.cpp
+++ b/tests/TestMerge.cpp
@@ -1164,6 +1164,65 @@ void TestMerge::testMetadata()
// will be used - exception is the target has no recycle bin activated
}
+void TestMerge::testCustomdata()
+{
+ QScopedPointer<Database> dbDestination(new Database());
+ QScopedPointer<Database> dbSource(createTestDatabase());
+ QScopedPointer<Database> dbDestination2(new Database());
+ QScopedPointer<Database> dbSource2(createTestDatabase());
+
+ m_clock->advanceSecond(1);
+
+ dbDestination->metadata()->customData()->set("toBeDeleted", "value");
+ dbDestination->metadata()->customData()->set("key3", "oldValue");
+
+ dbSource2->metadata()->customData()->set("key1", "value1");
+ dbSource2->metadata()->customData()->set("key2", "value2");
+ dbSource2->metadata()->customData()->set("key3", "newValue");
+ dbSource2->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
+
+ m_clock->advanceSecond(1);
+
+ dbSource->metadata()->customData()->set("key1", "value1");
+ dbSource->metadata()->customData()->set("key2", "value2");
+ dbSource->metadata()->customData()->set("key3", "newValue");
+ dbSource->metadata()->customData()->set("Browser", "n'8=3W@L^6d->d.]St_>]");
+
+ dbDestination2->metadata()->customData()->set("notToBeDeleted", "value");
+ dbDestination2->metadata()->customData()->set("key3", "oldValue");
+
+ // Sanity check.
+ QVERIFY(!dbSource->metadata()->customData()->isEmpty());
+ QVERIFY(!dbSource2->metadata()->customData()->isEmpty());
+
+ m_clock->advanceSecond(1);
+
+ Merger merger(dbSource.data(), dbDestination.data());
+ merger.merge();
+
+ Merger merger2(dbSource2.data(), dbDestination2.data());
+ merger2.merge();
+
+ // Source is newer, data should be merged
+ QVERIFY(!dbDestination->metadata()->customData()->isEmpty());
+ QVERIFY(dbDestination->metadata()->customData()->contains("key1"));
+ QVERIFY(dbDestination->metadata()->customData()->contains("key2"));
+ QVERIFY(dbDestination->metadata()->customData()->contains("Browser"));
+ QVERIFY(!dbDestination->metadata()->customData()->contains("toBeDeleted"));
+ QCOMPARE(dbDestination->metadata()->customData()->value("key1"), QString("value1"));
+ QCOMPARE(dbDestination->metadata()->customData()->value("key2"), QString("value2"));
+ QCOMPARE(dbDestination->metadata()->customData()->value("Browser"), QString("n'8=3W@L^6d->d.]St_>]"));
+ QCOMPARE(dbDestination->metadata()->customData()->value("key3"), QString("newValue")); // Old value should be replaced
+
+ // Target is newer, no data is merged
+ QVERIFY(!dbDestination2->metadata()->customData()->isEmpty());
+ QVERIFY(!dbDestination2->metadata()->customData()->contains("key1"));
+ QVERIFY(!dbDestination2->metadata()->customData()->contains("key2"));
+ QVERIFY(!dbDestination2->metadata()->customData()->contains("Browser"));
+ QVERIFY(dbDestination2->metadata()->customData()->contains("notToBeDeleted"));
+ QCOMPARE(dbDestination2->metadata()->customData()->value("key3"), QString("oldValue")); // Old value should not be replaced
+}
+
void TestMerge::testDeletedEntry()
{
QScopedPointer<Database> dbDestination(createTestDatabase());
diff --git a/tests/TestMerge.h b/tests/TestMerge.h
index 357f85262..15f67ca79 100644
--- a/tests/TestMerge.h
+++ b/tests/TestMerge.h
@@ -59,6 +59,7 @@ private slots:
void testMergeCustomIcons();
void testMergeDuplicateCustomIcons();
void testMetadata();
+ void testCustomdata();
void testDeletedEntry();
void testDeletedGroup();
void testDeletedRevertedEntry();
diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp
index 63e36fbaa..0db2a5dfb 100644
--- a/tests/gui/TestGui.cpp
+++ b/tests/gui/TestGui.cpp
@@ -1286,20 +1286,29 @@ void TestGui::testTrayRestoreHide()
QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test...");
}
+ m_mainWindow->hideWindow();
+ QVERIFY(!m_mainWindow->isVisible());
+
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
- QVERIFY(m_mainWindow->isVisible());
+ QVERIFY(trayIcon);
trayIcon->activated(QSystemTrayIcon::Trigger);
- QTRY_VERIFY(!m_mainWindow->isVisible());
+ QTRY_VERIFY(m_mainWindow->isVisible());
trayIcon->activated(QSystemTrayIcon::Trigger);
+ QTRY_VERIFY(!m_mainWindow->isVisible());
+
+ trayIcon->activated(QSystemTrayIcon::MiddleClick);
QTRY_VERIFY(m_mainWindow->isVisible());
- trayIcon->activated(QSystemTrayIcon::Trigger);
+ trayIcon->activated(QSystemTrayIcon::MiddleClick);
QTRY_VERIFY(!m_mainWindow->isVisible());
- trayIcon->activated(QSystemTrayIcon::Trigger);
+ trayIcon->activated(QSystemTrayIcon::DoubleClick);
QTRY_VERIFY(m_mainWindow->isVisible());
+
+ trayIcon->activated(QSystemTrayIcon::DoubleClick);
+ QTRY_VERIFY(!m_mainWindow->isVisible());
}
int TestGui::addCannedEntries()