Merge pull request #2351 from keepassxreboot/feature/coverage

Improve test coverage, reformat CMakeFiles, and cleanup CLI
This commit is contained in:
Jonathan White 2018-10-19 19:44:36 -04:00 committed by GitHub
commit c749f7018e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 3306 additions and 2101 deletions

View File

@ -1,4 +1,4 @@
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
#
# 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()
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(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")
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 "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
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 "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
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 <sys/prctl.h>
check_cxx_source_compiles("#include <sys/prctl.h>
int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }"
HAVE_PR_SET_DUMPABLE)
HAVE_PR_SET_DUMPABLE)
check_cxx_source_compiles("#include <sys/resource.h>
check_cxx_source_compiles("#include <sys/resource.h>
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 <sys/types.h>
if(APPLE)
check_cxx_source_compiles("#include <sys/types.h>
#include <sys/ptrace.h>
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()

View File

@ -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"

View File

@ -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}"

View File

@ -14,45 +14,43 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -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)
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(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
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(CMAKE_CXX_FLAGS_COVERAGE
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
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"
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()
# 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)
# 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)
IF(NOT LCOV_PATH)
MESSAGE(FATAL_ERROR "lcov not found! Aborting...")
ENDIF() # NOT LCOV_PATH
set(options NONE)
set(oneValueArgs NAME)
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
IF(NOT GENHTML_PATH)
MESSAGE(FATAL_ERROR "genhtml not found! Aborting...")
ENDIF() # NOT GENHTML_PATH
if(NOT LCOV_PATH)
message(FATAL_ERROR "lcov not found! Aborting...")
endif() # NOT LCOV_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")
if(NOT GENHTML_PATH)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif() # NOT GENHTML_PATH
SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}")
# Setup target
add_custom_target(${Coverage_NAME}
# Setup target
ADD_CUSTOM_TARGET(${_targetname}
# 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
# Cleanup lcov
${LCOV_PATH} --directory . --zerocounters
# Run tests
COMMAND ${Coverage_EXECUTABLE}
# Run tests
COMMAND ${test_command} ${ARGV3}
# 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
# 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 ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
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 ${_targetname} POST_BUILD
COMMAND ;
COMMENT "Open ./${_outputname}/index.html in your browser to view the 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
endfunction() # SETUP_TARGET_FOR_COVERAGE_LCOV
# 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)
# 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)
IF(NOT PYTHON_EXECUTABLE)
MESSAGE(FATAL_ERROR "Python not found! Aborting...")
ENDIF() # NOT PYTHON_EXECUTABLE
set(options NONE)
set(oneValueArgs NAME)
set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
IF(NOT GCOVR_PATH)
MESSAGE(FATAL_ERROR "gcovr not found! Aborting...")
ENDIF() # NOT GCOVR_PATH
if(NOT SIMPLE_PYTHON_EXECUTABLE)
message(FATAL_ERROR "python not found! Aborting...")
endif() # NOT SIMPLE_PYTHON_EXECUTABLE
ADD_CUSTOM_TARGET(${_targetname}
if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH
# Run tests
${_testrunner} ${ARGV3}
# 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()
# 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."
)
add_custom_target(${Coverage_NAME}
# Run tests
${Coverage_EXECUTABLE}
# Show info where to find the report
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
COMMAND ;
COMMENT "Cobertura code coverage report saved in ${_outputname}.xml."
)
# 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."
)
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA
# 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

View File

@ -14,21 +14,21 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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)

View File

@ -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()

View File

@ -1,4 +1,4 @@
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
#
# 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)
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(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()
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)
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")
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()
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")
# 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})
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)
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)
install(CODE "
set(gp_tool \"objdump\")
" COMPONENT Runtime)
include(DeployQt4)
install_qt4_executable(${PROGNAME}.exe)
# install Qt5 plugins
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
install(FILES
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
DESTINATION "platforms")
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
install(FILES
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
DESTINATION "imageformats")
include(DeployQt4)
install_qt4_executable(${PROGNAME}.exe)
# install CA cert chains
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
# install Qt5 plugins
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
install(FILES
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
DESTINATION "platforms")
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
install(FILES
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
DESTINATION "imageformats")
# install CA cert chains
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
endif()

View File

@ -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")
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)
endif()
elseif(APPLE)
add_subdirectory(mac)
elseif(WIN32)
add_subdirectory(windows)
endif()
if(X11_FOUND AND X11_Xi_FOUND AND X11_XTest_FOUND AND Qt5X11Extras_FOUND)
add_subdirectory(xcb)
if(WITH_TESTS)
add_subdirectory(test)
endif()
elseif(APPLE)
add_subdirectory(mac)
elseif(WIN32)
add_subdirectory(windows)
endif()
if(WITH_TESTS)
add_subdirectory(test)
endif()
endif()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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})

