From 18b22834c1d9657ab98ca5160571e51837bb6366 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 29 Sep 2018 14:55:33 +0200 Subject: [PATCH 1/6] Update and fix test coverage report generation Generation of unit test coverage reports used to be quite complicated and required a lot of different settings, including a custom CMake build type. This patch updates the coverage CMake module to only require -DWITH_COVERAGE=ON to be set on a normal Debug build in order to create a coverage target. This patch also moves away from lcov in favor of gcovr, since lcov appears to be broken in GCC 8. However, the routines for generating lcov reports still exist, so provided lcov receives updates and there is sufficient reason to switch back, it is easy to do so. --- CMakeLists.txt | 21 ++- cmake/CodeCoverage.cmake | 378 +++++++++++++++++++++++++-------------- src/crypto/Crypto.cpp | 2 +- 3 files changed, 256 insertions(+), 145 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41a6b9403f..0f8a4b6725 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,12 +204,6 @@ endif() if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align") - - if(WITH_COVERAGE) - # Include code coverage, use with -DCMAKE_BUILD_TYPE=Coverage - include(CodeCoverage) - setup_target_for_coverage(kp_coverage "make test" coverage) - endif() endif() if(CMAKE_COMPILER_IS_GNUCC) @@ -291,6 +285,21 @@ if(WITH_TESTS) enable_testing() endif(WITH_TESTS) +if(WITH_COVERAGE) + # Include code coverage, use with -DCMAKE_BUILD_TYPE=Debug + include(CodeCoverage) + set(COVERAGE_GCOVR_EXCLUDES + "\\(.+/\\)?tests/.\\*" + ".\\*/moc_\\[^/\\]+\\.cpp" + ".\\*/ui_\\[^/\\]+\\.h" + "\\(.+/\\)?zxcvbn/.\\*") + append_coverage_compiler_flags() + setup_target_for_coverage_gcovr_html( + NAME coverage + EXECUTABLE $(MAKE) && $(MAKE) test + ) +endif() + include(CLangFormat) if(UNIX AND NOT APPLE) diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index d10f79723a..d10791745a 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -1,4 +1,4 @@ -# Copyright (c) 2012 - 2015, Lars Bilke +# Copyright (c) 2012 - 2017, Lars Bilke # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -26,7 +26,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# +# CHANGES: # # 2012-01-31, Lars Bilke # - Enable Code Coverage @@ -35,167 +35,269 @@ # - Added support for Clang. # - Some additional usage instructions. # +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# # USAGE: - -# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: -# http://stackoverflow.com/a/22404544/80480 # # 1. Copy this file into your cmake modules path. # # 2. Add the following line to your CMakeLists.txt: -# INCLUDE(CodeCoverage) +# include(CodeCoverage) # -# 3. Set compiler flags to turn off optimization and enable coverage: -# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") -# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# 3. Append necessary compiler flags: +# APPEND_COVERAGE_COMPILER_FLAGS() # -# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target -# which runs your test executable and produces a lcov code coverage report: +# 4. If you need to exclude additional directories from the report, specify them +# using the COVERAGE_LCOV_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE_LCOV. # Example: -# SETUP_TARGET_FOR_COVERAGE( -# my_coverage_target # Name for custom target. -# test_driver # Name of the test driver executable that runs the tests. -# # NOTE! This should always have a ZERO as exit code -# # otherwise the coverage generation will not complete. -# coverage # Name of output directory. -# ) +# set(COVERAGE_LCOV_EXCLUDES 'dir1/*' 'dir2/*') # -# 4. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. # +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target # +include(CMakeParseArguments) + # Check prereqs -FIND_PROGRAM( GCOV_PATH gcov ) -FIND_PROGRAM( LCOV_PATH lcov ) -FIND_PROGRAM( GENHTML_PATH genhtml ) -FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) - -IF(NOT GCOV_PATH) - MESSAGE(FATAL_ERROR "gcov not found! Aborting...") -ENDIF() # NOT GCOV_PATH - -IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - IF("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) - MESSAGE(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - ENDIF() -ELSEIF(NOT CMAKE_COMPILER_IS_GNUCXX) - MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") -ENDIF() # CHECK VALID COMPILER - -SET(CMAKE_CXX_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( SIMPLE_PYTHON_EXECUTABLE python ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() +elseif(NOT CMAKE_COMPILER_IS_GNUCXX) + message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") +endif() + +set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE INTERNAL "") + +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE ) -SET(CMAKE_C_FLAGS_COVERAGE - "-g -O0 --coverage -fprofile-arcs -ftest-coverage" +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} CACHE STRING "Flags used by the C compiler during coverage builds." FORCE ) -SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used for linking binaries during coverage builds." FORCE ) -SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE ) -MARK_AS_ADVANCED( +mark_as_advanced( CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) -IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) - MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) -ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" - - -# Param _targetname The name of new the custom make target -# Param _testrunner The name of the target which runs the tests. -# MUST return ZERO always, even on errors. -# If not, no coverage report will be created! -# Param _outputname lcov output is generated as _outputname.info -# HTML report is generated in _outputname/index.html -# Optional fourth parameter is passed as arguments to _testrunner -# Pass them in list form, e.g.: "-j;2" for -j 2 -FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - - IF(NOT LCOV_PATH) - MESSAGE(FATAL_ERROR "lcov not found! Aborting...") - ENDIF() # NOT LCOV_PATH - - IF(NOT GENHTML_PATH) - MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") - ENDIF() # NOT GENHTML_PATH - - SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") - IF(MINGW) - # Replace C:/ with /C for MINGW - STRING(REGEX REPLACE "^([a-zA-Z]):" "/\\1" coverage_info ${coverage_info}) - ENDIF() - SET(coverage_cleaned "${coverage_info}.cleaned") - - SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") - - # Setup target - ADD_CUSTOM_TARGET(${_targetname} - - # Cleanup lcov - ${LCOV_PATH} --directory . --zerocounters - - # Run tests - COMMAND ${test_command} ${ARGV3} - - # Capturing lcov counters and generating report - COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} - COMMAND ${LCOV_PATH} --remove ${coverage_info} 'tests/*' '/usr/*' --output-file ${coverage_cleaned} - COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} - COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned} - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) - - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." - ) - -ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE - -# Param _targetname The name of new the custom make target -# Param _testrunner The name of the target which runs the tests -# Param _outputname cobertura output is generated as _outputname.xml -# Optional fourth parameter is passed as arguments to _testrunner -# Pass them in list form, e.g.: "-j;2" for -j 2 -FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) - - IF(NOT PYTHON_EXECUTABLE) - MESSAGE(FATAL_ERROR "Python not found! Aborting...") - ENDIF() # NOT PYTHON_EXECUTABLE - - IF(NOT GCOVR_PATH) - MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") - ENDIF() # NOT GCOVR_PATH - - ADD_CUSTOM_TARGET(${_targetname} - - # Run tests - ${_testrunner} ${ARGV3} - - # Running gcovr - COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) - - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." - ) - -ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +else() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# SETUP_TARGET_FOR_COVERAGE_LCOV( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# ) +function(SETUP_TARGET_FOR_COVERAGE_LCOV) + + set(options NONE) + set(oneValueArgs NAME) + set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup lcov + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} -directory . --zerocounters + # Create baseline to make sure untouched files show up in the report + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base + + # Run tests + COMMAND ${Coverage_EXECUTABLE} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info + # add baseline counters + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total + COMMAND ${LCOV_PATH} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_LCOV_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # SETUP_TARGET_FOR_COVERAGE_LCOV + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# SETUP_TARGET_FOR_COVERAGE_GCOVR_XML( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# ) +function(SETUP_TARGET_FOR_COVERAGE_GCOVR_XML) + + set(options NONE) + set(oneValueArgs NAME) + set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT SIMPLE_PYTHON_EXECUTABLE) + message(FATAL_ERROR "python not found! Aborting...") + endif() # NOT SIMPLE_PYTHON_EXECUTABLE + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDES "-e") + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} + + # Running gcovr + COMMAND ${GCOVR_PATH} --xml + -r ${PROJECT_SOURCE_DIR} ${GCOVR_EXCLUDES} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) + +endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_XML + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# ) +function(SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML) + + set(options NONE) + set(oneValueArgs NAME) + set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT SIMPLE_PYTHON_EXECUTABLE) + message(FATAL_ERROR "python not found! Aborting...") + endif() # NOT SIMPLE_PYTHON_EXECUTABLE + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDES "-e") + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + + add_custom_target(${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} + + # Create folder + COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + + # Running gcovr + COMMAND ${GCOVR_PATH} --html --html-details + -r ${PROJECT_SOURCE_DIR} ${GCOVR_EXCLUDES} + --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML + +function(APPEND_COVERAGE_COMPILER_FLAGS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # APPEND_COVERAGE_COMPILER_FLAGS \ No newline at end of file diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index c8bac11e5e..0ed6f003a6 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -50,7 +50,7 @@ bool Crypto::init() // has to be set before testing Crypto classes m_initalized = true; - if (!selfTest()) { + if (!backendSelfTest() || !selfTest()) { m_initalized = false; return false; } From 113c8eb702dbdfefaf23a76bfe69603b54522b28 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 29 Sep 2018 19:00:47 +0200 Subject: [PATCH 2/6] Add CLI tests and improve coding style and i18n The CLI module was lacking unit test coverage and showed some severe coding style violations, which this patch addresses. In addition, all uses of qCritical() with untranslatble raw char* sequences were removed in favor of proper locale strings. These are written to STDERR through QTextStreams and support output redirection for testing purposes. With this change, error messages don't depend on the global Qt logging settings and targets anymore and go directly to the terminal or into a file if needed. This patch also fixes a bug discovered during unit test development, where the extract command would just dump the raw XML contents without decrypting embedded Salsa20-protected values first, making the XML export mostly useless, since passwords are scrambled. Lastly, all CLI commands received a dedicated -h/--help option. --- CMakeLists.txt | 8 +- Dockerfile | 4 +- ci/trusty/Dockerfile | 2 + src/CMakeLists.txt | 1 + src/browser/BrowserAction.cpp | 1 - src/browser/BrowserSettings.cpp | 5 - src/browser/BrowserSettings.h | 1 - src/cli/Add.cpp | 46 +- src/cli/Clip.cpp | 37 +- src/cli/Command.cpp | 11 +- src/cli/Command.h | 2 +- src/cli/Diceware.cpp | 22 +- src/cli/Edit.cpp | 52 +- src/cli/Estimate.cpp | 84 +- src/cli/Extract.cpp | 40 +- src/cli/Generate.cpp | 39 +- src/cli/List.cpp | 39 +- src/cli/List.h | 2 +- src/cli/Locate.cpp | 29 +- src/cli/Locate.h | 2 +- src/cli/Merge.cpp | 38 +- src/cli/Remove.cpp | 33 +- src/cli/Remove.h | 2 +- src/cli/Show.cpp | 41 +- src/cli/Show.h | 2 +- src/cli/Utils.cpp | 77 +- src/cli/Utils.h | 15 +- src/cli/keepassxc-cli.cpp | 14 +- src/core/Bootstrap.cpp | 236 ++++++ src/core/Bootstrap.h | 34 + src/core/Database.cpp | 24 +- src/core/Database.h | 3 +- src/core/PasswordGenerator.cpp | 36 +- src/core/PasswordGenerator.h | 1 - src/core/Tools.cpp | 368 +++------ src/core/Tools.h | 42 +- src/crypto/CryptoHash.cpp | 7 - src/crypto/CryptoHash.h | 1 - src/crypto/SymmetricCipher.cpp | 8 +- src/crypto/SymmetricCipherGcrypt.cpp | 2 - src/format/Kdbx3Reader.cpp | 8 - src/format/Kdbx4Reader.cpp | 8 - src/format/KdbxReader.cpp | 31 +- src/format/KdbxReader.h | 2 + src/format/KdbxXmlReader.cpp | 1 - src/format/KdbxXmlWriter.cpp | 31 +- src/format/KdbxXmlWriter.h | 4 + src/format/KeePass2.cpp | 5 +- src/format/KeePass2.h | 3 +- src/format/KeePass2Reader.cpp | 2 +- src/gui/Application.h | 2 +- src/gui/DatabaseWidget.h | 2 +- .../DatabaseSettingsWidgetEncryption.cpp | 2 +- src/main.cpp | 87 +- tests/CMakeLists.txt | 12 +- tests/TestCli.cpp | 756 ++++++++++++++++++ tests/TestCli.h | 69 ++ tests/TestKdbx4.cpp | 12 +- tests/TestPasswordGenerator.cpp | 131 +++ tests/TestPasswordGenerator.h | 33 + tests/TestSymmetricCipher.cpp | 337 ++++---- tests/TestSymmetricCipher.h | 13 +- tests/gui/CMakeLists.txt | 2 +- tests/gui/TemporaryFile.cpp | 93 --- tests/gui/TemporaryFile.h | 65 -- tests/gui/TestGui.cpp | 358 +++++---- tests/gui/TestGui.h | 9 +- 67 files changed, 2249 insertions(+), 1240 deletions(-) create mode 100644 src/core/Bootstrap.cpp create mode 100644 src/core/Bootstrap.h create mode 100644 tests/TestCli.cpp create mode 100644 tests/TestCli.h create mode 100644 tests/TestPasswordGenerator.cpp create mode 100644 tests/TestPasswordGenerator.h delete mode 100644 tests/gui/TemporaryFile.cpp delete mode 100644 tests/gui/TemporaryFile.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f8a4b6725..f14b363b38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,16 +303,16 @@ endif() include(CLangFormat) if(UNIX AND NOT APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools DBus REQUIRED) + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools DBus REQUIRED) elseif(APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH ) find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH ) else() - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg Test LinguistTools REQUIRED) + find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED) endif() if(Qt5Core_VERSION VERSION_LESS "5.2.0") @@ -345,9 +345,7 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) find_package(LibGPGError REQUIRED) find_package(Gcrypt 1.7.0 REQUIRED) find_package(Argon2 REQUIRED) - find_package(ZLIB REQUIRED) - find_package(QREncode REQUIRED) set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) diff --git a/Dockerfile b/Dockerfile index 1054872adb..e1fd26d404 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,7 +57,9 @@ RUN set -x \ mesa-common-dev \ libyubikey-dev \ libykpers-1-dev \ - libqrencode-dev + libqrencode-dev \ + xclip \ + xvfb ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}" ENV CMAKE_PREFIX_PATH="/opt/${QT5_VERSION}/lib/cmake" diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile index cd69639d27..6a3d0567fc 100644 --- a/ci/trusty/Dockerfile +++ b/ci/trusty/Dockerfile @@ -39,6 +39,7 @@ RUN set -x \ clang-3.6 \ libclang-common-3.6-dev \ clang-format-3.6 \ + llvm-3.6 \ cmake3 \ make \ libgcrypt20-18-dev \ @@ -56,6 +57,7 @@ RUN set -x \ libxi-dev \ libxtst-dev \ libqrencode-dev \ + xclip \ xvfb ENV PATH="/opt/${QT5_VERSION}/bin:${PATH}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c70de316b8..3b41b38783 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(keepassx_SOURCES core/EntryAttributes.cpp core/EntrySearcher.cpp core/FilePath.cpp + core/Bootstrap.cpp core/Global.h core/Group.cpp core/InactivityTimer.cpp diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index c073bb4b27..6001acb186 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -271,7 +271,6 @@ QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const { const QString nonce = json.value("nonce").toString(); const QString password = browserSettings()->generatePassword(); - const QString bits = QString::number(browserSettings()->getbits()); // For some reason this always returns 1140 bits? if (nonce.isEmpty() || password.isEmpty()) { return QJsonObject(); diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index f7abdece98..96d8f98d7b 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -485,11 +485,6 @@ QString BrowserSettings::generatePassword() } } -int BrowserSettings::getbits() -{ - return m_passwordGenerator.getbits(); -} - void BrowserSettings::updateBinaryPaths(QString customProxyLocation) { bool isProxy = supportBrowserProxy(); diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index 3e84ba37dd..5dc28593a1 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -112,7 +112,6 @@ class BrowserSettings PasswordGenerator::CharClasses passwordCharClasses(); PasswordGenerator::GeneratorFlags passwordGeneratorFlags(); QString generatePassword(); - int getbits(); void updateBinaryPaths(QString customProxyLocation = QString()); private: diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 5c97299b82..81a5cad135 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -41,22 +41,20 @@ Add::~Add() int Add::execute(const QStringList& arguments) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream inputTextStream(Utils::STDIN, QIODevice::ReadOnly); + QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption username(QStringList() << "u" - << "username", + QCommandLineOption username(QStringList() << "u" << "username", QObject::tr("Username for the entry."), QObject::tr("username")); parser.addOption(username); @@ -64,23 +62,22 @@ int Add::execute(const QStringList& arguments) QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL")); parser.addOption(url); - QCommandLineOption prompt(QStringList() << "p" - << "password-prompt", + QCommandLineOption prompt(QStringList() << "p" << "password-prompt", QObject::tr("Prompt for the entry's password.")); parser.addOption(prompt); - QCommandLineOption generate(QStringList() << "g" - << "generate", + QCommandLineOption generate(QStringList() << "g" << "generate", QObject::tr("Generate a password for the entry.")); parser.addOption(generate); - QCommandLineOption length(QStringList() << "l" - << "password-length", + QCommandLineOption length(QStringList() << "l" << "password-length", QObject::tr("Length for the generated password."), QObject::tr("length")); parser.addOption(length); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to add.")); + + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -89,11 +86,11 @@ int Add::execute(const QStringList& arguments) return EXIT_FAILURE; } - QString databasePath = args.at(0); - QString entryPath = args.at(1); + const QString& databasePath = args.at(0); + const QString& entryPath = args.at(1); - Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile)); - if (db == nullptr) { + Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR); + if (!db) { return EXIT_FAILURE; } @@ -101,13 +98,13 @@ int Add::execute(const QStringList& arguments) // the entry. QString passwordLength = parser.value(length); if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - qCritical("Invalid value for password length %s.", qPrintable(passwordLength)); + errorTextStream << QObject::tr("Invalid value for password length %1.").arg(passwordLength) << endl; return EXIT_FAILURE; } Entry* entry = db->rootGroup()->addEntryWithPath(entryPath); if (!entry) { - qCritical("Could not create entry with path %s.", qPrintable(entryPath)); + errorTextStream << QObject::tr("Could not create entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -120,8 +117,7 @@ int Add::execute(const QStringList& arguments) } if (parser.isSet(prompt)) { - outputTextStream << "Enter password for new entry: "; - outputTextStream.flush(); + outputTextStream << QObject::tr("Enter password for new entry: ") << flush; QString password = Utils::getPassword(); entry->setPassword(password); } else if (parser.isSet(generate)) { @@ -130,7 +126,7 @@ int Add::execute(const QStringList& arguments) if (passwordLength.isEmpty()) { passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { - passwordGenerator.setLength(passwordLength.toInt()); + passwordGenerator.setLength(static_cast(passwordLength.toInt())); } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); @@ -141,10 +137,10 @@ int Add::execute(const QStringList& arguments) QString errorMessage = db->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Writing the database failed %s.", qPrintable(errorMessage)); + errorTextStream << QObject::tr("Writing the database failed %1.").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << "Successfully added entry " << entry->title() << "." << endl; + outputTextStream << QObject::tr("Successfully added entry %1.").arg(entry->title()) << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 886f8ecc71..0b78a24b40 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -42,20 +42,19 @@ Clip::~Clip() int Clip::execute(const QStringList& arguments) { - - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.", "clip = copy to clipboard")); - parser.addPositionalArgument( - "timeout", QObject::tr("Timeout in seconds before clearing the clipboard."), QString("[timeout]")); + parser.addPositionalArgument("timeout", + QObject::tr("Timeout in seconds before clearing the clipboard."), "[timeout]"); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -64,29 +63,30 @@ int Clip::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); + Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR); if (!db) { return EXIT_FAILURE; } - return this->clipEntry(db, args.at(1), args.value(2)); + return clipEntry(db, args.at(1), args.value(2)); } int Clip::clipEntry(Database* database, QString entryPath, QString timeout) { + QTextStream err(Utils::STDERR); int timeoutSeconds = 0; if (!timeout.isEmpty() && !timeout.toInt()) { - qCritical("Invalid timeout value %s.", qPrintable(timeout)); + err << QObject::tr("Invalid timeout value %1.").arg(timeout) << endl; return EXIT_FAILURE; } else if (!timeout.isEmpty()) { timeoutSeconds = timeout.toInt(); } - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntry(entryPath); if (!entry) { - qCritical("Entry %s not found.", qPrintable(entryPath)); + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -95,20 +95,23 @@ int Clip::clipEntry(Database* database, QString entryPath, QString timeout) return exitCode; } - outputTextStream << "Entry's password copied to the clipboard!" << endl; + outputTextStream << QObject::tr("Entry's password copied to the clipboard!") << endl; if (!timeoutSeconds) { return exitCode; } + QString lastLine = ""; while (timeoutSeconds > 0) { - outputTextStream << "\rClearing the clipboard in " << timeoutSeconds << " seconds..."; - outputTextStream.flush(); + outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; + lastLine = QObject::tr("Clearing the clipboard in %1 second(s)...", "", timeoutSeconds).arg(timeoutSeconds); + outputTextStream << lastLine << flush; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - timeoutSeconds--; + --timeoutSeconds; } Utils::clipText(""); - outputTextStream << "\nClipboard cleared!" << endl; + outputTextStream << '\r' << QString(lastLine.size(), ' ') << '\r'; + outputTextStream << QObject::tr("Clipboard cleared!") << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index ef69488889..c85e5d95de 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -41,19 +41,14 @@ Command::~Command() { } -int Command::execute(const QStringList&) -{ - return EXIT_FAILURE; -} - QString Command::getDescriptionLine() { - QString response = this->name; + QString response = name; QString space(" "); - QString spaces = space.repeated(15 - this->name.length()); + QString spaces = space.repeated(15 - name.length()); response = response.append(spaces); - response = response.append(this->description); + response = response.append(description); response = response.append("\n"); return response; } diff --git a/src/cli/Command.h b/src/cli/Command.h index 2ebdd77b9b..7ad49440aa 100644 --- a/src/cli/Command.h +++ b/src/cli/Command.h @@ -29,7 +29,7 @@ class Command { public: virtual ~Command(); - virtual int execute(const QStringList& arguments); + virtual int execute(const QStringList& arguments) = 0; QString name; QString description; QString getDescriptionLine(); diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index 4cdda0a73a..72cbd19605 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -24,6 +24,7 @@ #include #include "core/PassphraseGenerator.h" +#include "Utils.h" Diceware::Diceware() { @@ -37,26 +38,25 @@ Diceware::~Diceware() int Diceware::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); - QCommandLineOption words(QStringList() << "W" - << "words", + parser.setApplicationDescription(description); + QCommandLineOption words(QStringList() << "W" << "words", QObject::tr("Word count for the diceware passphrase."), - QObject::tr("count")); + QObject::tr("count", "CLI parameter")); parser.addOption(words); - QCommandLineOption wordlistFile(QStringList() << "w" - << "word-list", + QCommandLineOption wordlistFile(QStringList() << "w" << "word-list", QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"), QObject::tr("path")); parser.addOption(wordlistFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } @@ -76,12 +76,12 @@ int Diceware::execute(const QStringList& arguments) } if (!dicewareGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } QString password = dicewareGenerator.generatePassphrase(); - outputTextStream << password << endl; + out << password << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 056a0a595a..c2f0677941 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -41,22 +41,20 @@ Edit::~Edit() int Edit::execute(const QStringList& arguments) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption username(QStringList() << "u" - << "username", + QCommandLineOption username(QStringList() << "u" << "username", QObject::tr("Username for the entry."), QObject::tr("username")); parser.addOption(username); @@ -64,61 +62,58 @@ int Edit::execute(const QStringList& arguments) QCommandLineOption url(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL")); parser.addOption(url); - QCommandLineOption title(QStringList() << "t" - << "title", + QCommandLineOption title(QStringList() << "t" << "title", QObject::tr("Title for the entry."), QObject::tr("title")); parser.addOption(title); - QCommandLineOption prompt(QStringList() << "p" - << "password-prompt", + QCommandLineOption prompt(QStringList() << "p" << "password-prompt", QObject::tr("Prompt for the entry's password.")); parser.addOption(prompt); - QCommandLineOption generate(QStringList() << "g" - << "generate", + QCommandLineOption generate(QStringList() << "g" << "generate", QObject::tr("Generate a password for the entry.")); parser.addOption(generate); - QCommandLineOption length(QStringList() << "l" - << "password-length", + QCommandLineOption length(QStringList() << "l" << "password-length", QObject::tr("Length for the generated password."), QObject::tr("length")); parser.addOption(length); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to edit.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli edit"); return EXIT_FAILURE; } - QString databasePath = args.at(0); - QString entryPath = args.at(1); + const QString& databasePath = args.at(0); + const QString& entryPath = args.at(1); - Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile)); - if (db == nullptr) { + Database* db = Database::unlockFromStdin(databasePath, parser.value(keyFile), Utils::STDOUT, Utils::STDERR); + if (!db) { return EXIT_FAILURE; } QString passwordLength = parser.value(length); if (!passwordLength.isEmpty() && !passwordLength.toInt()) { - qCritical("Invalid value for password length %s.", qPrintable(passwordLength)); + err << QObject::tr("Invalid value for password length: %1").arg(passwordLength) << endl; return EXIT_FAILURE; } Entry* entry = db->rootGroup()->findEntryByPath(entryPath); if (!entry) { - qCritical("Could not find entry with path %s.", qPrintable(entryPath)); + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } if (parser.value("username").isEmpty() && parser.value("url").isEmpty() && parser.value("title").isEmpty() && !parser.isSet(prompt) && !parser.isSet(generate)) { - qCritical("Not changing any field for entry %s.", qPrintable(entryPath)); + err << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -137,8 +132,7 @@ int Edit::execute(const QStringList& arguments) } if (parser.isSet(prompt)) { - outputTextStream << "Enter new password for entry: "; - outputTextStream.flush(); + out << QObject::tr("Enter new password for entry: ") << flush; QString password = Utils::getPassword(); entry->setPassword(password); } else if (parser.isSet(generate)) { @@ -147,7 +141,7 @@ int Edit::execute(const QStringList& arguments) if (passwordLength.isEmpty()) { passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { - passwordGenerator.setLength(passwordLength.toInt()); + passwordGenerator.setLength(static_cast(passwordLength.toInt())); } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); @@ -160,10 +154,10 @@ int Edit::execute(const QStringList& arguments) QString errorMessage = db->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Writing the database failed %s.", qPrintable(errorMessage)); + err << QObject::tr("Writing the database failed: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - outputTextStream << "Successfully edited entry " << entry->title() << "." << endl; + out << QObject::tr("Successfully edited entry %1.").arg(entry->title()) << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp index 9a2ab0b0f7..c249d7b1f0 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -16,6 +16,7 @@ */ #include "Estimate.h" +#include "cli/Utils.h" #include #include @@ -44,117 +45,126 @@ Estimate::~Estimate() static void estimate(const char* pwd, bool advanced) { - double e; - int len = strlen(pwd); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + + double e = 0.0; + int len = static_cast(strlen(pwd)); if (!advanced) { - e = ZxcvbnMatch(pwd, 0, 0); - printf("Length %d\tEntropy %.3f\tLog10 %.3f\n", len, e, e * 0.301029996); + e = ZxcvbnMatch(pwd, nullptr, nullptr); + out << QObject::tr("Length %1").arg(len, 0) << '\t' + << QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t' + << QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << endl; } else { - int ChkLen; + int ChkLen = 0; ZxcMatch_t *info, *p; double m = 0.0; - e = ZxcvbnMatch(pwd, 0, &info); + e = ZxcvbnMatch(pwd, nullptr, &info); for (p = info; p; p = p->Next) { m += p->Entrpy; } m = e - m; - printf("Length %d\tEntropy %.3f\tLog10 %.3f\n Multi-word extra bits %.1f\n", len, e, e * 0.301029996, m); + out << QObject::tr("Length %1").arg(len) << '\t' + << QObject::tr("Entropy %1").arg(e, 0, 'f', 3) << '\t' + << QObject::tr("Log10 %1").arg(e * 0.301029996, 0, 'f', 3) << "\n " + << QObject::tr("Multi-word extra bits %1").arg(m, 0, 'f', 1) << endl; p = info; ChkLen = 0; while (p) { int n; switch (static_cast(p->Type)) { case BRUTE_MATCH: - printf(" Type: Bruteforce "); + out << " " << QObject::tr("Type: Bruteforce") << " "; break; case DICTIONARY_MATCH: - printf(" Type: Dictionary "); + out << " " << QObject::tr("Type: Dictionary") << " "; break; case DICT_LEET_MATCH: - printf(" Type: Dict+Leet "); + out << " " << QObject::tr("Type: Dict+Leet") << " "; break; case USER_MATCH: - printf(" Type: User Words "); + out << " " << QObject::tr("Type: User Words") << " "; break; case USER_LEET_MATCH: - printf(" Type: User+Leet "); + out << " " << QObject::tr("Type: User+Leet") << " "; break; case REPEATS_MATCH: - printf(" Type: Repeated "); + out << " " << QObject::tr("Type: Repeated") << " "; break; case SEQUENCE_MATCH: - printf(" Type: Sequence "); + out << " " << QObject::tr("Type: Sequence") << " "; break; case SPATIAL_MATCH: - printf(" Type: Spatial "); + out << " " << QObject::tr("Type: Spatial") << " "; break; case DATE_MATCH: - printf(" Type: Date "); + out << " " << QObject::tr("Type: Date") << " "; break; case BRUTE_MATCH + MULTIPLE_MATCH: - printf(" Type: Bruteforce(Rep)"); + out << " " << QObject::tr("Type: Bruteforce(Rep)") << " "; break; case DICTIONARY_MATCH + MULTIPLE_MATCH: - printf(" Type: Dictionary(Rep)"); + out << " " << QObject::tr("Type: Dictionary(Rep)") << " "; break; case DICT_LEET_MATCH + MULTIPLE_MATCH: - printf(" Type: Dict+Leet(Rep) "); + out << " " << QObject::tr("Type: Dict+Leet(Rep)") << " "; break; case USER_MATCH + MULTIPLE_MATCH: - printf(" Type: User Words(Rep)"); + out << " " << QObject::tr("Type: User Words(Rep)") << " "; break; case USER_LEET_MATCH + MULTIPLE_MATCH: - printf(" Type: User+Leet(Rep) "); + out << " " << QObject::tr("Type: User+Leet(Rep)") << " "; break; case REPEATS_MATCH + MULTIPLE_MATCH: - printf(" Type: Repeated(Rep) "); + out << " " << QObject::tr("Type: Repeated(Rep)") << " "; break; case SEQUENCE_MATCH + MULTIPLE_MATCH: - printf(" Type: Sequence(Rep) "); + out << " " << QObject::tr("Type: Sequence(Rep)") << " "; break; case SPATIAL_MATCH + MULTIPLE_MATCH: - printf(" Type: Spatial(Rep) "); + out << " " << QObject::tr("Type: Spatial(Rep)") << " "; break; case DATE_MATCH + MULTIPLE_MATCH: - printf(" Type: Date(Rep) "); + out << " " << QObject::tr("Type: Date(Rep)") << " "; break; default: - printf(" Type: Unknown%d ", p->Type); + out << " " << QObject::tr("Type: Unknown%1").arg(p->Type) << " "; break; } ChkLen += p->Length; - printf(" Length %d Entropy %6.3f (%.2f) ", p->Length, p->Entrpy, p->Entrpy * 0.301029996); + + out << QObject::tr("Length %1").arg(p->Length) << '\t' + << QObject::tr("Entropy %1 (%2)").arg(p->Entrpy, 6, 'f', 3).arg(p->Entrpy * 0.301029996, 0, 'f', 2) << '\t'; for (n = 0; n < p->Length; ++n, ++pwd) { - printf("%c", *pwd); + out << *pwd; } - printf("\n"); + out << endl; p = p->Next; } ZxcvbnFreeInfo(info); if (ChkLen != len) { - printf("*** Password length (%d) != sum of length of parts (%d) ***\n", len, ChkLen); + out << QObject::tr("*** Password length (%1) != sum of length of parts (%2) ***").arg(len).arg(ChkLen) << endl; } } } int Estimate::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("password", QObject::tr("Password for which to estimate the entropy."), "[password]"); - QCommandLineOption advancedOption(QStringList() << "a" - << "advanced", + QCommandLineOption advancedOption(QStringList() << "a" << "advanced", QObject::tr("Perform advanced analysis on the password.")); parser.addOption(advancedOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() > 1) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli estimate"); return EXIT_FAILURE; } @@ -162,7 +172,7 @@ int Estimate::execute(const QStringList& arguments) if (args.size() == 1) { password = args.at(0); } else { - password = inputTextStream.readLine(); + password = in.readLine(); } estimate(password.toLatin1(), parser.isSet(advancedOption)); diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index f0004e6882..cc39c469ad 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -43,17 +43,17 @@ Extract::~Extract() int Extract::execute(const QStringList& arguments) { - QTextStream out(stdout); - QTextStream errorTextStream(stderr); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database to extract.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -62,8 +62,7 @@ int Extract::execute(const QStringList& arguments) return EXIT_FAILURE; } - out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)); - out.flush(); + out << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)) << flush; auto compositeKey = QSharedPointer::create(); @@ -74,52 +73,51 @@ int Extract::execute(const QStringList& arguments) QString keyFilePath = parser.value(keyFile); if (!keyFilePath.isEmpty()) { + // LCOV_EXCL_START auto fileKey = QSharedPointer::create(); QString errorMsg; if (!fileKey->load(keyFilePath, &errorMsg)) { - errorTextStream << QObject::tr("Failed to load key file %1 : %2").arg(keyFilePath).arg(errorMsg); - errorTextStream << endl; + err << QObject::tr("Failed to load key file %1: %2").arg(keyFilePath).arg(errorMsg) << endl; return EXIT_FAILURE; } if (fileKey->type() != FileKey::Hashed) { - errorTextStream << QObject::tr("WARNING: You are using a legacy key file format which may become\n" - "unsupported in the future.\n\n" - "Please consider generating a new key file."); - errorTextStream << endl; + err << QObject::tr("WARNING: You are using a legacy key file format which may become\n" + "unsupported in the future.\n\n" + "Please consider generating a new key file.") << endl; } + // LCOV_EXCL_STOP compositeKey->addKey(fileKey); } - QString databaseFilename = args.at(0); + const QString& databaseFilename = args.at(0); QFile dbFile(databaseFilename); if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); + err << QObject::tr("File %1 does not exist.").arg(databaseFilename) << endl; return EXIT_FAILURE; } if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + err << QObject::tr("Unable to open file %1.").arg(databaseFilename) << endl; return EXIT_FAILURE; } KeePass2Reader reader; reader.setSaveXml(true); - Database* db = reader.readDatabase(&dbFile, compositeKey); - delete db; + QScopedPointer db(reader.readDatabase(&dbFile, compositeKey)); QByteArray xmlData = reader.reader()->xmlData(); if (reader.hasError()) { if (xmlData.isEmpty()) { - qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString())); + err << QObject::tr("Error while reading the database:\n%1").arg(reader.errorString()) << endl; } else { - qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + err << QObject::tr("Error while parsing the database:\n%1").arg(reader.errorString()) << endl; } return EXIT_FAILURE; } - out << xmlData.constData() << "\n"; + out << xmlData.constData() << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 6a5be3f07d..7780aa8294 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -19,6 +19,7 @@ #include #include "Generate.h" +#include "cli/Utils.h" #include #include @@ -37,38 +38,32 @@ Generate::~Generate() int Generate::execute(const QStringList& arguments) { - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + out.setCodec("UTF-8"); // force UTF-8 to prevent ??? characters in extended-ASCII passwords QCommandLineParser parser; - parser.setApplicationDescription(this->description); - QCommandLineOption len(QStringList() << "L" - << "length", + parser.setApplicationDescription(description); + QCommandLineOption len(QStringList() << "L" << "length", QObject::tr("Length of the generated password"), QObject::tr("length")); parser.addOption(len); - QCommandLineOption lower(QStringList() << "l" - << "lower", + QCommandLineOption lower(QStringList() << "l" << "lower", QObject::tr("Use lowercase characters")); parser.addOption(lower); - QCommandLineOption upper(QStringList() << "u" - << "upper", + QCommandLineOption upper(QStringList() << "u" << "upper", QObject::tr("Use uppercase characters")); parser.addOption(upper); - QCommandLineOption numeric(QStringList() << "n" - << "numeric", + QCommandLineOption numeric(QStringList() << "n" << "numeric", QObject::tr("Use numbers.")); parser.addOption(numeric); - QCommandLineOption special(QStringList() << "s" - << "special", + QCommandLineOption special(QStringList() << "s" << "special", QObject::tr("Use special characters")); parser.addOption(special); - QCommandLineOption extended(QStringList() << "e" - << "extended", + QCommandLineOption extended(QStringList() << "e" << "extended", QObject::tr("Use extended ASCII")); parser.addOption(extended); - QCommandLineOption exclude(QStringList() << "x" - << "exclude", + QCommandLineOption exclude(QStringList() << "x" << "exclude", QObject::tr("Exclude character set"), QObject::tr("chars")); parser.addOption(exclude); @@ -78,12 +73,12 @@ int Generate::execute(const QStringList& arguments) QCommandLineOption every_group(QStringList() << "every-group", QObject::tr("Include characters from every selected group")); parser.addOption(every_group); - + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (!args.isEmpty()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } @@ -93,7 +88,7 @@ int Generate::execute(const QStringList& arguments) passwordGenerator.setLength(PasswordGenerator::DefaultLength); } else { int length = parser.value(len).toInt(); - passwordGenerator.setLength(length); + passwordGenerator.setLength(static_cast(length)); } PasswordGenerator::CharClasses classes = 0x0; @@ -128,12 +123,12 @@ int Generate::execute(const QStringList& arguments) passwordGenerator.setExcludedChars(parser.value(exclude)); if (!passwordGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } QString password = passwordGenerator.generatePassword(); - outputTextStream << password << endl; + out << password << endl; return EXIT_SUCCESS; } diff --git a/src/cli/List.cpp b/src/cli/List.cpp index b39b3fc147..4d1ebcfc50 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -19,6 +19,7 @@ #include #include "List.h" +#include "cli/Utils.h" #include #include @@ -39,22 +40,20 @@ List::~List() int List::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), QString("[group]")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), "[group]"); + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - - QCommandLineOption recursiveOption(QStringList() << "R" - << "recursive", + QCommandLineOption recursiveOption(QStringList() << "R" << "recursive", QObject::tr("Recursive mode, list elements recursively")); parser.addOption(recursiveOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -65,33 +64,33 @@ int List::execute(const QStringList& arguments) bool recursive = parser.isSet(recursiveOption); - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } if (args.size() == 2) { - return this->listGroup(db, recursive, args.at(1)); + return listGroup(db.data(), recursive, args.at(1)); } - return this->listGroup(db, recursive); + return listGroup(db.data(), recursive); } -int List::listGroup(Database* database, bool recursive, QString groupPath) +int List::listGroup(Database* database, bool recursive, const QString& groupPath) { - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); + if (groupPath.isEmpty()) { - outputTextStream << database->rootGroup()->print(recursive); - outputTextStream.flush(); + out << database->rootGroup()->print(recursive) << flush; return EXIT_SUCCESS; } Group* group = database->rootGroup()->findGroupByPath(groupPath); - if (group == nullptr) { - qCritical("Cannot find group %s.", qPrintable(groupPath)); + if (!group) { + err << QObject::tr("Cannot find group %1.").arg(groupPath) << endl; return EXIT_FAILURE; } - outputTextStream << group->print(recursive); - outputTextStream.flush(); + out << group->print(recursive) << flush; return EXIT_SUCCESS; } diff --git a/src/cli/List.h b/src/cli/List.h index 00c3769722..5697d93904 100644 --- a/src/cli/List.h +++ b/src/cli/List.h @@ -26,7 +26,7 @@ class List : public Command List(); ~List(); int execute(const QStringList& arguments); - int listGroup(Database* database, bool recursive, QString groupPath = QString("")); + int listGroup(Database* database, bool recursive, const QString& groupPath = {}); }; #endif // KEEPASSXC_LIST_H diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index f803728855..3bca8ae1da 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -1,3 +1,5 @@ +#include + /* * Copyright (C) 2017 KeePassXC Team * @@ -25,6 +27,7 @@ #include #include "cli/Utils.h" +#include "core/Global.h" #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" @@ -41,18 +44,17 @@ Locate::~Locate() int Locate::execute(const QStringList& arguments) { - - QTextStream out(stdout); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); parser.addPositionalArgument("term", QObject::tr("Search term.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -61,26 +63,27 @@ int Locate::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); if (!db) { return EXIT_FAILURE; } - return this->locateEntry(db, args.at(1)); + return locateEntry(db.data(), args.at(1)); } -int Locate::locateEntry(Database* database, QString searchTerm) +int Locate::locateEntry(Database* database, const QString& searchTerm) { + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); QStringList results = database->rootGroup()->locate(searchTerm); if (results.isEmpty()) { - outputTextStream << "No results for that search term" << endl; - return EXIT_SUCCESS; + err << "No results for that search term." << endl; + return EXIT_FAILURE; } - for (QString result : results) { - outputTextStream << result << endl; + for (const QString& result : asConst(results)) { + out << result << endl; } return EXIT_SUCCESS; } diff --git a/src/cli/Locate.h b/src/cli/Locate.h index 3677a034df..3355d41ec6 100644 --- a/src/cli/Locate.h +++ b/src/cli/Locate.h @@ -26,7 +26,7 @@ class Locate : public Command Locate(); ~Locate(); int execute(const QStringList& arguments); - int locateEntry(Database* database, QString searchTerm); + int locateEntry(Database* database, const QString& searchTerm); }; #endif // KEEPASSXC_LOCATE_H diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index ea7e6636a2..a5b4a2cb7e 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -15,8 +15,6 @@ * along with this program. If not, see . */ -#include - #include "Merge.h" #include @@ -24,6 +22,9 @@ #include "core/Database.h" #include "core/Merger.h" +#include "cli/Utils.h" + +#include Merge::Merge() { @@ -37,29 +38,28 @@ Merge::~Merge() int Merge::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database1", QObject::tr("Path of the database to merge into.")); parser.addPositionalArgument("database2", QObject::tr("Path of the database to merge from.")); - QCommandLineOption samePasswordOption(QStringList() << "s" - << "same-credentials", + QCommandLineOption samePasswordOption(QStringList() << "s" << "same-credentials", QObject::tr("Use the same credentials for both database files.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - QCommandLineOption keyFileFrom(QStringList() << "f" - << "key-file-from", + QCommandLineOption keyFileFrom(QStringList() << "f" << "key-file-from", QObject::tr("Key file of the database to merge from."), QObject::tr("path")); parser.addOption(keyFileFrom); parser.addOption(samePasswordOption); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -68,30 +68,30 @@ int Merge::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db1 == nullptr) { + QScopedPointer db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db1) { return EXIT_FAILURE; } - Database* db2; + QScopedPointer db2; if (!parser.isSet("same-credentials")) { - db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom)); + db2.reset(Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom), Utils::STDOUT, Utils::STDERR)); } else { - db2 = Database::openDatabaseFile(args.at(1), db1->key()); + db2.reset(Database::openDatabaseFile(args.at(1), db1->key())); } - if (db2 == nullptr) { + if (!db2) { return EXIT_FAILURE; } - Merger merger(db2, db1); + Merger merger(db2.data(), db1.data()); merger.merge(); QString errorMessage = db1->saveToFile(args.at(0)); if (!errorMessage.isEmpty()) { - qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); + err << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl; return EXIT_FAILURE; } - out << "Successfully merged the database files.\n"; + out << "Successfully merged the database files." << endl; return EXIT_SUCCESS; } diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index 64a5976e9a..5bbfd67e43 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -44,40 +44,41 @@ Remove::~Remove() int Remove::execute(const QStringList& arguments) { - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(Utils::STDERR, QIODevice::WriteOnly); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "Remove an entry from the database.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + parser.setApplicationDescription(QCoreApplication::tr("main", "Remove an entry from the database.")); + parser.addPositionalArgument("database", QCoreApplication::tr("main", "Path of the database.")); + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); - parser.addPositionalArgument("entry", QCoreApplication::translate("main", "Path of the entry to remove.")); + parser.addPositionalArgument("entry", QCoreApplication::tr("main", "Path of the entry to remove.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); if (args.size() != 2) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli rm"); return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } - return this->removeEntry(db, args.at(0), args.at(1)); + return removeEntry(db.data(), args.at(0), args.at(1)); } -int Remove::removeEntry(Database* database, QString databasePath, QString entryPath) +int Remove::removeEntry(Database* database, const QString& databasePath, const QString& entryPath) { + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { - qCritical("Entry %s not found.", qPrintable(entryPath)); + err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -92,14 +93,14 @@ int Remove::removeEntry(Database* database, QString databasePath, QString entryP QString errorMessage = database->saveToFile(databasePath); if (!errorMessage.isEmpty()) { - qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); + err << QObject::tr("Unable to save database to file: %1").arg(errorMessage) << endl; return EXIT_FAILURE; } if (recycled) { - outputTextStream << "Successfully recycled entry " << entryTitle << "." << endl; + out << QObject::tr("Successfully recycled entry %1.").arg(entryTitle) << endl; } else { - outputTextStream << "Successfully deleted entry " << entryTitle << "." << endl; + out << QObject::tr("Successfully deleted entry %1.").arg(entryTitle) << endl; } return EXIT_SUCCESS; diff --git a/src/cli/Remove.h b/src/cli/Remove.h index 5465530eda..33d62f4bd6 100644 --- a/src/cli/Remove.h +++ b/src/cli/Remove.h @@ -28,7 +28,7 @@ class Remove : public Command Remove(); ~Remove(); int execute(const QStringList& arguments); - int removeEntry(Database* database, QString databasePath, QString entryPath); + int removeEntry(Database* database, const QString& databasePath, const QString& entryPath); }; #endif // KEEPASSXC_REMOVE_H diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index be188c75c9..5e2ec14b4f 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -15,17 +15,19 @@ * along with this program. If not, see . */ +#include "Show.h" + #include #include -#include "Show.h" - #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" +#include "core/Global.h" +#include "Utils.h" Show::Show() { @@ -39,19 +41,17 @@ Show::~Show() int Show::execute(const QStringList& arguments) { - QTextStream out(stdout); + QTextStream out(Utils::STDOUT); QCommandLineParser parser; - parser.setApplicationDescription(this->description); + parser.setApplicationDescription(description); parser.addPositionalArgument("database", QObject::tr("Path of the database.")); - QCommandLineOption keyFile(QStringList() << "k" - << "key-file", + QCommandLineOption keyFile(QStringList() << "k" << "key-file", QObject::tr("Key file of the database."), QObject::tr("path")); parser.addOption(keyFile); QCommandLineOption attributes( - QStringList() << "a" - << "attributes", + QStringList() << "a" << "attributes", QObject::tr( "Names of the attributes to show. " "This option can be specified more than once, with each attribute shown one-per-line in the given order. " @@ -59,6 +59,7 @@ int Show::execute(const QStringList& arguments) QObject::tr("attribute")); parser.addOption(attributes); parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show.")); + parser.addHelpOption(); parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -67,23 +68,23 @@ int Show::execute(const QStringList& arguments) return EXIT_FAILURE; } - Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); - if (db == nullptr) { + QScopedPointer db(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR)); + if (!db) { return EXIT_FAILURE; } - return this->showEntry(db, parser.values(attributes), args.at(1)); + return showEntry(db.data(), parser.values(attributes), args.at(1)); } -int Show::showEntry(Database* database, QStringList attributes, QString entryPath) +int Show::showEntry(Database* database, QStringList attributes, const QString& entryPath) { - - QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream in(Utils::STDIN, QIODevice::ReadOnly); + QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); + QTextStream err(Utils::STDERR, QIODevice::WriteOnly); Entry* entry = database->rootGroup()->findEntry(entryPath); if (!entry) { - qCritical("Could not find entry with path %s.", qPrintable(entryPath)); + err << QObject::tr("Could not find entry with path %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -95,16 +96,16 @@ int Show::showEntry(Database* database, QStringList attributes, QString entryPat // Iterate over the attributes and output them line-by-line. bool sawUnknownAttribute = false; - for (QString attribute : attributes) { + for (const QString& attribute : asConst(attributes)) { if (!entry->attributes()->contains(attribute)) { sawUnknownAttribute = true; - qCritical("ERROR: unknown attribute '%s'.", qPrintable(attribute)); + err << QObject::tr("ERROR: unknown attribute %1.").arg(attribute) << endl; continue; } if (showAttributeNames) { - outputTextStream << attribute << ": "; + out << attribute << ": "; } - outputTextStream << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl; + out << entry->resolveMultiplePlaceholders(entry->attributes()->value(attribute)) << endl; } return sawUnknownAttribute ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/cli/Show.h b/src/cli/Show.h index 18b6d7049e..fe16546c3b 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -26,7 +26,7 @@ class Show : public Command Show(); ~Show(); int execute(const QStringList& arguments); - int showEntry(Database* database, QStringList attributes, QString entryPath); + int showEntry(Database* database, QStringList attributes, const QString& entryPath); }; #endif // KEEPASSXC_SHOW_H diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index 35e7cce389..8a0f5abe3f 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -25,9 +25,25 @@ #endif #include -#include -void Utils::setStdinEcho(bool enable = true) +namespace Utils +{ +/** + * STDOUT file handle for the CLI. + */ +FILE* STDOUT = stdout; + +/** + * STDERR file handle for the CLI. + */ +FILE* STDERR = stderr; + +/** + * STDIN file handle for the CLI. + */ +FILE* STDIN = stdin; + +void setStdinEcho(bool enable = true) { #ifdef Q_OS_WIN HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); @@ -56,28 +72,55 @@ void Utils::setStdinEcho(bool enable = true) #endif } -QString Utils::getPassword() +QStringList nextPasswords = {}; + +/** + * Set the next password returned by \link getPassword() instead of reading it from STDIN. + * Multiple calls to this method will fill a queue of passwords. + * This function is intended for testing purposes. + * + * @param password password to return next + */ +void setNextPassword(const QString& password) +{ + nextPasswords.append(password); +} + +/** + * Read a user password from STDIN or return a password previously + * set by \link setNextPassword(). + * + * @return the password + */ +QString getPassword() { - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - static QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QTextStream out(STDOUT, QIODevice::WriteOnly); + + // return preset password if one is set + if (!nextPasswords.isEmpty()) { + auto password = nextPasswords.takeFirst(); + // simulate user entering newline + out << endl; + return password; + } + + QTextStream in(STDIN, QIODevice::ReadOnly); setStdinEcho(false); - QString line = inputTextStream.readLine(); + QString line = in.readLine(); setStdinEcho(true); - - // The new line was also not echoed, but we do want to echo it. - outputTextStream << "\n"; - outputTextStream.flush(); + out << endl; return line; } -/* +/** * A valid and running event loop is needed to use the global QClipboard, * so we need to use this from the CLI. */ -int Utils::clipText(const QString& text) +int clipText(const QString& text) { + QTextStream err(Utils::STDERR); QString programName = ""; QStringList arguments; @@ -98,16 +141,18 @@ int Utils::clipText(const QString& text) #endif if (programName.isEmpty()) { - qCritical("No program defined for clipboard manipulation"); + err << QObject::tr("No program defined for clipboard manipulation"); + err.flush(); return EXIT_FAILURE; } - QProcess* clipProcess = new QProcess(nullptr); + auto* clipProcess = new QProcess(nullptr); clipProcess->start(programName, arguments); clipProcess->waitForStarted(); if (clipProcess->state() != QProcess::Running) { - qCritical("Unable to start program %s", qPrintable(programName)); + err << QObject::tr("Unable to start program %1").arg(programName); + err.flush(); return EXIT_FAILURE; } @@ -120,3 +165,5 @@ int Utils::clipText(const QString& text) return clipProcess->exitCode(); } + +} // namespace Utils diff --git a/src/cli/Utils.h b/src/cli/Utils.h index 1f80511833..868ccdef5b 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -19,13 +19,18 @@ #define KEEPASSXC_UTILS_H #include +#include -class Utils +namespace Utils { -public: - static void setStdinEcho(bool enable); - static QString getPassword(); - static int clipText(const QString& text); +extern FILE* STDOUT; +extern FILE* STDERR; +extern FILE* STDIN; + +void setStdinEcho(bool enable); +QString getPassword(); +void setNextPassword(const QString& password); +int clipText(const QString& text); }; #endif // KEEPASSXC_UTILS_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 1462f92b91..97efd8c08f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -25,7 +25,7 @@ #include #include "config-keepassx.h" -#include "core/Tools.h" +#include "core/Bootstrap.h" #include "crypto/Crypto.h" #if defined(WITH_ASAN) && defined(WITH_LSAN) @@ -34,17 +34,17 @@ int main(int argc, char** argv) { -#ifdef QT_NO_DEBUG - Tools::disableCoreDumps(); -#endif - if (!Crypto::init()) { qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); return EXIT_FAILURE; } QCoreApplication app(argc, argv); - app.setApplicationVersion(KEEPASSX_VERSION); + QCoreApplication::setApplicationVersion(KEEPASSX_VERSION); + +#ifdef QT_NO_DEBUG + Bootstrap::bootstrapApplication(); +#endif QTextStream out(stdout); QStringList arguments; @@ -69,7 +69,7 @@ int main(int argc, char** argv) // recognized by this parser. parser.parse(arguments); - if (parser.positionalArguments().size() < 1) { + if (parser.positionalArguments().empty()) { if (parser.isSet("version")) { // Switch to parser.showVersion() when available (QT 5.4). out << KEEPASSX_VERSION << endl; diff --git a/src/core/Bootstrap.cpp b/src/core/Bootstrap.cpp new file mode 100644 index 0000000000..2c25b25056 --- /dev/null +++ b/src/core/Bootstrap.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#include "Bootstrap.h" +#include "core/Config.h" +#include "core/Translator.h" + +#ifdef Q_OS_WIN +#include // for createWindowsDACL() +#include // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ... +#endif + +namespace Bootstrap +{ +/** + * When QNetworkAccessManager is instantiated it regularly starts polling + * all network interfaces to see if anything changes and if so, what. This + * creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >= + * when on a wifi connection. + * So here we disable it for lack of better measure. + * This will also cause this message: QObject::startTimer: Timers cannot + * have negative intervals + * For more info see: + * - https://bugreports.qt.io/browse/QTBUG-40332 + * - https://bugreports.qt.io/browse/QTBUG-46015 + */ +static inline void applyEarlyQNetworkAccessManagerWorkaround() +{ + qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); +} + +/** + * Perform early application bootstrapping such as setting up search paths, + * configuration OS security properties, and loading translators. + * A QApplication object has to be instantiated before calling this function. + */ +void bootstrapApplication() +{ +#ifdef QT_NO_DEBUG + disableCoreDumps(); +#endif + setupSearchPaths(); + applyEarlyQNetworkAccessManagerWorkaround(); + Translator::installTranslators(); + +#ifdef Q_OS_MAC + // Don't show menu icons on OSX + QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); +#endif +} + +/** + * Restore the main window's state after launch + * + * @param mainWindow the main window whose state to restore + */ +void restoreMainWindowState(MainWindow& mainWindow) +{ + // start minimized if configured + bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); + bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool(); +#ifndef Q_OS_LINUX + if (minimizeOnStartup) { +#else + // On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at + // the same time (which would happen if both minimize on startup and minimize to tray are set) + // since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough. + if (minimizeOnStartup && !minimizeToTray) { +#endif + mainWindow.setWindowState(Qt::WindowMinimized); + } + if (!(minimizeOnStartup && minimizeToTray)) { + mainWindow.show(); + } + + if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { + const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); + for (const QString& filename : fileNames) { + if (!filename.isEmpty() && QFile::exists(filename)) { + mainWindow.openDatabase(filename); + } + } + } +} + +// LCOV_EXCL_START +void disableCoreDumps() +{ + // default to true + // there is no point in printing a warning if this is not implemented on the platform + bool success = true; + +#if defined(HAVE_RLIMIT_CORE) + struct rlimit limit; + limit.rlim_cur = 0; + limit.rlim_max = 0; + success = success && (setrlimit(RLIMIT_CORE, &limit) == 0); +#endif + +#if defined(HAVE_PR_SET_DUMPABLE) + success = success && (prctl(PR_SET_DUMPABLE, 0) == 0); +#endif + +// Mac OS X +#ifdef HAVE_PT_DENY_ATTACH + success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); +#endif + +#ifdef Q_OS_WIN + success = success && createWindowsDACL(); +#endif + + if (!success) { + qWarning("Unable to disable core dumps."); + } +} + +// +// This function grants the user associated with the process token minimal access rights and +// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and +// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory(). +// We do this using a discretionary access control list (DACL). Effectively this prevents +// crash dumps and disallows other processes from accessing our memory. This works as long +// as you do not have admin privileges, since then you are able to grant yourself the +// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL. +// +bool createWindowsDACL() +{ + bool bSuccess = false; + +#ifdef Q_OS_WIN + // Process token and user + HANDLE hToken = nullptr; + PTOKEN_USER pTokenUser = nullptr; + DWORD cbBufferSize = 0; + + // Access control list + PACL pACL = nullptr; + DWORD cbACL = 0; + + // Open the access token associated with the calling process + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + goto Cleanup; + } + + // Retrieve the token information in a TOKEN_USER structure + GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize); + + pTokenUser = static_cast(HeapAlloc(GetProcessHeap(), 0, cbBufferSize)); + if (pTokenUser == nullptr) { + goto Cleanup; + } + + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) { + goto Cleanup; + } + + if (!IsValidSid(pTokenUser->User.Sid)) { + goto Cleanup; + } + + // Calculate the amount of memory that must be allocated for the DACL + cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid); + + // Create and initialize an ACL + pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); + if (pACL == nullptr) { + goto Cleanup; + } + + if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { + goto Cleanup; + } + + // Add allowed access control entries, everything else is denied + if (!AddAccessAllowedAce( + pACL, + ACL_REVISION, + SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process + pTokenUser->User.Sid // pointer to the trustee's SID + )) { + goto Cleanup; + } + + // Set discretionary access control list + bSuccess = ERROR_SUCCESS + == SetSecurityInfo(GetCurrentProcess(), // object handle + SE_KERNEL_OBJECT, // type of object + DACL_SECURITY_INFORMATION, // change only the objects DACL + nullptr, + nullptr, // do not change owner or group + pACL, // DACL specified + nullptr // do not change SACL + ); + +Cleanup: + + if (pACL != nullptr) { + HeapFree(GetProcessHeap(), 0, pACL); + } + if (pTokenUser != nullptr) { + HeapFree(GetProcessHeap(), 0, pTokenUser); + } + if (hToken != nullptr) { + CloseHandle(hToken); + } +#endif + + return bSuccess; +} +// LCOV_EXCL_STOP + +void setupSearchPaths() +{ +#ifdef Q_OS_WIN + // Make sure Windows doesn't load DLLs from the current working directory + SetDllDirectoryA(""); + SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE); +#endif +} + +} // namespace Bootstrap diff --git a/src/core/Bootstrap.h b/src/core/Bootstrap.h new file mode 100644 index 0000000000..0e9db155a3 --- /dev/null +++ b/src/core/Bootstrap.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + + +#ifndef KEEPASSXC_BOOTSTRAP_H +#define KEEPASSXC_BOOTSTRAP_H + +#include "gui/MainWindow.h" + +namespace Bootstrap +{ +void bootstrapApplication(); +void restoreMainWindowState(MainWindow& mainWindow); +void disableCoreDumps(); +bool createWindowsDACL(); +void setupSearchPaths(); +}; + + +#endif //KEEPASSXC_BOOTSTRAP_H diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 5b7a3c07d2..5116fd1996 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -47,7 +47,7 @@ Database::Database() , m_emitModified(false) , m_uuid(QUuid::createUuid()) { - m_data.cipher = KeePass2::CIPHER_AES; + m_data.cipher = KeePass2::CIPHER_AES256; m_data.compressionAlgo = CompressionGZip; // instantiate default AES-KDF with legacy KDBX3 flag set @@ -501,14 +501,14 @@ Database* Database::openDatabaseFile(const QString& fileName, QSharedPointer::create(); - QTextStream outputTextStream(stdout); - QTextStream errorTextStream(stderr); + QTextStream out(outputDescriptor); + QTextStream err(errorDescriptor); - outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename); - outputTextStream.flush(); + out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename); + out.flush(); QString line = Utils::getPassword(); auto passwordKey = QSharedPointer::create(); @@ -518,11 +518,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam if (!keyFilename.isEmpty()) { auto fileKey = QSharedPointer::create(); QString errorMessage; + // LCOV_EXCL_START if (!fileKey->load(keyFilename, &errorMessage)) { - errorTextStream << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage); - errorTextStream << endl; + err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage)<< endl; return nullptr; } + + if (fileKey->type() != FileKey::Hashed) { + err << QObject::tr("WARNING: You are using a legacy key file format which may become\n" + "unsupported in the future.\n\n" + "Please consider generating a new key file.") << endl; + } + // LCOV_EXCL_STOP + compositeKey->addKey(fileKey); } diff --git a/src/core/Database.h b/src/core/Database.h index a5ae3effad..7108ded310 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -131,7 +131,8 @@ class Database : public QObject static Database* databaseByUuid(const QUuid& uuid); static Database* openDatabaseFile(const QString& fileName, QSharedPointer key); - static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString("")); + static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = {}, + FILE* outputDescriptor = stdout, FILE* errorDescriptor = stderr); signals: void groupDataChanged(Group* group); diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 01b7150721..3dbcdaad8b 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -80,21 +80,21 @@ QString PasswordGenerator::generatePassword() const QString password; if (m_flags & CharFromEveryGroup) { - for (int i = 0; i < groups.size(); i++) { - int pos = randomGen()->randomUInt(groups[i].size()); + for (const auto& group : groups) { + int pos = randomGen()->randomUInt(static_cast(group.size())); - password.append(groups[i][pos]); + password.append(group[pos]); } for (int i = groups.size(); i < m_length; i++) { - int pos = randomGen()->randomUInt(passwordChars.size()); + int pos = randomGen()->randomUInt(static_cast(passwordChars.size())); password.append(passwordChars[pos]); } // shuffle chars for (int i = (password.size() - 1); i >= 1; i--) { - int j = randomGen()->randomUInt(i + 1); + int j = randomGen()->randomUInt(static_cast(i + 1)); QChar tmp = password[i]; password[i] = password[j]; @@ -102,7 +102,7 @@ QString PasswordGenerator::generatePassword() const } } else { for (int i = 0; i < m_length; i++) { - int pos = randomGen()->randomUInt(passwordChars.size()); + int pos = randomGen()->randomUInt(static_cast(passwordChars.size())); password.append(passwordChars[pos]); } @@ -111,21 +111,6 @@ QString PasswordGenerator::generatePassword() const return password; } -int PasswordGenerator::getbits() const -{ - const QVector groups = passwordGroups(); - - int bits = 0; - QVector passwordChars; - for (const PasswordGroup& group : groups) { - bits += group.size(); - } - - bits *= m_length; - - return bits; -} - bool PasswordGenerator::isValid() const { if (m_classes == 0) { @@ -138,11 +123,8 @@ bool PasswordGenerator::isValid() const return false; } - if (passwordGroups().size() == 0) { - return false; - } + return !passwordGroups().isEmpty(); - return true; } QVector PasswordGenerator::passwordGroups() const @@ -298,9 +280,9 @@ QVector PasswordGenerator::passwordGroups() const j = group.indexOf(ch); } } - if (group.size() > 0) { + if (!group.isEmpty()) { passwordGroups.replace(i, group); - i++; + ++i; } else { passwordGroups.remove(i); } diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 1d6ac73f67..cb6402d0fc 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -66,7 +66,6 @@ class PasswordGenerator bool isValid() const; QString generatePassword() const; - int getbits() const; static const int DefaultLength = 16; static const char* DefaultExcludedChars; diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 458d429888..60c50b4516 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -18,19 +18,20 @@ */ #include "Tools.h" +#include "core/Config.h" +#include "core/Translator.h" #include #include #include #include #include -#include - #include +#include + #ifdef Q_OS_WIN -#include // for SetSecurityInfo() -#include // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ... +#include // for Sleep() #endif #ifdef Q_OS_UNIX @@ -56,294 +57,161 @@ namespace Tools { - - QString humanReadableFileSize(qint64 bytes, quint32 precision) - { - constexpr auto kibibyte = 1024; - double size = bytes; - - QStringList units = QStringList() << "B" - << "KiB" - << "MiB" - << "GiB"; - int i = 0; - int maxI = units.size() - 1; - - while ((size >= kibibyte) && (i < maxI)) { - size /= kibibyte; - i++; - } - - return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); +QString humanReadableFileSize(qint64 bytes, quint32 precision) +{ + constexpr auto kibibyte = 1024; + double size = bytes; + + QStringList units = QStringList() << "B" + << "KiB" + << "MiB" + << "GiB"; + int i = 0; + int maxI = units.size() - 1; + + while ((size >= kibibyte) && (i < maxI)) { + size /= kibibyte; + i++; } - bool hasChild(const QObject* parent, const QObject* child) - { - if (!parent || !child) { - return false; - } + return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); +} - const QObjectList children = parent->children(); - for (QObject* c : children) { - if (child == c || hasChild(c, child)) { - return true; - } - } +bool hasChild(const QObject* parent, const QObject* child) +{ + if (!parent || !child) { return false; } - bool readFromDevice(QIODevice* device, QByteArray& data, int size) - { - QByteArray buffer; - buffer.resize(size); - - qint64 readResult = device->read(buffer.data(), size); - if (readResult == -1) { - return false; - } else { - buffer.resize(readResult); - data = buffer; + const QObjectList children = parent->children(); + for (QObject* c : children) { + if (child == c || hasChild(c, child)) { return true; } } + return false; +} - bool readAllFromDevice(QIODevice* device, QByteArray& data) - { - QByteArray result; - qint64 readBytes = 0; - qint64 readResult; - do { - result.resize(result.size() + 16384); - readResult = device->read(result.data() + readBytes, result.size() - readBytes); - if (readResult > 0) { - readBytes += readResult; - } - } while (readResult > 0); +bool readFromDevice(QIODevice* device, QByteArray& data, int size) +{ + QByteArray buffer; + buffer.resize(size); - if (readResult == -1) { - return false; - } else { - result.resize(static_cast(readBytes)); - data = result; - return true; - } + qint64 readResult = device->read(buffer.data(), size); + if (readResult == -1) { + return false; + } else { + buffer.resize(readResult); + data = buffer; + return true; } +} - QString imageReaderFilter() - { - const QList formats = QImageReader::supportedImageFormats(); - QStringList formatsStringList; - - for (const QByteArray& format : formats) { - for (int i = 0; i < format.size(); i++) { - if (!QChar(format.at(i)).isLetterOrNumber()) { - continue; - } - } - - formatsStringList.append("*." + QString::fromLatin1(format).toLower()); +bool readAllFromDevice(QIODevice* device, QByteArray& data) +{ + QByteArray result; + qint64 readBytes = 0; + qint64 readResult; + do { + result.resize(result.size() + 16384); + readResult = device->read(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0) { + readBytes += readResult; } - - return formatsStringList.join(" "); } + while (readResult > 0); - bool isHex(const QByteArray& ba) - { - for (const unsigned char c : ba) { - if (!std::isxdigit(c)) { - return false; - } - } - + if (readResult == -1) { + return false; + } else { + result.resize(static_cast(readBytes)); + data = result; return true; } +} - bool isBase64(const QByteArray& ba) - { - constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)"; - QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2); - - QString base64 = QString::fromLatin1(ba.constData(), ba.size()); - - return regexp.exactMatch(base64); - } - - void sleep(int ms) - { - Q_ASSERT(ms >= 0); +QString imageReaderFilter() +{ + const QList formats = QImageReader::supportedImageFormats(); + QStringList formatsStringList; - if (ms == 0) { - return; + for (const QByteArray& format : formats) { + for (int i = 0; i < format.size(); i++) { + if (!QChar(format.at(i)).isLetterOrNumber()) { + continue; + } } -#ifdef Q_OS_WIN - Sleep(uint(ms)); -#else - timespec ts; - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000 * 1000; - nanosleep(&ts, nullptr); -#endif + formatsStringList.append("*." + QString::fromLatin1(format).toLower()); } - void wait(int ms) - { - Q_ASSERT(ms >= 0); + return formatsStringList.join(" "); +} - if (ms == 0) { - return; - } - - QElapsedTimer timer; - timer.start(); - - if (ms <= 50) { - QCoreApplication::processEvents(QEventLoop::AllEvents, ms); - sleep(qMax(ms - static_cast(timer.elapsed()), 0)); - } else { - int timeLeft; - do { - timeLeft = ms - timer.elapsed(); - if (timeLeft > 0) { - QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft); - sleep(10); - } - } while (!timer.hasExpired(ms)); +bool isHex(const QByteArray& ba) +{ + for (const unsigned char c : ba) { + if (!std::isxdigit(c)) { + return false; } } - void disableCoreDumps() - { - // default to true - // there is no point in printing a warning if this is not implemented on the platform - bool success = true; + return true; +} -#if defined(HAVE_RLIMIT_CORE) - struct rlimit limit; - limit.rlim_cur = 0; - limit.rlim_max = 0; - success = success && (setrlimit(RLIMIT_CORE, &limit) == 0); -#endif +bool isBase64(const QByteArray& ba) +{ + constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)"; + QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2); -#if defined(HAVE_PR_SET_DUMPABLE) - success = success && (prctl(PR_SET_DUMPABLE, 0) == 0); -#endif + QString base64 = QString::fromLatin1(ba.constData(), ba.size()); -// Mac OS X -#ifdef HAVE_PT_DENY_ATTACH - success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); -#endif + return regexp.exactMatch(base64); +} -#ifdef Q_OS_WIN - success = success && createWindowsDACL(); -#endif +void sleep(int ms) +{ + Q_ASSERT(ms >= 0); - if (!success) { - qWarning("Unable to disable core dumps."); - } + if (ms == 0) { + return; } - void setupSearchPaths() - { #ifdef Q_OS_WIN - // Make sure Windows doesn't load DLLs from the current working directory - SetDllDirectoryA(""); - SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE); + Sleep(uint(ms)); +#else + timespec ts; + ts.tv_sec = ms/1000; + ts.tv_nsec = (ms%1000)*1000*1000; + nanosleep(&ts, nullptr); #endif - } - - // - // This function grants the user associated with the process token minimal access rights and - // denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and - // PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory(). - // We do this using a discretionary access control list (DACL). Effectively this prevents - // crash dumps and disallows other processes from accessing our memory. This works as long - // as you do not have admin privileges, since then you are able to grant yourself the - // SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL. - // - bool createWindowsDACL() - { - bool bSuccess = false; - -#ifdef Q_OS_WIN - // Process token and user - HANDLE hToken = nullptr; - PTOKEN_USER pTokenUser = nullptr; - DWORD cbBufferSize = 0; - - // Access control list - PACL pACL = nullptr; - DWORD cbACL = 0; - - // Open the access token associated with the calling process - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { - goto Cleanup; - } - - // Retrieve the token information in a TOKEN_USER structure - GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize); - - pTokenUser = static_cast(HeapAlloc(GetProcessHeap(), 0, cbBufferSize)); - if (pTokenUser == nullptr) { - goto Cleanup; - } - - if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) { - goto Cleanup; - } - - if (!IsValidSid(pTokenUser->User.Sid)) { - goto Cleanup; - } +} - // Calculate the amount of memory that must be allocated for the DACL - cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid); - - // Create and initialize an ACL - pACL = static_cast(HeapAlloc(GetProcessHeap(), 0, cbACL)); - if (pACL == nullptr) { - goto Cleanup; - } +void wait(int ms) +{ + Q_ASSERT(ms >= 0); - if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) { - goto Cleanup; - } + if (ms == 0) { + return; + } - // Add allowed access control entries, everything else is denied - if (!AddAccessAllowedAce( - pACL, - ACL_REVISION, - SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process - pTokenUser->User.Sid // pointer to the trustee's SID - )) { - goto Cleanup; - } + QElapsedTimer timer; + timer.start(); - // Set discretionary access control list - bSuccess = ERROR_SUCCESS - == SetSecurityInfo(GetCurrentProcess(), // object handle - SE_KERNEL_OBJECT, // type of object - DACL_SECURITY_INFORMATION, // change only the objects DACL - nullptr, - nullptr, // do not change owner or group - pACL, // DACL specified - nullptr // do not change SACL - ); - - Cleanup: - - if (pACL != nullptr) { - HeapFree(GetProcessHeap(), 0, pACL); - } - if (pTokenUser != nullptr) { - HeapFree(GetProcessHeap(), 0, pTokenUser); - } - if (hToken != nullptr) { - CloseHandle(hToken); + if (ms <= 50) { + QCoreApplication::processEvents(QEventLoop::AllEvents, ms); + sleep(qMax(ms - static_cast(timer.elapsed()), 0)); + } else { + int timeLeft; + do { + timeLeft = ms - timer.elapsed(); + if (timeLeft > 0) { + QCoreApplication::processEvents(QEventLoop::AllEvents, timeLeft); + sleep(10); + } } -#endif - - return bSuccess; + while (!timer.hasExpired(ms)); } +} } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index 4f75b750bc..aec9373047 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -30,31 +30,27 @@ class QIODevice; namespace Tools { +QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); +bool hasChild(const QObject* parent, const QObject* child); +bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); +bool readAllFromDevice(QIODevice* device, QByteArray& data); +QString imageReaderFilter(); +bool isHex(const QByteArray& ba); +bool isBase64(const QByteArray& ba); +void sleep(int ms); +void wait(int ms); + +template +RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) +{ + RandomAccessIterator it = std::lower_bound(begin, end, value); - QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); - bool hasChild(const QObject* parent, const QObject* child); - bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); - bool readAllFromDevice(QIODevice* device, QByteArray& data); - QString imageReaderFilter(); - bool isHex(const QByteArray& ba); - bool isBase64(const QByteArray& ba); - void sleep(int ms); - void wait(int ms); - void disableCoreDumps(); - void setupSearchPaths(); - bool createWindowsDACL(); - - template - RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) - { - RandomAccessIterator it = std::lower_bound(begin, end, value); - - if ((it == end) || (value < *it)) { - return end; - } else { - return it; - } + if ((it == end) || (value < *it)) { + return end; + } else { + return it; } +} } // namespace Tools diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 12c6bf7910..3c5e4c2840 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -98,13 +98,6 @@ void CryptoHash::setKey(const QByteArray& data) Q_ASSERT(error == 0); } -void CryptoHash::reset() -{ - Q_D(CryptoHash); - - gcry_md_reset(d->ctx); -} - QByteArray CryptoHash::result() const { Q_D(const CryptoHash); diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h index bd312121ab..02f90eb4de 100644 --- a/src/crypto/CryptoHash.h +++ b/src/crypto/CryptoHash.h @@ -34,7 +34,6 @@ class CryptoHash explicit CryptoHash(Algorithm algo, bool hmac = false); ~CryptoHash(); void addData(const QByteArray& data); - void reset(); QByteArray result() const; void setKey(const QByteArray& data); diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 0467ad7c2f..828d3a998a 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -94,7 +94,7 @@ QString SymmetricCipher::errorString() const SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher) { - if (cipher == KeePass2::CIPHER_AES) { + if (cipher == KeePass2::CIPHER_AES256) { return Aes256; } else if (cipher == KeePass2::CIPHER_CHACHA20) { return ChaCha20; @@ -109,15 +109,17 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& ciphe QUuid SymmetricCipher::algorithmToCipher(Algorithm algo) { switch (algo) { + case Aes128: + return KeePass2::CIPHER_AES128; case Aes256: - return KeePass2::CIPHER_AES; + return KeePass2::CIPHER_AES256; case ChaCha20: return KeePass2::CIPHER_CHACHA20; case Twofish: return KeePass2::CIPHER_TWOFISH; default: qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); - return QUuid(); + return {}; } } diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index c7a5e6a071..e3bc88cbf0 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -185,8 +185,6 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data) bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) { - // TODO: check block size - gcry_error_t error; char* rawData = data.data(); diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index aeacaad3de..0114a0b767 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -110,14 +110,6 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, return nullptr; } - QBuffer buffer; - if (saveXml()) { - m_xmlData = xmlDevice->readAll(); - buffer.setBuffer(&m_xmlData); - buffer.open(QIODevice::ReadOnly); - xmlDevice = &buffer; - } - Q_ASSERT(xmlDevice); KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1); diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 7b94d34f81..5a024a254a 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -124,14 +124,6 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, return nullptr; } - QBuffer buffer; - if (saveXml()) { - m_xmlData = xmlDevice->readAll(); - buffer.setBuffer(&m_xmlData); - buffer.open(QIODevice::ReadOnly); - xmlDevice = &buffer; - } - Q_ASSERT(xmlDevice); KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool()); diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp index 5e14b8a9f1..13a792fd5d 100644 --- a/src/format/KdbxReader.cpp +++ b/src/format/KdbxReader.cpp @@ -1,3 +1,5 @@ +#include + /* * Copyright (C) 2018 KeePassXC Team * @@ -18,6 +20,9 @@ #include "KdbxReader.h" #include "core/Database.h" #include "core/Endian.h" +#include "format/KdbxXmlWriter.h" + +#include #define UUID_LENGTH 16 @@ -92,7 +97,14 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer m_db; QPair m_kdbxSignature; diff --git a/src/format/KdbxXmlReader.cpp b/src/format/KdbxXmlReader.cpp index 76fa032217..c9e5c31af9 100644 --- a/src/format/KdbxXmlReader.cpp +++ b/src/format/KdbxXmlReader.cpp @@ -82,7 +82,6 @@ Database* KdbxXmlReader::readDatabase(QIODevice* device) * @param db database to read into * @param randomStream random stream to use for decryption */ -#include "QDebug" void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; diff --git a/src/format/KdbxXmlWriter.cpp b/src/format/KdbxXmlWriter.cpp index 5ad1e34aea..c26d316dc3 100644 --- a/src/format/KdbxXmlWriter.cpp +++ b/src/format/KdbxXmlWriter.cpp @@ -358,10 +358,10 @@ void KdbxXmlWriter::writeEntry(const Entry* entry) bool protect = (((key == "Title") && m_meta->protectTitle()) || ((key == "UserName") && m_meta->protectUsername()) - || ((key == "Password") && m_meta->protectPassword()) - || ((key == "URL") && m_meta->protectUrl()) - || ((key == "Notes") && m_meta->protectNotes()) - || entry->attributes()->isProtected(key)); + || ((key == "Password") && m_meta->protectPassword()) + || ((key == "URL") && m_meta->protectUrl()) + || ((key == "Notes") && m_meta->protectNotes()) + || entry->attributes()->isProtected(key)); writeString("Key", key); @@ -369,7 +369,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry) QString value; if (protect) { - if (m_randomStream) { + if (!m_innerStreamProtectionDisabled && m_randomStream) { m_xml.writeAttribute("Protected", "True"); bool ok; QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok); @@ -596,3 +596,24 @@ void KdbxXmlWriter::raiseError(const QString& errorMessage) m_error = true; m_errorStr = errorMessage; } + +/** + * Disable inner stream protection and write protected fields + * in plaintext instead. This is useful for plaintext XML exports + * where the inner stream key is not available. + * + * @param disable true to disable protection + */ +void KdbxXmlWriter::disableInnerStreamProtection(bool disable) +{ + m_innerStreamProtectionDisabled = disable; +} + +/** + * @return true if inner stream protection is disabled and protected + * fields will be saved in plaintext + */ +bool KdbxXmlWriter::innerStreamProtectionDisabled() const +{ + return m_innerStreamProtectionDisabled; +} diff --git a/src/format/KdbxXmlWriter.h b/src/format/KdbxXmlWriter.h index 51a8034979..2f7215b46a 100644 --- a/src/format/KdbxXmlWriter.h +++ b/src/format/KdbxXmlWriter.h @@ -41,6 +41,8 @@ class KdbxXmlWriter KeePass2RandomStream* randomStream = nullptr, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); + void disableInnerStreamProtection(bool disable); + bool innerStreamProtectionDisabled() const; bool hasError(); QString errorString(); @@ -81,6 +83,8 @@ class KdbxXmlWriter const quint32 m_kdbxVersion; + bool m_innerStreamProtectionDisabled = false; + QXmlStreamWriter m_xml; QPointer m_db; QPointer m_meta; diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 5aad1f7f21..9c07144844 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -23,7 +23,8 @@ #define UUID_LENGTH 16 -const QUuid KeePass2::CIPHER_AES = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff"); +const QUuid KeePass2::CIPHER_AES128 = QUuid("61ab05a1-9464-41c3-8d74-3a563df8dd35"); +const QUuid KeePass2::CIPHER_AES256 = QUuid("31c1f2e6-bf71-4350-be58-05216afc5aff"); const QUuid KeePass2::CIPHER_TWOFISH = QUuid("ad68f29f-576f-4bb9-a36a-d47af965346c"); const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb59a"); @@ -47,7 +48,7 @@ const QString KeePass2::KDFPARAM_ARGON2_SECRET("K"); const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); const QList> KeePass2::CIPHERS{ - qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), + qMakePair(KeePass2::CIPHER_AES256, QObject::tr("AES: 256-bit")), qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) }; diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index a8e97c5bdf..02fe635ca0 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -46,7 +46,8 @@ namespace KeePass2 const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; -extern const QUuid CIPHER_AES; +extern const QUuid CIPHER_AES128; +extern const QUuid CIPHER_AES256; extern const QUuid CIPHER_TWOFISH; extern const QUuid CIPHER_CHACHA20; diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 2a6d611182..8cdb8ff430 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -93,7 +93,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointersetSaveXml(m_saveXml); - return m_reader->readDatabase(device, key, keepDatabase); + return m_reader->readDatabase(device, std::move(key), keepDatabase); } bool KeePass2Reader::hasError() const diff --git a/src/gui/Application.h b/src/gui/Application.h index a6c6fdf905..3fdd8af90e 100644 --- a/src/gui/Application.h +++ b/src/gui/Application.h @@ -22,8 +22,8 @@ #include #include -class QLockFile; +class QLockFile; class QSocketNotifier; class Application : public QApplication diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 0e2bc96e88..828aace51e 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -216,7 +216,7 @@ private slots: void setIconFromParent(); void replaceDatabase(Database* db); - Database* m_db; + QPointer m_db; QWidget* m_mainWidget; EditEntryWidget* m_editEntryWidget; EditEntryWidget* m_historyEditEntryWidget; diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp index 2a3cf7cbbb..63a1ccef8f 100644 --- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp +++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp @@ -81,7 +81,7 @@ void DatabaseSettingsWidgetEncryption::initialize() } if (!m_db->key()) { m_db->setKey(QSharedPointer::create()); - m_db->setCipher(KeePass2::CIPHER_AES); + m_db->setCipher(KeePass2::CIPHER_AES256); isDirty = true; } diff --git a/src/main.cpp b/src/main.cpp index 903974fa70..74af06953c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,19 +16,18 @@ * along with this program. If not, see . */ -#include #include #include +#include #include "config-keepassx.h" -#include "core/Config.h" +#include "core/Bootstrap.h" #include "core/Tools.h" -#include "core/Translator.h" +#include "core/Config.h" #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" - #include "cli/Utils.h" #if defined(WITH_ASAN) && defined(WITH_LSAN) @@ -45,55 +44,29 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) #endif #endif -static inline void earlyQNetworkAccessManagerWorkaround() -{ - // When QNetworkAccessManager is instantiated it regularly starts polling - // all network interfaces to see if anything changes and if so, what. This - // creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >= - // when on a wifi connection. - // So here we disable it for lack of better measure. - // This will also cause this message: QObject::startTimer: Timers cannot - // have negative intervals - // For more info see: - // - https://bugreports.qt.io/browse/QTBUG-40332 - // - https://bugreports.qt.io/browse/QTBUG-46015 - qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); -} - int main(int argc, char** argv) { -#ifdef QT_NO_DEBUG - Tools::disableCoreDumps(); -#endif - Tools::setupSearchPaths(); - - earlyQNetworkAccessManagerWorkaround(); - Application app(argc, argv); Application::setApplicationName("keepassxc"); Application::setApplicationVersion(KEEPASSX_VERSION); // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) + Bootstrap::bootstrapApplication(); QCommandLineParser parser; - parser.setApplicationDescription( - QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); - parser.addPositionalArgument( - "filename", - QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), - "[filename(s)]"); + parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC - cross-platform password manager")); + parser.addPositionalArgument("filename", + QCoreApplication::translate("main", "filenames of the password databases to open (*.kdbx)"), "[filename(s)]"); QCommandLineOption configOption( "config", QCoreApplication::translate("main", "path to a custom config file"), "config"); QCommandLineOption keyfileOption( "keyfile", QCoreApplication::translate("main", "key file of the database"), "keyfile"); QCommandLineOption pwstdinOption("pw-stdin", - QCoreApplication::translate("main", "read password of the database from stdin")); + QCoreApplication::translate("main", "read password of the database from stdin")); // This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method - QCommandLineOption parentWindowOption(QStringList() << "pw" - << "parent-window", - QCoreApplication::translate("main", "Parent window handle"), - "handle"); + QCommandLineOption parentWindowOption( + QStringList() << "pw" << "parent-window", QCoreApplication::translate("main", "Parent window handle"), "handle"); QCommandLineOption helpOption = parser.addHelpOption(); QCommandLineOption versionOption = parser.addVersionOption(); @@ -115,9 +88,7 @@ int main(int argc, char** argv) if (!fileNames.isEmpty()) { app.sendFileNamesToRunningInstance(fileNames); } - qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.") - .toUtf8() - .constData(); + qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData(); return 0; } @@ -135,46 +106,14 @@ int main(int argc, char** argv) Config::createConfigFromFile(parser.value(configOption)); } - Translator::installTranslators(); - -#ifdef Q_OS_MAC - // Don't show menu icons on OSX - QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); -#endif - MainWindow mainWindow; app.setMainWindow(&mainWindow); - QObject::connect(&app, SIGNAL(anotherInstanceStarted()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront())); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection); - // start minimized if configured - bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool(); - bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool(); -#ifndef Q_OS_LINUX - if (minimizeOnStartup) { -#else - // On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at - // the same time (which would happen if both minimize on startup and minimize to tray are set) - // since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough. - if (minimizeOnStartup && !minimizeToTray) { -#endif - mainWindow.setWindowState(Qt::WindowMinimized); - } - if (!(minimizeOnStartup && minimizeToTray)) { - mainWindow.show(); - } - - if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { - const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList(); - for (const QString& filename : fileNames) { - if (!filename.isEmpty() && QFile::exists(filename)) { - mainWindow.openDatabase(filename); - } - } - } + Bootstrap::restoreMainWindowState(mainWindow); const bool pwstdin = parser.isSet(pwstdinOption); for (const QString& filename : fileNames) { @@ -193,7 +132,7 @@ int main(int argc, char** argv) } } - int exitCode = app.exec(); + int exitCode = Application::exec(); #if defined(WITH_ASAN) && defined(WITH_LSAN) // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 73262bae07..562b45e4d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -163,7 +163,10 @@ add_unit_test(NAME testentry SOURCES TestEntry.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testmerge SOURCES TestMerge.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) + +add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp + LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testtotp SOURCES TestTotp.cpp LIBS ${TEST_LIBRARIES}) @@ -180,7 +183,7 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp +add_unit_test(NAME testcsveporter SOURCES TestCsvExporter.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testykchallengeresponsekey @@ -193,6 +196,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) + # CLI clip tests need X environment on Linux + add_unit_test(NAME testcli SOURCES TestCli.cpp + LIBS testsupport cli ${TEST_LIBRARIES}) + add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp new file mode 100644 index 0000000000..02bc0ba3f5 --- /dev/null +++ b/tests/TestCli.cpp @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#include "TestCli.h" +#include "config-keepassx-tests.h" +#include "core/Global.h" +#include "core/Config.h" +#include "core/Bootstrap.h" +#include "core/Tools.h" +#include "core/PasswordGenerator.h" +#include "crypto/Crypto.h" +#include "format/KeePass2.h" +#include "format/Kdbx3Reader.h" +#include "format/Kdbx4Reader.h" +#include "format/Kdbx4Writer.h" +#include "format/Kdbx3Writer.h" +#include "format/KdbxXmlReader.h" + +#include "cli/Command.h" +#include "cli/Utils.h" +#include "cli/Add.h" +#include "cli/Clip.h" +#include "cli/Diceware.h" +#include "cli/Edit.h" +#include "cli/Estimate.h" +#include "cli/Extract.h" +#include "cli/Generate.h" +#include "cli/List.h" +#include "cli/Locate.h" +#include "cli/Merge.h" +#include "cli/Remove.h" +#include "cli/Show.h" + +#include +#include +#include +#include +#include + +#include + +QTEST_MAIN(TestCli) + +void TestCli::initTestCase() +{ + QVERIFY(Crypto::init()); + + Config::createTempFileInstance(); + Bootstrap::bootstrapApplication(); + + // Load the NewDatabase.kdbx file into temporary storage + QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); + QVERIFY(sourceDbFile.open(QIODevice::ReadOnly)); + QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData)); + sourceDbFile.close(); +} + +void TestCli::init() +{ + m_dbFile.reset(new QTemporaryFile()); + m_dbFile->open(); + m_dbFile->write(m_dbData); + m_dbFile->flush(); + + m_stdinFile.reset(new QTemporaryFile()); + m_stdinFile->open(); + m_stdinHandle = fdopen(m_stdinFile->handle(), "r+"); + Utils::STDIN = m_stdinHandle; + + m_stdoutFile.reset(new QTemporaryFile()); + m_stdoutFile->open(); + m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+"); + Utils::STDOUT = m_stdoutHandle; + + m_stderrFile.reset(new QTemporaryFile()); + m_stderrFile->open(); + m_stderrHandle = fdopen(m_stderrFile->handle(), "r+"); + Utils::STDERR = m_stderrHandle; +} + +void TestCli::cleanup() +{ + m_dbFile.reset(); + + m_stdinFile.reset(); + m_stdinHandle = stdin; + Utils::STDIN = stdin; + + m_stdoutFile.reset(); + Utils::STDOUT = stdout; + m_stdoutHandle = stdout; + + m_stderrFile.reset(); + m_stderrHandle = stderr; + Utils::STDERR = stderr; +} + +void TestCli::cleanupTestCase() +{ +} + +QSharedPointer TestCli::readTestDatabase() const +{ + Utils::setNextPassword("a"); + auto db = QSharedPointer(Database::unlockFromStdin(m_dbFile->fileName(), "", m_stdoutHandle)); + m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles + return db; +} + +void TestCli::testCommand() +{ + QCOMPARE(Command::getCommands().size(), 12); + QVERIFY(Command::getCommand("add")); + QVERIFY(Command::getCommand("clip")); + QVERIFY(Command::getCommand("diceware")); + QVERIFY(Command::getCommand("edit")); + QVERIFY(Command::getCommand("estimate")); + QVERIFY(Command::getCommand("extract")); + QVERIFY(Command::getCommand("generate")); + QVERIFY(Command::getCommand("locate")); + QVERIFY(Command::getCommand("ls")); + QVERIFY(Command::getCommand("merge")); + QVERIFY(Command::getCommand("rm")); + QVERIFY(Command::getCommand("show")); + QVERIFY(!Command::getCommand("doesnotexist")); +} + +void TestCli::testAdd() +{ + Add addCmd; + QVERIFY(!addCmd.name.isEmpty()); + QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name)); + + Utils::setNextPassword("a"); + addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"}); + + auto db = readTestDatabase(); + auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://example.com/")); + QCOMPARE(entry->password().size(), 20); + + Utils::setNextPassword("a"); + Utils::setNextPassword("newpassword"); + addCmd.execute({"add", "-u", "newuser2", "--url", "https://example.net/", "-g", "-l", "20", "-p", m_dbFile->fileName(), "/newuser-entry2"}); + + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newuser-entry2"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser2")); + QCOMPARE(entry->url(), QString("https://example.net/")); + QCOMPARE(entry->password(), QString("newpassword")); +} + +void TestCli::testClip() +{ + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->clear(); + + Clip clipCmd; + QVERIFY(!clipCmd.name.isEmpty()); + QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name)); + + Utils::setNextPassword("a"); + clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"}); + + m_stderrFile->reset(); + QString errorOutput(m_stderrFile->readAll()); + + if (errorOutput.contains("Unable to start program") + || errorOutput.contains("No program defined for clipboard manipulation")) { + QSKIP("Clip test skipped due to missing clipboard tool"); + } + + QCOMPARE(clipboard->text(), QString("Password")); + + Utils::setNextPassword("a"); + QFuture future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"}); + + QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500); + QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString(""), 1500); + + future.waitForFinished(); +} + +void TestCli::testDiceware() +{ + Diceware dicewareCmd; + QVERIFY(!dicewareCmd.name.isEmpty()); + QVERIFY(dicewareCmd.getDescriptionLine().contains(dicewareCmd.name)); + + dicewareCmd.execute({"diceware"}); + m_stdoutFile->reset(); + QString passphrase(m_stdoutFile->readLine()); + QVERIFY(!passphrase.isEmpty()); + + dicewareCmd.execute({"diceware", "-W", "2"}); + m_stdoutFile->seek(passphrase.toLatin1().size()); + passphrase = m_stdoutFile->readLine(); + QCOMPARE(passphrase.split(" ").size(), 2); + + auto pos = m_stdoutFile->pos(); + dicewareCmd.execute({"diceware", "-W", "10"}); + m_stdoutFile->seek(pos); + passphrase = m_stdoutFile->readLine(); + QCOMPARE(passphrase.split(" ").size(), 10); + + QTemporaryFile wordFile; + wordFile.open(); + for (int i = 0; i < 4500; ++i) { + wordFile.write(QString("word" + QString::number(i) + "\n").toLatin1()); + } + wordFile.close(); + + pos = m_stdoutFile->pos(); + dicewareCmd.execute({"diceware", "-W", "11", "-w", wordFile.fileName()}); + m_stdoutFile->seek(pos); + passphrase = m_stdoutFile->readLine(); + const auto words = passphrase.split(" "); + QCOMPARE(words.size(), 11); + QRegularExpression regex("^word\\d+$"); + for (const auto& word: words) { + QVERIFY2(regex.match(word).hasMatch(), qPrintable("Word " + word + " was not on the word list")); + } +} + +void TestCli::testEdit() +{ + Edit editCmd; + QVERIFY(!editCmd.name.isEmpty()); + QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"}); + + auto db = readTestDatabase(); + auto* entry = db->rootGroup()->findEntryByPath("/newtitle"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QCOMPARE(entry->password(), QString("Password")); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/newtitle"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QVERIFY(!entry->password().isEmpty()); + QVERIFY(entry->password() != QString("Password")); + + Utils::setNextPassword("a"); + editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/yet another title"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); + QVERIFY(entry->password() != QString("Password")); + QCOMPARE(entry->password().size(), 34); + + Utils::setNextPassword("a"); + Utils::setNextPassword("newpassword"); + editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"}); + db = readTestDatabase(); + entry = db->rootGroup()->findEntryByPath("/yet another title"); + QVERIFY(entry); + QCOMPARE(entry->password(), QString("newpassword")); +} + +void TestCli::testEstimate_data() +{ + QTest::addColumn("input"); + QTest::addColumn("length"); + QTest::addColumn("entropy"); + QTest::addColumn("log10"); + QTest::addColumn("searchStrings"); + + QTest::newRow("Dictionary") + << "password" << "8" << "1.0" << "0.3" + << QStringList{"Type: Dictionary", "\tpassword"}; + + QTest::newRow("Spatial") + << "zxcv" << "4" << "10.3" << "3.1" + << QStringList{"Type: Spatial", "\tzxcv"}; + + QTest::newRow("Spatial(Rep)") + << "sdfgsdfg" << "8" << "11.3" << "3.4" + << QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"}; + + QTest::newRow("Dictionary / Sequence") + << "password123" << "11" << "4.5" << "1.3" + << QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"}; + + QTest::newRow("Dict+Leet") + << "p455w0rd" << "8" << "2.5" << "0.7" + << QStringList{"Type: Dict+Leet", "\tp455w0rd"}; + + QTest::newRow("Dictionary(Rep)") + << "hellohello" << "10" << "7.3" << "2.2" + << QStringList{"Type: Dictionary(Rep)", "\thellohello"}; + + QTest::newRow("Sequence(Rep) / Dictionary") + << "456456foobar" << "12" << "16.7" << "5.0" + << QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"}; + + QTest::newRow("Bruteforce(Rep) / Bruteforce") + << "xzxzy" << "5" << "16.1" << "4.8" + << QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"}; + + QTest::newRow("Dictionary / Date(Rep)") + << "pass20182018" << "12" << "15.1" << "4.56" + << QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"}; + + QTest::newRow("Dictionary / Date / Bruteforce") + << "mypass2018-2" << "12" << "32.9" << "9.9" + << QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"}; + + QTest::newRow("Strong Password") + << "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/" << "28" << "165.7" << "49.8" + << QStringList{"Type: Bruteforce", "\tE*"}; + + // TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347) + QTest::newRow("Strong Passphrase") + << "squint wooing resupply dangle isolation axis headsman" << "53" << "151.2" << "45.5" + << QStringList{"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"}; +} + +void TestCli::testEstimate() +{ + QFETCH(QString, input); + QFETCH(QString, length); + QFETCH(QString, entropy); + QFETCH(QString, log10); + QFETCH(QStringList, searchStrings); + + Estimate estimateCmd; + QVERIFY(!estimateCmd.name.isEmpty()); + QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name)); + + QTextStream in(m_stdinFile.data()); + QTextStream out(m_stdoutFile.data()); + + in << input << endl; + auto inEnd = in.pos(); + in.seek(0); + estimateCmd.execute({"estimate"}); + auto outEnd = out.pos(); + out.seek(0); + auto result = out.readAll(); + QVERIFY(result.startsWith("Length " + length)); + QVERIFY(result.contains("Entropy " + entropy)); + QVERIFY(result.contains("Log10 " + log10)); + + // seek to end of stream + in.seek(inEnd); + out.seek(outEnd); + + in << input << endl; + in.seek(inEnd); + estimateCmd.execute({"estimate", "-a"}); + out.seek(outEnd); + result = out.readAll(); + QVERIFY(result.startsWith("Length " + length)); + QVERIFY(result.contains("Entropy " + entropy)); + QVERIFY(result.contains("Log10 " + log10)); + for (const auto& string: asConst(searchStrings)) { + QVERIFY2(result.contains(string), qPrintable("String " + string + " missing")); + } +} + +void TestCli::testExtract() +{ + Extract extractCmd; + QVERIFY(!extractCmd.name.isEmpty()); + QVERIFY(extractCmd.getDescriptionLine().contains(extractCmd.name)); + + Utils::setNextPassword("a"); + extractCmd.execute({"extract", m_dbFile->fileName()}); + + m_stdoutFile->seek(0); + m_stdoutFile->readLine(); // skip prompt line + + KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); + QScopedPointer db(new Database()); + reader.readDatabase(m_stdoutFile.data(), db.data()); + QVERIFY(!reader.hasError()); + QVERIFY(db.data()); + auto* entry = db->rootGroup()->findEntryByPath("/Sample Entry"); + QVERIFY(entry); + QCOMPARE(entry->password(), QString("Password")); +} + +void TestCli::testGenerate_data() +{ + QTest::addColumn("parameters"); + QTest::addColumn("pattern"); + + QTest::newRow("default") << QStringList{"generate"} << "^[^\r\n]+$"; + QTest::newRow("length") << QStringList{"generate", "-L", "13"} << "^.{13}$"; + QTest::newRow("lowercase") << QStringList{"generate", "-L", "14", "-l"} << "^[a-z]{14}$"; + QTest::newRow("uppercase") << QStringList{"generate", "-L", "15", "-u"} << "^[A-Z]{15}$"; + QTest::newRow("numbers")<< QStringList{"generate", "-L", "16", "-n"} << "^[0-9]{16}$"; + QTest::newRow("special") + << QStringList{"generate", "-L", "200", "-s"} + << R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!+-<=>?#$%&^`@~]{200}$)"; + QTest::newRow("special (exclude)") + << QStringList{"generate", "-L", "200", "-s" , "-x", "+.?@&"} + << R"(^[\(\)\[\]\{\}\.\-*|\\,:;"'\/\_!-<=>#$%^`~]{200}$)"; + QTest::newRow("extended") + << QStringList{"generate", "-L", "50", "-e"} + << R"(^[^a-zA-Z0-9\(\)\[\]\{\}\.\-\*\|\\,:;"'\/\_!+-<=>?#$%&^`@~]{50}$)"; + QTest::newRow("numbers + lowercase + uppercase") + << QStringList{"generate", "-L", "16", "-n", "-u", "-l"} + << "^[0-9a-zA-Z]{16}$"; + QTest::newRow("numbers + lowercase + uppercase (exclude)") + << QStringList{"generate", "-L", "500", "-n", "-u", "-l", "-x", "abcdefg0123@"} + << "^[^abcdefg0123@]{500}$"; + QTest::newRow("numbers + lowercase + uppercase (exclude similar)") + << QStringList{"generate", "-L", "200", "-n", "-u", "-l", "--exclude-similar"} + << "^[^l1IO0]{200}$"; + QTest::newRow("uppercase + lowercase (every)") + << QStringList{"generate", "-L", "2", "-u", "-l", "--every-group"} + << "^[a-z][A-Z]|[A-Z][a-z]$"; + QTest::newRow("numbers + lowercase (every)") + << QStringList{"generate", "-L", "2", "-n", "-l", "--every-group"} + << "^[a-z][0-9]|[0-9][a-z]$"; +} + +void TestCli::testGenerate() +{ + QFETCH(QStringList, parameters); + QFETCH(QString, pattern); + + Generate generateCmd; + QVERIFY(!generateCmd.name.isEmpty()); + QVERIFY(generateCmd.getDescriptionLine().contains(generateCmd.name)); + + qint64 pos = 0; + // run multiple times to make accidental passes unlikely + for (int i = 0; i < 10; ++i) { + generateCmd.execute(parameters); + m_stdoutFile->seek(pos); + QRegularExpression regex(pattern); + QString password = QString::fromUtf8(m_stdoutFile->readLine()); + pos = m_stdoutFile->pos(); + QVERIFY2(regex.match(password).hasMatch(), qPrintable("Password " + password + " does not match pattern " + pattern)); + } +} + +void TestCli::testList() +{ + List listCmd; + QVERIFY(!listCmd.name.isEmpty()); + QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name)); + + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName()}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "General/\n" + "Windows/\n" + "Network/\n" + "Internet/\n" + "eMail/\n" + "Homebanking/\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", "-R", m_dbFile->fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "General/\n" + " [empty]\n" + "Windows/\n" + " [empty]\n" + "Network/\n" + " [empty]\n" + "Internet/\n" + " [empty]\n" + "eMail/\n" + " [empty]\n" + "Homebanking/\n" + " [empty]\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName(), "/General/"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + listCmd.execute({"ls", m_dbFile->fileName(), "/DoesNotExist/"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("Cannot find group /DoesNotExist/.\n")); +} + +void TestCli::testLocate() +{ + Locate locateCmd; + QVERIFY(!locateCmd.name.isEmpty()); + QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name)); + + Utils::setNextPassword("a"); + locateCmd.execute({"locate", m_dbFile->fileName(), "Sample"}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", m_dbFile->fileName(), "Does Not Exist"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("No results for that search term.\n")); + + // write a modified database + auto db = readTestDatabase(); + QVERIFY(db); + auto* group = db->rootGroup()->findGroupByPath("/General/"); + QVERIFY(group); + auto* entry = new Entry(); + entry->setUuid(QUuid::createUuid()); + entry->setTitle("New Entry"); + group->addEntry(entry); + QTemporaryFile tmpFile; + tmpFile.open(); + Kdbx4Writer writer; + writer.writeDatabase(&tmpFile, db.data()); + tmpFile.close(); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", tmpFile.fileName(), "New"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/General/New Entry\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + locateCmd.execute({"locate", tmpFile.fileName(), "Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n/General/New Entry\n")); +} + +void TestCli::testMerge() +{ + Merge mergeCmd; + QVERIFY(!mergeCmd.name.isEmpty()); + QVERIFY(mergeCmd.getDescriptionLine().contains(mergeCmd.name)); + + Kdbx4Writer writer; + Kdbx4Reader reader; + + // load test database and save a copy + auto db = readTestDatabase(); + QVERIFY(db); + QTemporaryFile targetFile1; + targetFile1.open(); + writer.writeDatabase(&targetFile1, db.data()); + targetFile1.close(); + + // save another copy with a different password + QTemporaryFile targetFile2; + targetFile2.open(); + auto oldKey = db->key(); + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("b")); + db->setKey(key); + writer.writeDatabase(&targetFile2, db.data()); + targetFile2.close(); + db->setKey(oldKey); + + // then add a new entry to the in-memory database and save another copy + auto* entry = new Entry(); + entry->setUuid(QUuid::createUuid()); + entry->setTitle("Some Website"); + entry->setPassword("secretsecretsecret"); + auto* group = db->rootGroup()->findGroupByPath("/Internet/"); + QVERIFY(group); + group->addEntry(entry); + QTemporaryFile sourceFile; + sourceFile.open(); + writer.writeDatabase(&sourceFile, db.data()); + sourceFile.close(); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n")); + + QFile readBack(targetFile1.fileName()); + readBack.open(QIODevice::ReadOnly); + QScopedPointer mergedDb(reader.readDatabase(&readBack, oldKey)); + readBack.close(); + QVERIFY(mergedDb); + auto* entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); + QVERIFY(entry1); + QCOMPARE(entry1->title(), QString("Some Website")); + QCOMPARE(entry1->password(), QString("secretsecretsecret")); + + // try again with different passwords for both files + pos = m_stdoutFile->pos(); + Utils::setNextPassword("b"); + Utils::setNextPassword("a"); + mergeCmd.execute({"merge", targetFile2.fileName(), sourceFile.fileName()}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); + m_stdoutFile->readLine(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n")); + + readBack.setFileName(targetFile2.fileName()); + readBack.open(QIODevice::ReadOnly); + mergedDb.reset(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(mergedDb); + entry1 = mergedDb->rootGroup()->findEntryByPath("/Internet/Some Website"); + QVERIFY(entry1); + QCOMPARE(entry1->title(), QString("Some Website")); + QCOMPARE(entry1->password(), QString("secretsecretsecret")); +} + +void TestCli::testRemove() +{ + Remove removeCmd; + QVERIFY(!removeCmd.name.isEmpty()); + QVERIFY(removeCmd.getDescriptionLine().contains(removeCmd.name)); + + Kdbx3Reader reader; + Kdbx3Writer writer; + + // load test database and save a copy with disabled recycle bin + auto db = readTestDatabase(); + QVERIFY(db); + QTemporaryFile fileCopy; + fileCopy.open(); + db->metadata()->setRecycleBinEnabled(false); + writer.writeDatabase(&fileCopy, db.data()); + fileCopy.close(); + + qint64 pos = m_stdoutFile->pos(); + + // delete entry and verify + Utils::setNextPassword("a"); + removeCmd.execute({"rm", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully recycled entry Sample Entry.\n")); + + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create("a")); + QFile readBack(m_dbFile->fileName()); + readBack.open(QIODevice::ReadOnly); + QScopedPointer readBackDb(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(readBackDb); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry")); + + pos = m_stdoutFile->pos(); + + // try again, this time without recycle bin + Utils::setNextPassword("a"); + removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully deleted entry Sample Entry.\n")); + + readBack.setFileName(fileCopy.fileName()); + readBack.open(QIODevice::ReadOnly); + readBackDb.reset(reader.readDatabase(&readBack, key)); + readBack.close(); + QVERIFY(readBackDb); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Sample Entry")); + QVERIFY(!readBackDb->rootGroup()->findEntryByPath("/Recycle Bin/Sample Entry")); + + pos = m_stdoutFile->pos(); + + // finally, try deleting a non-existent entry + Utils::setNextPassword("a"); + removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("Entry /Sample Entry not found.\n")); +} + +void TestCli::testShow() +{ + Show showCmd; + QVERIFY(!showCmd.name.isEmpty()); + QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name)); + + Utils::setNextPassword("a"); + showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->reset(); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Title: Sample Entry\n" + "UserName: User Name\n" + "Password: Password\n" + "URL: http://www.somesite.com/\n" + "Notes: Notes\n")); + + qint64 pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "http://www.somesite.com/\n")); + + pos = m_stdoutFile->pos(); + Utils::setNextPassword("a"); + showCmd.execute({"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"}); + m_stdoutFile->seek(pos); + m_stdoutFile->readLine(); // skip password prompt + m_stderrFile->reset(); + QCOMPARE(m_stdoutFile->readAll(), QByteArray("")); + QCOMPARE(m_stderrFile->readAll(), QByteArray("ERROR: unknown attribute DoesNotExist.\n")); +} diff --git a/tests/TestCli.h b/tests/TestCli.h new file mode 100644 index 0000000000..532d84a793 --- /dev/null +++ b/tests/TestCli.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_TESTCLI_H +#define KEEPASSXC_TESTCLI_H + +#include "core/Database.h" + +#include +#include +#include +#include +#include + +class TestCli : public QObject +{ + Q_OBJECT + +private: + QSharedPointer readTestDatabase() const; + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void testCommand(); + void testAdd(); + void testClip(); + void testDiceware(); + void testEdit(); + void testEstimate_data(); + void testEstimate(); + void testExtract(); + void testGenerate_data(); + void testGenerate(); + void testList(); + void testLocate(); + void testMerge(); + void testRemove(); + void testShow(); + +private: + QByteArray m_dbData; + QScopedPointer m_dbFile; + QScopedPointer m_stdoutFile; + QScopedPointer m_stderrFile; + QScopedPointer m_stdinFile; + FILE* m_stdoutHandle = stdout; + FILE* m_stderrHandle = stderr; + FILE* m_stdinHandle = stdin; +}; + +#endif //KEEPASSXC_TESTCLI_H diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index 72c2d4c834..297cde284e 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -199,12 +199,12 @@ void TestKdbx4::testFormat400Upgrade_data() auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK; auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; - QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << false << kdbx4; - QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << false << kdbx4; - QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << false << kdbx3; - QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << true << kdbx4; - QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << true << kdbx4; - QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << true << kdbx4; + QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << false << kdbx4; + QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4; + QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3; + QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << true << kdbx4; + QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4; + QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4; QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4; QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4; diff --git a/tests/TestPasswordGenerator.cpp b/tests/TestPasswordGenerator.cpp new file mode 100644 index 0000000000..53cf25c312 --- /dev/null +++ b/tests/TestPasswordGenerator.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#include "TestPasswordGenerator.h" +#include "core/PasswordGenerator.h" +#include "crypto/Crypto.h" + +#include +#include + +QTEST_GUILESS_MAIN(TestPasswordGenerator) + +void TestPasswordGenerator::initTestCase() +{ + QVERIFY(Crypto::init()); +} + +void TestPasswordGenerator::testCharClasses() +{ + PasswordGenerator generator; + QVERIFY(!generator.isValid()); + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters); + generator.setLength(16); + QVERIFY(generator.isValid()); + QCOMPARE(generator.generatePassword().size(), 16); + + generator.setLength(2000); + QString password = generator.generatePassword(); + QCOMPARE(password.size(), 2000); + QRegularExpression regex(R"(^[a-z]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::UpperLetters); + password = generator.generatePassword(); + regex.setPattern(R"(^[A-Z]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Numbers); + password = generator.generatePassword(); + regex.setPattern(R"(^\d+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Braces); + password = generator.generatePassword(); + regex.setPattern(R"(^[\(\)\[\]\{\}]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Punctuation); + password = generator.generatePassword(); + regex.setPattern(R"(^[\.,:;]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Quotes); + password = generator.generatePassword(); + regex.setPattern(R"(^["']+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Dashes); + password = generator.generatePassword(); + regex.setPattern(R"(^[\-/\\_|]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Math); + password = generator.generatePassword(); + regex.setPattern(R"(^[!\*\+\-<=>\?]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Logograms); + password = generator.generatePassword(); + regex.setPattern(R"(^[#`~%&^$@]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::EASCII); + password = generator.generatePassword(); + regex.setPattern(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters + | PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Braces); + password = generator.generatePassword(); + regex.setPattern(R"(^[a-zA-Z\(\)\[\]\{\}]+$)"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::Quotes + | PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::Dashes); + password = generator.generatePassword(); + regex.setPattern(R"(^["'\d\-/\\_|]+$)"); + QVERIFY(regex.match(password).hasMatch()); +} + +void TestPasswordGenerator::testLookalikeExclusion() +{ + PasswordGenerator generator; + generator.setLength(2000); + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters); + QVERIFY(generator.isValid()); + QString password = generator.generatePassword(); + QCOMPARE(password.size(), 2000); + + generator.setFlags(PasswordGenerator::GeneratorFlag::ExcludeLookAlike); + password = generator.generatePassword(); + QRegularExpression regex("^[^lI0]+$"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters | + PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers); + password = generator.generatePassword(); + regex.setPattern("^[^lI01]+$"); + QVERIFY(regex.match(password).hasMatch()); + + generator.setCharClasses(PasswordGenerator::CharClass::LowerLetters + | PasswordGenerator::CharClass::UpperLetters | PasswordGenerator::CharClass::Numbers + | PasswordGenerator::CharClass::EASCII); + password = generator.generatePassword(); + regex.setPattern("^[^lI01ï¹’]+$"); + QVERIFY(regex.match(password).hasMatch()); +} diff --git a/tests/TestPasswordGenerator.h b/tests/TestPasswordGenerator.h new file mode 100644 index 0000000000..5287e5bdef --- /dev/null +++ b/tests/TestPasswordGenerator.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_TESTPASSWORDGENERATOR_H +#define KEEPASSXC_TESTPASSWORDGENERATOR_H + +#include + +class TestPasswordGenerator : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testCharClasses(); + void testLookalikeExclusion(); +}; + +#endif //KEEPASSXC_TESTPASSWORDGENERATOR_H diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index bec894c06f..b69e463b1d 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -26,162 +26,173 @@ #include "streams/SymmetricCipherStream.h" QTEST_GUILESS_MAIN(TestSymmetricCipher) +Q_DECLARE_METATYPE(SymmetricCipher::Algorithm); +Q_DECLARE_METATYPE(SymmetricCipher::Mode); +Q_DECLARE_METATYPE(SymmetricCipher::Direction); void TestSymmetricCipher::initTestCase() { QVERIFY(Crypto::init()); } -void TestSymmetricCipher::testAes128CbcEncryption() +void TestSymmetricCipher::testAlgorithmToCipher() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - - QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); - cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText, &ok), cipherText); - QVERIFY(ok); - - QBuffer buffer; - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::WriteOnly); - QVERIFY(stream.open(QIODevice::WriteOnly)); - QVERIFY(stream.reset()); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(16)), qint64(16)); - QCOMPARE(buffer.data(), cipherText.left(16)); - QVERIFY(stream.reset()); - // make sure padding is written - QCOMPARE(buffer.data().size(), 32); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - QVERIFY(buffer.data().isEmpty()); - - QVERIFY(stream.reset()); - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - stream.close(); - QCOMPARE(buffer.data().size(), 16); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes128), KeePass2::CIPHER_AES128); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Aes256), KeePass2::CIPHER_AES256); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::Twofish), KeePass2::CIPHER_TWOFISH); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::ChaCha20), KeePass2::CIPHER_CHACHA20); + QCOMPARE(SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm::InvalidAlgorithm), QUuid()); } -void TestSymmetricCipher::testAes128CbcDecryption() +void TestSymmetricCipher::testEncryptionDecryption_data() { - QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); - cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); - - // padded with 16 0x10 bytes - QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); - QBuffer buffer(&cipherTextPadded); - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::ReadOnly); - QVERIFY(stream.open(QIODevice::ReadOnly)); + QTest::addColumn("algorithm"); + QTest::addColumn("mode"); + QTest::addColumn("direction"); + QTest::addColumn("key"); + QTest::addColumn("iv"); + QTest::addColumn("plainText"); + QTest::addColumn("cipherText"); - QCOMPARE(stream.read(10), plainText.left(10)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(20), plainText.left(20)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(16), plainText.left(16)); - buffer.reset(); - QVERIFY(stream.reset()); - QCOMPARE(stream.read(100), plainText); + // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + QTest::newRow("AES128-CBC Encryption") + << SymmetricCipher::Aes128 + << SymmetricCipher::Cbc + << SymmetricCipher::Encrypt + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"); + + QTest::newRow("AES128-CBC Decryption") + << SymmetricCipher::Aes128 + << SymmetricCipher::Cbc + << SymmetricCipher::Decrypt + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + + QTest::newRow("AES256-CBC Encryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Cbc + << SymmetricCipher::Encrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d"); + + QTest::newRow("AES256-CBC Decryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Cbc + << SymmetricCipher::Decrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); + + QTest::newRow("AES256-CTR Encryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Ctr + << SymmetricCipher::Encrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"); + + QTest::newRow("AES256-CTR Decryption") + << SymmetricCipher::Aes256 + << SymmetricCipher::Ctr + << SymmetricCipher::Decrypt + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + << QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"); } -void TestSymmetricCipher::testAes256CbcEncryption() +void TestSymmetricCipher::testEncryptionDecryption() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + QFETCH(SymmetricCipher::Algorithm, algorithm); + QFETCH(SymmetricCipher::Mode, mode); + QFETCH(SymmetricCipher::Direction, direction); + QFETCH(QByteArray, key); + QFETCH(QByteArray, iv); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, cipherText); - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + SymmetricCipher cipher(algorithm, mode, direction); QVERIFY(cipher.init(key, iv)); QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText, &ok), cipherText); QVERIFY(ok); - QBuffer buffer; - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - QVERIFY(stream.init(key, iv)); - buffer.open(QIODevice::WriteOnly); - QVERIFY(stream.open(QIODevice::WriteOnly)); - QVERIFY(stream.reset()); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(16)), qint64(16)); - QCOMPARE(buffer.data(), cipherText.left(16)); - QVERIFY(stream.reset()); - // make sure padding is written - QCOMPARE(buffer.data().size(), 32); - - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - QVERIFY(buffer.data().isEmpty()); - - QVERIFY(stream.reset()); - buffer.reset(); - buffer.buffer().clear(); - QCOMPARE(stream.write(plainText.left(10)), qint64(10)); - stream.close(); - QCOMPARE(buffer.data().size(), 16); + if (mode == SymmetricCipher::Cbc) { + QBuffer buffer; + SymmetricCipherStream stream(&buffer, algorithm, mode, direction); + QVERIFY(stream.init(key, iv)); + buffer.open(QIODevice::WriteOnly); + QVERIFY(stream.open(QIODevice::WriteOnly)); + QVERIFY(stream.reset()); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(16)), qint64(16)); + QCOMPARE(buffer.data(), cipherText.left(16)); + QVERIFY(stream.reset()); + // make sure padding is written + QCOMPARE(buffer.data().size(), 32); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + QVERIFY(buffer.data().isEmpty()); + + QVERIFY(stream.reset()); + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + stream.close(); + QCOMPARE(buffer.data().size(), 16); + } } -void TestSymmetricCipher::testAes256CbcDecryption() +void TestSymmetricCipher::testAesCbcPadding_data() { - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); - QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); - cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; + QTest::addColumn("key"); + QTest::addColumn("iv"); + QTest::addColumn("cipherText"); + QTest::addColumn("plainText"); + QTest::addColumn("padding"); + + QTest::newRow("AES128") + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); + + QTest::newRow("AES256") + << QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + << QByteArray::fromHex("000102030405060708090a0b0c0d0e0f") + << QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51") + << QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); +} - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, iv)); - QCOMPARE(cipher.blockSize(), 16); +void TestSymmetricCipher::testAesCbcPadding() +{ + QFETCH(QByteArray, key); + QFETCH(QByteArray, iv); + QFETCH(QByteArray, cipherText); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, padding); - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); + // padded with 16 0x10 bytes + QByteArray cipherTextPadded = cipherText + padding; - // padded with 16 0x16 bytes - QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); QBuffer buffer(&cipherTextPadded); - SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::ReadOnly); QVERIFY(stream.open(QIODevice::ReadOnly)); @@ -198,42 +209,48 @@ void TestSymmetricCipher::testAes256CbcDecryption() QCOMPARE(stream.read(100), plainText); } -void TestSymmetricCipher::testAes256CtrEncryption() +void TestSymmetricCipher::testInplaceEcb_data() { - // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf - - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228"); - cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Encrypt); - QVERIFY(cipher.init(key, ctr)); - QCOMPARE(cipher.blockSize(), 16); - - QCOMPARE(cipher.process(plainText, &ok), cipherText); - QVERIFY(ok); + QTest::addColumn("key"); + QTest::addColumn("plainText"); + QTest::addColumn("cipherText"); + + QTest::newRow("AES128") + << QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c") + << QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a") + << QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97"); } -void TestSymmetricCipher::testAes256CtrDecryption() +void TestSymmetricCipher::testInplaceEcb() { - QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); - QByteArray ctr = QByteArray::fromHex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); - QByteArray cipherText = QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228"); - cipherText.append(QByteArray::fromHex("f443e3ca4d62b59aca84e990cacaf5c5")); - QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); - plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); - bool ok; - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt); - QVERIFY(cipher.init(key, ctr)); - QCOMPARE(cipher.blockSize(), 16); - - QCOMPARE(cipher.process(cipherText, &ok), plainText); - QVERIFY(ok); + QFETCH(QByteArray, key); + QFETCH(QByteArray, plainText); + QFETCH(QByteArray, cipherText); + + SymmetricCipher cipherInPlaceEnc(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); + QVERIFY(cipherInPlaceEnc.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceEnc.blockSize(), 16); + auto data = QByteArray(plainText); + QVERIFY(cipherInPlaceEnc.processInPlace(data)); + QCOMPARE(data, cipherText); + + SymmetricCipher cipherInPlaceDec(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); + QVERIFY(cipherInPlaceDec.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceDec.blockSize(), 16); + QVERIFY(cipherInPlaceDec.processInPlace(data)); + QCOMPARE(data, plainText); + + SymmetricCipher cipherInPlaceEnc2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); + QVERIFY(cipherInPlaceEnc2.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceEnc2.blockSize(), 16); + data = QByteArray(plainText); + QVERIFY(cipherInPlaceEnc2.processInPlace(data, 100)); + + SymmetricCipher cipherInPlaceDec2(SymmetricCipher::Aes128, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); + QVERIFY(cipherInPlaceDec2.init(key, QByteArray(16, 0))); + QCOMPARE(cipherInPlaceDec2.blockSize(), 16); + QVERIFY(cipherInPlaceDec2.processInPlace(data, 100)); + QCOMPARE(data, plainText); } void TestSymmetricCipher::testTwofish256CbcEncryption() diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 9b82fd88a3..5eede0953a 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -27,12 +27,13 @@ class TestSymmetricCipher : public QObject private slots: void initTestCase(); - void testAes128CbcEncryption(); - void testAes128CbcDecryption(); - void testAes256CbcEncryption(); - void testAes256CbcDecryption(); - void testAes256CtrEncryption(); - void testAes256CtrDecryption(); + void testAlgorithmToCipher(); + void testEncryptionDecryption_data(); + void testEncryptionDecryption(); + void testAesCbcPadding_data(); + void testAesCbcPadding(); + void testInplaceEcb_data(); + void testInplaceEcb(); void testTwofish256CbcEncryption(); void testTwofish256CbcDecryption(); void testSalsa20(); diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index 6cae888303..a6e876f475 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -15,6 +15,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -add_unit_test(NAME testgui SOURCES TestGui.cpp TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/gui/TemporaryFile.cpp b/tests/gui/TemporaryFile.cpp deleted file mode 100644 index b6d20848b3..0000000000 --- a/tests/gui/TemporaryFile.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2016 Danny Su - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#include "TemporaryFile.h" - -#include - -#ifdef Q_OS_WIN -const QString TemporaryFile::SUFFIX = ".win"; - -TemporaryFile::~TemporaryFile() -{ - if (m_tempFile.autoRemove()) { - m_file.remove(); - } -} -#endif - -bool TemporaryFile::open() -{ -#ifdef Q_OS_WIN - // Still call QTemporaryFile::open() so that it figures out the temporary - // file name to use. Assuming that by appending the SUFFIX to whatever - // QTemporaryFile chooses is also an available file. - bool tempFileOpened = m_tempFile.open(); - if (tempFileOpened) { - m_file.setFileName(filePath()); - return m_file.open(QIODevice::WriteOnly); - } - return false; -#else - return m_tempFile.open(); -#endif -} - -void TemporaryFile::close() -{ - m_tempFile.close(); -#ifdef Q_OS_WIN - m_file.close(); -#endif -} - -qint64 TemporaryFile::write(const char* data, qint64 maxSize) -{ -#ifdef Q_OS_WIN - return m_file.write(data, maxSize); -#else - return m_tempFile.write(data, maxSize); -#endif -} - -qint64 TemporaryFile::write(const QByteArray& byteArray) -{ -#ifdef Q_OS_WIN - return m_file.write(byteArray); -#else - return m_tempFile.write(byteArray); -#endif -} - -QString TemporaryFile::fileName() const -{ -#ifdef Q_OS_WIN - return QFileInfo(m_tempFile).fileName() + TemporaryFile::SUFFIX; -#else - return QFileInfo(m_tempFile).fileName(); -#endif -} - -QString TemporaryFile::filePath() const -{ -#ifdef Q_OS_WIN - return m_tempFile.fileName() + TemporaryFile::SUFFIX; -#else - return m_tempFile.fileName(); -#endif -} diff --git a/tests/gui/TemporaryFile.h b/tests/gui/TemporaryFile.h deleted file mode 100644 index 8a98d92359..0000000000 --- a/tests/gui/TemporaryFile.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Danny Su - * Copyright (C) 2017 KeePassXC Team - * - * 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 . - */ - -#ifndef KEEPASSX_TEMPORARYFILE_H -#define KEEPASSX_TEMPORARYFILE_H - -#include -#include -#include - -/** - * QTemporaryFile::close() doesn't actually close the file according to - * http://doc.qt.io/qt-5/qtemporaryfile.html: "For as long as the - * QTemporaryFile object itself is not destroyed, the unique temporary file - * will exist and be kept open internally by QTemporaryFile." - * - * This behavior causes issues when running tests on Windows. If the file is - * not closed, the testSave test will fail due to Access Denied. The - * auto-reload test also fails from Windows not triggering file change - * notification because the file isn't actually closed by QTemporaryFile. - * - * This class isolates the Windows specific logic that uses QFile to really - * close the test file when requested to. - */ -class TemporaryFile : public QObject -{ - Q_OBJECT - -public: -#ifdef Q_OS_WIN - ~TemporaryFile(); -#endif - - bool open(); - void close(); - qint64 write(const char* data, qint64 maxSize); - qint64 write(const QByteArray& byteArray); - - QString fileName() const; - QString filePath() const; - -private: - QTemporaryFile m_tempFile; -#ifdef Q_OS_WIN - QFile m_file; - static const QString SUFFIX; -#endif -}; - -#endif // KEEPASSX_TEMPORARYFILE_H diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index e3671567cd..ee53eb7777 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -18,6 +18,7 @@ #include "TestGui.h" #include "TestGlobal.h" +#include "gui/Application.h" #include #include @@ -33,12 +34,12 @@ #include #include #include -#include #include #include #include #include "config-keepassx-tests.h" +#include "core/Bootstrap.h" #include "core/Config.h" #include "core/Database.h" #include "core/Entry.h" @@ -59,7 +60,6 @@ #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" #include "gui/FileDialog.h" -#include "gui/MainWindow.h" #include "gui/MessageBox.h" #include "gui/PasswordEdit.h" #include "gui/SearchWidget.h" @@ -74,22 +74,23 @@ #include "gui/masterkey/KeyComponentWidget.h" #include "keys/PasswordKey.h" +QTEST_MAIN(TestGui) + void TestGui::initTestCase() { QVERIFY(Crypto::init()); Config::createTempFileInstance(); // Disable autosave so we can test the modified file indicator config()->set("AutoSaveAfterEveryChange", false); - // Enable the tray icon so we can test hiding/restoring the window + // Enable the tray icon so we can test hiding/restoring the windowQByteArray config()->set("GUI/ShowTrayIcon", true); // Disable advanced settings mode (activate within individual tests to test advanced settings) config()->set("GUI/AdvancedSettings", false); - m_mainWindow = new MainWindow(); + m_mainWindow.reset(new MainWindow()); + Bootstrap::restoreMainWindowState(*m_mainWindow); m_tabWidget = m_mainWindow->findChild("tabWidget"); m_mainWindow->show(); - m_mainWindow->activateWindow(); - Tools::wait(50); // Load the NewDatabase.kdbx file into temporary storage QFile sourceDbFile(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx")); @@ -101,29 +102,32 @@ void TestGui::initTestCase() // Every test starts with opening the temp database void TestGui::init() { + m_dbFile.reset(new QTemporaryFile()); // Write the temp storage to a temp database file for use in our tests - QVERIFY(m_dbFile.open()); - QCOMPARE(m_dbFile.write(m_dbData), static_cast((m_dbData.size()))); - m_dbFile.close(); - - m_dbFileName = m_dbFile.fileName(); - m_dbFilePath = m_dbFile.filePath(); + QVERIFY(m_dbFile->open()); + QCOMPARE(m_dbFile->write(m_dbData), static_cast((m_dbData.size()))); + m_dbFileName = QFileInfo(m_dbFile->fileName()).fileName(); + m_dbFilePath = m_dbFile->fileName(); + m_dbFile->close(); fileDialog()->setNextFileName(m_dbFilePath); triggerAction("actionDatabaseOpen"); - QWidget* databaseOpenWidget = m_mainWindow->findChild("databaseOpenWidget"); - QLineEdit* editPassword = databaseOpenWidget->findChild("editPassword"); + auto* databaseOpenWidget = m_mainWindow->findChild("databaseOpenWidget"); + auto* editPassword = databaseOpenWidget->findChild("editPassword"); QVERIFY(editPassword); QTest::keyClicks(editPassword, "a"); QTest::keyClick(editPassword, Qt::Key_Enter); - Tools::wait(100); - QVERIFY(m_tabWidget->currentDatabaseWidget()); + QTRY_VERIFY(m_tabWidget->currentDatabaseWidget()); m_dbWidget = m_tabWidget->currentDatabaseWidget(); m_db = m_dbWidget->database(); + + // make sure window is activated or focus tests may fail + m_mainWindow->activateWindow(); + QApplication::processEvents(); } // Every test ends with closing the temp database without saving @@ -132,17 +136,21 @@ void TestGui::cleanup() // DO NOT save the database MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); + QApplication::processEvents(); if (m_db) { delete m_db; } - m_db = nullptr; - if (m_dbWidget) { delete m_dbWidget; } - m_dbWidget = nullptr; + + m_dbFile->remove(); +} + +void TestGui::cleanupTestCase() +{ + m_dbFile->remove(); } void TestGui::testSettingsDefaultTabOrder() @@ -187,8 +195,9 @@ void TestGui::testCreateDatabase() // check key and encryption QCOMPARE(m_db->key()->keys().size(), 2); + QCOMPARE(m_db->kdf()->rounds(), 2); QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2); - QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES); + QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES256); auto compositeKey = QSharedPointer::create(); compositeKey->addKey(QSharedPointer::create("test")); auto fileKey = QSharedPointer::create(); @@ -213,7 +222,40 @@ void TestGui::createDatabaseCallback() QTest::keyClick(wizard, Qt::Key_Enter); QCOMPARE(wizard->currentId(), 1); - QTest::keyClick(wizard, Qt::Key_Enter); + auto decryptionTimeSlider = wizard->currentPage()->findChild("decryptionTimeSlider"); + auto algorithmComboBox = wizard->currentPage()->findChild("algorithmComboBox"); + QTRY_VERIFY(decryptionTimeSlider->isVisible()); + QVERIFY(!algorithmComboBox->isVisible()); + auto advancedToggle = wizard->currentPage()->findChild("advancedSettingsButton"); + QTest::mouseClick(advancedToggle, Qt::MouseButton::LeftButton); + QTRY_VERIFY(!decryptionTimeSlider->isVisible()); + QVERIFY(algorithmComboBox->isVisible()); + + auto rounds = wizard->currentPage()->findChild("transformRoundsSpinBox"); + QVERIFY(rounds); + QVERIFY(rounds->isVisible()); + QTest::mouseClick(rounds, Qt::MouseButton::LeftButton); + QTest::keyClick(rounds, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(rounds, "2"); + QTest::keyClick(rounds, Qt::Key_Tab); + QTest::keyClick(rounds, Qt::Key_Tab); + + auto memory = wizard->currentPage()->findChild("memorySpinBox"); + QVERIFY(memory); + QVERIFY(memory->isVisible()); + QTest::mouseClick(memory, Qt::MouseButton::LeftButton); + QTest::keyClick(memory, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(memory, "50"); + QTest::keyClick(memory, Qt::Key_Tab); + + auto parallelism = wizard->currentPage()->findChild("parallelismSpinBox"); + QVERIFY(parallelism); + QVERIFY(parallelism->isVisible()); + QTest::mouseClick(parallelism, Qt::MouseButton::LeftButton); + QTest::keyClick(parallelism, Qt::Key_A, Qt::ControlModifier); + QTest::keyClicks(parallelism, "1"); + QTest::keyClick(parallelism, Qt::Key_Enter); + QCOMPARE(wizard->currentId(), 2); // enter password @@ -222,7 +264,7 @@ void TestGui::createDatabaseCallback() auto* passwordEdit = passwordWidget->findChild("enterPasswordEdit"); auto* passwordRepeatEdit = passwordWidget->findChild("repeatPasswordEdit"); QTRY_VERIFY(passwordEdit->isVisible()); - QVERIFY(passwordEdit->hasFocus()); + QTRY_VERIFY(passwordEdit->hasFocus()); QTest::keyClicks(passwordEdit, "test"); QTest::keyClick(passwordEdit, Qt::Key::Key_Tab); QTest::keyClicks(passwordRepeatEdit, "test"); @@ -247,25 +289,26 @@ void TestGui::createDatabaseCallback() QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); // save database to temporary file - TemporaryFile tmpFile; + QTemporaryFile tmpFile; QVERIFY(tmpFile.open()); tmpFile.close(); - fileDialog()->setNextFileName(tmpFile.filePath()); + fileDialog()->setNextFileName(tmpFile.fileName()); QTest::keyClick(fileCombo, Qt::Key::Key_Enter); + tmpFile.remove(); } void TestGui::testMergeDatabase() { // It is safe to ignore the warning this line produces - QSignalSpy dbMergeSpy(m_dbWidget, SIGNAL(databaseMerged(Database*))); + QSignalSpy dbMergeSpy(m_dbWidget.data(), SIGNAL(databaseMerged(Database*))); // set file to merge from fileDialog()->setNextFileName(QString(KEEPASSX_TEST_DATA_DIR).append("/MergeDatabase.kdbx")); triggerAction("actionDatabaseMerge"); - QWidget* databaseOpenMergeWidget = m_mainWindow->findChild("databaseOpenMergeWidget"); - QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild("editPassword"); + auto* databaseOpenMergeWidget = m_mainWindow->findChild("databaseOpenMergeWidget"); + auto* editPasswordMerge = databaseOpenMergeWidget->findChild("editPassword"); QVERIFY(editPasswordMerge->isVisible()); m_tabWidget->currentDatabaseWidget()->setCurrentWidget(databaseOpenMergeWidget); @@ -300,11 +343,11 @@ void TestGui::testAutoreloadDatabase() // Test accepting new file in autoreload MessageBox::setNextAnswer(QMessageBox::Yes); // Overwrite the current database with the temp data - QVERIFY(m_dbFile.open()); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + QVERIFY(m_dbFile->open()); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); // the General group contains one entry from the new db data @@ -318,10 +361,10 @@ void TestGui::testAutoreloadDatabase() // Test rejecting new file in autoreload MessageBox::setNextAnswer(QMessageBox::No); // Overwrite the current temp database with a new file - m_dbFile.open(); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + m_dbFile->open(); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); @@ -342,10 +385,10 @@ void TestGui::testAutoreloadDatabase() // This is saying yes to merging the entries MessageBox::setNextAnswer(QMessageBox::Yes); // Overwrite the current database with the temp data - QVERIFY(m_dbFile.open()); - QVERIFY(m_dbFile.write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); - m_dbFile.close(); - Tools::wait(1500); + QVERIFY(m_dbFile->open()); + QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast(unmodifiedMergeDatabase.size()))); + m_dbFile->close(); + Tools::wait(800); m_db = m_dbWidget->database(); @@ -361,17 +404,17 @@ void TestGui::testTabs() void TestGui::testEditEntry() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); int editCount = 0; // Select the first entry in the database - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QModelIndex entryItem = entryView->model()->index(0, 1); Entry* entry = entryView->entryFromIndex(entryItem); clickIndex(entryItem, entryView, Qt::LeftButton); // Confirm the edit action button is enabled - QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); + auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QVERIFY(entryEditAction->isEnabled()); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QVERIFY(entryEditWidget->isVisible()); @@ -380,12 +423,12 @@ void TestGui::testEditEntry() // Edit the first entry ("Sample Entry") QTest::mouseClick(entryEditWidget, Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "_test"); // Apply the edit - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); QCOMPARE(entry->title(), QString("Sample Entry_test")); @@ -410,7 +453,7 @@ void TestGui::testEditEntry() // Test protected attributes editEntryWidget->setCurrentPage(1); - QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + auto* attrTextEdit = editEntryWidget->findChild("attributesEdit"); QTest::mouseClick(editEntryWidget->findChild("addAttributeButton"), Qt::LeftButton); QString attrText = "TEST TEXT"; QTest::keyClicks(attrTextEdit, attrText); @@ -422,11 +465,11 @@ void TestGui::testEditEntry() editEntryWidget->setCurrentPage(0); // Test mismatch passwords - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); QString originalPassword = passwordEdit->text(); passwordEdit->setText("newpass"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - MessageWidget* messageWiget = editEntryWidget->findChild("messageWidget"); + auto* messageWiget = editEntryWidget->findChild("messageWidget"); QTRY_VERIFY(messageWiget->isVisible()); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); QCOMPARE(passwordEdit->text(), QString("newpass")); @@ -469,9 +512,9 @@ void TestGui::testSearchEditEntry() // Regression test for Issue #1447 -- Uses example from issue description // Find buttons for group creation - EditGroupWidget* editGroupWidget = m_dbWidget->findChild("editGroupWidget"); - QLineEdit* nameEdit = editGroupWidget->findChild("editName"); - QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild("buttonBox"); + auto* editGroupWidget = m_dbWidget->findChild("editGroupWidget"); + auto* nameEdit = editGroupWidget->findChild("editName"); + auto* editGroupWidgetButtonBox = editGroupWidget->findChild("buttonBox"); // Add groups "Good" and "Bad" m_dbWidget->createGroup(); @@ -484,11 +527,11 @@ void TestGui::testSearchEditEntry() m_dbWidget->groupView()->setCurrentGroup(m_db->rootGroup()); // Find buttons for entry creation - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild("actionEntryNew")); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); // Create "Doggy" in "Good" Group* goodGroup = m_dbWidget->currentGroup()->findChildByName(QString("Good")); @@ -501,8 +544,8 @@ void TestGui::testSearchEditEntry() m_dbWidget->groupView()->setCurrentGroup(badGroup); // Search for "Doggy" entry - SearchWidget* searchWidget = toolBar->findChild("SearchWidget"); - QLineEdit* searchTextEdit = searchWidget->findChild("searchEdit"); + auto* searchWidget = toolBar->findChild("SearchWidget"); + auto* searchTextEdit = searchWidget->findChild("searchEdit"); QTest::mouseClick(searchTextEdit, Qt::LeftButton); QTest::keyClicks(searchTextEdit, "Doggy"); QTRY_VERIFY(m_dbWidget->isInSearchMode()); @@ -518,11 +561,11 @@ void TestGui::testSearchEditEntry() void TestGui::testAddEntry() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -535,10 +578,10 @@ void TestGui::testAddEntry() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); @@ -551,28 +594,12 @@ void TestGui::testAddEntry() // Add entry "something 2" QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 2"); - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); - QLineEdit* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); QTest::keyClicks(passwordEdit, "something 2"); QTest::keyClicks(passwordRepeatEdit, "something 2"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); -/* All apply tests disabled due to data loss workaround - * that disables apply button on new entry creation - * - // Add entry "something 3" using the apply button then click ok - QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 3"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - - // Add entry "something 4" using the apply button then click cancel - QTest::mouseClick(entryNewWidget, Qt::LeftButton); - QTest::keyClicks(titleEdit, "something 4"); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Apply), Qt::LeftButton); - QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Cancel), Qt::LeftButton); -*/ - // Add entry "something 5" but click cancel button (does NOT add entry) QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "something 5"); @@ -587,10 +614,10 @@ void TestGui::testAddEntry() void TestGui::testPasswordEntryEntropy() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -603,18 +630,18 @@ void TestGui::testPasswordEntryEntropy() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); // Open the password generator - QToolButton* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); + auto* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); QTest::mouseClick(generatorButton, Qt::LeftButton); // Type in some password - QLineEdit* editNewPassword = editEntryWidget->findChild("editNewPassword"); - QLabel* entropyLabel = editEntryWidget->findChild("entropyLabel"); - QLabel* strengthLabel = editEntryWidget->findChild("strengthLabel"); + auto* editNewPassword = editEntryWidget->findChild("editNewPassword"); + auto* entropyLabel = editEntryWidget->findChild("entropyLabel"); + auto* strengthLabel = editEntryWidget->findChild("strengthLabel"); editNewPassword->setText(""); QTest::keyClicks(editNewPassword, "hello"); @@ -659,10 +686,10 @@ void TestGui::testPasswordEntryEntropy() void TestGui::testDicewareEntryEntropy() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -675,27 +702,27 @@ void TestGui::testDicewareEntryEntropy() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); // Open the password generator - QToolButton* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); + auto* generatorButton = editEntryWidget->findChild("togglePasswordGeneratorButton"); QTest::mouseClick(generatorButton, Qt::LeftButton); // Select Diceware - QTabWidget* tabWidget = editEntryWidget->findChild("tabWidget"); - QWidget* dicewareWidget = editEntryWidget->findChild("dicewareWidget"); + auto* tabWidget = editEntryWidget->findChild("tabWidget"); + auto* dicewareWidget = editEntryWidget->findChild("dicewareWidget"); tabWidget->setCurrentWidget(dicewareWidget); - QComboBox* comboBoxWordList = dicewareWidget->findChild("comboBoxWordList"); + auto* comboBoxWordList = dicewareWidget->findChild("comboBoxWordList"); comboBoxWordList->setCurrentText("eff_large.wordlist"); - QSpinBox* spinBoxWordCount = dicewareWidget->findChild("spinBoxWordCount"); + auto* spinBoxWordCount = dicewareWidget->findChild("spinBoxWordCount"); spinBoxWordCount->setValue(6); // Type in some password - QLabel* entropyLabel = editEntryWidget->findChild("entropyLabel"); - QLabel* strengthLabel = editEntryWidget->findChild("strengthLabel"); + auto* entropyLabel = editEntryWidget->findChild("entropyLabel"); + auto* strengthLabel = editEntryWidget->findChild("strengthLabel"); QCOMPARE(entropyLabel->text(), QString("Entropy: 77.55 bit")); QCOMPARE(strengthLabel->text(), QString("Password Quality: Good")); @@ -703,8 +730,8 @@ void TestGui::testDicewareEntryEntropy() void TestGui::testTotp() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); @@ -716,36 +743,36 @@ void TestGui::testTotp() triggerAction("actionEntrySetupTotp"); - TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild("TotpSetupDialog"); + auto* setupTotpDialog = m_dbWidget->findChild("TotpSetupDialog"); - Tools::wait(100); + QApplication::processEvents(); - QLineEdit* seedEdit = setupTotpDialog->findChild("seedEdit"); + auto* seedEdit = setupTotpDialog->findChild("seedEdit"); QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq"; QTest::keyClicks(seedEdit, exampleSeed); - QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); + auto* setupTotpButtonBox = setupTotpDialog->findChild("buttonBox"); QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); - QAction* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); + auto* entryEditAction = m_mainWindow->findChild("actionEntryEdit"); QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction); QTest::mouseClick(entryEditWidget, Qt::LeftButton); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); editEntryWidget->setCurrentPage(1); - QPlainTextEdit* attrTextEdit = editEntryWidget->findChild("attributesEdit"); + auto* attrTextEdit = editEntryWidget->findChild("attributesEdit"); QTest::mouseClick(editEntryWidget->findChild("revealAttributeButton"), Qt::LeftButton); QCOMPARE(attrTextEdit->toPlainText(), exampleSeed); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); triggerAction("actionEntryTotp"); - TotpDialog* totpDialog = m_dbWidget->findChild("TotpDialog"); - QLabel* totpLabel = totpDialog->findChild("totpLabel"); + auto* totpDialog = m_dbWidget->findChild("TotpDialog"); + auto* totpLabel = totpDialog->findChild("totpLabel"); QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp()); } @@ -755,16 +782,16 @@ void TestGui::testSearch() // Add canned entries for consistent testing Q_UNUSED(addCannedEntries()); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); - SearchWidget* searchWidget = toolBar->findChild("SearchWidget"); + auto* searchWidget = toolBar->findChild("SearchWidget"); QVERIFY(searchWidget->isEnabled()); - QLineEdit* searchTextEdit = searchWidget->findChild("searchEdit"); + auto* searchTextEdit = searchWidget->findChild("searchEdit"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QVERIFY(entryView->isVisible()); - QAction* clearButton = searchWidget->findChild("clearIcon"); + auto* clearButton = searchWidget->findChild("clearIcon"); QVERIFY(!clearButton->isVisible()); // Enter search @@ -801,7 +828,7 @@ void TestGui::testSearch() QTest::keyClick(searchTextEdit, Qt::Key_Down); QTRY_VERIFY(entryView->hasFocus()); // Restore focus and search text selection - QTest::keyClick(m_mainWindow, Qt::Key_F, Qt::ControlModifier); + QTest::keyClick(m_mainWindow.data(), Qt::Key_F, Qt::ControlModifier); QTRY_COMPARE(searchTextEdit->selectedText(), QString("someTHING")); // Ensure Down focuses on entry view when search text is selected QTest::keyClick(searchTextEdit, Qt::Key_Down); @@ -862,7 +889,7 @@ void TestGui::testSearch() QCOMPARE(entry->title(), origTitle.append("_edited")); // Cancel search, should return to normal view - QTest::keyClick(m_mainWindow, Qt::Key_Escape); + QTest::keyClick(m_mainWindow.data(), Qt::Key_Escape); QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); } @@ -871,10 +898,10 @@ void TestGui::testDeleteEntry() // Add canned entries for consistent testing Q_UNUSED(addCannedEntries()); - GroupView* groupView = m_dbWidget->findChild("groupView"); - EntryView* entryView = m_dbWidget->findChild("entryView"); - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - QAction* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); + auto* groupView = m_dbWidget->findChild("groupView"); + auto* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryDeleteAction = m_mainWindow->findChild("actionEntryDelete"); QWidget* entryDeleteWidget = toolBar->widgetForAction(entryDeleteAction); QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); @@ -934,7 +961,7 @@ void TestGui::testDeleteEntry() void TestGui::testCloneEntry() { - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* entryView = m_dbWidget->findChild("entryView"); QCOMPARE(entryView->model()->rowCount(), 1); @@ -944,8 +971,8 @@ void TestGui::testCloneEntry() triggerAction("actionEntryClone"); - CloneDialog* cloneDialog = m_dbWidget->findChild("CloneDialog"); - QDialogButtonBox* cloneButtonBox = cloneDialog->findChild("buttonBox"); + auto* cloneDialog = m_dbWidget->findChild("CloneDialog"); + auto* cloneButtonBox = cloneDialog->findChild("buttonBox"); QTest::mouseClick(cloneButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 2); @@ -956,11 +983,11 @@ void TestGui::testCloneEntry() void TestGui::testEntryPlaceholders() { - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); - EntryView* entryView = m_dbWidget->findChild("entryView"); + auto* toolBar = m_mainWindow->findChild("toolBar"); + auto* entryView = m_dbWidget->findChild("entryView"); // Find the new entry action - QAction* entryNewAction = m_mainWindow->findChild("actionEntryNew"); + auto* entryNewAction = m_mainWindow->findChild("actionEntryNew"); QVERIFY(entryNewAction->isEnabled()); // Find the button associated with the new entry action @@ -973,14 +1000,14 @@ void TestGui::testEntryPlaceholders() QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode); // Add entry "test" and confirm added - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); QTest::keyClicks(titleEdit, "test"); QLineEdit* usernameEdit = editEntryWidget->findChild("usernameEdit"); QTest::keyClicks(usernameEdit, "john"); QLineEdit* urlEdit = editEntryWidget->findChild("urlEdit"); QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); QCOMPARE(entryView->model()->rowCount(), 2); @@ -1000,8 +1027,8 @@ void TestGui::testEntryPlaceholders() void TestGui::testDragAndDropEntry() { - EntryView* entryView = m_dbWidget->findChild("entryView"); - GroupView* groupView = m_dbWidget->findChild("groupView"); + auto* entryView = m_dbWidget->findChild("entryView"); + auto* groupView = m_dbWidget->findChild("groupView"); QAbstractItemModel* groupModel = groupView->model(); QModelIndex sourceIndex = entryView->model()->index(0, 1); @@ -1029,11 +1056,7 @@ void TestGui::testDragAndDropGroup() // dropping parent on child is supposed to fail dragAndDropGroup(groupModel->index(0, 0, rootIndex), - groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), - -1, - false, - "NewDatabase", - 0); + groupModel->index(0, 0, groupModel->index(0, 0, rootIndex)), -1, false, "NewDatabase", 0); dragAndDropGroup(groupModel->index(1, 0, rootIndex), rootIndex, 0, true, "NewDatabase", 0); @@ -1063,6 +1086,7 @@ void TestGui::testSaveAs() fileInfo.refresh(); QCOMPARE(fileInfo.lastModified(), lastModified); + tmpFile.remove(); } void TestGui::testSave() @@ -1123,7 +1147,7 @@ void TestGui::testKeePass1Import() // Close the KeePass1 Database MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); + QApplication::processEvents(); } void TestGui::testDatabaseLocking() @@ -1135,13 +1159,13 @@ void TestGui::testDatabaseLocking() QCOMPARE(m_tabWidget->tabText(0).remove('&'), origDbName + " [locked]"); - QAction* actionDatabaseMerge = m_mainWindow->findChild("actionDatabaseMerge", Qt::FindChildrenRecursively); + auto* actionDatabaseMerge = m_mainWindow->findChild("actionDatabaseMerge", Qt::FindChildrenRecursively); QCOMPARE(actionDatabaseMerge->isEnabled(), false); - QAction* actionDatabaseSave = m_mainWindow->findChild("actionDatabaseSave", Qt::FindChildrenRecursively); + auto* actionDatabaseSave = m_mainWindow->findChild("actionDatabaseSave", Qt::FindChildrenRecursively); QCOMPARE(actionDatabaseSave->isEnabled(), false); QWidget* dbWidget = m_tabWidget->currentDatabaseWidget(); - QWidget* unlockDatabaseWidget = dbWidget->findChild("unlockDatabaseWidget"); + auto* unlockDatabaseWidget = dbWidget->findChild("unlockDatabaseWidget"); QWidget* editPassword = unlockDatabaseWidget->findChild("editPassword"); QVERIFY(editPassword); @@ -1162,11 +1186,11 @@ void TestGui::testDragAndDropKdbxFiles() QMimeData badMimeData; badMimeData.setUrls({QUrl::fromLocalFile(badDatabaseFilePath)}); QDragEnterEvent badDragEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &badDragEvent); + qApp->notify(m_mainWindow.data(), &badDragEvent); QCOMPARE(badDragEvent.isAccepted(), false); QDropEvent badDropEvent(QPoint(1, 1), Qt::LinkAction, &badMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &badDropEvent); + qApp->notify(m_mainWindow.data(), &badDropEvent); QCOMPARE(badDropEvent.isAccepted(), false); QCOMPARE(m_tabWidget->count(), openedDatabasesCount); @@ -1175,20 +1199,19 @@ void TestGui::testDragAndDropKdbxFiles() QMimeData goodMimeData; goodMimeData.setUrls({QUrl::fromLocalFile(goodDatabaseFilePath)}); QDragEnterEvent goodDragEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &goodDragEvent); + qApp->notify(m_mainWindow.data(), &goodDragEvent); QCOMPARE(goodDragEvent.isAccepted(), true); QDropEvent goodDropEvent(QPoint(1, 1), Qt::LinkAction, &goodMimeData, Qt::LeftButton, Qt::NoModifier); - qApp->notify(m_mainWindow, &goodDropEvent); + qApp->notify(m_mainWindow.data(), &goodDropEvent); QCOMPARE(goodDropEvent.isAccepted(), true); QCOMPARE(m_tabWidget->count(), openedDatabasesCount + 1); MessageBox::setNextAnswer(QMessageBox::No); triggerAction("actionDatabaseClose"); - Tools::wait(100); - QCOMPARE(m_tabWidget->count(), openedDatabasesCount); + QTRY_COMPARE(m_tabWidget->count(), openedDatabasesCount); } void TestGui::testTrayRestoreHide() @@ -1197,29 +1220,20 @@ void TestGui::testTrayRestoreHide() QSKIP("QSystemTrayIcon::isSystemTrayAvailable() = false, skipping tray restore/hide test..."); } - QSystemTrayIcon* trayIcon = m_mainWindow->findChild(); + auto* trayIcon = m_mainWindow->findChild(); QVERIFY(m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(!m_mainWindow->isVisible()); + QTRY_VERIFY(!m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(m_mainWindow->isVisible()); + QTRY_VERIFY(m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(!m_mainWindow->isVisible()); + QTRY_VERIFY(!m_mainWindow->isVisible()); trayIcon->activated(QSystemTrayIcon::Trigger); - Tools::wait(100); - QVERIFY(m_mainWindow->isVisible()); -} - -void TestGui::cleanupTestCase() -{ - delete m_mainWindow; + QTRY_VERIFY(m_mainWindow->isVisible()); } int TestGui::addCannedEntries() @@ -1227,17 +1241,17 @@ int TestGui::addCannedEntries() int entries_added = 0; // Find buttons - QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + auto* toolBar = m_mainWindow->findChild("toolBar"); QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild("actionEntryNew")); - EditEntryWidget* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); - QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); - QLineEdit* passwordEdit = editEntryWidget->findChild("passwordEdit"); - QLineEdit* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); + auto* editEntryWidget = m_dbWidget->findChild("editEntryWidget"); + auto* titleEdit = editEntryWidget->findChild("titleEdit"); + auto* passwordEdit = editEntryWidget->findChild("passwordEdit"); + auto* passwordRepeatEdit = editEntryWidget->findChild("passwordRepeatEdit"); // Add entry "test" and confirm added QTest::mouseClick(entryNewWidget, Qt::LeftButton); QTest::keyClicks(titleEdit, "test"); - QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + auto* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton); ++entries_added; @@ -1274,7 +1288,7 @@ void TestGui::checkDatabase(QString dbFileName) void TestGui::triggerAction(const QString& name) { - QAction* action = m_mainWindow->findChild(name); + auto* action = m_mainWindow->findChild(name); QVERIFY(action); QVERIFY(action->isEnabled()); action->trigger(); @@ -1312,5 +1326,3 @@ void TestGui::clickIndex(const QModelIndex& index, { QTest::mouseClick(view->viewport(), button, stateKey, view->visualRect(index).center()); } - -QTEST_MAIN(TestGui) diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index dc8a05e9b8..5e0b441b1d 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -19,17 +19,18 @@ #ifndef KEEPASSX_TESTGUI_H #define KEEPASSX_TESTGUI_H -#include "TemporaryFile.h" +#include "gui/MainWindow.h" #include #include #include +#include +#include class Database; class DatabaseTabWidget; class DatabaseWidget; class QAbstractItemView; -class MainWindow; class TestGui : public QObject { @@ -84,12 +85,12 @@ private slots: Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0); - QPointer m_mainWindow; + QScopedPointer m_mainWindow; QPointer m_tabWidget; QPointer m_dbWidget; QPointer m_db; QByteArray m_dbData; - TemporaryFile m_dbFile; + QScopedPointer m_dbFile; QString m_dbFileName; QString m_dbFilePath; }; From 108e4efc8ad280142653d2f6ce3a7e3a630ee2f2 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 19 Oct 2018 20:10:37 +0200 Subject: [PATCH 3/6] Fix tests on Windows --- tests/CMakeLists.txt | 2 +- tests/TestCli.cpp | 24 ++++++++-------- tests/TestCli.h | 9 +++--- tests/gui/CMakeLists.txt | 2 +- tests/gui/TestGui.cpp | 6 ++-- tests/gui/TestGui.h | 4 +-- tests/util/TemporaryFile.cpp | 55 ++++++++++++++++++++++++++++++++++++ tests/util/TemporaryFile.h | 50 ++++++++++++++++++++++++++++++++ 8 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 tests/util/TemporaryFile.cpp create mode 100644 tests/util/TemporaryFile.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 562b45e4d3..fc2df2f623 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -95,7 +95,7 @@ set(TEST_LIBRARIES ${ZLIB_LIBRARIES} ) -set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp) +set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp util/TemporaryFile.cpp) add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 02bc0ba3f5..fd51aa2e42 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -71,22 +71,22 @@ void TestCli::initTestCase() void TestCli::init() { - m_dbFile.reset(new QTemporaryFile()); + m_dbFile.reset(new TemporaryFile()); m_dbFile->open(); m_dbFile->write(m_dbData); - m_dbFile->flush(); + m_dbFile->close(); - m_stdinFile.reset(new QTemporaryFile()); + m_stdinFile.reset(new TemporaryFile()); m_stdinFile->open(); m_stdinHandle = fdopen(m_stdinFile->handle(), "r+"); Utils::STDIN = m_stdinHandle; - m_stdoutFile.reset(new QTemporaryFile()); + m_stdoutFile.reset(new TemporaryFile()); m_stdoutFile->open(); m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+"); Utils::STDOUT = m_stdoutHandle; - m_stderrFile.reset(new QTemporaryFile()); + m_stderrFile.reset(new TemporaryFile()); m_stderrFile->open(); m_stderrHandle = fdopen(m_stderrFile->handle(), "r+"); Utils::STDERR = m_stderrHandle; @@ -147,6 +147,7 @@ void TestCli::testAdd() Utils::setNextPassword("a"); addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"}); + m_stderrFile->reset(); auto db = readTestDatabase(); auto* entry = db->rootGroup()->findEntryByPath("/newuser-entry"); @@ -220,7 +221,7 @@ void TestCli::testDiceware() passphrase = m_stdoutFile->readLine(); QCOMPARE(passphrase.split(" ").size(), 10); - QTemporaryFile wordFile; + TemporaryFile wordFile; wordFile.open(); for (int i = 0; i < 4500; ++i) { wordFile.write(QString("word" + QString::number(i) + "\n").toLatin1()); @@ -548,7 +549,7 @@ void TestCli::testLocate() entry->setUuid(QUuid::createUuid()); entry->setTitle("New Entry"); group->addEntry(entry); - QTemporaryFile tmpFile; + TemporaryFile tmpFile; tmpFile.open(); Kdbx4Writer writer; writer.writeDatabase(&tmpFile, db.data()); @@ -581,13 +582,13 @@ void TestCli::testMerge() // load test database and save a copy auto db = readTestDatabase(); QVERIFY(db); - QTemporaryFile targetFile1; + TemporaryFile targetFile1; targetFile1.open(); writer.writeDatabase(&targetFile1, db.data()); targetFile1.close(); // save another copy with a different password - QTemporaryFile targetFile2; + TemporaryFile targetFile2; targetFile2.open(); auto oldKey = db->key(); auto key = QSharedPointer::create(); @@ -605,7 +606,7 @@ void TestCli::testMerge() auto* group = db->rootGroup()->findGroupByPath("/Internet/"); QVERIFY(group); group->addEntry(entry); - QTemporaryFile sourceFile; + TemporaryFile sourceFile; sourceFile.open(); writer.writeDatabase(&sourceFile, db.data()); sourceFile.close(); @@ -615,6 +616,7 @@ void TestCli::testMerge() mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); + m_stderrFile->reset(); QCOMPARE(m_stdoutFile->readAll(), QByteArray("Successfully merged the database files.\n")); QFile readBack(targetFile1.fileName()); @@ -660,7 +662,7 @@ void TestCli::testRemove() // load test database and save a copy with disabled recycle bin auto db = readTestDatabase(); QVERIFY(db); - QTemporaryFile fileCopy; + TemporaryFile fileCopy; fileCopy.open(); db->metadata()->setRecycleBinEnabled(false); writer.writeDatabase(&fileCopy, db.data()); diff --git a/tests/TestCli.h b/tests/TestCli.h index 532d84a793..691269840e 100644 --- a/tests/TestCli.h +++ b/tests/TestCli.h @@ -19,6 +19,7 @@ #define KEEPASSXC_TESTCLI_H #include "core/Database.h" +#include "util/TemporaryFile.h" #include #include @@ -57,10 +58,10 @@ private slots: private: QByteArray m_dbData; - QScopedPointer m_dbFile; - QScopedPointer m_stdoutFile; - QScopedPointer m_stderrFile; - QScopedPointer m_stdinFile; + QScopedPointer m_dbFile; + QScopedPointer m_stdoutFile; + QScopedPointer m_stderrFile; + QScopedPointer m_stdinFile; FILE* m_stdoutHandle = stdout; FILE* m_stderrHandle = stderr; FILE* m_stdinHandle = stdin; diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index a6e876f475..8542e58cbb 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -15,6 +15,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) -add_unit_test(NAME testgui SOURCES TestGui.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testgui SOURCES TestGui.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index ee53eb7777..450f09474c 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -102,7 +102,7 @@ void TestGui::initTestCase() // Every test starts with opening the temp database void TestGui::init() { - m_dbFile.reset(new QTemporaryFile()); + m_dbFile.reset(new TemporaryFile()); // Write the temp storage to a temp database file for use in our tests QVERIFY(m_dbFile->open()); QCOMPARE(m_dbFile->write(m_dbData), static_cast((m_dbData.size()))); @@ -289,7 +289,7 @@ void TestGui::createDatabaseCallback() QCOMPARE(fileCombo->currentText(), QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key")); // save database to temporary file - QTemporaryFile tmpFile; + TemporaryFile tmpFile; QVERIFY(tmpFile.open()); tmpFile.close(); fileDialog()->setNextFileName(tmpFile.fileName()); @@ -1071,7 +1071,7 @@ void TestGui::testSaveAs() m_db->metadata()->setName("testSaveAs"); // open temporary file so it creates a filename - QTemporaryFile tmpFile; + TemporaryFile tmpFile; QVERIFY(tmpFile.open()); QString tmpFileName = tmpFile.fileName(); tmpFile.remove(); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 5e0b441b1d..4df606f4a2 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -20,12 +20,12 @@ #define KEEPASSX_TESTGUI_H #include "gui/MainWindow.h" +#include "util/TemporaryFile.h" #include #include #include #include -#include class Database; class DatabaseTabWidget; @@ -90,7 +90,7 @@ private slots: QPointer m_dbWidget; QPointer m_db; QByteArray m_dbData; - QScopedPointer m_dbFile; + QScopedPointer m_dbFile; QString m_dbFileName; QString m_dbFilePath; }; diff --git a/tests/util/TemporaryFile.cpp b/tests/util/TemporaryFile.cpp new file mode 100644 index 0000000000..476313b02e --- /dev/null +++ b/tests/util/TemporaryFile.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#include "TemporaryFile.h" + +#ifdef Q_OS_WIN + +TemporaryFile::TemporaryFile() + : TemporaryFile(nullptr) +{ +} + +TemporaryFile::TemporaryFile(const QString& templateName) + : TemporaryFile(templateName, nullptr) +{ +} + +TemporaryFile::TemporaryFile(QObject* parent) + : QFile(parent) +{ + QTemporaryFile tmp; + tmp.open(); + QFile::setFileName(tmp.fileName()); + tmp.close(); +} + +TemporaryFile::TemporaryFile(const QString& templateName, QObject* parent) + : QFile(parent) +{ + QTemporaryFile tmp(templateName); + tmp.open(); + QFile::setFileName(tmp.fileName()); + tmp.close(); +} + +bool TemporaryFile::open() +{ + return QFile::open(QIODevice::ReadWrite); +} + +#endif diff --git a/tests/util/TemporaryFile.h b/tests/util/TemporaryFile.h new file mode 100644 index 0000000000..4e39a9ae70 --- /dev/null +++ b/tests/util/TemporaryFile.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 . + */ + +#ifndef KEEPASSXC_TEMPORARYFILE_H +#define KEEPASSXC_TEMPORARYFILE_H + +#include + +#ifdef Q_OS_WIN +/** + * QTemporaryFile does not actually close a file when close() is + * called, which causes the file to be locked on Windows. + * This class extends a QFile with the extra functionality + * of a QTemporaryFile to circumvent this problem. + */ +class TemporaryFile : public QFile +#else +class TemporaryFile : public QTemporaryFile +#endif +{ + Q_OBJECT + +#ifdef Q_OS_WIN +public: + TemporaryFile(); + explicit TemporaryFile(const QString& templateName); + explicit TemporaryFile(QObject* parent); + TemporaryFile(const QString& templateName, QObject* parent); + ~TemporaryFile() override = default; + + using QFile::open; + bool open(); +#endif +}; + +#endif //KEEPASSXC_TEMPORARYFILE_H From bea31f9bcc42010f6bbb72449292fb95f171cad7 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 19 Oct 2018 20:22:28 +0200 Subject: [PATCH 4/6] Rename TestClock to MockClock and move it to the mock directory --- tests/CMakeLists.txt | 2 +- tests/TestGroup.cpp | 10 ++--- tests/TestKeePass2Format.cpp | 42 +++++++++---------- tests/TestMerge.cpp | 10 ++--- tests/TestModified.cpp | 10 ++--- .../TestClock.cpp => mock/MockClock.cpp} | 26 ++++++------ tests/{stub/TestClock.h => mock/MockClock.h} | 6 +-- 7 files changed, 53 insertions(+), 53 deletions(-) rename tests/{stub/TestClock.cpp => mock/MockClock.cpp} (71%) rename tests/{stub/TestClock.h => mock/MockClock.h} (89%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc2df2f623..96f6841719 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -95,7 +95,7 @@ set(TEST_LIBRARIES ${ZLIB_LIBRARIES} ) -set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp util/TemporaryFile.cpp) +set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp mock/MockClock.cpp util/TemporaryFile.cpp) add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index e97f7ac258..9ee9389c07 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -18,7 +18,7 @@ #include "TestGroup.h" #include "TestGlobal.h" -#include "stub/TestClock.h" +#include "mock/MockClock.h" #include @@ -29,7 +29,7 @@ QTEST_GUILESS_MAIN(TestGroup) namespace { - TestClock* m_clock = nullptr; + MockClock* m_clock = nullptr; } void TestGroup::initTestCase() @@ -42,13 +42,13 @@ void TestGroup::initTestCase() void TestGroup::init() { Q_ASSERT(m_clock == nullptr); - m_clock = new TestClock(2010, 5, 5, 10, 30, 10); - TestClock::setup(m_clock); + m_clock = new MockClock(2010, 5, 5, 10, 30, 10); + MockClock::setup(m_clock); } void TestGroup::cleanup() { - TestClock::teardown(); + MockClock::teardown(); m_clock = nullptr; } diff --git a/tests/TestKeePass2Format.cpp b/tests/TestKeePass2Format.cpp index 201c4a64a6..c2c3f75d37 100644 --- a/tests/TestKeePass2Format.cpp +++ b/tests/TestKeePass2Format.cpp @@ -17,7 +17,7 @@ #include "TestKeePass2Format.h" #include "TestGlobal.h" -#include "stub/TestClock.h" +#include "mock/MockClock.h" #include "core/Metadata.h" #include "crypto/Crypto.h" @@ -78,14 +78,14 @@ void TestKeePass2Format::testXmlMetadata() { QCOMPARE(m_xmlDb->metadata()->generator(), QString("KeePass")); QCOMPARE(m_xmlDb->metadata()->name(), QString("ANAME")); - QCOMPARE(m_xmlDb->metadata()->nameChanged(), TestClock::datetimeUtc(2010, 8, 8, 17, 24, 53)); + QCOMPARE(m_xmlDb->metadata()->nameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 53)); QCOMPARE(m_xmlDb->metadata()->description(), QString("ADESC")); - QCOMPARE(m_xmlDb->metadata()->descriptionChanged(), TestClock::datetimeUtc(2010, 8, 8, 17, 27, 12)); + QCOMPARE(m_xmlDb->metadata()->descriptionChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 12)); QCOMPARE(m_xmlDb->metadata()->defaultUserName(), QString("DEFUSERNAME")); - QCOMPARE(m_xmlDb->metadata()->defaultUserNameChanged(), TestClock::datetimeUtc(2010, 8, 8, 17, 27, 45)); + QCOMPARE(m_xmlDb->metadata()->defaultUserNameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 45)); QCOMPARE(m_xmlDb->metadata()->maintenanceHistoryDays(), 127); QCOMPARE(m_xmlDb->metadata()->color(), QColor(0xff, 0xef, 0x00)); - QCOMPARE(m_xmlDb->metadata()->masterKeyChanged(), TestClock::datetimeUtc(2012, 4, 5, 17, 9, 34)); + QCOMPARE(m_xmlDb->metadata()->masterKeyChanged(), MockClock::datetimeUtc(2012, 4, 5, 17, 9, 34)); QCOMPARE(m_xmlDb->metadata()->masterKeyChangeRec(), 101); QCOMPARE(m_xmlDb->metadata()->masterKeyChangeForce(), -1); QCOMPARE(m_xmlDb->metadata()->protectTitle(), false); @@ -96,9 +96,9 @@ void TestKeePass2Format::testXmlMetadata() QCOMPARE(m_xmlDb->metadata()->recycleBinEnabled(), true); QVERIFY(m_xmlDb->metadata()->recycleBin() != nullptr); QCOMPARE(m_xmlDb->metadata()->recycleBin()->name(), QString("Recycle Bin")); - QCOMPARE(m_xmlDb->metadata()->recycleBinChanged(), TestClock::datetimeUtc(2010, 8, 25, 16, 12, 57)); + QCOMPARE(m_xmlDb->metadata()->recycleBinChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57)); QVERIFY(m_xmlDb->metadata()->entryTemplatesGroup() == nullptr); - QCOMPARE(m_xmlDb->metadata()->entryTemplatesGroupChanged(), TestClock::datetimeUtc(2010, 8, 8, 17, 24, 19)); + QCOMPARE(m_xmlDb->metadata()->entryTemplatesGroupChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 19)); QVERIFY(m_xmlDb->metadata()->lastSelectedGroup() != nullptr); QCOMPARE(m_xmlDb->metadata()->lastSelectedGroup()->name(), QString("NewDatabase")); QVERIFY(m_xmlDb->metadata()->lastTopVisibleGroup() == m_xmlDb->metadata()->lastSelectedGroup()); @@ -136,13 +136,13 @@ void TestKeePass2Format::testXmlGroupRoot() QCOMPARE(group->iconUuid(), QUuid()); QVERIFY(group->isExpanded()); TimeInfo ti = group->timeInfo(); - QCOMPARE(ti.lastModificationTime(), TestClock::datetimeUtc(2010, 8, 8, 17, 24, 27)); - QCOMPARE(ti.creationTime(), TestClock::datetimeUtc(2010, 8, 7, 17, 24, 27)); - QCOMPARE(ti.lastAccessTime(), TestClock::datetimeUtc(2010, 8, 9, 9, 9, 44)); - QCOMPARE(ti.expiryTime(), TestClock::datetimeUtc(2010, 8, 8, 17, 24, 17)); + QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27)); + QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 7, 17, 24, 27)); + QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 9, 9, 9, 44)); + QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 17)); QVERIFY(!ti.expires()); QCOMPARE(ti.usageCount(), 52); - QCOMPARE(ti.locationChanged(), TestClock::datetimeUtc(2010, 8, 8, 17, 24, 27)); + QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27)); QCOMPARE(group->defaultAutoTypeSequence(), QString("")); QCOMPARE(group->autoTypeEnabled(), Group::Inherit); QCOMPARE(group->searchingEnabled(), Group::Inherit); @@ -203,13 +203,13 @@ void TestKeePass2Format::testXmlEntry1() QCOMPARE(entry->tags(), QString("a b c")); const TimeInfo ti = entry->timeInfo(); - QCOMPARE(ti.lastModificationTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 19, 25)); - QCOMPARE(ti.creationTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); - QCOMPARE(ti.lastAccessTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 19, 25)); - QCOMPARE(ti.expiryTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 12, 57)); + QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25)); + QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); + QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25)); + QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57)); QVERIFY(!ti.expires()); QCOMPARE(ti.usageCount(), 8); - QCOMPARE(ti.locationChanged(), TestClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); + QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); QList attrs = entry->attributes()->keys(); QCOMPARE(entry->attributes()->value("Notes"), QString("Notes")); @@ -308,7 +308,7 @@ void TestKeePass2Format::testXmlEntryHistory() const Entry* entry = entryMain->historyItems().at(0); QCOMPARE(entry->uuid(), entryMain->uuid()); QVERIFY(!entry->parent()); - QCOMPARE(entry->timeInfo().lastModificationTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); + QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54)); QCOMPARE(entry->timeInfo().usageCount(), 3); QCOMPARE(entry->title(), QString("Sample Entry")); QCOMPARE(entry->url(), QString("http://www.somesite.com/")); @@ -318,7 +318,7 @@ void TestKeePass2Format::testXmlEntryHistory() const Entry* entry = entryMain->historyItems().at(1); QCOMPARE(entry->uuid(), entryMain->uuid()); QVERIFY(!entry->parent()); - QCOMPARE(entry->timeInfo().lastModificationTime(), TestClock::datetimeUtc(2010, 8, 25, 16, 15, 43)); + QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 15, 43)); QCOMPARE(entry->timeInfo().usageCount(), 7); QCOMPARE(entry->title(), QString("Sample Entry 1")); QCOMPARE(entry->url(), QString("http://www.somesite.com/")); @@ -332,11 +332,11 @@ void TestKeePass2Format::testXmlDeletedObjects() delObj = objList.takeFirst(); QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("5K/bzWCSmkCv5OZxYl4N/w=="))); - QCOMPARE(delObj.deletionTime, TestClock::datetimeUtc(2010, 8, 25, 16, 14, 12)); + QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 12)); delObj = objList.takeFirst(); QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("80h8uSNWgkKhKCp1TgXF7g=="))); - QCOMPARE(delObj.deletionTime, TestClock::datetimeUtc(2010, 8, 25, 16, 14, 14)); + QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 14)); QVERIFY(objList.isEmpty()); } diff --git a/tests/TestMerge.cpp b/tests/TestMerge.cpp index 0da304f071..4a50817770 100644 --- a/tests/TestMerge.cpp +++ b/tests/TestMerge.cpp @@ -17,7 +17,7 @@ #include "TestMerge.h" #include "TestGlobal.h" -#include "stub/TestClock.h" +#include "mock/MockClock.h" #include "core/Merger.h" #include "core/Metadata.h" @@ -34,7 +34,7 @@ namespace return timeInfo; } - TestClock* m_clock = nullptr; + MockClock* m_clock = nullptr; } void TestMerge::initTestCase() @@ -47,13 +47,13 @@ void TestMerge::initTestCase() void TestMerge::init() { Q_ASSERT(m_clock == nullptr); - m_clock = new TestClock(2010, 5, 5, 10, 30, 10); - TestClock::setup(m_clock); + m_clock = new MockClock(2010, 5, 5, 10, 30, 10); + MockClock::setup(m_clock); } void TestMerge::cleanup() { - TestClock::teardown(); + MockClock::teardown(); m_clock = nullptr; } diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index b1ad09443b..63013fd5ea 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -16,7 +16,7 @@ */ #include "TestModified.h" -#include "stub/TestClock.h" +#include "mock/MockClock.h" #include #include @@ -31,7 +31,7 @@ QTEST_GUILESS_MAIN(TestModified) namespace { - TestClock* m_clock = nullptr; + MockClock* m_clock = nullptr; } void TestModified::initTestCase() @@ -42,13 +42,13 @@ void TestModified::initTestCase() void TestModified::init() { Q_ASSERT(m_clock == nullptr); - m_clock = new TestClock(2010, 5, 5, 10, 30, 10); - TestClock::setup(m_clock); + m_clock = new MockClock(2010, 5, 5, 10, 30, 10); + MockClock::setup(m_clock); } void TestModified::cleanup() { - TestClock::teardown(); + MockClock::teardown(); m_clock = nullptr; } diff --git a/tests/stub/TestClock.cpp b/tests/mock/MockClock.cpp similarity index 71% rename from tests/stub/TestClock.cpp rename to tests/mock/MockClock.cpp index d3222febdb..36873cd693 100644 --- a/tests/stub/TestClock.cpp +++ b/tests/mock/MockClock.cpp @@ -15,72 +15,72 @@ * along with this program. If not, see . */ -#include "TestClock.h" +#include "MockClock.h" -TestClock::TestClock(int year, int month, int day, int hour, int min, int second) +MockClock::MockClock(int year, int month, int day, int hour, int min, int second) : Clock() , m_utcCurrent(datetimeUtc(year, month, day, hour, min, second)) { } -TestClock::TestClock(QDateTime utcBase) +MockClock::MockClock(QDateTime utcBase) : Clock() , m_utcCurrent(utcBase) { } -const QDateTime& TestClock::advanceSecond(int seconds) +const QDateTime& MockClock::advanceSecond(int seconds) { m_utcCurrent = m_utcCurrent.addSecs(seconds); return m_utcCurrent; } -const QDateTime& TestClock::advanceMinute(int minutes) +const QDateTime& MockClock::advanceMinute(int minutes) { m_utcCurrent = m_utcCurrent.addSecs(minutes * 60); return m_utcCurrent; } -const QDateTime& TestClock::advanceHour(int hours) +const QDateTime& MockClock::advanceHour(int hours) { m_utcCurrent = m_utcCurrent.addSecs(hours * 60 * 60); return m_utcCurrent; } -const QDateTime& TestClock::advanceDay(int days) +const QDateTime& MockClock::advanceDay(int days) { m_utcCurrent = m_utcCurrent.addDays(days); return m_utcCurrent; } -const QDateTime& TestClock::advanceMonth(int months) +const QDateTime& MockClock::advanceMonth(int months) { m_utcCurrent = m_utcCurrent.addMonths(months); return m_utcCurrent; } -const QDateTime& TestClock::advanceYear(int years) +const QDateTime& MockClock::advanceYear(int years) { m_utcCurrent = m_utcCurrent.addYears(years); return m_utcCurrent; } -void TestClock::setup(Clock* clock) +void MockClock::setup(Clock* clock) { Clock::setInstance(clock); } -void TestClock::teardown() +void MockClock::teardown() { Clock::resetInstance(); } -QDateTime TestClock::currentDateTimeUtcImpl() const +QDateTime MockClock::currentDateTimeUtcImpl() const { return m_utcCurrent; } -QDateTime TestClock::currentDateTimeImpl() const +QDateTime MockClock::currentDateTimeImpl() const { return m_utcCurrent.toLocalTime(); } diff --git a/tests/stub/TestClock.h b/tests/mock/MockClock.h similarity index 89% rename from tests/stub/TestClock.h rename to tests/mock/MockClock.h index 02405edcb1..4bc61b70ad 100644 --- a/tests/stub/TestClock.h +++ b/tests/mock/MockClock.h @@ -22,12 +22,12 @@ #include -class TestClock : public Clock +class MockClock : public Clock { public: - TestClock(int year, int month, int day, int hour, int min, int second); + MockClock(int year, int month, int day, int hour, int min, int second); - TestClock(QDateTime utcBase = QDateTime::currentDateTimeUtc()); + MockClock(QDateTime utcBase = QDateTime::currentDateTimeUtc()); const QDateTime& advanceSecond(int seconds); const QDateTime& advanceMinute(int minutes); From 0ca7fd369aaa364ad437de694714b9488e5e5636 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 19 Oct 2018 21:41:42 +0200 Subject: [PATCH 5/6] Implement review feedback --- src/browser/BrowserAction.cpp | 2 +- src/cli/Remove.cpp | 5 +-- src/cli/Utils.cpp | 7 ++-- src/cli/Utils.h | 6 +++- src/cli/keepassxc-cli.cpp | 4 +-- src/config-keepassx.h.cmake | 2 +- src/core/Tools.cpp | 15 --------- src/core/Tools.h | 1 - src/gui/AboutDialog.cpp | 4 +-- src/gui/DatabaseWidget.cpp | 8 +++-- src/gui/WelcomeWidget.cpp | 2 +- src/gui/group/GroupModel.cpp | 6 ++-- src/main.cpp | 2 +- tests/TestCli.cpp | 60 +++++++++++++++++------------------ 14 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 6001acb186..e0c3a85016 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -376,7 +376,7 @@ QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorC QJsonObject BrowserAction::buildMessage(const QString& nonce) const { QJsonObject message; - message["version"] = KEEPASSX_VERSION; + message["version"] = KEEPASSXC_VERSION; message["success"] = "true"; message["nonce"] = nonce; return message; diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index 5bbfd67e43..6523cff97d 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -76,7 +76,7 @@ int Remove::removeEntry(Database* database, const QString& databasePath, const Q QTextStream out(Utils::STDOUT, QIODevice::WriteOnly); QTextStream err(Utils::STDERR, QIODevice::WriteOnly); - Entry* entry = database->rootGroup()->findEntryByPath(entryPath); + QPointer entry = database->rootGroup()->findEntryByPath(entryPath); if (!entry) { err << QObject::tr("Entry %1 not found.").arg(entryPath) << endl; return EXIT_FAILURE; @@ -84,7 +84,8 @@ int Remove::removeEntry(Database* database, const QString& databasePath, const Q QString entryTitle = entry->title(); bool recycled = true; - if (Tools::hasChild(database->metadata()->recycleBin(), entry) || !database->metadata()->recycleBinEnabled()) { + auto* recycleBin = database->metadata()->recycleBin(); + if (!database->metadata()->recycleBinEnabled() || (recycleBin && recycleBin->findEntryByUuid(entry->uuid()))) { delete entry; recycled = false; } else { diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index 8a0f5abe3f..a0f75bc8e3 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -72,6 +72,8 @@ void setStdinEcho(bool enable = true) #endif } +namespace Test +{ QStringList nextPasswords = {}; /** @@ -85,6 +87,7 @@ void setNextPassword(const QString& password) { nextPasswords.append(password); } +} // namespace Test /** * Read a user password from STDIN or return a password previously @@ -97,8 +100,8 @@ QString getPassword() QTextStream out(STDOUT, QIODevice::WriteOnly); // return preset password if one is set - if (!nextPasswords.isEmpty()) { - auto password = nextPasswords.takeFirst(); + if (!Test::nextPasswords.isEmpty()) { + auto password = Test::nextPasswords.takeFirst(); // simulate user entering newline out << endl; return password; diff --git a/src/cli/Utils.h b/src/cli/Utils.h index 868ccdef5b..1d5e0f356e 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -29,8 +29,12 @@ extern FILE* STDIN; void setStdinEcho(bool enable); QString getPassword(); -void setNextPassword(const QString& password); int clipText(const QString& text); + +namespace Test +{ +void setNextPassword(const QString& password); +} }; #endif // KEEPASSXC_UTILS_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 97efd8c08f..0419086638 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -40,7 +40,7 @@ int main(int argc, char** argv) } QCoreApplication app(argc, argv); - QCoreApplication::setApplicationVersion(KEEPASSX_VERSION); + QCoreApplication::setApplicationVersion(KEEPASSXC_VERSION); #ifdef QT_NO_DEBUG Bootstrap::bootstrapApplication(); @@ -72,7 +72,7 @@ int main(int argc, char** argv) if (parser.positionalArguments().empty()) { if (parser.isSet("version")) { // Switch to parser.showVersion() when available (QT 5.4). - out << KEEPASSX_VERSION << endl; + out << KEEPASSXC_VERSION << endl; return EXIT_SUCCESS; } parser.showHelp(); diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake index d1f0723b42..52d14ce949 100644 --- a/src/config-keepassx.h.cmake +++ b/src/config-keepassx.h.cmake @@ -3,7 +3,7 @@ #ifndef KEEPASSX_CONFIG_KEEPASSX_H #define KEEPASSX_CONFIG_KEEPASSX_H -#define KEEPASSX_VERSION "@KEEPASSXC_VERSION@" +#define KEEPASSXC_VERSION "@KEEPASSXC_VERSION@" #define KEEPASSX_SOURCE_DIR "@CMAKE_SOURCE_DIR@" #define KEEPASSX_BINARY_DIR "@CMAKE_BINARY_DIR@" diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 60c50b4516..8467fb4163 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -77,21 +77,6 @@ QString humanReadableFileSize(qint64 bytes, quint32 precision) return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); } -bool hasChild(const QObject* parent, const QObject* child) -{ - if (!parent || !child) { - return false; - } - - const QObjectList children = parent->children(); - for (QObject* c : children) { - if (child == c || hasChild(c, child)) { - return true; - } - } - return false; -} - bool readFromDevice(QIODevice* device, QByteArray& data, int size) { QByteArray buffer; diff --git a/src/core/Tools.h b/src/core/Tools.h index aec9373047..13d9869f7d 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -31,7 +31,6 @@ class QIODevice; namespace Tools { QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); -bool hasChild(const QObject* parent, const QObject* child); bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); bool readAllFromDevice(QIODevice* device, QByteArray& data); QString imageReaderFilter(); diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index c7c75a11eb..f6a9d15f9d 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -37,7 +37,7 @@ AboutDialog::AboutDialog(QWidget* parent) setWindowFlags(Qt::Sheet); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - m_ui->nameLabel->setText(m_ui->nameLabel->text().replace("${VERSION}", KEEPASSX_VERSION)); + m_ui->nameLabel->setText(m_ui->nameLabel->text().replace("${VERSION}", KEEPASSXC_VERSION)); QFont nameLabelFont = m_ui->nameLabel->font(); nameLabelFont.setPointSize(nameLabelFont.pointSize() + 4); m_ui->nameLabel->setFont(nameLabelFont); @@ -52,7 +52,7 @@ AboutDialog::AboutDialog(QWidget* parent) } QString debugInfo = "KeePassXC - "; - debugInfo.append(tr("Version %1").arg(KEEPASSX_VERSION).append("\n")); + debugInfo.append(tr("Version %1").arg(KEEPASSXC_VERSION).append("\n")); #ifndef KEEPASSXC_BUILD_TYPE_RELEASE debugInfo.append(tr("Build Type: %1").arg(KEEPASSXC_BUILD_TYPE).append("\n")); #endif diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index c90fc52f79..effae45c19 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -460,7 +460,8 @@ void DatabaseWidget::deleteEntries() selectedEntries.append(m_entryView->entryFromIndex(index)); } - bool inRecycleBin = Tools::hasChild(m_db->metadata()->recycleBin(), selectedEntries.first()); + auto* recycleBin = m_db->metadata()->recycleBin(); + bool inRecycleBin = recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid()); if (inRecycleBin || !m_db->metadata()->recycleBinEnabled()) { QString prompt; if (selected.size() == 1) { @@ -688,9 +689,10 @@ void DatabaseWidget::deleteGroup() return; } - bool inRecycleBin = Tools::hasChild(m_db->metadata()->recycleBin(), currentGroup); + auto* recycleBin = m_db->metadata()->recycleBin(); + bool inRecycleBin = recycleBin && recycleBin->findGroupByUuid(currentGroup->uuid()); bool isRecycleBin = (currentGroup == m_db->metadata()->recycleBin()); - bool isRecycleBinSubgroup = Tools::hasChild(currentGroup, m_db->metadata()->recycleBin()); + bool isRecycleBinSubgroup = currentGroup->findGroupByUuid(m_db->metadata()->recycleBin()->uuid()); if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) { QMessageBox::StandardButton result = MessageBox::question( this, diff --git a/src/gui/WelcomeWidget.cpp b/src/gui/WelcomeWidget.cpp index 7bb4484c7a..ed0ca2936a 100644 --- a/src/gui/WelcomeWidget.cpp +++ b/src/gui/WelcomeWidget.cpp @@ -29,7 +29,7 @@ WelcomeWidget::WelcomeWidget(QWidget* parent) { m_ui->setupUi(this); - m_ui->welcomeLabel->setText(tr("Welcome to KeePassXC %1").arg(KEEPASSX_VERSION)); + m_ui->welcomeLabel->setText(tr("Welcome to KeePassXC %1").arg(KEEPASSXC_VERSION)); QFont welcomeLabelFont = m_ui->welcomeLabel->font(); welcomeLabelFont.setBold(true); welcomeLabelFont.setPointSize(welcomeLabelFont.pointSize() + 4); diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index e8f51909f4..a3c72b7924 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -234,11 +234,11 @@ bool GroupModel::dropMimeData(const QMimeData* data, } Group* dragGroup = db->resolveGroup(groupUuid); - if (!dragGroup || !Tools::hasChild(db, dragGroup) || dragGroup == db->rootGroup()) { + if (!dragGroup || !db->rootGroup()->findGroupByUuid(dragGroup->uuid()) || dragGroup == db->rootGroup()) { return false; } - if (dragGroup == parentGroup || Tools::hasChild(dragGroup, parentGroup)) { + if (dragGroup == parentGroup || dragGroup->findGroupByUuid(parentGroup->uuid())) { return false; } @@ -278,7 +278,7 @@ bool GroupModel::dropMimeData(const QMimeData* data, } Entry* dragEntry = db->resolveEntry(entryUuid); - if (!dragEntry || !Tools::hasChild(db, dragEntry)) { + if (!dragEntry || !db->rootGroup()->findEntryByUuid(dragEntry->uuid())) { continue; } diff --git a/src/main.cpp b/src/main.cpp index 74af06953c..a37648ec1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char** argv) { Application app(argc, argv); Application::setApplicationName("keepassxc"); - Application::setApplicationVersion(KEEPASSX_VERSION); + Application::setApplicationVersion(KEEPASSXC_VERSION); // don't set organizationName as that changes the return value of // QStandardPaths::writableLocation(QDesktopServices::DataLocation) Bootstrap::bootstrapApplication(); diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index fd51aa2e42..e10a651d77 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -115,7 +115,7 @@ void TestCli::cleanupTestCase() QSharedPointer TestCli::readTestDatabase() const { - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); auto db = QSharedPointer(Database::unlockFromStdin(m_dbFile->fileName(), "", m_stdoutHandle)); m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles return db; @@ -145,7 +145,7 @@ void TestCli::testAdd() QVERIFY(!addCmd.name.isEmpty()); QVERIFY(addCmd.getDescriptionLine().contains(addCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); addCmd.execute({"add", "-u", "newuser", "--url", "https://example.com/", "-g", "-l", "20", m_dbFile->fileName(), "/newuser-entry"}); m_stderrFile->reset(); @@ -156,8 +156,8 @@ void TestCli::testAdd() QCOMPARE(entry->url(), QString("https://example.com/")); QCOMPARE(entry->password().size(), 20); - Utils::setNextPassword("a"); - Utils::setNextPassword("newpassword"); + Utils::Test::setNextPassword("a"); + Utils::Test::setNextPassword("newpassword"); addCmd.execute({"add", "-u", "newuser2", "--url", "https://example.net/", "-g", "-l", "20", "-p", m_dbFile->fileName(), "/newuser-entry2"}); db = readTestDatabase(); @@ -177,7 +177,7 @@ void TestCli::testClip() QVERIFY(!clipCmd.name.isEmpty()); QVERIFY(clipCmd.getDescriptionLine().contains(clipCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); clipCmd.execute({"clip", m_dbFile->fileName(), "/Sample Entry"}); m_stderrFile->reset(); @@ -190,7 +190,7 @@ void TestCli::testClip() QCOMPARE(clipboard->text(), QString("Password")); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); QFuture future = QtConcurrent::run(&clipCmd, &Clip::execute, QStringList{"clip", m_dbFile->fileName(), "/Sample Entry", "1"}); QTRY_COMPARE_WITH_TIMEOUT(clipboard->text(), QString("Password"), 500); @@ -246,7 +246,7 @@ void TestCli::testEdit() QVERIFY(!editCmd.name.isEmpty()); QVERIFY(editCmd.getDescriptionLine().contains(editCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); editCmd.execute({"edit", "-u", "newuser", "--url", "https://otherurl.example.com/", "-t", "newtitle", m_dbFile->fileName(), "/Sample Entry"}); auto db = readTestDatabase(); @@ -256,7 +256,7 @@ void TestCli::testEdit() QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); QCOMPARE(entry->password(), QString("Password")); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); editCmd.execute({"edit", "-g", m_dbFile->fileName(), "/newtitle"}); db = readTestDatabase(); entry = db->rootGroup()->findEntryByPath("/newtitle"); @@ -266,7 +266,7 @@ void TestCli::testEdit() QVERIFY(!entry->password().isEmpty()); QVERIFY(entry->password() != QString("Password")); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); editCmd.execute({"edit", "-g", "-l", "34", "-t", "yet another title", m_dbFile->fileName(), "/newtitle"}); db = readTestDatabase(); entry = db->rootGroup()->findEntryByPath("/yet another title"); @@ -276,8 +276,8 @@ void TestCli::testEdit() QVERIFY(entry->password() != QString("Password")); QCOMPARE(entry->password().size(), 34); - Utils::setNextPassword("a"); - Utils::setNextPassword("newpassword"); + Utils::Test::setNextPassword("a"); + Utils::Test::setNextPassword("newpassword"); editCmd.execute({"edit", "-p", m_dbFile->fileName(), "/yet another title"}); db = readTestDatabase(); entry = db->rootGroup()->findEntryByPath("/yet another title"); @@ -392,7 +392,7 @@ void TestCli::testExtract() QVERIFY(!extractCmd.name.isEmpty()); QVERIFY(extractCmd.getDescriptionLine().contains(extractCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); extractCmd.execute({"extract", m_dbFile->fileName()}); m_stdoutFile->seek(0); @@ -471,7 +471,7 @@ void TestCli::testList() QVERIFY(!listCmd.name.isEmpty()); QVERIFY(listCmd.getDescriptionLine().contains(listCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); listCmd.execute({"ls", m_dbFile->fileName()}); m_stdoutFile->reset(); m_stdoutFile->readLine(); // skip password prompt @@ -484,7 +484,7 @@ void TestCli::testList() "Homebanking/\n")); qint64 pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); listCmd.execute({"ls", "-R", m_dbFile->fileName()}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -503,14 +503,14 @@ void TestCli::testList() " [empty]\n")); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); listCmd.execute({"ls", m_dbFile->fileName(), "/General/"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); QCOMPARE(m_stdoutFile->readAll(), QByteArray("[empty]\n")); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); listCmd.execute({"ls", m_dbFile->fileName(), "/DoesNotExist/"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -525,14 +525,14 @@ void TestCli::testLocate() QVERIFY(!locateCmd.name.isEmpty()); QVERIFY(locateCmd.getDescriptionLine().contains(locateCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); locateCmd.execute({"locate", m_dbFile->fileName(), "Sample"}); m_stdoutFile->reset(); m_stdoutFile->readLine(); // skip password prompt QCOMPARE(m_stdoutFile->readAll(), QByteArray("/Sample Entry\n")); qint64 pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); locateCmd.execute({"locate", m_dbFile->fileName(), "Does Not Exist"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -556,14 +556,14 @@ void TestCli::testLocate() tmpFile.close(); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); locateCmd.execute({"locate", tmpFile.fileName(), "New"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt QCOMPARE(m_stdoutFile->readAll(), QByteArray("/General/New Entry\n")); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); locateCmd.execute({"locate", tmpFile.fileName(), "Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -612,7 +612,7 @@ void TestCli::testMerge() sourceFile.close(); qint64 pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); mergeCmd.execute({"merge", "-s", targetFile1.fileName(), sourceFile.fileName()}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); @@ -631,8 +631,8 @@ void TestCli::testMerge() // try again with different passwords for both files pos = m_stdoutFile->pos(); - Utils::setNextPassword("b"); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("b"); + Utils::Test::setNextPassword("a"); mergeCmd.execute({"merge", targetFile2.fileName(), sourceFile.fileName()}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); @@ -671,7 +671,7 @@ void TestCli::testRemove() qint64 pos = m_stdoutFile->pos(); // delete entry and verify - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); removeCmd.execute({"rm", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -690,7 +690,7 @@ void TestCli::testRemove() pos = m_stdoutFile->pos(); // try again, this time without recycle bin - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -707,7 +707,7 @@ void TestCli::testRemove() pos = m_stdoutFile->pos(); // finally, try deleting a non-existent entry - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); removeCmd.execute({"rm", fileCopy.fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -722,7 +722,7 @@ void TestCli::testShow() QVERIFY(!showCmd.name.isEmpty()); QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name)); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); showCmd.execute({"show", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->reset(); m_stdoutFile->readLine(); // skip password prompt @@ -733,14 +733,14 @@ void TestCli::testShow() "Notes: Notes\n")); qint64 pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); showCmd.execute({"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n")); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); showCmd.execute({"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt @@ -748,7 +748,7 @@ void TestCli::testShow() "http://www.somesite.com/\n")); pos = m_stdoutFile->pos(); - Utils::setNextPassword("a"); + Utils::Test::setNextPassword("a"); showCmd.execute({"show", "-a", "DoesNotExist", m_dbFile->fileName(), "/Sample Entry"}); m_stdoutFile->seek(pos); m_stdoutFile->readLine(); // skip password prompt From 77adbef401caa02bd78e478cb0736063c0211b7c Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 19 Oct 2018 21:41:56 +0200 Subject: [PATCH 6/6] Reformat CMakeLists.txt files --- CMakeLists.txt | 287 ++++++------- cmake/CLangFormat.cmake | 62 ++- cmake/FindArgon2.cmake | 28 +- cmake/FindGcrypt.cmake | 2 +- src/CMakeLists.txt | 643 ++++++++++++++-------------- src/autotype/CMakeLists.txt | 36 +- src/autotype/mac/CMakeLists.txt | 26 +- src/autotype/test/CMakeLists.txt | 4 +- src/autotype/windows/CMakeLists.txt | 6 +- src/autotype/xcb/CMakeLists.txt | 4 +- src/browser/CMakeLists.txt | 25 +- src/cli/CMakeLists.txt | 68 +-- tests/CMakeLists.txt | 168 ++++---- 13 files changed, 671 insertions(+), 688 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f14b363b38..0c1ca4292b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2017 KeePassXC Team +# Copyright (C) 2018 KeePassXC Team # Copyright (C) 2010 Felix Geyer # # This program is free software: you can redistribute it and/or modify @@ -19,9 +19,9 @@ cmake_minimum_required(VERSION 3.1.0) 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." - FORCE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel." + FORCE) endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -48,19 +48,19 @@ 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) if(APPLE) - option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) + option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) endif() if(WITH_XC_ALL) - # Enable all options - set(WITH_XC_AUTOTYPE ON) - set(WITH_XC_NETWORKING ON) - set(WITH_XC_BROWSER ON) - set(WITH_XC_YUBIKEY ON) - set(WITH_XC_SSHAGENT ON) - if(APPLE) - set(WITH_XC_TOUCHID ON) - endif() + # Enable all options + set(WITH_XC_AUTOTYPE ON) + set(WITH_XC_NETWORKING ON) + set(WITH_XC_BROWSER ON) + set(WITH_XC_YUBIKEY ON) + set(WITH_XC_SSHAGENT ON) + if(APPLE) + set(WITH_XC_TOUCHID ON) + endif() endif() set(KEEPASSXC_VERSION_MAJOR "2") @@ -76,34 +76,34 @@ execute_process(COMMAND git tag --points-at HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_TAG) if(GIT_TAG) - set(OVERRIDE_VERSION ${GIT_TAG}) + set(OVERRIDE_VERSION ${GIT_TAG}) elseif(EXISTS ${CMAKE_SOURCE_DIR}/.version) - file(READ ${CMAKE_SOURCE_DIR}/.version OVERRIDE_VERSION) + file(READ ${CMAKE_SOURCE_DIR}/.version OVERRIDE_VERSION) endif() string(REGEX REPLACE "(\r?\n)+" "" OVERRIDE_VERSION "${OVERRIDE_VERSION}") if(OVERRIDE_VERSION) - if(OVERRIDE_VERSION MATCHES "^[\\.0-9]+-(alpha|beta)[0-9]+$") - set(KEEPASSXC_BUILD_TYPE PreRelease) - set(KEEPASSXC_VERSION ${OVERRIDE_VERSION}) - elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$") - set(KEEPASSXC_BUILD_TYPE Release) - set(KEEPASSXC_VERSION ${OVERRIDE_VERSION}) - endif() + if(OVERRIDE_VERSION MATCHES "^[\\.0-9]+-(alpha|beta)[0-9]+$") + set(KEEPASSXC_BUILD_TYPE PreRelease) + set(KEEPASSXC_VERSION ${OVERRIDE_VERSION}) + elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$") + set(KEEPASSXC_BUILD_TYPE Release) + set(KEEPASSXC_VERSION ${OVERRIDE_VERSION}) + endif() endif() if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease" AND NOT OVERRIDE_VERSION) - set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview") + set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview") elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot") - set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot") + set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot") endif() if(KEEPASSXC_BUILD_TYPE STREQUAL "Release") - set(KEEPASSXC_BUILD_TYPE_RELEASE ON) + set(KEEPASSXC_BUILD_TYPE_RELEASE ON) elseif(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease") - set(KEEPASSXC_BUILD_TYPE_PRE_RELEASE ON) + set(KEEPASSXC_BUILD_TYPE_PRE_RELEASE ON) else() - set(KEEPASSXC_BUILD_TYPE_SNAPSHOT ON) + set(KEEPASSXC_BUILD_TYPE_SNAPSHOT ON) endif() message(STATUS "Setting up build for KeePassXC v${KEEPASSXC_VERSION}\n") @@ -113,46 +113,46 @@ set(KEEPASSXC_DIST ON) set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution Type") set_property(CACHE KEEPASSXC_DIST_TYPE PROPERTY STRINGS Snap AppImage Other) if(KEEPASSXC_DIST_TYPE STREQUAL "Snap") - set(KEEPASSXC_DIST_SNAP ON) + set(KEEPASSXC_DIST_SNAP ON) elseif(KEEPASSXC_DIST_TYPE STREQUAL "AppImage") - set(KEEPASSXC_DIST_APPIMAGE ON) + set(KEEPASSXC_DIST_APPIMAGE ON) elseif(KEEPASSXC_DIST_TYPE STREQUAL "Other") - unset(KEEPASSXC_DIST) + unset(KEEPASSXC_DIST) endif() if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4") - set(IS_32BIT TRUE) + set(IS_32BIT TRUE) endif() if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set(CMAKE_COMPILER_IS_CLANG 1) + set(CMAKE_COMPILER_IS_CLANG 1) endif() if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(CMAKE_COMPILER_IS_CLANGXX 1) + set(CMAKE_COMPILER_IS_CLANGXX 1) endif() macro(add_gcc_compiler_cxxflags FLAGS) - if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS}") - endif() + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS}") + endif() endmacro(add_gcc_compiler_cxxflags) macro(add_gcc_compiler_cflags FLAGS) - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}") - endif() + if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}") + endif() endmacro(add_gcc_compiler_cflags) macro(add_gcc_compiler_flags FLAGS) - add_gcc_compiler_cxxflags("${FLAGS}") - add_gcc_compiler_cflags("${FLAGS}") + add_gcc_compiler_cxxflags("${FLAGS}") + add_gcc_compiler_cflags("${FLAGS}") endmacro(add_gcc_compiler_flags) add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII) if(WITH_APP_BUNDLE) - add_definitions(-DWITH_APP_BUNDLE) + add_definitions(-DWITH_APP_BUNDLE) endif() add_gcc_compiler_flags("-fno-common") @@ -162,7 +162,7 @@ add_gcc_compiler_flags("-fvisibility=hidden") add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden") if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_gcc_compiler_flags("-Werror") + add_gcc_compiler_flags("-Werror") endif() if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.8.999) OR CMAKE_COMPILER_IS_CLANGXX) @@ -176,147 +176,144 @@ add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virt add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") if(WITH_ASAN) - if(NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR APPLE)) - message(FATAL_ERROR "WITH_ASAN is only supported on Linux / macOS at the moment.") - endif() + if(NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR APPLE)) + message(FATAL_ERROR "WITH_ASAN is only supported on Linux / macOS at the moment.") + endif() - add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") + add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) - add_gcc_compiler_flags("-fsanitize=leak -DWITH_LSAN") + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)) + add_gcc_compiler_flags("-fsanitize=leak -DWITH_LSAN") + endif() endif() - endif() 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") +if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") + add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2") endif() check_c_compiler_flag("-Werror=format-security -Werror=implicit-function-declaration" WERROR_C_AVAILABLE) check_cxx_compiler_flag("-Werror=format-security" WERROR_CXX_AVAILABLE) if(WERROR_C_AVAILABLE AND WERROR_CXX_AVAILABLE) - add_gcc_compiler_flags("-Werror=format-security") - add_gcc_compiler_cflags("-Werror=implicit-function-declaration") + add_gcc_compiler_flags("-Werror=format-security") + add_gcc_compiler_cflags("-Werror=implicit-function-declaration") endif() if(CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align") endif() if(CMAKE_COMPILER_IS_GNUCC) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wcast-align") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wcast-align") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - if (CMAKE_COMPILER_IS_CLANGXX) - add_gcc_compiler_flags("-Qunused-arguments") - endif() - add_gcc_compiler_flags("-pie -fPIE") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now") + if(CMAKE_COMPILER_IS_CLANGXX) + add_gcc_compiler_flags("-Qunused-arguments") + endif() + add_gcc_compiler_flags("-pie -fPIE") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed -Wl,--no-undefined") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro,-z,now") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-add-needed -Wl,--as-needed") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now") endif() add_gcc_compiler_cflags("-std=c99") add_gcc_compiler_cxxflags("-std=c++11") if(APPLE) - add_gcc_compiler_cxxflags("-stdlib=libc++") + add_gcc_compiler_cxxflags("-stdlib=libc++") endif() if(WITH_DEV_BUILD) - add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED) + add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED) endif() if(MINGW) - set(CMAKE_RC_COMPILER_INIT windres) - enable_language(RC) - set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") - if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE 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") - # Enable high entropy ASLR for 64-bit builds - if(NOT IS_32BIT) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va") + set(CMAKE_RC_COMPILER_INIT windres) + enable_language(RC) + set(CMAKE_RC_COMPILE_OBJECT " -O coff -i -o ") + if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE 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") + # Enable high entropy ASLR for 64-bit builds + if(NOT IS_32BIT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va") + endif() endif() - endif() endif() if(APPLE AND WITH_APP_BUNDLE OR MINGW) - set(PROGNAME KeePassXC) + set(PROGNAME KeePassXC) else() - set(PROGNAME keepassxc) + set(PROGNAME keepassxc) endif() if(APPLE AND WITH_APP_BUNDLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local") - set(CMAKE_INSTALL_PREFIX "/Applications") - set(CMAKE_INSTALL_MANDIR "/usr/local/share/man") + set(CMAKE_INSTALL_PREFIX "/Applications") + set(CMAKE_INSTALL_MANDIR "/usr/local/share/man") endif() if(MINGW) - set(CLI_INSTALL_DIR ".") - set(PROXY_INSTALL_DIR ".") - set(BIN_INSTALL_DIR ".") - set(PLUGIN_INSTALL_DIR ".") - set(DATA_INSTALL_DIR "share") + set(CLI_INSTALL_DIR ".") + set(PROXY_INSTALL_DIR ".") + set(BIN_INSTALL_DIR ".") + set(PLUGIN_INSTALL_DIR ".") + set(DATA_INSTALL_DIR "share") elseif(APPLE AND WITH_APP_BUNDLE) - set(CLI_INSTALL_DIR "/usr/local/bin") - set(PROXY_INSTALL_DIR "/usr/local/bin") - set(BIN_INSTALL_DIR ".") - set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") - set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources") + set(CLI_INSTALL_DIR "/usr/local/bin") + set(PROXY_INSTALL_DIR "/usr/local/bin") + set(BIN_INSTALL_DIR ".") + set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns") + set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources") else() - include(GNUInstallDirs) + include(GNUInstallDirs) - set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") - set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") - set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") - set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc") - set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc") + set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") + set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") + set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") + set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc") + set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc") endif() if(WITH_TESTS) - enable_testing() + enable_testing() endif(WITH_TESTS) if(WITH_COVERAGE) - # Include code coverage, use with -DCMAKE_BUILD_TYPE=Debug - include(CodeCoverage) - set(COVERAGE_GCOVR_EXCLUDES - "\\(.+/\\)?tests/.\\*" - ".\\*/moc_\\[^/\\]+\\.cpp" - ".\\*/ui_\\[^/\\]+\\.h" - "\\(.+/\\)?zxcvbn/.\\*") - append_coverage_compiler_flags() - setup_target_for_coverage_gcovr_html( - NAME coverage - EXECUTABLE $(MAKE) && $(MAKE) test - ) + # Include code coverage, use with -DCMAKE_BUILD_TYPE=Debug + include(CodeCoverage) + set(COVERAGE_GCOVR_EXCLUDES + "\\(.+/\\)?tests/.\\*" + ".\\*/moc_\\[^/\\]+\\.cpp" + ".\\*/ui_\\[^/\\]+\\.h" + "\\(.+/\\)?zxcvbn/.\\*") + append_coverage_compiler_flags() + setup_target_for_coverage_gcovr_html( + NAME coverage + EXECUTABLE $(MAKE) && $(MAKE) test + ) endif() include(CLangFormat) +set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools) if(UNIX AND NOT APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools DBus REQUIRED) + find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED) elseif(APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED - HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH - ) - find_package(Qt5 COMPONENTS MacExtras - HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH - ) + find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH) + find_package(Qt5 COMPONENTS MacExtras HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH) else() - find_package(Qt5 COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools REQUIRED) + find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED) endif() if(Qt5Core_VERSION VERSION_LESS "5.2.0") - message(FATAL_ERROR "Qt version 5.2.0 or higher is required") + message(FATAL_ERROR "Qt version 5.2.0 or higher is required") endif() get_filename_component(Qt5_PREFIX ${Qt5_DIR}/../../.. REALPATH) @@ -329,13 +326,13 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) 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") - else() - message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}") - endif() + 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") + else() + message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}") + endif() endif() # Debian sets the the build type to None for package builds. @@ -351,24 +348,24 @@ find_package(QREncode REQUIRED) set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) 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() include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) # Optional if(WITH_XC_YUBIKEY) - find_package(YubiKey REQUIRED) + find_package(YubiKey REQUIRED) - include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS}) + include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS}) endif() if(UNIX) - check_cxx_source_compiles("#include + check_cxx_source_compiles("#include int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }" - HAVE_PR_SET_DUMPABLE) + HAVE_PR_SET_DUMPABLE) - check_cxx_source_compiles("#include + check_cxx_source_compiles("#include int main() { struct rlimit limit; limit.rlim_cur = 0; @@ -377,12 +374,12 @@ if(UNIX) return 0; }" HAVE_RLIMIT_CORE) - if(APPLE) - check_cxx_source_compiles("#include + if(APPLE) + check_cxx_source_compiles("#include #include int main() { ptrace(PT_DENY_ATTACH, 0, 0, 0); return 0; }" - HAVE_PT_DENY_ATTACH) - endif() + HAVE_PT_DENY_ATTACH) + endif() endif() include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) @@ -392,14 +389,14 @@ include(FeatureSummary) add_subdirectory(src) add_subdirectory(share) if(WITH_TESTS) - add_subdirectory(tests) + add_subdirectory(tests) endif(WITH_TESTS) if(PRINT_SUMMARY) - # This will print ENABLED, REQUIRED and DISABLED - feature_summary(WHAT ALL) + # This will print ENABLED, REQUIRED and DISABLED + feature_summary(WHAT ALL) else() - # This will only print ENABLED and DISABLED feature - feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") - feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") + # This will only print ENABLED and DISABLED feature + feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") + feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") endif() diff --git a/cmake/CLangFormat.cmake b/cmake/CLangFormat.cmake index 8c26db93be..481bba27b2 100644 --- a/cmake/CLangFormat.cmake +++ b/cmake/CLangFormat.cmake @@ -14,45 +14,43 @@ # along with this program. If not, see . set(EXCLUDED_DIRS - # third-party directories - zxcvbn/ - streams/QtIOCompressor - # objective-c directories - autotype/mac -) + # third-party directories + zxcvbn/ + streams/QtIOCompressor + # objective-c directories + autotype/mac) set(EXCLUDED_FILES - # third-party files - streams/qtiocompressor.cpp - streams/qtiocompressor.h - gui/KMessageWidget.h - gui/KMessageWidget.cpp - gui/MainWindowAdaptor.h - gui/MainWindowAdaptor.cpp - sshagent/bcrypt_pbkdf.cpp - sshagent/blf.h - sshagent/blowfish.c - tests/modeltest.cpp - tests/modeltest.h - # objective-c files - core/ScreenLockListenerMac.h - core/ScreenLockListenerMac.cpp -) + # third-party files + streams/qtiocompressor.cpp + streams/qtiocompressor.h + gui/KMessageWidget.h + gui/KMessageWidget.cpp + gui/MainWindowAdaptor.h + gui/MainWindowAdaptor.cpp + sshagent/bcrypt_pbkdf.cpp + sshagent/blf.h + sshagent/blowfish.c + tests/modeltest.cpp + tests/modeltest.h + # objective-c files + core/ScreenLockListenerMac.h + core/ScreenLockListenerMac.cpp) file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h) -foreach (SOURCE_FILE ${ALL_SOURCE_FILES}) - foreach (EXCLUDED_DIR ${EXCLUDED_DIRS}) +foreach(SOURCE_FILE ${ALL_SOURCE_FILES}) + foreach(EXCLUDED_DIR ${EXCLUDED_DIRS}) string(FIND ${SOURCE_FILE} ${EXCLUDED_DIR} SOURCE_FILE_EXCLUDED) - if (NOT ${SOURCE_FILE_EXCLUDED} EQUAL -1) + if(NOT ${SOURCE_FILE_EXCLUDED} EQUAL -1) list(REMOVE_ITEM ALL_SOURCE_FILES ${SOURCE_FILE}) - endif () - endforeach () - foreach (EXCLUDED_FILE ${EXCLUDED_FILES}) - if (${SOURCE_FILE} MATCHES ".*${EXCLUDED_FILE}$") + endif() + endforeach() + foreach(EXCLUDED_FILE ${EXCLUDED_FILES}) + if(${SOURCE_FILE} MATCHES ".*${EXCLUDED_FILE}$") list(REMOVE_ITEM ALL_SOURCE_FILES ${SOURCE_FILE}) - endif () - endforeach () -endforeach () + endif() + endforeach() +endforeach() add_custom_target( format diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake index bb2f5811d8..766e659b37 100644 --- a/cmake/FindArgon2.cmake +++ b/cmake/FindArgon2.cmake @@ -14,21 +14,21 @@ # along with this program. If not, see . find_path(ARGON2_INCLUDE_DIR argon2.h) -if (MINGW) - # find static library on Windows, and redefine used symbols to - # avoid definition name conflicts with libsodium - find_library(ARGON2_SYS_LIBRARIES libargon2.a) - message(STATUS "Patching libargon2...\n") - execute_process(COMMAND objcopy - --redefine-sym argon2_hash=libargon2_argon2_hash - --redefine-sym _argon2_hash=_libargon2_argon2_hash - --redefine-sym argon2_error_message=libargon2_argon2_error_message - --redefine-sym _argon2_error_message=_libargon2_argon2_error_message - ${ARGON2_SYS_LIBRARIES} ${CMAKE_BINARY_DIR}/libargon2_patched.a - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) - find_library(ARGON2_LIBRARIES libargon2_patched.a PATHS ${CMAKE_BINARY_DIR} NO_DEFAULT_PATH) +if(MINGW) + # find static library on Windows, and redefine used symbols to + # avoid definition name conflicts with libsodium + find_library(ARGON2_SYS_LIBRARIES libargon2.a) + message(STATUS "Patching libargon2...\n") + execute_process(COMMAND objcopy + --redefine-sym argon2_hash=libargon2_argon2_hash + --redefine-sym _argon2_hash=_libargon2_argon2_hash + --redefine-sym argon2_error_message=libargon2_argon2_error_message + --redefine-sym _argon2_error_message=_libargon2_argon2_error_message + ${ARGON2_SYS_LIBRARIES} ${CMAKE_BINARY_DIR}/libargon2_patched.a + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + find_library(ARGON2_LIBRARIES libargon2_patched.a PATHS ${CMAKE_BINARY_DIR} NO_DEFAULT_PATH) else() - find_library(ARGON2_LIBRARIES argon2) + find_library(ARGON2_LIBRARIES argon2) endif() mark_as_advanced(ARGON2_LIBRARIES ARGON2_INCLUDE_DIR) diff --git a/cmake/FindGcrypt.cmake b/cmake/FindGcrypt.cmake index 0775704625..59c6f473ad 100644 --- a/cmake/FindGcrypt.cmake +++ b/cmake/FindGcrypt.cmake @@ -22,7 +22,7 @@ mark_as_advanced(GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) if(GCRYPT_INCLUDE_DIR AND EXISTS "${GCRYPT_INCLUDE_DIR}/gcrypt.h") file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" GCRYPT_H REGEX "^#define GCRYPT_VERSION \"[^\"]*\"$") string(REGEX REPLACE "^.*GCRYPT_VERSION \"([0-9]+).*$" "\\1" GCRYPT_VERSION_MAJOR "${GCRYPT_H}") - string(REGEX REPLACE "^.*GCRYPT_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" GCRYPT_VERSION_MINOR "${GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" GCRYPT_VERSION_MINOR "${GCRYPT_H}") string(REGEX REPLACE "^.*GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" GCRYPT_VERSION_PATCH "${GCRYPT_H}") set(GCRYPT_VERSION_STRING "${GCRYPT_VERSION_MAJOR}.${GCRYPT_VERSION_MINOR}.${GCRYPT_VERSION_PATCH}") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b41b38783..56726e43d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2017 KeePassXC Team +# Copyright (C) 2018 KeePassXC Team # Copyright (C) 2010 Felix Geyer # # This program is free software: you can redistribute it and/or modify @@ -22,193 +22,191 @@ include(GetGitRevisionDescription) get_git_head_revision(GIT_REFSPEC GIT_HEAD) git_describe(GIT_DESCRIBE --long) -if (NOT GIT_HEAD OR NOT GIT_DESCRIBE) +if(NOT GIT_HEAD OR NOT GIT_DESCRIBE) set(GIT_HEAD "") set(GIT_DESCRIBE "") endif() find_library(ZXCVBN_LIBRARIES zxcvbn) if(NOT ZXCVBN_LIBRARIES) - add_library(zxcvbn STATIC zxcvbn/zxcvbn.c) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zxcvbn) - set(ZXCVBN_LIBRARIES zxcvbn) + add_library(zxcvbn STATIC zxcvbn/zxcvbn.c) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zxcvbn) + set(ZXCVBN_LIBRARIES zxcvbn) endif(NOT ZXCVBN_LIBRARIES) configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES - core/AutoTypeAssociations.cpp - core/AsyncTask.h - core/AutoTypeMatch.cpp - core/Compare.cpp - core/Config.cpp - core/CsvParser.cpp - core/CustomData.cpp - core/Database.cpp - core/DatabaseIcons.cpp - core/Entry.cpp - core/EntryAttachments.cpp - core/EntryAttributes.cpp - core/EntrySearcher.cpp - core/FilePath.cpp - core/Bootstrap.cpp - core/Global.h - core/Group.cpp - core/InactivityTimer.cpp - core/ListDeleter.h - core/Merger.cpp - core/Metadata.cpp - core/PasswordGenerator.cpp - core/PassphraseGenerator.cpp - core/SignalMultiplexer.cpp - core/ScreenLockListener.cpp - core/ScreenLockListener.h - core/ScreenLockListenerPrivate.h - core/ScreenLockListenerPrivate.cpp - core/TimeDelta.cpp - core/TimeInfo.cpp - core/Clock.cpp - core/Tools.cpp - core/Translator.cpp - core/Base32.h - core/Base32.cpp - cli/Utils.cpp - cli/Utils.h - crypto/Crypto.cpp - crypto/CryptoHash.cpp - crypto/Random.cpp - crypto/SymmetricCipher.cpp - crypto/SymmetricCipherBackend.h - crypto/SymmetricCipherGcrypt.cpp - crypto/kdf/Kdf.cpp - crypto/kdf/Kdf_p.h - crypto/kdf/AesKdf.cpp - crypto/kdf/Argon2Kdf.cpp - format/CsvExporter.cpp - format/KeePass1.h - format/KeePass1Reader.cpp - format/KeePass2.cpp - format/KeePass2RandomStream.cpp - format/KdbxReader.cpp - format/KdbxWriter.cpp - format/KdbxXmlReader.cpp - format/KeePass2Reader.cpp - format/KeePass2Writer.cpp - format/Kdbx3Reader.cpp - format/Kdbx3Writer.cpp - format/Kdbx4Reader.cpp - format/Kdbx4Writer.cpp - format/KdbxXmlWriter.cpp - gui/AboutDialog.cpp - gui/Application.cpp - gui/CategoryListWidget.cpp - gui/Clipboard.cpp - gui/CloneDialog.cpp - gui/DatabaseOpenWidget.cpp - gui/DatabaseTabWidget.cpp - gui/DatabaseWidget.cpp - gui/DatabaseWidgetStateSync.cpp - gui/EntryPreviewWidget.cpp - gui/DialogyWidget.cpp - gui/DragTabBar.cpp - gui/EditWidget.cpp - gui/EditWidgetIcons.cpp - gui/EditWidgetProperties.cpp - gui/FileDialog.cpp - gui/Font.cpp - gui/IconModels.cpp - gui/KeePass1OpenWidget.cpp - gui/KMessageWidget.cpp - gui/LineEdit.cpp - gui/MainWindow.cpp - gui/MessageBox.cpp - gui/MessageWidget.cpp - gui/PasswordEdit.cpp - gui/PasswordGeneratorWidget.cpp - gui/ApplicationSettingsWidget.cpp - gui/SearchWidget.cpp - gui/SortFilterHideProxyModel.cpp - gui/SquareSvgWidget.cpp - gui/TotpSetupDialog.cpp - gui/TotpDialog.cpp - gui/TotpExportSettingsDialog.cpp - gui/UnlockDatabaseWidget.cpp - gui/UnlockDatabaseDialog.cpp - gui/WelcomeWidget.cpp - gui/widgets/ElidedLabel.cpp - gui/csvImport/CsvImportWidget.cpp - gui/csvImport/CsvImportWizard.cpp - gui/csvImport/CsvParserModel.cpp - gui/entry/AutoTypeAssociationsModel.cpp - gui/entry/AutoTypeMatchModel.cpp - gui/entry/AutoTypeMatchView.cpp - gui/entry/EditEntryWidget.cpp - gui/entry/EditEntryWidget_p.h - gui/entry/EntryAttachmentsModel.cpp - gui/entry/EntryAttachmentsWidget.cpp - gui/entry/EntryAttributesModel.cpp - gui/entry/EntryHistoryModel.cpp - gui/entry/EntryModel.cpp - gui/entry/EntryView.cpp - gui/group/EditGroupWidget.cpp - gui/group/GroupModel.cpp - gui/group/GroupView.cpp - gui/masterkey/KeyComponentWidget.cpp - gui/masterkey/PasswordEditWidget.cpp - gui/masterkey/YubiKeyEditWidget.cpp - gui/masterkey/KeyFileEditWidget.cpp - gui/dbsettings/DatabaseSettingsWidget.cpp - gui/dbsettings/DatabaseSettingsDialog.cpp - gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp - gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp - gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp - gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp - gui/settings/SettingsWidget.cpp - gui/wizard/NewDatabaseWizard.cpp - gui/wizard/NewDatabaseWizardPage.cpp - gui/wizard/NewDatabaseWizardPageMetaData.cpp - gui/wizard/NewDatabaseWizardPageEncryption.cpp - gui/wizard/NewDatabaseWizardPageMasterKey.cpp - keys/ChallengeResponseKey.h - keys/CompositeKey.cpp - keys/drivers/YubiKey.h - keys/FileKey.cpp - keys/Key.h - keys/PasswordKey.cpp - keys/YkChallengeResponseKey.cpp - streams/HashedBlockStream.cpp - streams/HmacBlockStream.cpp - streams/LayeredStream.cpp - streams/qtiocompressor.cpp - streams/StoreDataStream.cpp - streams/SymmetricCipherStream.cpp - totp/totp.h - totp/totp.cpp) + core/AutoTypeAssociations.cpp + core/AsyncTask.h + core/AutoTypeMatch.cpp + core/Compare.cpp + core/Config.cpp + core/CsvParser.cpp + core/CustomData.cpp + core/Database.cpp + core/DatabaseIcons.cpp + core/Entry.cpp + core/EntryAttachments.cpp + core/EntryAttributes.cpp + core/EntrySearcher.cpp + core/FilePath.cpp + core/Bootstrap.cpp + core/Global.h + core/Group.cpp + core/InactivityTimer.cpp + core/ListDeleter.h + core/Merger.cpp + core/Metadata.cpp + core/PasswordGenerator.cpp + core/PassphraseGenerator.cpp + core/SignalMultiplexer.cpp + core/ScreenLockListener.cpp + core/ScreenLockListener.h + core/ScreenLockListenerPrivate.h + core/ScreenLockListenerPrivate.cpp + core/TimeDelta.cpp + core/TimeInfo.cpp + core/Clock.cpp + core/Tools.cpp + core/Translator.cpp + core/Base32.h + core/Base32.cpp + cli/Utils.cpp + cli/Utils.h + crypto/Crypto.cpp + crypto/CryptoHash.cpp + crypto/Random.cpp + crypto/SymmetricCipher.cpp + crypto/SymmetricCipherBackend.h + crypto/SymmetricCipherGcrypt.cpp + crypto/kdf/Kdf.cpp + crypto/kdf/Kdf_p.h + crypto/kdf/AesKdf.cpp + crypto/kdf/Argon2Kdf.cpp + format/CsvExporter.cpp + format/KeePass1.h + format/KeePass1Reader.cpp + format/KeePass2.cpp + format/KeePass2RandomStream.cpp + format/KdbxReader.cpp + format/KdbxWriter.cpp + format/KdbxXmlReader.cpp + format/KeePass2Reader.cpp + format/KeePass2Writer.cpp + format/Kdbx3Reader.cpp + format/Kdbx3Writer.cpp + format/Kdbx4Reader.cpp + format/Kdbx4Writer.cpp + format/KdbxXmlWriter.cpp + gui/AboutDialog.cpp + gui/Application.cpp + gui/CategoryListWidget.cpp + gui/Clipboard.cpp + gui/CloneDialog.cpp + gui/DatabaseOpenWidget.cpp + gui/DatabaseTabWidget.cpp + gui/DatabaseWidget.cpp + gui/DatabaseWidgetStateSync.cpp + gui/EntryPreviewWidget.cpp + gui/DialogyWidget.cpp + gui/DragTabBar.cpp + gui/EditWidget.cpp + gui/EditWidgetIcons.cpp + gui/EditWidgetProperties.cpp + gui/FileDialog.cpp + gui/Font.cpp + gui/IconModels.cpp + gui/KeePass1OpenWidget.cpp + gui/KMessageWidget.cpp + gui/LineEdit.cpp + gui/MainWindow.cpp + gui/MessageBox.cpp + gui/MessageWidget.cpp + gui/PasswordEdit.cpp + gui/PasswordGeneratorWidget.cpp + gui/ApplicationSettingsWidget.cpp + gui/SearchWidget.cpp + gui/SortFilterHideProxyModel.cpp + gui/SquareSvgWidget.cpp + gui/TotpSetupDialog.cpp + gui/TotpDialog.cpp + gui/TotpExportSettingsDialog.cpp + gui/UnlockDatabaseWidget.cpp + gui/UnlockDatabaseDialog.cpp + gui/WelcomeWidget.cpp + gui/widgets/ElidedLabel.cpp + gui/csvImport/CsvImportWidget.cpp + gui/csvImport/CsvImportWizard.cpp + gui/csvImport/CsvParserModel.cpp + gui/entry/AutoTypeAssociationsModel.cpp + gui/entry/AutoTypeMatchModel.cpp + gui/entry/AutoTypeMatchView.cpp + gui/entry/EditEntryWidget.cpp + gui/entry/EditEntryWidget_p.h + gui/entry/EntryAttachmentsModel.cpp + gui/entry/EntryAttachmentsWidget.cpp + gui/entry/EntryAttributesModel.cpp + gui/entry/EntryHistoryModel.cpp + gui/entry/EntryModel.cpp + gui/entry/EntryView.cpp + gui/group/EditGroupWidget.cpp + gui/group/GroupModel.cpp + gui/group/GroupView.cpp + gui/masterkey/KeyComponentWidget.cpp + gui/masterkey/PasswordEditWidget.cpp + gui/masterkey/YubiKeyEditWidget.cpp + gui/masterkey/KeyFileEditWidget.cpp + gui/dbsettings/DatabaseSettingsWidget.cpp + gui/dbsettings/DatabaseSettingsDialog.cpp + gui/dbsettings/DatabaseSettingsWidgetGeneral.cpp + gui/dbsettings/DatabaseSettingsWidgetMetaDataSimple.cpp + gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp + gui/dbsettings/DatabaseSettingsWidgetMasterKey.cpp + gui/settings/SettingsWidget.cpp + gui/wizard/NewDatabaseWizard.cpp + gui/wizard/NewDatabaseWizardPage.cpp + gui/wizard/NewDatabaseWizardPageMetaData.cpp + gui/wizard/NewDatabaseWizardPageEncryption.cpp + gui/wizard/NewDatabaseWizardPageMasterKey.cpp + keys/ChallengeResponseKey.h + keys/CompositeKey.cpp + keys/drivers/YubiKey.h + keys/FileKey.cpp + keys/Key.h + keys/PasswordKey.cpp + keys/YkChallengeResponseKey.cpp + streams/HashedBlockStream.cpp + streams/HmacBlockStream.cpp + streams/LayeredStream.cpp + streams/qtiocompressor.cpp + streams/StoreDataStream.cpp + streams/SymmetricCipherStream.cpp + totp/totp.h + totp/totp.cpp) if(APPLE) - set(keepassx_SOURCES ${keepassx_SOURCES} - core/ScreenLockListenerMac.h - core/ScreenLockListenerMac.cpp - core/MacPasteboard.h - core/MacPasteboard.cpp - ) + set(keepassx_SOURCES + ${keepassx_SOURCES} + core/ScreenLockListenerMac.h + core/ScreenLockListenerMac.cpp + core/MacPasteboard.h + core/MacPasteboard.cpp) endif() if(UNIX AND NOT APPLE) - set(keepassx_SOURCES ${keepassx_SOURCES} - core/ScreenLockListenerDBus.h - core/ScreenLockListenerDBus.cpp - gui/MainWindowAdaptor.cpp - ) + set(keepassx_SOURCES + ${keepassx_SOURCES} + core/ScreenLockListenerDBus.h + core/ScreenLockListenerDBus.cpp + gui/MainWindowAdaptor.cpp) endif() if(MINGW) - set(keepassx_SOURCES ${keepassx_SOURCES} - core/ScreenLockListenerWin.h - core/ScreenLockListenerWin.cpp - ) + set(keepassx_SOURCES + ${keepassx_SOURCES} + core/ScreenLockListenerWin.h + core/ScreenLockListenerWin.cpp) endif() -set(keepassx_SOURCES_MAINEXE - main.cpp -) +set(keepassx_SOURCES_MAINEXE main.cpp) 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)") @@ -237,32 +235,29 @@ if(WITH_XC_SSHAGENT) endif() set(autotype_SOURCES - core/Tools.cpp - autotype/AutoType.cpp - autotype/AutoTypeAction.cpp - autotype/AutoTypePlatformPlugin.h - autotype/AutoTypeSelectDialog.cpp - autotype/AutoTypeSelectView.cpp - autotype/ShortcutWidget.cpp - autotype/WildcardMatcher.cpp - autotype/WindowSelectComboBox.cpp - autotype/test/AutoTypeTestInterface.h -) + core/Tools.cpp + autotype/AutoType.cpp + autotype/AutoTypeAction.cpp + autotype/AutoTypePlatformPlugin.h + autotype/AutoTypeSelectDialog.cpp + autotype/AutoTypeSelectView.cpp + autotype/ShortcutWidget.cpp + autotype/WildcardMatcher.cpp + autotype/WindowSelectComboBox.cpp + autotype/test/AutoTypeTestInterface.h) if(MINGW) - set(keepassx_SOURCES_MAINEXE - ${keepassx_SOURCES_MAINEXE} - ${CMAKE_SOURCE_DIR}/share/windows/icon.rc) + set(keepassx_SOURCES_MAINEXE ${keepassx_SOURCES_MAINEXE} ${CMAKE_SOURCE_DIR}/share/windows/icon.rc) endif() if(WITH_XC_YUBIKEY) - list(APPEND keepassx_SOURCES keys/drivers/YubiKey.cpp) + list(APPEND keepassx_SOURCES keys/drivers/YubiKey.cpp) else() - list(APPEND keepassx_SOURCES keys/drivers/YubiKeyStub.cpp) + list(APPEND keepassx_SOURCES keys/drivers/YubiKeyStub.cpp) endif() if(WITH_XC_TOUCHID) - list(APPEND keepassx_SOURCES touchid/TouchID.mm) + list(APPEND keepassx_SOURCES touchid/TouchID.mm) endif() add_library(autotype STATIC ${autotype_SOURCES}) @@ -272,35 +267,35 @@ add_library(keepassx_core STATIC ${keepassx_SOURCES}) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) target_link_libraries(keepassx_core - autotype - ${keepassxcbrowser_LIB} - ${sshagent_LIB} - ${qrcode_LIB} - Qt5::Core - Qt5::Concurrent - Qt5::Network - Qt5::Widgets - ${CURL_LIBRARIES} - ${YUBIKEY_LIBRARIES} - ${ZXCVBN_LIBRARIES} - ${ARGON2_LIBRARIES} - ${GCRYPT_LIBRARIES} - ${GPGERROR_LIBRARIES} - ${YUBIKEY_LIBRARIES} - ${ZLIB_LIBRARIES} - ${ZXCVBN_LIBRARIES}) + autotype + ${keepassxcbrowser_LIB} + ${sshagent_LIB} + ${qrcode_LIB} + Qt5::Core + Qt5::Concurrent + Qt5::Network + Qt5::Widgets + ${CURL_LIBRARIES} + ${YUBIKEY_LIBRARIES} + ${ZXCVBN_LIBRARIES} + ${ARGON2_LIBRARIES} + ${GCRYPT_LIBRARIES} + ${GPGERROR_LIBRARIES} + ${YUBIKEY_LIBRARIES} + ${ZLIB_LIBRARIES} + ${ZXCVBN_LIBRARIES}) if(APPLE) target_link_libraries(keepassx_core "-framework Foundation") if(Qt5MacExtras_FOUND) - target_link_libraries(keepassx_core Qt5::MacExtras) + target_link_libraries(keepassx_core Qt5::MacExtras) endif() if(WITH_XC_TOUCHID) - target_link_libraries(keepassx_core "-framework Security") - target_link_libraries(keepassx_core "-framework LocalAuthentication") + target_link_libraries(keepassx_core "-framework Security") + target_link_libraries(keepassx_core "-framework LocalAuthentication") endif() endif() -if (UNIX AND NOT APPLE) +if(UNIX AND NOT APPLE) target_link_libraries(keepassx_core Qt5::DBus) endif() if(MINGW) @@ -308,15 +303,15 @@ if(MINGW) endif() if(MINGW) - include(GenerateProductVersion) - generate_product_version( - WIN32_ProductVersionFiles - NAME "KeePassXC" - COMPANY_NAME "KeePassXC Team" - VERSION_MAJOR ${KEEPASSXC_VERSION_MAJOR} - VERSION_MINOR ${KEEPASSXC_VERSION_MINOR} - VERSION_PATCH ${KEEPASSXC_VERSION_PATCH} - ) + include(GenerateProductVersion) + generate_product_version( + WIN32_ProductVersionFiles + NAME "KeePassXC" + COMPANY_NAME "KeePassXC Team" + VERSION_MAJOR ${KEEPASSXC_VERSION_MAJOR} + VERSION_MINOR ${KEEPASSXC_VERSION_MINOR} + VERSION_PATCH ${KEEPASSXC_VERSION_PATCH} + ) endif() add_executable(${PROGNAME} WIN32 ${keepassx_SOURCES_MAINEXE} ${WIN32_ProductVersionFiles}) @@ -325,116 +320,116 @@ target_link_libraries(${PROGNAME} keepassx_core) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) if(APPLE AND WITH_APP_BUNDLE) - configure_file(${CMAKE_SOURCE_DIR}/share/macosx/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) - set_target_properties(${PROGNAME} PROPERTIES - MACOSX_BUNDLE ON - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) - - if(WITH_XC_TOUCHID) - set_target_properties(${PROGNAME} PROPERTIES - CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/share/macosx/keepassxc.entitlements" ) - endif() - - if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib") - install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib" - DESTINATION "${DATA_INSTALL_DIR}") - endif() - - set(CPACK_GENERATOR "DragNDrop") - set(CPACK_DMG_FORMAT "UDBZ") - set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/share/macosx/DS_Store.in") - set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/share/macosx/background.tiff") - set(CPACK_DMG_VOLUME_NAME "${PROGNAME}") - set(CPACK_SYSTEM_NAME "OSX") - set(CPACK_STRIP_FILES ON) - set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}") - include(CPack) - - add_custom_command(TARGET ${PROGNAME} - POST_BUILD - COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src - COMMENT "Deploying app bundle") + configure_file(${CMAKE_SOURCE_DIR}/share/macosx/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + set_target_properties(${PROGNAME} PROPERTIES + MACOSX_BUNDLE ON + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + + if(WITH_XC_TOUCHID) + set_target_properties(${PROGNAME} PROPERTIES + CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/share/macosx/keepassxc.entitlements") + endif() + + if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib") + install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib" + DESTINATION "${DATA_INSTALL_DIR}") + endif() + + set(CPACK_GENERATOR "DragNDrop") + set(CPACK_DMG_FORMAT "UDBZ") + set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/share/macosx/DS_Store.in") + set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/share/macosx/background.tiff") + set(CPACK_DMG_VOLUME_NAME "${PROGNAME}") + set(CPACK_SYSTEM_NAME "OSX") + set(CPACK_STRIP_FILES ON) + set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}") + include(CPack) + + add_custom_command(TARGET ${PROGNAME} + POST_BUILD + COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src + COMMENT "Deploying app bundle") endif() install(TARGETS ${PROGNAME} - BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) if(MINGW) - if(${CMAKE_SIZEOF_VOID_P} EQUAL "8") - set(OUTPUT_FILE_POSTFIX "Win64") - else() - set(OUTPUT_FILE_POSTFIX "Win32") - endif() - - # We have to copy the license file in the configuration phase. - # CMake checks that CPACK_RESOURCE_FILE_LICENSE actually exists and - # we have to copy it because WiX needs it to have a .txt extension. - execute_process(COMMAND ${CMAKE_COMMAND} -E copy - "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2" - "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt") - - string(REGEX REPLACE "-snapshot$" "" KEEPASSXC_VERSION_CLEAN ${KEEPASSXC_VERSION}) - - set(CPACK_GENERATOR "ZIP;NSIS") - set(CPACK_STRIP_FILES OFF) - set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}") - set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME}) - set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION_CLEAN}) - set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team") - string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp") - set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt") - set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) - set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico") - set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}") - set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe") - string(REGEX REPLACE "/" "\\\\\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/share/windows/installer-wizard.bmp") - set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}") - set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'") - set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'") - set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'") - set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'") - set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'") - set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'") - set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org") - set(CPACK_NSIS_DISPLAY_NAME ${PROGNAME}) - set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}") - set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe") - set(CPACK_WIX_UPGRADE_GUID 88785A72-3EAE-4F29-89E3-BC6B19BA9A5B) - set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico") - set(CPACK_WIX_UI_BANNER "${CMAKE_SOURCE_DIR}/share/windows/wix-banner.bmp") - set(CPACK_WIX_UI_DIALOG "${CMAKE_SOURCE_DIR}/share/windows/wix-dialog.bmp") - set(CPACK_WIX_TEMPLATE "${CMAKE_SOURCE_DIR}/share/windows/wix-template.xml") - set(CPACK_WIX_PATCH_FILE "${CMAKE_SOURCE_DIR}/share/windows/wix-patch.xml") - set(CPACK_WIX_PROPERTY_ARPURLINFOABOUT "https://keepassxc.org") - set(CPACK_WIX_EXTENSIONS "WixUtilExtension.dll") - include(CPack) - - 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$<$:d>.dll - ${PLUGINS_DIR}/platforms/qdirect2d$<$:d>.dll - DESTINATION "platforms") - install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$:d>.dll DESTINATION "styles") - install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$:d>.dll DESTINATION "platforminputcontexts") - install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$:d>.dll DESTINATION "iconengines") - install(FILES - ${PLUGINS_DIR}/imageformats/qgif$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qicns$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qico$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qjpeg$<$:d>.dll - ${PLUGINS_DIR}/imageformats/qwebp$<$:d>.dll - DESTINATION "imageformats") - - # install CA cert chains - install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs") + if(${CMAKE_SIZEOF_VOID_P} EQUAL "8") + set(OUTPUT_FILE_POSTFIX "Win64") + else() + set(OUTPUT_FILE_POSTFIX "Win32") + endif() + + # We have to copy the license file in the configuration phase. + # CMake checks that CPACK_RESOURCE_FILE_LICENSE actually exists and + # we have to copy it because WiX needs it to have a .txt extension. + execute_process(COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2" + "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt") + + string(REGEX REPLACE "-snapshot$" "" KEEPASSXC_VERSION_CLEAN ${KEEPASSXC_VERSION}) + + set(CPACK_GENERATOR "ZIP;NSIS") + set(CPACK_STRIP_FILES OFF) + set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME}) + set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION_CLEAN}) + set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team") + string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt") + set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) + set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico") + set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}") + set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe") + string(REGEX REPLACE "/" "\\\\\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/share/windows/installer-wizard.bmp") + set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'") + set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'") + set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'") + set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'") + set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'") + set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'") + set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org") + set(CPACK_NSIS_DISPLAY_NAME ${PROGNAME}) + set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}") + set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe") + set(CPACK_WIX_UPGRADE_GUID 88785A72-3EAE-4F29-89E3-BC6B19BA9A5B) + set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico") + set(CPACK_WIX_UI_BANNER "${CMAKE_SOURCE_DIR}/share/windows/wix-banner.bmp") + set(CPACK_WIX_UI_DIALOG "${CMAKE_SOURCE_DIR}/share/windows/wix-dialog.bmp") + set(CPACK_WIX_TEMPLATE "${CMAKE_SOURCE_DIR}/share/windows/wix-template.xml") + set(CPACK_WIX_PATCH_FILE "${CMAKE_SOURCE_DIR}/share/windows/wix-patch.xml") + set(CPACK_WIX_PROPERTY_ARPURLINFOABOUT "https://keepassxc.org") + set(CPACK_WIX_EXTENSIONS "WixUtilExtension.dll") + include(CPack) + + 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$<$:d>.dll + ${PLUGINS_DIR}/platforms/qdirect2d$<$:d>.dll + DESTINATION "platforms") + install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$:d>.dll DESTINATION "styles") + install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$:d>.dll DESTINATION "platforminputcontexts") + install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$:d>.dll DESTINATION "iconengines") + install(FILES + ${PLUGINS_DIR}/imageformats/qgif$<$:d>.dll + ${PLUGINS_DIR}/imageformats/qicns$<$:d>.dll + ${PLUGINS_DIR}/imageformats/qico$<$:d>.dll + ${PLUGINS_DIR}/imageformats/qjpeg$<$:d>.dll + ${PLUGINS_DIR}/imageformats/qwebp$<$:d>.dll + DESTINATION "imageformats") + + # install CA cert chains + install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs") endif() diff --git a/src/autotype/CMakeLists.txt b/src/autotype/CMakeLists.txt index 4b36105388..df0483a08a 100644 --- a/src/autotype/CMakeLists.txt +++ b/src/autotype/CMakeLists.txt @@ -1,23 +1,23 @@ if(WITH_XC_AUTOTYPE) - if(UNIX AND NOT APPLE) - find_package(X11) - find_package(Qt5X11Extras 5.2) - if(PRINT_SUMMARY) - add_feature_info(libXi X11_Xi_FOUND "The X11 Xi Protocol library is required for auto-type") - add_feature_info(libXtst X11_XTest_FOUND "The X11 XTEST Protocol library is required for auto-type") - add_feature_info(Qt5X11Extras Qt5X11Extras_FOUND "The Qt5X11Extras library is required for auto-type") - endif() + if(UNIX AND NOT APPLE) + find_package(X11) + find_package(Qt5X11Extras 5.2) + if(PRINT_SUMMARY) + add_feature_info(libXi X11_Xi_FOUND "The X11 Xi Protocol library is required for auto-type") + add_feature_info(libXtst X11_XTest_FOUND "The X11 XTEST Protocol library is required for auto-type") + add_feature_info(Qt5X11Extras Qt5X11Extras_FOUND "The Qt5X11Extras library is required for auto-type") + endif() - if(X11_FOUND AND X11_Xi_FOUND AND X11_XTest_FOUND AND Qt5X11Extras_FOUND) - add_subdirectory(xcb) + if(X11_FOUND AND X11_Xi_FOUND AND X11_XTest_FOUND AND Qt5X11Extras_FOUND) + add_subdirectory(xcb) + endif() + elseif(APPLE) + add_subdirectory(mac) + elseif(WIN32) + add_subdirectory(windows) endif() - elseif(APPLE) - add_subdirectory(mac) - elseif(WIN32) - add_subdirectory(windows) - endif() - if(WITH_TESTS) - add_subdirectory(test) - endif() + if(WITH_TESTS) + add_subdirectory(test) + endif() endif() diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 08c5327847..f2915bc0ab 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -1,24 +1,20 @@ -set(autotype_mac_SOURCES - AutoTypeMac.cpp -) +set(autotype_mac_SOURCES AutoTypeMac.cpp) -set(autotype_mac_mm_SOURCES - AppKitImpl.mm -) +set(autotype_mac_mm_SOURCES AppKitImpl.mm) 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") target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) if(WITH_APP_BUNDLE) - add_custom_command(TARGET keepassx-autotype-cocoa - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} - COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src - COMMENT "Deploying autotype plugin") + add_custom_command(TARGET keepassx-autotype-cocoa + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} + COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src + COMMENT "Deploying autotype plugin") else() - install(TARGETS keepassx-autotype-cocoa - BUNDLE DESTINATION . COMPONENT Runtime - LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) + install(TARGETS keepassx-autotype-cocoa + BUNDLE DESTINATION . COMPONENT Runtime + LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) endif() diff --git a/src/autotype/test/CMakeLists.txt b/src/autotype/test/CMakeLists.txt index f41fa1b50c..a9cf998dfa 100644 --- a/src/autotype/test/CMakeLists.txt +++ b/src/autotype/test/CMakeLists.txt @@ -1,6 +1,4 @@ -set(autotype_test_SOURCES - AutoTypeTest.cpp -) +set(autotype_test_SOURCES AutoTypeTest.cpp) add_library(keepassx-autotype-test MODULE ${autotype_test_SOURCES}) target_link_libraries(keepassx-autotype-test keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) diff --git a/src/autotype/windows/CMakeLists.txt b/src/autotype/windows/CMakeLists.txt index cc3ad4b448..be74b7aa7d 100644 --- a/src/autotype/windows/CMakeLists.txt +++ b/src/autotype/windows/CMakeLists.txt @@ -1,9 +1,7 @@ -set(autotype_win_SOURCES - AutoTypeWindows.cpp -) +set(autotype_win_SOURCES AutoTypeWindows.cpp) add_library(keepassx-autotype-windows MODULE ${autotype_win_SOURCES}) target_link_libraries(keepassx-autotype-windows keepassx_core ${autotype_LIB} Qt5::Core Qt5::Widgets) install(TARGETS keepassx-autotype-windows BUNDLE DESTINATION . COMPONENT Runtime -LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) + LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime) diff --git a/src/autotype/xcb/CMakeLists.txt b/src/autotype/xcb/CMakeLists.txt index 7e7f252d72..e41d4a0993 100644 --- a/src/autotype/xcb/CMakeLists.txt +++ b/src/autotype/xcb/CMakeLists.txt @@ -1,8 +1,6 @@ include_directories(SYSTEM ${X11_X11_INCLUDE_PATH}) -set(autotype_XCB_SOURCES - AutoTypeXCB.cpp -) +set(autotype_XCB_SOURCES AutoTypeXCB.cpp) add_library(keepassx-autotype-xcb MODULE ${autotype_XCB_SOURCES}) target_link_libraries(keepassx-autotype-xcb keepassx_core Qt5::Core Qt5::Widgets Qt5::X11Extras ${X11_X11_LIB} ${X11_Xi_LIB} ${X11_XTest_LIB}) diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt index bb5a01908b..10189d931c 100755 --- a/src/browser/CMakeLists.txt +++ b/src/browser/CMakeLists.txt @@ -19,19 +19,18 @@ if(WITH_XC_BROWSER) find_package(sodium 1.0.12 REQUIRED) set(keepassxcbrowser_SOURCES - BrowserAccessControlDialog.cpp - BrowserAction.cpp - BrowserClients.cpp - BrowserEntryConfig.cpp - BrowserEntrySaveDialog.cpp - BrowserOptionDialog.cpp - BrowserService.cpp - BrowserSettings.cpp - HostInstaller.cpp - NativeMessagingBase.cpp - NativeMessagingHost.cpp - Variant.cpp - ) + BrowserAccessControlDialog.cpp + BrowserAction.cpp + BrowserClients.cpp + BrowserEntryConfig.cpp + BrowserEntrySaveDialog.cpp + BrowserOptionDialog.cpp + BrowserService.cpp + BrowserSettings.cpp + HostInstaller.cpp + NativeMessagingBase.cpp + NativeMessagingHost.cpp + Variant.cpp) add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES}) target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index a5126f9997..f6377aa079 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,46 +14,46 @@ # along with this program. If not, see . set(cli_SOURCES - Add.cpp - Add.h - Clip.cpp - Clip.h - Command.cpp - Command.h - Diceware.cpp - Diceware.h - Edit.cpp - Edit.h - Estimate.cpp - Estimate.h - Extract.cpp - Extract.h - Generate.cpp - Generate.h - List.cpp - List.h - Locate.cpp - Locate.h - Merge.cpp - Merge.h - Remove.cpp - Remove.h - Show.cpp - Show.h) + Add.cpp + Add.h + Clip.cpp + Clip.h + Command.cpp + Command.h + Diceware.cpp + Diceware.h + Edit.cpp + Edit.h + Estimate.cpp + Estimate.h + Extract.cpp + Extract.h + Generate.cpp + Generate.h + List.cpp + List.h + Locate.cpp + Locate.h + Merge.cpp + Merge.h + Remove.cpp + Remove.h + Show.cpp + Show.h) add_library(cli STATIC ${cli_SOURCES}) target_link_libraries(cli Qt5::Core Qt5::Widgets) add_executable(keepassxc-cli keepassxc-cli.cpp) target_link_libraries(keepassxc-cli - cli - keepassx_core - Qt5::Core - ${GCRYPT_LIBRARIES} - ${ARGON2_LIBRARIES} - ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES} - ${ZXCVBN_LIBRARIES}) + cli + keepassx_core + Qt5::Core + ${GCRYPT_LIBRARIES} + ${ARGON2_LIBRARIES} + ${GPGERROR_LIBRARIES} + ${ZLIB_LIBRARIES} + ${ZXCVBN_LIBRARIES}) install(TARGETS keepassxc-cli BUNDLE DESTINATION . COMPONENT Runtime diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 96f6841719..3839b58e42 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,4 @@ +# Copyright (C) 2018 KeePassXC Team # Copyright (C) 2010 Felix Geyer # # This program is free software: you can redistribute it and/or modify @@ -13,7 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/../src) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR}/../src) add_definitions(-DQT_TEST_LIB) @@ -21,99 +26,98 @@ set(KEEPASSX_TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) configure_file(config-keepassx-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx-tests.h) macro(parse_arguments prefix arg_names option_names) - set(DEFAULT_ARGS) - foreach(arg_name ${arg_names}) - set(${prefix}_${arg_name}) - endforeach(arg_name) - foreach(option ${option_names}) - set(${prefix}_${option} FALSE) - endforeach(option) - - set(current_arg_name DEFAULT_ARGS) - set(current_arg_list) - foreach(arg ${ARGN}) - set(larg_names ${arg_names}) - list(FIND larg_names "${arg}" is_arg_name) - if(is_arg_name GREATER -1) - set(${prefix}_${current_arg_name} ${current_arg_list}) - set(current_arg_name ${arg}) - set(current_arg_list) - else() - set(loption_names ${option_names}) - list(FIND loption_names "${arg}" is_option) - if(is_option GREATER -1) - set(${prefix}_${arg} TRUE) - else(is_option GREATER -1) - set(current_arg_list ${current_arg_list} ${arg}) - endif() - endif() - endforeach(arg) - set(${prefix}_${current_arg_name} ${current_arg_list}) + set(DEFAULT_ARGS) + foreach(arg_name ${arg_names}) + set(${prefix}_${arg_name}) + endforeach(arg_name) + foreach(option ${option_names}) + set(${prefix}_${option} FALSE) + endforeach(option) + + set(current_arg_name DEFAULT_ARGS) + set(current_arg_list) + foreach(arg ${ARGN}) + set(larg_names ${arg_names}) + list(FIND larg_names "${arg}" is_arg_name) + if(is_arg_name GREATER -1) + set(${prefix}_${current_arg_name} ${current_arg_list}) + set(current_arg_name ${arg}) + set(current_arg_list) + else() + set(loption_names ${option_names}) + list(FIND loption_names "${arg}" is_option) + if(is_option GREATER -1) + set(${prefix}_${arg} TRUE) + else(is_option GREATER -1) + set(current_arg_list ${current_arg_list} ${arg}) + endif() + endif() + endforeach(arg) + set(${prefix}_${current_arg_name} ${current_arg_list}) endmacro(parse_arguments) macro(add_unit_test) - parse_arguments(TEST "NAME;SOURCES;LIBS" "" ${ARGN}) - set(_test_NAME ${TEST_NAME}) - set(_srcList ${TEST_SOURCES}) - add_executable(${_test_NAME} ${_srcList}) - target_link_libraries(${_test_NAME} ${TEST_LIBS}) - - if(NOT TEST_OUTPUT) - set(TEST_OUTPUT plaintext) - endif(NOT TEST_OUTPUT) - set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests") - - if(KDE4_TEST_OUTPUT STREQUAL "xml") - add_test(${_test_NAME} ${_test_NAME} -xml -o ${_test_NAME}.tml) - else(KDE4_TEST_OUTPUT STREQUAL "xml") - add_test(${_test_NAME} ${_test_NAME}) - endif(KDE4_TEST_OUTPUT STREQUAL "xml") - - if(NOT MSVC_IDE) #not needed for the ide - # if the tests are EXCLUDE_FROM_ALL, add a target "buildtests" to build all tests - if(NOT WITH_TESTS) - get_directory_property(_buildtestsAdded BUILDTESTS_ADDED) - if(NOT _buildtestsAdded) - add_custom_target(buildtests) - set_directory_properties(PROPERTIES BUILDTESTS_ADDED TRUE) - endif() - add_dependencies(buildtests ${_test_NAME}) + parse_arguments(TEST "NAME;SOURCES;LIBS" "" ${ARGN}) + set(_test_NAME ${TEST_NAME}) + set(_srcList ${TEST_SOURCES}) + add_executable(${_test_NAME} ${_srcList}) + target_link_libraries(${_test_NAME} ${TEST_LIBS}) + + if(NOT TEST_OUTPUT) + set(TEST_OUTPUT plaintext) + endif(NOT TEST_OUTPUT) + set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests") + + if(KDE4_TEST_OUTPUT STREQUAL "xml") + add_test(${_test_NAME} ${_test_NAME} -xml -o ${_test_NAME}.tml) + else(KDE4_TEST_OUTPUT STREQUAL "xml") + add_test(${_test_NAME} ${_test_NAME}) + endif(KDE4_TEST_OUTPUT STREQUAL "xml") + + if(NOT MSVC_IDE) #not needed for the ide + # if the tests are EXCLUDE_FROM_ALL, add a target "buildtests" to build all tests + if(NOT WITH_TESTS) + get_directory_property(_buildtestsAdded BUILDTESTS_ADDED) + if(NOT _buildtestsAdded) + add_custom_target(buildtests) + set_directory_properties(PROPERTIES BUILDTESTS_ADDED TRUE) + endif() + add_dependencies(buildtests ${_test_NAME}) + endif() endif() - endif() endmacro(add_unit_test) set(TEST_LIBRARIES - keepassx_core - ${keepasshttp_LIB} - ${autotype_LIB} - Qt5::Core - Qt5::Concurrent - Qt5::Widgets - Qt5::Test - ${GCRYPT_LIBRARIES} - ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES} -) + keepassx_core + ${keepasshttp_LIB} + ${autotype_LIB} + Qt5::Core + Qt5::Concurrent + Qt5::Widgets + Qt5::Test + ${GCRYPT_LIBRARIES} + ${GPGERROR_LIBRARIES} + ${ZLIB_LIBRARIES}) set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp mock/MockClock.cpp util/TemporaryFile.cpp) add_library(testsupport STATIC ${testsupport_SOURCES}) target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test) if(YUBIKEY_FOUND) - set(TEST_LIBRARIES ${TEST_LIBRARIES} ${YUBIKEY_LIBRARIES}) + set(TEST_LIBRARIES ${TEST_LIBRARIES} ${YUBIKEY_LIBRARIES}) endif() add_unit_test(NAME testgroup SOURCES TestGroup.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) add_unit_test(NAME testkdbx2 SOURCES TestKdbx2.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testkdbx3 SOURCES TestKeePass2Format.cpp FailDevice.cpp TestKdbx3.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) add_unit_test(NAME testkdbx4 SOURCES TestKeePass2Format.cpp FailDevice.cpp mock/MockChallengeResponseKey.cpp TestKdbx4.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) add_unit_test(NAME testkeys SOURCES TestKeys.cpp mock/MockChallengeResponseKey.cpp LIBS ${TEST_LIBRARIES}) @@ -137,7 +141,7 @@ add_unit_test(NAME testkeepass2randomstream SOURCES TestKeePass2RandomStream.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testmodified SOURCES TestModified.cpp - LIBS testsupport ${TEST_LIBRARIES}) + LIBS testsupport ${TEST_LIBRARIES}) add_unit_test(NAME testdeletedobjects SOURCES TestDeletedObjects.cpp LIBS ${TEST_LIBRARIES}) @@ -149,14 +153,14 @@ add_unit_test(NAME testwildcardmatcher SOURCES TestWildcardMatcher.cpp LIBS ${TEST_LIBRARIES}) if(WITH_XC_AUTOTYPE) - add_unit_test(NAME testautotype SOURCES TestAutoType.cpp - LIBS ${TEST_LIBRARIES}) - set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) + add_unit_test(NAME testautotype SOURCES TestAutoType.cpp + LIBS ${TEST_LIBRARIES}) + set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) endif() if(WITH_XC_SSHAGENT) - add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp - LIBS sshagent ${TEST_LIBRARIES}) + add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp + LIBS sshagent ${TEST_LIBRARIES}) endif() add_unit_test(NAME testentry SOURCES TestEntry.cpp @@ -183,7 +187,7 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testcsveporter SOURCES TestCsvExporter.cpp +add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testykchallengeresponsekey @@ -198,9 +202,9 @@ add_unit_test(NAME testtools SOURCES TestTools.cpp if(WITH_GUI_TESTS) - # CLI clip tests need X environment on Linux - add_unit_test(NAME testcli SOURCES TestCli.cpp - LIBS testsupport cli ${TEST_LIBRARIES}) + # CLI clip tests need X environment on Linux + add_unit_test(NAME testcli SOURCES TestCli.cpp + LIBS testsupport cli ${TEST_LIBRARIES}) - add_subdirectory(gui) + add_subdirectory(gui) endif(WITH_GUI_TESTS)