diff --git a/CMakeLists.txt b/CMakeLists.txt index 41a6b9403f..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,138 +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") - - if(WITH_COVERAGE) - # Include code coverage, use with -DCMAKE_BUILD_TYPE=Coverage - include(CodeCoverage) - setup_target_for_coverage(kp_coverage "make test" coverage) - endif() + 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 + ) +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 Widgets Svg Test LinguistTools DBus REQUIRED) + find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED) elseif(APPLE) - find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Svg 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 Widgets Svg 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) @@ -320,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. @@ -336,32 +342,30 @@ 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}) 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; @@ -370,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}) @@ -385,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/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/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/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/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 c70de316b8..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,192 +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/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)") @@ -236,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}) @@ -271,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) @@ -307,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}) @@ -324,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/BrowserAction.cpp b/src/browser/BrowserAction.cpp index c073bb4b27..e0c3a85016 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(); @@ -377,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/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/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/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/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/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..6523cff97d 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -44,46 +44,48 @@ 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); + QPointer 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; } 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 { @@ -92,14 +94,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..a0f75bc8e3 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,58 @@ void Utils::setStdinEcho(bool enable = true) #endif } -QString Utils::getPassword() +namespace Test { - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - static QTextStream outputTextStream(stdout, QIODevice::WriteOnly); +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); +} +} // namespace Test + +/** + * Read a user password from STDIN or return a password previously + * set by \link setNextPassword(). + * + * @return the password + */ +QString getPassword() +{ + QTextStream out(STDOUT, QIODevice::WriteOnly); + + // return preset password if one is set + if (!Test::nextPasswords.isEmpty()) { + auto password = Test::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 +144,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 +168,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..1d5e0f356e 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -19,13 +19,22 @@ #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(); +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 1462f92b91..0419086638 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(KEEPASSXC_VERSION); + +#ifdef QT_NO_DEBUG + Bootstrap::bootstrapApplication(); +#endif QTextStream out(stdout); QStringList arguments; @@ -69,10 +69,10 @@ 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; + 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/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..8467fb4163 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,146 @@ 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 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; + return true; } +} - 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; - return true; +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 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); - - if (readResult == -1) { - return false; - } else { - result.resize(static_cast(readBytes)); - data = result; - return true; - } + if (readResult == -1) { + return false; + } else { + result.resize(static_cast(readBytes)); + data = result; + 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()); - } - - return formatsStringList.join(" "); - } +QString imageReaderFilter() +{ + const QList formats = QImageReader::supportedImageFormats(); + QStringList formatsStringList; - bool isHex(const QByteArray& ba) - { - for (const unsigned char c : ba) { - if (!std::isxdigit(c)) { - return false; + for (const QByteArray& format : formats) { + for (int i = 0; i < format.size(); i++) { + if (!QChar(format.at(i)).isLetterOrNumber()) { + continue; } } - return true; + formatsStringList.append("*." + QString::fromLatin1(format).toLower()); } - 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); + return formatsStringList.join(" "); +} - QString base64 = QString::fromLatin1(ba.constData(), ba.size()); - - return regexp.exactMatch(base64); - } - - void sleep(int ms) - { - Q_ASSERT(ms >= 0); - - if (ms == 0) { - return; - } - -#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 - } - - void wait(int ms) - { - Q_ASSERT(ms >= 0); - - 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..13d9869f7d 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -30,31 +30,26 @@ class QIODevice; namespace Tools { +QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); +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/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; } 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/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/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.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/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/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/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/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 903974fa70..a37648ec1c 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); + Application::setApplicationVersion(KEEPASSXC_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..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} -) - -set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp) + 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,21 +153,24 @@ 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 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}) @@ -193,6 +200,11 @@ add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp add_unit_test(NAME testtools SOURCES TestTools.cpp LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) - add_subdirectory(gui) + # 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..e10a651d77 --- /dev/null +++ b/tests/TestCli.cpp @@ -0,0 +1,758 @@ +/* + * 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 TemporaryFile()); + m_dbFile->open(); + m_dbFile->write(m_dbData); + m_dbFile->close(); + + m_stdinFile.reset(new TemporaryFile()); + m_stdinFile->open(); + m_stdinHandle = fdopen(m_stdinFile->handle(), "r+"); + Utils::STDIN = m_stdinHandle; + + m_stdoutFile.reset(new TemporaryFile()); + m_stdoutFile->open(); + m_stdoutHandle = fdopen(m_stdoutFile->handle(), "r+"); + Utils::STDOUT = m_stdoutHandle; + + m_stderrFile.reset(new TemporaryFile()); + 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::Test::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::Test::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"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser")); + QCOMPARE(entry->url(), QString("https://example.com/")); + QCOMPARE(entry->password().size(), 20); + + 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(); + 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::Test::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::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); + 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); + + TemporaryFile 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::Test::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::Test::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::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"); + 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::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"); + 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::Test::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::Test::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::Test::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::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::Test::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::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::Test::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); + TemporaryFile tmpFile; + tmpFile.open(); + Kdbx4Writer writer; + writer.writeDatabase(&tmpFile, db.data()); + tmpFile.close(); + + pos = m_stdoutFile->pos(); + 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::Test::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); + TemporaryFile targetFile1; + targetFile1.open(); + writer.writeDatabase(&targetFile1, db.data()); + targetFile1.close(); + + // save another copy with a different password + TemporaryFile 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); + TemporaryFile sourceFile; + sourceFile.open(); + writer.writeDatabase(&sourceFile, db.data()); + sourceFile.close(); + + qint64 pos = m_stdoutFile->pos(); + Utils::Test::setNextPassword("a"); + 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()); + 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::Test::setNextPassword("b"); + Utils::Test::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); + TemporaryFile fileCopy; + fileCopy.open(); + db->metadata()->setRecycleBinEnabled(false); + writer.writeDatabase(&fileCopy, db.data()); + fileCopy.close(); + + qint64 pos = m_stdoutFile->pos(); + + // delete entry and verify + Utils::Test::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::Test::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::Test::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::Test::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::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::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 + QCOMPARE(m_stdoutFile->readAll(), QByteArray("Sample Entry\n" + "http://www.somesite.com/\n")); + + pos = m_stdoutFile->pos(); + Utils::Test::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..691269840e --- /dev/null +++ b/tests/TestCli.h @@ -0,0 +1,70 @@ +/* + * 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 "util/TemporaryFile.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/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/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/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/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..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 TemporaryFile.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/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..450f09474c 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 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()))); - 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"); @@ -250,22 +292,23 @@ void TestGui::createDatabaseCallback() TemporaryFile 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); @@ -1048,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(); @@ -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..4df606f4a2 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 "util/TemporaryFile.h" #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; }; 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); 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