View File

@ -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;

View File

@ -485,11 +485,6 @@ QString BrowserSettings::generatePassword()
}
}
int BrowserSettings::getbits()
{
return m_passwordGenerator.getbits();
}
void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
{
bool isProxy = supportBrowserProxy();

View File

@ -112,7 +112,6 @@ public:
PasswordGenerator::CharClasses passwordCharClasses();
PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
QString generatePassword();
int getbits();
void updateBinaryPaths(QString customProxyLocation = QString());
private:

View File

@ -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)

View File

@ -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<size_t>(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;
}

View File

@ -14,46 +14,46 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -24,6 +24,7 @@
#include <QTextStream>
#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;
}

View File

@ -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<size_t>(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;
}

View File

@ -16,6 +16,7 @@
*/
#include "Estimate.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include <QTextStream>
@ -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<int>(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<int>(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));

View File

@ -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<CompositeKey>::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<FileKey>::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<Database> 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;
}

View File

@ -19,6 +19,7 @@
#include <stdio.h>
#include "Generate.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include <QTextStream>
@ -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<size_t>(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;
}

View File

@ -19,6 +19,7 @@
#include <stdio.h>
#include "List.h"
#include "cli/Utils.h"
#include <QCommandLineParser>
#include <QTextStream>
@ -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<Database> 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;
}

View File

@ -26,7 +26,7 @@ public:
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

View File

@ -1,3 +1,5 @@
#include <utility>
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
@ -25,6 +27,7 @@
#include <QTextStream>
#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<Database> 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;
}

View File

@ -26,7 +26,7 @@ public:
Locate();
~Locate();
int execute(const QStringList& arguments);
int locateEntry(Database* database, QString searchTerm);
int locateEntry(Database* database, const QString& searchTerm);
};
#endif // KEEPASSXC_LOCATE_H

View File

@ -15,8 +15,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include "Merge.h"
#include <QCommandLineParser>
@ -24,6 +22,9 @@
#include "core/Database.h"
#include "core/Merger.h"
#include "cli/Utils.h"
#include <cstdlib>
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<Database> db1(Database::unlockFromStdin(args.at(0), parser.value(keyFile), Utils::STDOUT, Utils::STDERR));
if (!db1) {
return EXIT_FAILURE;
}
Database* db2;
QScopedPointer<Database> 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;
}

View File

@ -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<Database> 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> 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;

View File

@ -28,7 +28,7 @@ public:
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

View File

@ -15,17 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Show.h"
#include <cstdlib>
#include <stdio.h>
#include "Show.h"
#include <QCommandLineParser>
#include <QTextStream>
#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<Database> 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;
}

View File

@ -26,7 +26,7 @@ public:
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

View File

@ -25,9 +25,25 @@
#endif
#include <QProcess>
#include <QTextStream>
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

View File

@ -19,13 +19,22 @@
#define KEEPASSXC_UTILS_H
#include <QtCore/qglobal.h>
#include <QTextStream>
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

View File

@ -25,7 +25,7 @@
#include <cli/Command.h>
#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();

View File

@ -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@"

236
src/core/Bootstrap.cpp Normal file
View File

@ -0,0 +1,236 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Bootstrap.h"
#include "core/Config.h"
#include "core/Translator.h"
#ifdef Q_OS_WIN
#include <aclapi.h> // for createWindowsDACL()
#include <windows.h> // 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<PTOKEN_USER>(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<PACL>(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

34
src/core/Bootstrap.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef 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

View File

@ -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<con
return db;
}
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename)
Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename, FILE* outputDescriptor, FILE* errorDescriptor)
{
auto compositeKey = QSharedPointer<CompositeKey>::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<PasswordKey>::create();
@ -518,11 +518,19 @@ Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilenam
if (!keyFilename.isEmpty()) {
auto fileKey = QSharedPointer<FileKey>::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);
}

View File

@ -131,7 +131,8 @@ public:
static Database* databaseByUuid(const QUuid& uuid);
static Database* openDatabaseFile(const QString& fileName, QSharedPointer<const CompositeKey> 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);

View File

