mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-17 13:02:49 -05:00
Merge pull request #2351 from keepassxreboot/feature/coverage
Improve test coverage, reformat CMakeFiles, and cleanup CLI
This commit is contained in:
commit
c749f7018e
290
CMakeLists.txt
290
CMakeLists.txt
@ -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()
|
||||
|
@ -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"
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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;
|
||||
|
@ -485,11 +485,6 @@ QString BrowserSettings::generatePassword()
|
||||
}
|
||||
}
|
||||
|
||||
int BrowserSettings::getbits()
|
||||
{
|
||||
return m_passwordGenerator.getbits();
|
||||
}
|
||||
|
||||
void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
|
||||
{
|
||||
bool isProxy = supportBrowserProxy();
|
||||
|
@ -112,7 +112,6 @@ public:
|
||||
PasswordGenerator::CharClasses passwordCharClasses();
|
||||
PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
|
||||
QString generatePassword();
|
||||
int getbits();
|
||||
void updateBinaryPaths(QString customProxyLocation = QString());
|
||||
|
||||
private:
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
236
src/core/Bootstrap.cpp
Normal 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
34
src/core/Bootstrap.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
QString generatePassword() const;
|
||||
int getbits() const;
|
||||
|
||||
static const int DefaultLength = 16;
|
||||
static const char* DefaultExcludedChars;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"))
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -22,8 +22,8 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
class QLockFile;
|
||||
|
||||
class QLockFile;
|
||||
class QSocketNotifier;
|
||||
|
||||
class Application : public QApplication
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
89
src/main.cpp
89
src/main.cpp
@ -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
|
||||
|
@ -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
758
tests/TestCli.cpp
Normal 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
70
tests/TestCli.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
131
tests/TestPasswordGenerator.cpp
Normal file
131
tests/TestPasswordGenerator.cpp
Normal 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());
|
||||
}
|
33
tests/TestPasswordGenerator.h
Normal file
33
tests/TestPasswordGenerator.h
Normal 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
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
}
|
@ -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
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
55
tests/util/TemporaryFile.cpp
Normal file
55
tests/util/TemporaryFile.cpp
Normal 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
|
50
tests/util/TemporaryFile.h
Normal file
50
tests/util/TemporaryFile.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user