@ -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<quint32>(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<quint32>(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<quint32>(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<quint32>(passwordChars.size()));
password.append(passwordChars[pos]);
}
@ -111,21 +111,6 @@ QString PasswordGenerator::generatePassword() const
return password;
}
int PasswordGenerator::getbits() const
{
const QVector<PasswordGroup> groups = passwordGroups();
int bits = 0;
QVector<QChar> 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<PasswordGroup> PasswordGenerator::passwordGroups() const
@ -298,9 +280,9 @@ QVector<PasswordGroup> PasswordGenerator::passwordGroups() const
j = group.indexOf(ch);
}
}
if (group.size() > 0) {
if (!group.isEmpty()) {
passwordGroups.replace(i, group);
i++;
++i;
} else {
passwordGroups.remove(i);
}

View File

@ -66,7 +66,6 @@ public:
bool isValid() const;
QString generatePassword() const;
int getbits() const;
static const int DefaultLength = 16;
static const char* DefaultExcludedChars;

View File

@ -18,19 +18,20 @@
*/
#include "Tools.h"
#include "core/Config.h"
#include "core/Translator.h"
#include <QCoreApplication>
#include <QIODevice>
#include <QImageReader>
#include <QLocale>
#include <QStringList>
#include <cctype>
#include <QElapsedTimer>
#include <cctype>
#ifdef Q_OS_WIN
#include <aclapi.h> // for SetSecurityInfo()
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
#include <windows.h> // 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;
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;
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));
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;
}
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);
if (readResult == -1) {
return false;
} else {
result.resize(static_cast<int>(readBytes));
data = result;
return true;
}
}
QString imageReaderFilter()
{
const QList<QByteArray> 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(" ");
}
bool isHex(const QByteArray& ba)
{
for (const unsigned char c : ba) {
if (!std::isxdigit(c)) {
return false;
}
}
} else {
buffer.resize(readResult);
data = buffer;
return true;
}
}
bool isBase64(const QByteArray& ba)
{
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
return regexp.exactMatch(base64);
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);
void sleep(int ms)
{
Q_ASSERT(ms >= 0);
if (readResult == -1) {
return false;
} else {
result.resize(static_cast<int>(readBytes));
data = result;
return true;
}
}
if (ms == 0) {
return;
QString imageReaderFilter()
{
const QList<QByteArray> 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(" ");
}
bool isHex(const QByteArray& ba)
{
for (const unsigned char c : ba) {
if (!std::isxdigit(c)) {
return false;
}
}
return true;
}
bool isBase64(const QByteArray& ba)
{
constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)";
QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
QString base64 = QString::fromLatin1(ba.constData(), ba.size());
return regexp.exactMatch(base64);
}
void sleep(int ms)
{
Q_ASSERT(ms >= 0);
if (ms == 0) {
return;
}
#ifdef Q_OS_WIN
Sleep(uint(ms));
Sleep(uint(ms));
#else
timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000 * 1000;
nanosleep(&ts, nullptr);
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;
}
void wait(int ms)
{
Q_ASSERT(ms >= 0);
QElapsedTimer timer;
timer.start();
if (ms == 0) {
return;
}
QElapsedTimer timer;
timer.start();
if (ms <= 50) {
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
sleep(qMax(ms - static_cast<int>(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));
if (ms <= 50) {
QCoreApplication::processEvents(QEventLoop::AllEvents, ms);
sleep(qMax(ms - static_cast<int>(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));
}
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.");
}
}
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
}
//
// 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<PTOKEN_USER>(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<PACL>(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;
}
}
} // namespace Tools

View File

@ -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);
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 <typename RandomAccessIterator, typename T>
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
{
RandomAccessIterator it = std::lower_bound(begin, end, value);
template <typename RandomAccessIterator, typename T>
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

View File

@ -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;
}

View File

@ -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);

View File

@ -34,7 +34,6 @@ public:
explicit CryptoHash(Algorithm algo, bool hmac = false);
~CryptoHash();
void addData(const QByteArray& data);
void reset();
QByteArray result() const;
void setKey(const QByteArray& data);

View File

@ -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 {};
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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());

View File

@ -1,3 +1,5 @@
#include <utility>
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
@ -18,6 +20,9 @@
#include "KdbxReader.h"
#include "core/Database.h"
#include "core/Endian.h"
#include "format/KdbxXmlWriter.h"
#include <QBuffer>
#define UUID_LENGTH 16
@ -92,7 +97,14 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
}
// read payload
return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase);
auto* db = readDatabaseImpl(device, headerStream.storedData(), std::move(key), keepDatabase);
if (saveXml()) {
m_xmlData.clear();
decryptXmlInnerStream(m_xmlData, db);
}
return db;
}
bool KdbxReader::hasError() const
@ -258,6 +270,23 @@ void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
m_irsAlgo = irsAlgo;
}
/**
* Decrypt protected inner stream fields in XML dump on demand.
* Without the stream key from the KDBX header, the values become worthless.
*
* @param xmlOutput XML dump with decrypted fields
* @param db the database object for which to generate the decrypted XML dump
*/
void KdbxReader::decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const
{
QBuffer buffer;
buffer.setBuffer(&xmlOutput);
buffer.open(QIODevice::WriteOnly);
KdbxXmlWriter writer(m_kdbxVersion);
writer.disableInnerStreamProtection(true);
writer.writeDatabase(&buffer, db);
}
/**
* Raise an error. Use in case of an unexpected read error.
*

View File

@ -86,6 +86,8 @@ protected:
void raiseError(const QString& errorMessage);
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
QScopedPointer<Database> m_db;
QPair<quint32, quint32> m_kdbxSignature;

View File

@ -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;

View File

@ -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;
}

View File

@ -41,6 +41,8 @@ public:
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 @@ private:
const quint32 m_kdbxVersion;
bool m_innerStreamProtectionDisabled = false;
QXmlStreamWriter m_xml;
QPointer<Database> m_db;
QPointer<Metadata> m_meta;

View File

@ -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<QPair<QUuid, QString>> 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"))
};

View File

@ -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;

View File

@ -93,7 +93,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
}
m_reader->setSaveXml(m_saveXml);
return m_reader->readDatabase(device, key, keepDatabase);
return m_reader->readDatabase(device, std::move(key), keepDatabase);
}
bool KeePass2Reader::hasError() const

View File

@ -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

View File

@ -22,8 +22,8 @@
#include <QApplication>
#include <QtNetwork/QLocalServer>
class QLockFile;
class QLockFile;
class QSocketNotifier;
class Application : public QApplication

View File

@ -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,

View File

@ -216,7 +216,7 @@ private:
void setIconFromParent();
void replaceDatabase(Database* db);
Database* m_db;
QPointer<Database> m_db;
QWidget* m_mainWidget;
EditEntryWidget* m_editEntryWidget;
EditEntryWidget* m_historyEditEntryWidget;

View File

@ -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);

View File

@ -81,7 +81,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
}
if (!m_db->key()) {
m_db->setKey(QSharedPointer<CompositeKey>::create());
m_db->setCipher(KeePass2::CIPHER_AES);
m_db->setCipher(KeePass2::CIPHER_AES256);
isDirty = true;
}

View File

@ -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;
}

View File

@ -16,19 +16,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QCommandLineParser>
#include <QFile>
#include <QTextStream>
#include <QCommandLineParser>
#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

View File

@ -1,3 +1,4 @@
# Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
#
# 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 <http://www.gnu.org/licenses/>.
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(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(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})
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(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(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})
if(NOT MSVC_IDE) #not needed for the ide
# if the tests are EXCLUDE_FROM_ALL, add a target "buildtests" to build all tests
if(NOT WITH_TESTS)
get_directory_property(_buildtestsAdded BUILDTESTS_ADDED)
if(NOT _buildtestsAdded)
add_custom_target(buildtests)
set_directory_properties(PROPERTIES BUILDTESTS_ADDED TRUE)
endif()
add_dependencies(buildtests ${_test_NAME})
endif()
endif()
endif()
endmacro(add_unit_test)
set(TEST_LIBRARIES
keepassx_core
${keepasshttp_LIB}
${autotype_LIB}
Qt5::Core
Qt5::Concurrent
Qt5::Widgets
Qt5::Test
${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES}
)
keepassx_core
${keepasshttp_LIB}
${autotype_LIB}
Qt5::Core
Qt5::Concurrent
Qt5::Widgets
Qt5::Test
${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES}
${ZLIB_LIBRARIES})
set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp)
set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp mock/MockClock.cpp util/TemporaryFile.cpp)
add_library(testsupport STATIC ${testsupport_SOURCES})
target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test)
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)

758
tests/TestCli.cpp Normal file
View File

@ -0,0 +1,758 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "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 <QFile>
#include <QClipboard>
#include <QFuture>
#include <QtConcurrent>
#include <QSet>
#include <cstdio>
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<Database> TestCli::readTestDatabase() const
{
Utils::Test::setNextPassword("a");
auto db = QSharedPointer<Database>(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<void> 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<QString>("input");
QTest::addColumn<QString>("length");
QTest::addColumn<QString>("entropy");
QTest::addColumn<QString>("log10");
QTest::addColumn<QStringList>("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<Database> 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<QStringList>("parameters");
QTest::addColumn<QString>("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<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::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<Database> 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<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create("a"));
QFile readBack(m_dbFile->fileName());
readBack.open(QIODevice::ReadOnly);
QScopedPointer<Database> 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"));
}

70
tests/TestCli.h Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TESTCLI_H
#define KEEPASSXC_TESTCLI_H
#include "core/Database.h"
#include "util/TemporaryFile.h"
#include <QTest>
#include <QTextStream>
#include <QFile>
#include <QScopedPointer>
#include <QTemporaryFile>
class TestCli : public QObject
{
Q_OBJECT
private:
QSharedPointer<Database> 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<TemporaryFile> m_dbFile;
QScopedPointer<TemporaryFile> m_stdoutFile;
QScopedPointer<TemporaryFile> m_stderrFile;
QScopedPointer<TemporaryFile> m_stdinFile;
FILE* m_stdoutHandle = stdout;
FILE* m_stderrHandle = stderr;
FILE* m_stdinHandle = stdin;
};
#endif //KEEPASSXC_TESTCLI_H

View File

@ -18,7 +18,7 @@
#include "TestGroup.h"
#include "TestGlobal.h"
#include "stub/TestClock.h"
#include "mock/MockClock.h"
#include <QSignalSpy>
@ -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;
}

View File

@ -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;

View File

@ -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<QString> 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());
}

View File

@ -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;
}

View File

@ -16,7 +16,7 @@
*/
#include "TestModified.h"
#include "stub/TestClock.h"
#include "mock/MockClock.h"
#include <QSignalSpy>
#include <QTest>
@ -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;
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestPasswordGenerator.h"
#include "core/PasswordGenerator.h"
#include "crypto/Crypto.h"
#include <QTest>
#include <QRegularExpression>
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());
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TESTPASSWORDGENERATOR_H
#define KEEPASSXC_TESTPASSWORDGENERATOR_H
#include <QObject>
class TestPasswordGenerator : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testCharClasses();
void testLookalikeExclusion();
};
#endif //KEEPASSXC_TESTPASSWORDGENERATOR_H

View File

@ -26,76 +26,171 @@
#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()
{
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::testEncryptionDecryption_data()
{
QTest::addColumn<SymmetricCipher::Algorithm>("algorithm");
QTest::addColumn<SymmetricCipher::Mode>("mode");
QTest::addColumn<SymmetricCipher::Direction>("direction");
QTest::addColumn<QByteArray>("key");
QTest::addColumn<QByteArray>("iv");
QTest::addColumn<QByteArray>("plainText");
QTest::addColumn<QByteArray>("cipherText");
// 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::testEncryptionDecryption()
{
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("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);
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::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::WriteOnly);
QVERIFY(stream.open(QIODevice::WriteOnly));
QVERIFY(stream.reset());
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(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());
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);
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::testAes128CbcDecryption()
void TestSymmetricCipher::testAesCbcPadding_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;
QTest::addColumn<QByteArray>("key");
QTest::addColumn<QByteArray>("iv");
QTest::addColumn<QByteArray>("cipherText");
QTest::addColumn<QByteArray>("plainText");
QTest::addColumn<QByteArray>("padding");
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);
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");
}
void TestSymmetricCipher::testAesCbcPadding()
{
QFETCH(QByteArray, key);
QFETCH(QByteArray, iv);
QFETCH(QByteArray, cipherText);
QFETCH(QByteArray, plainText);
QFETCH(QByteArray, padding);
// padded with 16 0x10 bytes
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
QByteArray cipherTextPadded = cipherText + padding;
QBuffer buffer(&cipherTextPadded);
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
QVERIFY(stream.init(key, iv));
@ -114,126 +209,48 @@ void TestSymmetricCipher::testAes128CbcDecryption()
QCOMPARE(stream.read(100), plainText);
}
void TestSymmetricCipher::testAes256CbcEncryption()
void TestSymmetricCipher::testInplaceEcb_data()
{
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
QTest::addColumn<QByteArray>("key");
QTest::addColumn<QByteArray>("plainText");
QTest::addColumn<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);
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);
QTest::newRow("AES128")
<< QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c")
<< QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a")
<< QByteArray::fromHex("3ad77bb40d7a3660a89ecaf32466ef97");
}
void TestSymmetricCipher::testAes256CbcDecryption()
void TestSymmetricCipher::testInplaceEcb()
{
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;
QFETCH(QByteArray, key);
QFETCH(QByteArray, plainText);
QFETCH(QByteArray, cipherText);
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.blockSize(), 16);
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);
QCOMPARE(cipher.process(cipherText, &ok), plainText);
QVERIFY(ok);
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);
// padded with 16 0x16 bytes
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
QBuffer buffer(&cipherTextPadded);
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::ReadOnly);
QVERIFY(stream.open(QIODevice::ReadOnly));
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));
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);
}
void TestSymmetricCipher::testAes256CtrEncryption()
{
// 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);
}
void TestSymmetricCipher::testAes256CtrDecryption()
{
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);
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()

View File

@ -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();

View File

@ -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})

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TemporaryFile.h"
#include <QFileInfo>
#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
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (C) 2016 Danny Su <contact@dannysu.com>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TEMPORARYFILE_H
#define KEEPASSX_TEMPORARYFILE_H
#include <QFile>
#include <QObject>
#include <QTemporaryFile>
/**
* 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

View File

@ -18,6 +18,7 @@
#include "TestGui.h"
#include "TestGlobal.h"
#include "gui/Application.h"
#include <QAction>
#include <QApplication>
@ -33,12 +34,12 @@
#include <QPushButton>
#include <QSignalSpy>
#include <QSpinBox>
#include <QTemporaryFile>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#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<DatabaseTabWidget*>("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<qint64>((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<qint64>((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<QWidget*>("databaseOpenWidget");
QLineEdit* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
auto* databaseOpenWidget = m_mainWindow->findChild<QWidget*>("databaseOpenWidget");
auto* editPassword = databaseOpenWidget->findChild<QLineEdit*>("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<CompositeKey>::create();
compositeKey->addKey(QSharedPointer<PasswordKey>::create("test"));
auto fileKey = QSharedPointer<FileKey>::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<QSlider*>("decryptionTimeSlider");
auto algorithmComboBox = wizard->currentPage()->findChild<QComboBox*>("algorithmComboBox");
QTRY_VERIFY(decryptionTimeSlider->isVisible());
QVERIFY(!algorithmComboBox->isVisible());
auto advancedToggle = wizard->currentPage()->findChild<QPushButton*>("advancedSettingsButton");
QTest::mouseClick(advancedToggle, Qt::MouseButton::LeftButton);
QTRY_VERIFY(!decryptionTimeSlider->isVisible());
QVERIFY(algorithmComboBox->isVisible());
auto rounds = wizard->currentPage()->findChild<QSpinBox*>("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<QSpinBox*>("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<QSpinBox*>("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<QLineEdit*>("enterPasswordEdit");
auto* passwordRepeatEdit = passwordWidget->findChild<QLineEdit*>("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<QWidget*>("databaseOpenMergeWidget");
QLineEdit* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("editPassword");
auto* databaseOpenMergeWidget = m_mainWindow->findChild<QWidget*>("databaseOpenMergeWidget");
auto* editPasswordMerge = databaseOpenMergeWidget->findChild<QLineEdit*>("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<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile.close();
Tools::wait(1500);
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(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<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile.close();
Tools::wait(1500);
m_dbFile->open();
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(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<qint64>(unmodifiedMergeDatabase.size())));
m_dbFile.close();
Tools::wait(1500);
QVERIFY(m_dbFile->open());
QVERIFY(m_dbFile->write(unmodifiedMergeDatabase, static_cast<qint64>(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<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
int editCount = 0;
// Select the first entry in the database
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* entryView = m_dbWidget->findChild<EntryView*>("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<QAction*>("actionEntryEdit");
auto* entryEditAction = m_mainWindow->findChild<QAction*>("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*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "_test");
// Apply the edit
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("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<QPlainTextEdit*>("attributesEdit");
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("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<QLineEdit*>("passwordEdit");
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
QString originalPassword = passwordEdit->text();
passwordEdit->setText("newpass");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
MessageWidget* messageWiget = editEntryWidget->findChild<MessageWidget*>("messageWidget");
auto* messageWiget = editEntryWidget->findChild<MessageWidget*>("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*>("editGroupWidget");
QLineEdit* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
QDialogButtonBox* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editGroupWidget = m_dbWidget->findChild<EditGroupWidget*>("editGroupWidget");
auto* nameEdit = editGroupWidget->findChild<QLineEdit*>("editName");
auto* editGroupWidgetButtonBox = editGroupWidget->findChild<QDialogButtonBox*>("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<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("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*>("SearchWidget");
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("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<QToolBar*>("toolBar");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
// Find the new entry action
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
auto* entryNewAction = m_mainWindow->findChild<QAction*>("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*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "test");
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("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<QLineEdit*>("passwordEdit");
QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("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<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
// Find the new entry action
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
auto* entryNewAction = m_mainWindow->findChild<QAction*>("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*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "test");
// Open the password generator
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
auto* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
QTest::mouseClick(generatorButton, Qt::LeftButton);
// Type in some password
QLineEdit* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
auto* editNewPassword = editEntryWidget->findChild<QLineEdit*>("editNewPassword");
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
editNewPassword->setText("");
QTest::keyClicks(editNewPassword, "hello");
@ -659,10 +686,10 @@ void TestGui::testPasswordEntryEntropy()
void TestGui::testDicewareEntryEntropy()
{
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
// Find the new entry action
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
auto* entryNewAction = m_mainWindow->findChild<QAction*>("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*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "test");
// Open the password generator
QToolButton* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
auto* generatorButton = editEntryWidget->findChild<QToolButton*>("togglePasswordGeneratorButton");
QTest::mouseClick(generatorButton, Qt::LeftButton);
// Select Diceware
QTabWidget* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
QWidget* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
auto* tabWidget = editEntryWidget->findChild<QTabWidget*>("tabWidget");
auto* dicewareWidget = editEntryWidget->findChild<QWidget*>("dicewareWidget");
tabWidget->setCurrentWidget(dicewareWidget);
QComboBox* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
auto* comboBoxWordList = dicewareWidget->findChild<QComboBox*>("comboBoxWordList");
comboBoxWordList->setCurrentText("eff_large.wordlist");
QSpinBox* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
auto* spinBoxWordCount = dicewareWidget->findChild<QSpinBox*>("spinBoxWordCount");
spinBoxWordCount->setValue(6);
// Type in some password
QLabel* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
QLabel* strengthLabel = editEntryWidget->findChild<QLabel*>("strengthLabel");
auto* entropyLabel = editEntryWidget->findChild<QLabel*>("entropyLabel");
auto* strengthLabel = editEntryWidget->findChild<QLabel*>("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<QToolBar*>("toolBar");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QCOMPARE(entryView->model()->rowCount(), 1);
@ -716,36 +743,36 @@ void TestGui::testTotp()
triggerAction("actionEntrySetupTotp");
TotpSetupDialog* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
auto* setupTotpDialog = m_dbWidget->findChild<TotpSetupDialog*>("TotpSetupDialog");
Tools::wait(100);
QApplication::processEvents();
QLineEdit* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
auto* seedEdit = setupTotpDialog->findChild<QLineEdit*>("seedEdit");
QString exampleSeed = "gezdgnbvgy3tqojqgezdgnbvgy3tqojq";
QTest::keyClicks(seedEdit, exampleSeed);
QDialogButtonBox* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
auto* setupTotpButtonBox = setupTotpDialog->findChild<QDialogButtonBox*>("buttonBox");
QTest::mouseClick(setupTotpButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
QAction* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
auto* entryEditAction = m_mainWindow->findChild<QAction*>("actionEntryEdit");
QWidget* entryEditWidget = toolBar->widgetForAction(entryEditAction);
QTest::mouseClick(entryEditWidget, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::EditMode);
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
editEntryWidget->setCurrentPage(1);
QPlainTextEdit* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
auto* attrTextEdit = editEntryWidget->findChild<QPlainTextEdit*>("attributesEdit");
QTest::mouseClick(editEntryWidget->findChild<QAbstractButton*>("revealAttributeButton"), Qt::LeftButton);
QCOMPARE(attrTextEdit->toPlainText(), exampleSeed);
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
triggerAction("actionEntryTotp");
TotpDialog* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
QLabel* totpLabel = totpDialog->findChild<QLabel*>("totpLabel");
auto* totpDialog = m_dbWidget->findChild<TotpDialog*>("TotpDialog");
auto* totpLabel = totpDialog->findChild<QLabel*>("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<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
SearchWidget* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
auto* searchWidget = toolBar->findChild<SearchWidget*>("SearchWidget");
QVERIFY(searchWidget->isEnabled());
QLineEdit* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
auto* searchTextEdit = searchWidget->findChild<QLineEdit*>("searchEdit");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QVERIFY(entryView->isVisible());
QAction* clearButton = searchWidget->findChild<QAction*>("clearIcon");
auto* clearButton = searchWidget->findChild<QAction*>("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*>("groupView");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QAction* entryDeleteAction = m_mainWindow->findChild<QAction*>("actionEntryDelete");
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryDeleteAction = m_mainWindow->findChild<QAction*>("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*>("entryView");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QCOMPARE(entryView->model()->rowCount(), 1);
@ -944,8 +971,8 @@ void TestGui::testCloneEntry()
triggerAction("actionEntryClone");
CloneDialog* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
QDialogButtonBox* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("buttonBox");
auto* cloneDialog = m_dbWidget->findChild<CloneDialog*>("CloneDialog");
auto* cloneButtonBox = cloneDialog->findChild<QDialogButtonBox*>("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<QToolBar*>("toolBar");
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
// Find the new entry action
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
auto* entryNewAction = m_mainWindow->findChild<QAction*>("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*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QTest::keyClicks(titleEdit, "test");
QLineEdit* usernameEdit = editEntryWidget->findChild<QLineEdit*>("usernameEdit");
QTest::keyClicks(usernameEdit, "john");
QLineEdit* urlEdit = editEntryWidget->findChild<QLineEdit*>("urlEdit");
QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}");
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("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*>("entryView");
GroupView* groupView = m_dbWidget->findChild<GroupView*>("groupView");
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* groupView = m_dbWidget->findChild<GroupView*>("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<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
auto* actionDatabaseMerge = m_mainWindow->findChild<QAction*>("actionDatabaseMerge", Qt::FindChildrenRecursively);
QCOMPARE(actionDatabaseMerge->isEnabled(), false);
QAction* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
auto* actionDatabaseSave = m_mainWindow->findChild<QAction*>("actionDatabaseSave", Qt::FindChildrenRecursively);
QCOMPARE(actionDatabaseSave->isEnabled(), false);
QWidget* dbWidget = m_tabWidget->currentDatabaseWidget();
QWidget* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
auto* unlockDatabaseWidget = dbWidget->findChild<QWidget*>("unlockDatabaseWidget");
QWidget* editPassword = unlockDatabaseWidget->findChild<QLineEdit*>("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<QSystemTrayIcon*>();
auto* trayIcon = m_mainWindow->findChild<QSystemTrayIcon*>();
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<QToolBar*>("toolBar");
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QWidget* entryNewWidget = toolBar->widgetForAction(m_mainWindow->findChild<QAction*>("actionEntryNew"));
EditEntryWidget* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
QLineEdit* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QLineEdit* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
QLineEdit* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
auto* passwordEdit = editEntryWidget->findChild<QLineEdit*>("passwordEdit");
auto* passwordRepeatEdit = editEntryWidget->findChild<QLineEdit*>("passwordRepeatEdit");
// Add entry "test" and confirm added
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QTest::keyClicks(titleEdit, "test");
QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("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<QAction*>(name);
auto* action = m_mainWindow->findChild<QAction*>(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)

View File

@ -19,17 +19,18 @@
#ifndef KEEPASSX_TESTGUI_H
#define KEEPASSX_TESTGUI_H
#include "TemporaryFile.h"
#include "gui/MainWindow.h"
#include "util/TemporaryFile.h"
#include <QAbstractItemModel>
#include <QObject>
#include <QPointer>
#include <QScopedPointer>
class Database;
class DatabaseTabWidget;
class DatabaseWidget;
class QAbstractItemView;
class MainWindow;
class TestGui : public QObject
{
@ -84,12 +85,12 @@ private:
Qt::MouseButton button,
Qt::KeyboardModifiers stateKey = 0);
QPointer<MainWindow> m_mainWindow;
QScopedPointer<MainWindow> m_mainWindow;
QPointer<DatabaseTabWidget> m_tabWidget;
QPointer<DatabaseWidget> m_dbWidget;
QPointer<Database> m_db;
QByteArray m_dbData;
TemporaryFile m_dbFile;
QScopedPointer<TemporaryFile> m_dbFile;
QString m_dbFileName;
QString m_dbFilePath;
};

View File

@ -15,72 +15,72 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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();
}

View File

@ -22,12 +22,12 @@
#include <QDateTime>
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);

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "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

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TEMPORARYFILE_H
#define KEEPASSXC_TEMPORARYFILE_H
#include <QTemporaryFile>
#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