diff --git a/.gitignore b/.gitignore
index c96688e9f..903910a37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,5 @@ release*/
*.swp
.DS_Store
-.version
\ No newline at end of file
+.version
+\.scannerwork/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a4cb4d1fc..dfba16788 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -339,6 +339,8 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
endif()
+include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
+
# Optional
if(WITH_XC_YUBIKEY)
find_package(YubiKey REQUIRED)
diff --git a/COPYING b/COPYING
index 8095ef067..5108458d8 100644
--- a/COPYING
+++ b/COPYING
@@ -235,3 +235,7 @@ License: LGPL-2.1
Files: share/macosx/dmg-background.tiff
Copyright: 2008-2014, Andrey Tarantsov
License: MIT
+
+Files: cmake/DeployQt.cmake
+Copyright: 2018, Nathan Osman
+License: MIT
diff --git a/README.md b/README.md
index b09a5d208..fc63beda2 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
#
KeePassXC
-[/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [](https://coveralls.io/github/keepassxreboot/keepassxc)
+[/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [](https://ci.appveyor.com/project/droidmonkey/keepassxc) [](https://codecov.io/gh/keepassxreboot/keepassxc)
+
## About KeePassXC
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
@@ -32,7 +33,7 @@ so please check out your distribution's package list to see if KeePassXC is avai
- Browser integration with KeePassHTTP-Connector for
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
-[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP)
+[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#note-about-keepasshttp)
- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk)
- Many bug fixes
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..bcb9b9e05
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,31 @@
+image:
+ - Visual Studio 2017
+
+configuration:
+ - Debug
+
+environment:
+ matrix:
+ - CC: gcc
+ CXX: g++
+
+clone_depth: 1
+
+platform:
+ - x64
+
+matrix:
+ fast_finish: true
+
+install:
+ - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "pacman --noconfirm -Syu"
+ - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/install.sh"
+
+before_build:
+ - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/before-build.sh"
+
+build_script:
+ - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/build.sh"
+
+test_script:
+ - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/test.sh"
diff --git a/ci/appveyor/before-build.sh b/ci/appveyor/before-build.sh
new file mode 100644
index 000000000..ff8305985
--- /dev/null
+++ b/ci/appveyor/before-build.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/bash
+
+cd /c/projects/keepassxc
+mkdir build && cd build
+
+if [ "${configuration}" = "Debug" ]; then
+ cmake -G "MSYS Makefiles" \
+ -DCMAKE_BUILD_TYPE=${configuration} \
+ -DWITH_XC_ALL=ON \
+ -DWITH_TESTS=ON \
+ -DWITH_GUI_TESTS=ON \
+ -DWITH_ASAN=ON \
+ ..
+else
+ cmake -G "MSYS Makefiles" \
+ -DCMAKE_BUILD_TYPE=${configuration} \
+ -DWITH_XC_ALL=ON \
+ -DWITH_TESTS=ON \
+ -DWITH_GUI_TESTS=ON \
+ ..
+fi
diff --git a/ci/appveyor/build.sh b/ci/appveyor/build.sh
new file mode 100644
index 000000000..b89ebd3ad
--- /dev/null
+++ b/ci/appveyor/build.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/bash
+
+cd /c/projects/keepassxc/build
+
+make -j2
diff --git a/ci/appveyor/install.sh b/ci/appveyor/install.sh
new file mode 100644
index 000000000..c5c34c327
--- /dev/null
+++ b/ci/appveyor/install.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/bash
+
+# Pacman derived dependencies
+pacman --needed --noconfirm -S \
+ mingw-w64-$(uname -m)-cmake \
+ mingw-w64-$(uname -m)-libgcrypt \
+ mingw-w64-$(uname -m)-zlib \
+ mingw-w64-$(uname -m)-libsodium \
+ mingw-w64-$(uname -m)-argon2 \
+ mingw-w64-$(uname -m)-qt5
+
+# Yubikey library
+curl -O -J -L https://developers.yubico.com/yubikey-personalization/Releases/ykpers-1.18.1-win64.zip
+7z x ykpers-1.18.1-win64.zip -o"/mingw64/" -aoa
+
+# qrencode library
+curl -O -J -L https://fukuchi.org/works/qrencode/qrencode-4.0.0.tar.gz
+tar -xf qrencode-4.0.0.tar.gz
+
+cd qrencode-4.0.0
+mkdir build && cd build
+cmake -G "MSYS Makefiles" -DBUILD_SHARED_LIBS=YES -DWITH_TOOLS=NO -DCMAKE_BUILD_TYPE=${configuration} ..
+make -j2
+make install PREFIX="/mingw64"
\ No newline at end of file
diff --git a/ci/appveyor/test.sh b/ci/appveyor/test.sh
new file mode 100644
index 000000000..b7a8a2121
--- /dev/null
+++ b/ci/appveyor/test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/bash
+
+cd /c/projects/keepassxc/build
+
+make test ARGS+="-E testgui --output-on-failure --verbose"
+make test ARGS+="-R testgui --output-on-failure --verbose"
diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake
index a0b0ef526..d10f79723 100644
--- a/cmake/CodeCoverage.cmake
+++ b/cmake/CodeCoverage.cmake
@@ -131,6 +131,10 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
ENDIF() # NOT GENHTML_PATH
SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info")
+ IF(MINGW)
+ # Replace C:/ with /C for MINGW
+ STRING(REGEX REPLACE "^([a-zA-Z]):" "/\\1" coverage_info ${coverage_info})
+ ENDIF()
SET(coverage_cleaned "${coverage_info}.cleaned")
SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}")
diff --git a/cmake/DeployQt.cmake b/cmake/DeployQt.cmake
new file mode 100644
index 000000000..a9d239be8
--- /dev/null
+++ b/cmake/DeployQt.cmake
@@ -0,0 +1,88 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Nathan Osman
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+find_package(Qt5Core REQUIRED)
+
+# Retrieve the absolute path to qmake and then use that path to find
+# the windeployqt and macdeployqt binaries
+get_target_property(_qmake_executable Qt5::qmake IMPORTED_LOCATION)
+get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY)
+
+find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}")
+if(WIN32 AND NOT WINDEPLOYQT_EXECUTABLE)
+ message(FATAL_ERROR "windeployqt not found")
+endif()
+
+find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}")
+if(APPLE AND NOT MACDEPLOYQT_EXECUTABLE)
+ message(FATAL_ERROR "macdeployqt not found")
+endif()
+
+# Add commands that copy the required Qt files to the same directory as the
+# target after being built as well as including them in final installation
+function(windeployqt target)
+
+ # Run windeployqt immediately after build
+ add_custom_command(TARGET ${target} POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E
+ env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}"
+ --verbose 0
+ --no-compiler-runtime
+ --no-angle
+ --no-opengl-sw
+ \"$\"
+ COMMENT "Deploying Qt..."
+ )
+
+ # windeployqt doesn't work correctly with the system runtime libraries,
+ # so we fall back to one of CMake's own modules for copying them over
+
+ # Doing this with MSVC 2015 requires CMake 3.6+
+ if((MSVC_VERSION VERSION_EQUAL 1900 OR MSVC_VERSION VERSION_GREATER 1900)
+ AND CMAKE_VERSION VERSION_LESS "3.6")
+ message(WARNING "Deploying with MSVC 2015+ requires CMake 3.6+")
+ endif()
+
+ set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE)
+ include(InstallRequiredSystemLibraries)
+ foreach(lib ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS})
+ get_filename_component(filename "${lib}" NAME)
+ add_custom_command(TARGET ${target} POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E
+ copy_if_different "${lib}" \"$\"
+ COMMENT "Copying ${filename}..."
+ )
+ endforeach()
+endfunction()
+
+# Add commands that copy the required Qt files to the application bundle
+# represented by the target.
+function(macdeployqt target)
+ add_custom_command(TARGET ${target} POST_BUILD
+ COMMAND "${MACDEPLOYQT_EXECUTABLE}"
+ \"$/../..\"
+ -always-overwrite
+ COMMENT "Deploying Qt..."
+ )
+endfunction()
+
+mark_as_advanced(WINDEPLOYQT_EXECUTABLE MACDEPLOYQT_EXECUTABLE)
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 000000000..0ba2bee8f
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,18 @@
+# https://about.sonarcloud.io/get-started/
+# Run the SonarCloud tools with the follow parameters:
+# Run in the cmake build directory after cmake: build-wrapper-[platform]-x86-64 --out-dir bw-output make clean all
+# Run in the project root directory: sonar-scanner.bat -Dsonar.cfamily.build-wrapper-output=build/bw-output -Dsonar.cfamily.gcov.reportsPath=build -Dsonar.login=[AUTH_TOKEN]
+
+# required metadata
+sonar.projectKey=keepassxc
+sonar.organization=droidmonkey-github
+sonar.projectName=keepassxc
+sonar.host.url=https://sonarcloud.io
+
+# path to source directories (required)
+sonar.sources=src
+sonar.tests=tests
+
+sonar.cfamily.threads=2
+
+sonar.exclusions=**/zxcvbn/*
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 432d10eed..012fc29db 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -313,11 +313,8 @@ if(APPLE AND WITH_APP_BUNDLE)
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")
+ include(DeployQt)
+ macdeployqt(${PROGNAME})
endif()
install(TARGETS ${PROGNAME}
@@ -378,8 +375,8 @@ if(MINGW)
set(gp_tool \"objdump\")
" COMPONENT Runtime)
- include(DeployQt4)
- install_qt4_executable(${PROGNAME}.exe)
+ include(DeployQt)
+ windeployqt(${PROGNAME})
# install Qt5 plugins
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp
index 89c24e55e..aaa742a09 100644
--- a/src/autotype/AutoType.cpp
+++ b/src/autotype/AutoType.cpp
@@ -756,9 +756,10 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence)
}
} else if (AutoType::checkHighRepetition(sequence)) {
QMessageBox::StandardButton reply;
- reply =
- QMessageBox::question(nullptr, tr("Auto-Type"), tr("This Auto-Type command contains arguments which are "
- "repeated very often. Do you really want to proceed?"));
+ reply = QMessageBox::question(nullptr,
+ tr("Auto-Type"),
+ tr("This Auto-Type command contains arguments which are "
+ "repeated very often. Do you really want to proceed?"));
if (reply == QMessageBox::No) {
return false;
diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt
index 08c532784..9ec8b71ac 100644
--- a/src/autotype/mac/CMakeLists.txt
+++ b/src/autotype/mac/CMakeLists.txt
@@ -11,10 +11,11 @@ set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
if(WITH_APP_BUNDLE)
+ include(DeployQt)
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
+ COMMAND ${MACDEPLOYQT_EXECUTABLE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying autotype plugin")
else()
diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp
index 4a75797fd..76163b9d9 100644
--- a/src/browser/BrowserSettings.cpp
+++ b/src/browser/BrowserSettings.cpp
@@ -261,6 +261,66 @@ void BrowserSettings::setPasswordUseSpecial(bool useSpecial)
config()->set("generator/SpecialChars", useSpecial);
}
+bool BrowserSettings::passwordUseBraces()
+{
+ return config()->get("generator/Braces", PasswordGenerator::DefaultBraces).toBool();
+}
+
+void BrowserSettings::setPasswordUseBraces(bool useBraces)
+{
+ config()->set("generator/Braces", useBraces);
+}
+
+bool BrowserSettings::passwordUsePunctuation()
+{
+ return config()->get("generator/Punctuation", PasswordGenerator::DefaultQuotes).toBool();
+}
+
+void BrowserSettings::setPasswordUsePunctuation(bool usePunctuation)
+{
+ config()->set("generator/Punctuation", usePunctuation);
+}
+
+bool BrowserSettings::passwordUseQuotes()
+{
+ return config()->get("generator/Quotes", PasswordGenerator::DefaultQuotes).toBool();
+}
+
+void BrowserSettings::setPasswordUseQuotes(bool useQuotes)
+{
+ config()->set("generator/Quotes", useQuotes);
+}
+
+bool BrowserSettings::passwordUseDashes()
+{
+ return config()->get("generator/Dashes", PasswordGenerator::DefaultDashes).toBool();
+}
+
+void BrowserSettings::setPasswordUseDashes(bool useDashes)
+{
+ config()->set("generator/Dashes", useDashes);
+}
+
+bool BrowserSettings::passwordUseMath()
+{
+ return config()->get("generator/Math", PasswordGenerator::DefaultMath).toBool();
+}
+
+void BrowserSettings::setPasswordUseMath(bool useMath)
+{
+ config()->set("generator/Math", useMath);
+}
+
+bool BrowserSettings::passwordUseLogograms()
+{
+ return config()->get("generator/Logograms", PasswordGenerator::DefaultLogograms).toBool();
+}
+
+void BrowserSettings::setPasswordUseLogograms(bool useLogograms)
+{
+ config()->set("generator/Logograms", useLogograms);
+}
+
bool BrowserSettings::passwordUseEASCII()
{
return config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool();
@@ -271,6 +331,26 @@ void BrowserSettings::setPasswordUseEASCII(bool useEASCII)
config()->set("generator/EASCII", useEASCII);
}
+bool BrowserSettings::advancedMode()
+{
+ return config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool();
+}
+
+void BrowserSettings::setAdvancedMode(bool advancedMode)
+{
+ config()->set("generator/AdvancedMode", advancedMode);
+}
+
+QString BrowserSettings::passwordExcludedChars()
+{
+ return config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString();
+}
+
+void BrowserSettings::setPasswordExcludedChars(QString chars)
+{
+ config()->set("generator/ExcludedChars", chars);
+}
+
int BrowserSettings::passPhraseWordCount()
{
return config()->get("generator/WordCount", PassphraseGenerator::DefaultWordCount).toInt();
@@ -347,6 +427,24 @@ PasswordGenerator::CharClasses BrowserSettings::passwordCharClasses()
if (passwordUseSpecial()) {
classes |= PasswordGenerator::SpecialCharacters;
}
+ if (passwordUseBraces()) {
+ classes |= PasswordGenerator::Braces;
+ }
+ if (passwordUsePunctuation()) {
+ classes |= PasswordGenerator::Punctuation;
+ }
+ if (passwordUseQuotes()) {
+ classes |= PasswordGenerator::Quotes;
+ }
+ if (passwordUseDashes()) {
+ classes |= PasswordGenerator::Dashes;
+ }
+ if (passwordUseMath()) {
+ classes |= PasswordGenerator::Math;
+ }
+ if (passwordUseLogograms()) {
+ classes |= PasswordGenerator::Logograms;
+ }
if (passwordUseEASCII()) {
classes |= PasswordGenerator::EASCII;
}
diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h
index b02fd4759..3c79dbed2 100644
--- a/src/browser/BrowserSettings.h
+++ b/src/browser/BrowserSettings.h
@@ -76,8 +76,24 @@ public:
static void setPasswordUseUppercase(bool useUppercase);
static bool passwordUseSpecial();
static void setPasswordUseSpecial(bool useSpecial);
+ static bool passwordUseBraces();
+ static void setPasswordUseBraces(bool useBraces);
+ static bool passwordUsePunctuation();
+ static void setPasswordUsePunctuation(bool usePunctuation);
+ static bool passwordUseQuotes();
+ static void setPasswordUseQuotes(bool useQuotes);
+ static bool passwordUseDashes();
+ static void setPasswordUseDashes(bool useDashes);
+ static bool passwordUseMath();
+ static void setPasswordUseMath(bool useMath);
+ static bool passwordUseLogograms();
+ static void setPasswordUseLogograms(bool useLogograms);
static bool passwordUseEASCII();
static void setPasswordUseEASCII(bool useEASCII);
+ static bool advancedMode();
+ static void setAdvancedMode(bool advancedMode);
+ static QString passwordExcludedChars();
+ static void setPasswordExcludedChars(QString chars);
static int passPhraseWordCount();
static void setPassPhraseWordCount(int wordCount);
static QString passPhraseWordSeparator();
diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp
index 45b67259c..6a5be3f07 100644
--- a/src/cli/Generate.cpp
+++ b/src/cli/Generate.cpp
@@ -44,19 +44,41 @@ int Generate::execute(const QStringList& arguments)
parser.setApplicationDescription(this->description);
QCommandLineOption len(QStringList() << "L"
<< "length",
- QObject::tr("Length of the generated password."),
+ QObject::tr("Length of the generated password"),
QObject::tr("length"));
parser.addOption(len);
- QCommandLineOption lower(QStringList() << "l", QObject::tr("Use lowercase characters in the generated password."));
+ QCommandLineOption lower(QStringList() << "l"
+ << "lower",
+ QObject::tr("Use lowercase characters"));
parser.addOption(lower);
- QCommandLineOption upper(QStringList() << "u", QObject::tr("Use uppercase characters in the generated password."));
+ QCommandLineOption upper(QStringList() << "u"
+ << "upper",
+ QObject::tr("Use uppercase characters"));
parser.addOption(upper);
- QCommandLineOption numeric(QStringList() << "n", QObject::tr("Use numbers in the generated password."));
+ QCommandLineOption numeric(QStringList() << "n"
+ << "numeric",
+ QObject::tr("Use numbers."));
parser.addOption(numeric);
- QCommandLineOption special(QStringList() << "s", QObject::tr("Use special characters in the generated password."));
+ QCommandLineOption special(QStringList() << "s"
+ << "special",
+ QObject::tr("Use special characters"));
parser.addOption(special);
- QCommandLineOption extended(QStringList() << "e", QObject::tr("Use extended ASCII in the generated password."));
+ QCommandLineOption extended(QStringList() << "e"
+ << "extended",
+ QObject::tr("Use extended ASCII"));
parser.addOption(extended);
+ QCommandLineOption exclude(QStringList() << "x"
+ << "exclude",
+ QObject::tr("Exclude character set"),
+ QObject::tr("chars"));
+ parser.addOption(exclude);
+ QCommandLineOption exclude_similar(QStringList() << "exclude-similar",
+ QObject::tr("Exclude similar looking characters"));
+ parser.addOption(exclude_similar);
+ QCommandLineOption every_group(QStringList() << "every-group",
+ QObject::tr("Include characters from every selected group"));
+ parser.addOption(every_group);
+
parser.process(arguments);
const QStringList args = parser.positionalArguments();
@@ -92,8 +114,18 @@ int Generate::execute(const QStringList& arguments)
classes |= PasswordGenerator::EASCII;
}
+ PasswordGenerator::GeneratorFlags flags = 0x0;
+
+ if (parser.isSet(exclude_similar)) {
+ flags |= PasswordGenerator::ExcludeLookAlike;
+ }
+ if (parser.isSet(every_group)) {
+ flags |= PasswordGenerator::CharFromEveryGroup;
+ }
+
passwordGenerator.setCharClasses(classes);
- passwordGenerator.setFlags(PasswordGenerator::DefaultFlags);
+ passwordGenerator.setFlags(flags);
+ passwordGenerator.setExcludedChars(parser.value(exclude));
if (!passwordGenerator.isValid()) {
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp
index 00984d313..01b715072 100644
--- a/src/core/PasswordGenerator.cpp
+++ b/src/core/PasswordGenerator.cpp
@@ -21,10 +21,13 @@
#include "crypto/Random.h"
#include
+const char* PasswordGenerator::DefaultExcludedChars = "";
+
PasswordGenerator::PasswordGenerator()
: m_length(0)
, m_classes(0)
, m_flags(0)
+ , m_excluded(PasswordGenerator::DefaultExcludedChars)
{
}
@@ -56,6 +59,11 @@ void PasswordGenerator::setFlags(const GeneratorFlags& flags)
m_flags = flags;
}
+void PasswordGenerator::setExcludedChars(const QString& chars)
+{
+ m_excluded = chars;
+}
+
QString PasswordGenerator::generatePassword() const
{
Q_ASSERT(isValid());
@@ -130,6 +138,10 @@ bool PasswordGenerator::isValid() const
return false;
}
+ if (passwordGroups().size() == 0) {
+ return false;
+ }
+
return true;
}
@@ -140,7 +152,8 @@ QVector PasswordGenerator::passwordGroups() const
if (m_classes & LowerLetters) {
PasswordGroup group;
- for (int i = 97; i < (97 + 26); i++) {
+ for (int i = 97; i <= (97 + 25); i++) {
+
if ((m_flags & ExcludeLookAlike) && (i == 108)) { // "l"
continue;
}
@@ -153,7 +166,8 @@ QVector PasswordGenerator::passwordGroups() const
if (m_classes & UpperLetters) {
PasswordGroup group;
- for (int i = 65; i < (65 + 26); i++) {
+ for (int i = 65; i <= (65 + 25); i++) {
+
if ((m_flags & ExcludeLookAlike) && (i == 73 || i == 79)) { // "I" and "O"
continue;
}
@@ -176,28 +190,79 @@ QVector PasswordGenerator::passwordGroups() const
passwordGroups.append(group);
}
- if (m_classes & SpecialCharacters) {
+ if (m_classes & Braces) {
PasswordGroup group;
- for (int i = 33; i <= 47; i++) {
- group.append(i);
+ // ()[]{}
+ group.append(40);
+ group.append(41);
+ group.append(91);
+ group.append(93);
+ group.append(123);
+ group.append(125);
+
+ passwordGroups.append(group);
+ }
+ if (m_classes & Punctuation) {
+ PasswordGroup group;
+
+ // .,:;
+ group.append(44);
+ group.append(46);
+ group.append(58);
+ group.append(59);
+
+ passwordGroups.append(group);
+ }
+ if (m_classes & Quotes) {
+ PasswordGroup group;
+
+ // "'
+ group.append(34);
+ group.append(39);
+
+ passwordGroups.append(group);
+ }
+ if (m_classes & Dashes) {
+ PasswordGroup group;
+
+ // -/\_|
+ group.append(45);
+ group.append(47);
+ group.append(92);
+ group.append(95);
+ if (!(m_flags & ExcludeLookAlike)) {
+ group.append(124); // "|"
}
- for (int i = 58; i <= 64; i++) {
- group.append(i);
- }
-
- for (int i = 91; i <= 96; i++) {
- group.append(i);
- }
-
- for (int i = 123; i <= 126; i++) {
- if ((m_flags & ExcludeLookAlike) && (i == 124)) { // "|"
- continue;
- }
-
+ passwordGroups.append(group);
+ }
+ if (m_classes & Math) {
+ PasswordGroup group;
+
+ // !*+-<=>?
+ group.append(33);
+ group.append(42);
+ group.append(43);
+ group.append(60);
+ group.append(61);
+ group.append(62);
+ group.append(63);
+
+ passwordGroups.append(group);
+ }
+ if (m_classes & Logograms) {
+ PasswordGroup group;
+
+ // #$%&
+ for (int i = 35; i <= 38; i++) {
group.append(i);
}
+ // @^`~
+ group.append(64);
+ group.append(94);
+ group.append(96);
+ group.append(126);
passwordGroups.append(group);
}
@@ -220,6 +285,27 @@ QVector PasswordGenerator::passwordGroups() const
passwordGroups.append(group);
}
+ // Loop over character groups and remove excluded characters from them;
+ // remove empty groups
+ int i = 0;
+ while (i != passwordGroups.size()) {
+ PasswordGroup group = passwordGroups[i];
+
+ for (QChar ch : m_excluded) {
+ int j = group.indexOf(ch);
+ while (j != -1) {
+ group.remove(j);
+ j = group.indexOf(ch);
+ }
+ }
+ if (group.size() > 0) {
+ passwordGroups.replace(i, group);
+ i++;
+ } else {
+ passwordGroups.remove(i);
+ }
+ }
+
return passwordGroups;
}
@@ -236,7 +322,22 @@ int PasswordGenerator::numCharClasses() const
if (m_classes & Numbers) {
numClasses++;
}
- if (m_classes & SpecialCharacters) {
+ if (m_classes & Braces) {
+ numClasses++;
+ }
+ if (m_classes & Punctuation) {
+ numClasses++;
+ }
+ if (m_classes & Quotes) {
+ numClasses++;
+ }
+ if (m_classes & Dashes) {
+ numClasses++;
+ }
+ if (m_classes & Math) {
+ numClasses++;
+ }
+ if (m_classes & Logograms) {
numClasses++;
}
if (m_classes & EASCII) {
diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h
index d6866b5ef..1d6ac73f6 100644
--- a/src/core/PasswordGenerator.h
+++ b/src/core/PasswordGenerator.h
@@ -30,19 +30,26 @@ class PasswordGenerator
public:
enum CharClass
{
- LowerLetters = 0x1,
- UpperLetters = 0x2,
- Numbers = 0x4,
- SpecialCharacters = 0x8,
- EASCII = 0x10,
+ LowerLetters = (1 << 0),
+ UpperLetters = (1 << 1),
+ Numbers = (1 << 2),
+ Braces = (1 << 3),
+ Punctuation = (1 << 4),
+ Quotes = (1 << 5),
+ Dashes = (1 << 6),
+ Math = (1 << 7),
+ Logograms = (1 << 8),
+ SpecialCharacters = Braces | Punctuation | Quotes | Dashes | Math | Logograms,
+ EASCII = (1 << 9),
DefaultCharset = LowerLetters | UpperLetters | Numbers
};
Q_DECLARE_FLAGS(CharClasses, CharClass)
enum GeneratorFlag
{
- ExcludeLookAlike = 0x1,
- CharFromEveryGroup = 0x2,
+ ExcludeLookAlike = (1 << 0),
+ CharFromEveryGroup = (1 << 1),
+ AdvancedMode = (1 << 2),
DefaultFlags = ExcludeLookAlike | CharFromEveryGroup
};
Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag)
@@ -54,6 +61,7 @@ public:
void setLength(int length);
void setCharClasses(const CharClasses& classes);
void setFlags(const GeneratorFlags& flags);
+ void setExcludedChars(const QString& chars);
bool isValid() const;
@@ -61,10 +69,18 @@ public:
int getbits() const;
static const int DefaultLength = 16;
+ static const char* DefaultExcludedChars;
static constexpr bool DefaultLower = (DefaultCharset & LowerLetters) != 0;
static constexpr bool DefaultUpper = (DefaultCharset & UpperLetters) != 0;
static constexpr bool DefaultNumbers = (DefaultCharset & Numbers) != 0;
static constexpr bool DefaultSpecial = (DefaultCharset & SpecialCharacters) != 0;
+ static constexpr bool DefaultAdvancedMode = (DefaultFlags & AdvancedMode) != 0;
+ static constexpr bool DefaultBraces = (DefaultCharset & Braces) != 0;
+ static constexpr bool DefaultPunctuation = (DefaultCharset & Punctuation) != 0;
+ static constexpr bool DefaultQuotes = (DefaultCharset & Quotes) != 0;
+ static constexpr bool DefaultDashes = (DefaultCharset & Dashes) != 0;
+ static constexpr bool DefaultMath = (DefaultCharset & Math) != 0;
+ static constexpr bool DefaultLogograms = (DefaultCharset & Logograms) != 0;
static constexpr bool DefaultEASCII = (DefaultCharset & EASCII) != 0;
static constexpr bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0;
static constexpr bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0;
@@ -76,6 +92,7 @@ private:
int m_length;
CharClasses m_classes;
GeneratorFlags m_flags;
+ QString m_excluded;
Q_DISABLE_COPY(PasswordGenerator)
};
diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp
index 5f51fa47d..458d42988 100644
--- a/src/core/Tools.cpp
+++ b/src/core/Tools.cpp
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
@@ -56,8 +57,9 @@
namespace Tools
{
- QString humanReadableFileSize(qint64 bytes)
+ QString humanReadableFileSize(qint64 bytes, quint32 precision)
{
+ constexpr auto kibibyte = 1024;
double size = bytes;
QStringList units = QStringList() << "B"
@@ -67,12 +69,12 @@ namespace Tools
int i = 0;
int maxI = units.size() - 1;
- while ((size >= 1024) && (i < maxI)) {
- size /= 1024;
+ while ((size >= kibibyte) && (i < maxI)) {
+ size /= kibibyte;
i++;
}
- return QString("%1 %2").arg(QLocale().toString(size, 'f', 2), units.at(i));
+ return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i));
}
bool hasChild(const QObject* parent, const QObject* child)
@@ -147,8 +149,8 @@ namespace Tools
bool isHex(const QByteArray& ba)
{
- for (char c : ba) {
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ for (const unsigned char c : ba) {
+ if (!std::isxdigit(c)) {
return false;
}
}
@@ -158,8 +160,8 @@ namespace Tools
bool isBase64(const QByteArray& ba)
{
- QRegExp regexp(
- "^(?:[a-z0-9+/]{4})*(?:[a-z0-9+/]{3}=|[a-z0-9+/]{2}==)?$", Qt::CaseInsensitive, QRegExp::RegExp2);
+ 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());
@@ -318,14 +320,15 @@ namespace Tools
}
// 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
- );
+ 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:
diff --git a/src/core/Tools.h b/src/core/Tools.h
index b11760079..9fd497995 100644
--- a/src/core/Tools.h
+++ b/src/core/Tools.h
@@ -32,7 +32,7 @@ class QIODevice;
namespace Tools
{
- QString humanReadableFileSize(qint64 bytes);
+ 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);
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 7ee317ad0..f1eb1793b 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -27,7 +27,6 @@
#include
#include
#include
-#include
#include
#include
#include
diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp
index ce80c03b6..a58cc6d82 100644
--- a/src/gui/EditWidget.cpp
+++ b/src/gui/EditWidget.cpp
@@ -125,8 +125,14 @@ void EditWidget::enableApplyButton(bool enabled)
void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
- m_ui->messageWidget->setCloseButtonVisible(false);
- m_ui->messageWidget->showMessage(text, type, 2000);
+ // Show error messages for a longer time to make sure the user can read them
+ if (type == MessageWidget::Error) {
+ m_ui->messageWidget->setCloseButtonVisible(true);
+ m_ui->messageWidget->showMessage(text, type, 15000);
+ } else {
+ m_ui->messageWidget->setCloseButtonVisible(false);
+ m_ui->messageWidget->showMessage(text, type, 2000);
+ }
}
void EditWidget::hideMessage()
diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp
index 4292234e1..d5d97dde1 100644
--- a/src/gui/EditWidgetIcons.cpp
+++ b/src/gui/EditWidgetIcons.cpp
@@ -19,7 +19,6 @@
#include "EditWidgetIcons.h"
#include "ui_EditWidgetIcons.h"
-#include
#include
#include
@@ -93,6 +92,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
SIGNAL(widgetUpdated()));
m_ui->faviconButton->setVisible(false);
+ m_ui->addButton->setEnabled(true);
}
EditWidgetIcons::~EditWidgetIcons()
@@ -269,7 +269,9 @@ void EditWidgetIcons::fetchFinished()
}
if (!image.isNull()) {
- addCustomIcon(image);
+ if (!addCustomIcon(image)) {
+ emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
+ }
} else if (!m_urlsToTry.empty()) {
m_redirects = 0;
startFetchFavicon(m_urlsToTry.takeFirst());
@@ -325,35 +327,66 @@ void EditWidgetIcons::addCustomIconFromFile()
if (m_database) {
QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files"));
- QString filename = QFileDialog::getOpenFileName(this, tr("Select Image"), "", filter);
- if (!filename.isEmpty()) {
- auto icon = QImage(filename);
- if (!icon.isNull()) {
- addCustomIcon(QImage(filename));
+ auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter);
+ if (!filenames.empty()) {
+ QStringList errornames;
+ int numexisting = 0;
+ for (const auto& filename : filenames) {
+ if (!filename.isEmpty()) {
+ auto icon = QImage(filename);
+ if (icon.isNull()) {
+ errornames << filename;
+ } else if (!addCustomIcon(icon)) {
+ // Icon already exists in database
+ ++numexisting;
+ }
+ }
+ }
+
+ int numloaded = filenames.size() - errornames.size() - numexisting;
+ QString msg;
+
+ if (numloaded > 0) {
+ msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded);
} else {
- emit messageEditEntry(tr("Can't read icon"), MessageWidget::Error);
+ msg = tr("No icons were loaded");
+ }
+
+ if (numexisting > 0) {
+ msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting);
+ }
+
+ if (!errornames.empty()) {
+ // Show the first 8 icons that failed to load
+ errornames = errornames.mid(0, 8);
+ emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) +
+ "\n" + errornames.join("\n"), MessageWidget::Error);
+ } else if (numloaded > 0) {
+ emit messageEditEntry(msg, MessageWidget::Positive);
+ } else {
+ emit messageEditEntry(msg, MessageWidget::Information);
}
}
}
}
-void EditWidgetIcons::addCustomIcon(const QImage& icon)
+bool EditWidgetIcons::addCustomIcon(const QImage& icon)
{
+ bool added = false;
if (m_database) {
- Uuid uuid = m_database->metadata()->findCustomIcon(icon);
+ // Don't add an icon larger than 128x128, but retain original size if smaller
+ auto scaledicon = icon;
+ if (icon.width() > 128 || icon.height() > 128) {
+ scaledicon = icon.scaled(128, 128);
+ }
+
+ Uuid uuid = m_database->metadata()->findCustomIcon(scaledicon);
if (uuid.isNull()) {
uuid = Uuid::random();
- // Don't add an icon larger than 128x128, but retain original size if smaller
- if (icon.width() > 128 || icon.height() > 128) {
- m_database->metadata()->addCustomIcon(uuid, icon.scaled(128, 128));
- } else {
- m_database->metadata()->addCustomIcon(uuid, icon);
- }
-
+ m_database->metadata()->addCustomIcon(uuid, scaledicon);
m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(),
m_database->metadata()->customIconsOrder());
- } else {
- emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information);
+ added = true;
}
// Select the new or existing icon
@@ -363,6 +396,8 @@ void EditWidgetIcons::addCustomIcon(const QImage& icon)
emit widgetUpdated();
}
+
+ return added;
}
void EditWidgetIcons::removeCustomIcon()
@@ -459,7 +494,6 @@ void EditWidgetIcons::updateWidgetsDefaultIcons(bool check)
m_ui->defaultIconsView->setCurrentIndex(index);
}
m_ui->customIconsView->selectionModel()->clearSelection();
- m_ui->addButton->setEnabled(false);
m_ui->deleteButton->setEnabled(false);
}
}
@@ -474,7 +508,6 @@ void EditWidgetIcons::updateWidgetsCustomIcons(bool check)
m_ui->customIconsView->setCurrentIndex(index);
}
m_ui->defaultIconsView->selectionModel()->clearSelection();
- m_ui->addButton->setEnabled(true);
m_ui->deleteButton->setEnabled(true);
}
}
diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h
index 94630157d..e01ae4b1d 100644
--- a/src/gui/EditWidgetIcons.h
+++ b/src/gui/EditWidgetIcons.h
@@ -88,7 +88,7 @@ private slots:
void fetchReadyRead();
void fetchCanceled();
void addCustomIconFromFile();
- void addCustomIcon(const QImage& icon);
+ bool addCustomIcon(const QImage& icon);
void removeCustomIcon();
void updateWidgetsDefaultIcons(bool checked);
void updateWidgetsCustomIcons(bool checked);
diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp
index 3d8ebf7be..fed34754b 100644
--- a/src/gui/PasswordGeneratorWidget.cpp
+++ b/src/gui/PasswordGeneratorWidget.cpp
@@ -42,6 +42,10 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString)));
connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString)));
connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool)));
+ connect(m_ui->buttonSimpleMode, SIGNAL(clicked()), SLOT(selectSimpleMode()));
+ connect(m_ui->buttonAdvancedMode, SIGNAL(clicked()), SLOT(selectAdvancedMode()));
+ connect(m_ui->buttonAddHex, SIGNAL(clicked()), SLOT(excludeHexChars()));
+ connect(m_ui->editExcludedChars, SIGNAL(textChanged(QString)), SLOT(updateGenerator()));
connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword()));
connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword()));
connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword()));
@@ -94,11 +98,34 @@ void PasswordGeneratorWidget::loadSettings()
{
// Password config
m_ui->checkBoxLower->setChecked(config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool());
+ m_ui->checkBoxLowerAdv->setChecked(config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool());
m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool());
+ m_ui->checkBoxUpperAdv->setChecked(config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool());
m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool());
- m_ui->checkBoxSpecialChars->setChecked(
- config()->get("generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool());
+ m_ui->checkBoxNumbersAdv->setChecked(
+ config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool());
+ m_ui->advancedBar->setVisible(
+ config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool());
+ m_ui->excludedChars->setVisible(
+ config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool());
+ m_ui->checkBoxExcludeAlike->setVisible(
+ config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool());
+ m_ui->checkBoxEnsureEvery->setVisible(
+ config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool());
+ m_ui->editExcludedChars->setText(
+ config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString());
+ m_ui->simpleBar->setVisible(
+ !(config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool()));
+ m_ui->checkBoxBraces->setChecked(config()->get("generator/Braces", PasswordGenerator::DefaultBraces).toBool());
+ m_ui->checkBoxQuotes->setChecked(config()->get("generator/Quotes", PasswordGenerator::DefaultQuotes).toBool());
+ m_ui->checkBoxPunctuation->setChecked(
+ config()->get("generator/Punctuation", PasswordGenerator::DefaultPunctuation).toBool());
+ m_ui->checkBoxDashes->setChecked(config()->get("generator/Dashes", PasswordGenerator::DefaultDashes).toBool());
+ m_ui->checkBoxMath->setChecked(config()->get("generator/Math", PasswordGenerator::DefaultMath).toBool());
+ m_ui->checkBoxLogograms->setChecked(
+ config()->get("generator/Logograms", PasswordGenerator::DefaultLogograms).toBool());
m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool());
+ m_ui->checkBoxExtASCIIAdv->setChecked(config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool());
m_ui->checkBoxExcludeAlike->setChecked(
config()->get("generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool());
m_ui->checkBoxEnsureEvery->setChecked(
@@ -120,11 +147,25 @@ void PasswordGeneratorWidget::loadSettings()
void PasswordGeneratorWidget::saveSettings()
{
// Password config
- config()->set("generator/LowerCase", m_ui->checkBoxLower->isChecked());
- config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked());
- config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked());
+ if (m_ui->simpleBar->isVisible()) {
+ config()->set("generator/LowerCase", m_ui->checkBoxLower->isChecked());
+ config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked());
+ config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked());
+ config()->set("generator/EASCII", m_ui->checkBoxExtASCII->isChecked());
+ } else {
+ config()->set("generator/LowerCase", m_ui->checkBoxLowerAdv->isChecked());
+ config()->set("generator/UpperCase", m_ui->checkBoxUpperAdv->isChecked());
+ config()->set("generator/Numbers", m_ui->checkBoxNumbersAdv->isChecked());
+ config()->set("generator/EASCII", m_ui->checkBoxExtASCIIAdv->isChecked());
+ }
config()->set("generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked());
- config()->set("generator/EASCII", m_ui->checkBoxExtASCII->isChecked());
+ config()->set("generator/Braces", m_ui->checkBoxBraces->isChecked());
+ config()->set("generator/Punctuation", m_ui->checkBoxPunctuation->isChecked());
+ config()->set("generator/Quotes", m_ui->checkBoxQuotes->isChecked());
+ config()->set("generator/Dashes", m_ui->checkBoxDashes->isChecked());
+ config()->set("generator/Math", m_ui->checkBoxMath->isChecked());
+ config()->set("generator/Logograms", m_ui->checkBoxLogograms->isChecked());
+ config()->set("generator/ExcludedChars", m_ui->editExcludedChars->text());
config()->set("generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked());
config()->set("generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked());
config()->set("generator/Length", m_ui->spinBoxLength->value());
@@ -276,6 +317,48 @@ void PasswordGeneratorWidget::togglePasswordShown(bool showing)
m_ui->togglePasswordButton->blockSignals(blockSignals);
}
+void PasswordGeneratorWidget::selectSimpleMode()
+{
+ m_ui->advancedBar->hide();
+ m_ui->excludedChars->hide();
+ m_ui->checkBoxExcludeAlike->hide();
+ m_ui->checkBoxEnsureEvery->hide();
+ m_ui->checkBoxUpper->setChecked(m_ui->checkBoxUpperAdv->isChecked());
+ m_ui->checkBoxLower->setChecked(m_ui->checkBoxLowerAdv->isChecked());
+ m_ui->checkBoxNumbers->setChecked(m_ui->checkBoxNumbersAdv->isChecked());
+ m_ui->checkBoxSpecialChars->setChecked(m_ui->checkBoxBraces->isChecked() | m_ui->checkBoxPunctuation->isChecked()
+ | m_ui->checkBoxQuotes->isChecked()
+ | m_ui->checkBoxMath->isChecked()
+ | m_ui->checkBoxDashes->isChecked()
+ | m_ui->checkBoxLogograms->isChecked());
+ m_ui->checkBoxExtASCII->setChecked(m_ui->checkBoxExtASCIIAdv->isChecked());
+ m_ui->simpleBar->show();
+}
+
+void PasswordGeneratorWidget::selectAdvancedMode()
+{
+ m_ui->simpleBar->hide();
+ m_ui->checkBoxUpperAdv->setChecked(m_ui->checkBoxUpper->isChecked());
+ m_ui->checkBoxLowerAdv->setChecked(m_ui->checkBoxLower->isChecked());
+ m_ui->checkBoxNumbersAdv->setChecked(m_ui->checkBoxNumbers->isChecked());
+ m_ui->checkBoxBraces->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxPunctuation->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxQuotes->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxMath->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxDashes->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxLogograms->setChecked(m_ui->checkBoxSpecialChars->isChecked());
+ m_ui->checkBoxExtASCIIAdv->setChecked(m_ui->checkBoxExtASCII->isChecked());
+ m_ui->advancedBar->show();
+ m_ui->excludedChars->show();
+ m_ui->checkBoxExcludeAlike->show();
+ m_ui->checkBoxEnsureEvery->show();
+}
+
+void PasswordGeneratorWidget::excludeHexChars()
+{
+ m_ui->editExcludedChars->setText("GHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz");
+}
+
void PasswordGeneratorWidget::colorStrengthIndicator(double entropy)
{
// Take the existing stylesheet and convert the text and background color to arguments
@@ -306,24 +389,69 @@ PasswordGenerator::CharClasses PasswordGeneratorWidget::charClasses()
{
PasswordGenerator::CharClasses classes;
- if (m_ui->checkBoxLower->isChecked()) {
- classes |= PasswordGenerator::LowerLetters;
- }
+ if (m_ui->simpleBar->isVisible()) {
- if (m_ui->checkBoxUpper->isChecked()) {
- classes |= PasswordGenerator::UpperLetters;
- }
+ if (m_ui->checkBoxLower->isChecked()) {
+ classes |= PasswordGenerator::LowerLetters;
+ }
- if (m_ui->checkBoxNumbers->isChecked()) {
- classes |= PasswordGenerator::Numbers;
- }
+ if (m_ui->checkBoxUpper->isChecked()) {
+ classes |= PasswordGenerator::UpperLetters;
+ }
- if (m_ui->checkBoxSpecialChars->isChecked()) {
- classes |= PasswordGenerator::SpecialCharacters;
- }
+ if (m_ui->checkBoxNumbers->isChecked()) {
+ classes |= PasswordGenerator::Numbers;
+ }
- if (m_ui->checkBoxExtASCII->isChecked()) {
- classes |= PasswordGenerator::EASCII;
+ if (m_ui->checkBoxSpecialChars->isChecked()) {
+ classes |= PasswordGenerator::SpecialCharacters;
+ }
+
+ if (m_ui->checkBoxExtASCII->isChecked()) {
+ classes |= PasswordGenerator::EASCII;
+ }
+
+ } else {
+
+ if (m_ui->checkBoxLowerAdv->isChecked()) {
+ classes |= PasswordGenerator::LowerLetters;
+ }
+
+ if (m_ui->checkBoxUpperAdv->isChecked()) {
+ classes |= PasswordGenerator::UpperLetters;
+ }
+
+ if (m_ui->checkBoxNumbersAdv->isChecked()) {
+ classes |= PasswordGenerator::Numbers;
+ }
+
+ if (m_ui->checkBoxBraces->isChecked()) {
+ classes |= PasswordGenerator::Braces;
+ }
+
+ if (m_ui->checkBoxPunctuation->isChecked()) {
+ classes |= PasswordGenerator::Punctuation;
+ }
+
+ if (m_ui->checkBoxQuotes->isChecked()) {
+ classes |= PasswordGenerator::Quotes;
+ }
+
+ if (m_ui->checkBoxDashes->isChecked()) {
+ classes |= PasswordGenerator::Dashes;
+ }
+
+ if (m_ui->checkBoxMath->isChecked()) {
+ classes |= PasswordGenerator::Math;
+ }
+
+ if (m_ui->checkBoxLogograms->isChecked()) {
+ classes |= PasswordGenerator::Logograms;
+ }
+
+ if (m_ui->checkBoxExtASCIIAdv->isChecked()) {
+ classes |= PasswordGenerator::EASCII;
+ }
}
return classes;
@@ -361,7 +489,22 @@ void PasswordGeneratorWidget::updateGenerator()
if (classes.testFlag(PasswordGenerator::Numbers)) {
minLength++;
}
- if (classes.testFlag(PasswordGenerator::SpecialCharacters)) {
+ if (classes.testFlag(PasswordGenerator::Braces)) {
+ minLength++;
+ }
+ if (classes.testFlag(PasswordGenerator::Punctuation)) {
+ minLength++;
+ }
+ if (classes.testFlag(PasswordGenerator::Quotes)) {
+ minLength++;
+ }
+ if (classes.testFlag(PasswordGenerator::Dashes)) {
+ minLength++;
+ }
+ if (classes.testFlag(PasswordGenerator::Math)) {
+ minLength++;
+ }
+ if (classes.testFlag(PasswordGenerator::Logograms)) {
minLength++;
}
if (classes.testFlag(PasswordGenerator::EASCII)) {
@@ -382,6 +525,11 @@ void PasswordGeneratorWidget::updateGenerator()
m_passwordGenerator->setLength(m_ui->spinBoxLength->value());
m_passwordGenerator->setCharClasses(classes);
+ if (m_ui->simpleBar->isVisible()) {
+ m_passwordGenerator->setExcludedChars("");
+ } else {
+ m_passwordGenerator->setExcludedChars(m_ui->editExcludedChars->text());
+ }
m_passwordGenerator->setFlags(flags);
if (m_passwordGenerator->isValid()) {
diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h
index 84d5484b9..5b65d7d22 100644
--- a/src/gui/PasswordGeneratorWidget.h
+++ b/src/gui/PasswordGeneratorWidget.h
@@ -65,6 +65,9 @@ private slots:
void updateButtonsEnabled(const QString& password);
void updatePasswordStrength(const QString& password);
void togglePasswordShown(bool hidden);
+ void selectSimpleMode();
+ void selectAdvancedMode();
+ void excludeHexChars();
void passwordSliderMoved();
void passwordSpinBoxChanged();
diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui
index 03f70806f..e196ff89f 100644
--- a/src/gui/PasswordGeneratorWidget.ui
+++ b/src/gui/PasswordGeneratorWidget.ui
@@ -6,8 +6,8 @@
0
0
- 547
- 352
+ 571
+ 394
@@ -19,7 +19,7 @@
-
+
QLayout::SetMinimumSize
@@ -175,12 +175,6 @@ QProgressBar::chunk {
-
-
-
- 0
- 0
-
-
QTabWidget::North
@@ -204,155 +198,598 @@ QProgressBar::chunk {
-
-
-
-
-
-
-
- 0
- 25
-
-
-
- Qt::StrongFocus
-
-
- Upper Case Letters
-
-
- A-Z
-
-
- true
-
-
- optionButtons
-
-
-
- -
-
-
-
- 0
- 25
-
-
-
- Qt::StrongFocus
-
-
- Lower Case Letters
-
-
- a-z
-
-
- true
-
-
- optionButtons
-
-
-
- -
-
-
-
- 0
- 25
-
-
-
- Qt::StrongFocus
-
-
- Numbers
-
-
- 0-9
-
-
- true
-
-
- optionButtons
-
-
-
- -
-
-
-
- 0
- 25
-
-
-
- Qt::StrongFocus
-
-
- Special Characters
-
-
- /*_& ...
-
-
- true
-
-
- optionButtons
-
-
-
- -
-
-
-
- 0
- 25
-
-
-
-
- 16777215
- 16777215
-
-
-
- Qt::StrongFocus
-
-
- Extended ASCII
-
-
- Extended ASCII
-
-
- true
-
-
- optionButtons
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
-
+
+
+
+ 0
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Upper Case Letters
+
+
+ A-Z
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Lower Case Letters
+
+
+ a-z
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Numbers
+
+
+ 0-9
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Special Characters
+
+
+ /*_& ...
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Qt::StrongFocus
+
+
+ Extended ASCII
+
+
+ ExtendedASCII
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+ Switch to advanced mode
+
+
+ Advanced
+
+
+ optionButtons
+
+
+
+
+
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
-
+
+
+
+ 40
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Upper Case Letters A to F
+
+
+ A-Z
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 40
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Lower Case Letters A to F
+
+
+ a-z
+
+
+ true
+
+
+ optionButtons
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 40
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Numbers
+
+
+ 0-9
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 40
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Braces
+
+
+ {[(
+
+
+ true
+
+
+ optionButtons
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 35
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Punctuation
+
+
+ .,:;
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 35
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Quotes
+
+
+ " '
+
+
+ true
+
+
+ optionButtons
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 60
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Math
+
+
+ <*+!?=
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 60
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Dashes
+
+
+ \_|-/
+
+
+ true
+
+
+ optionButtons
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 105
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Logograms
+
+
+ #$%&&@^`~
+
+
+ true
+
+
+ optionButtons
+
+
+
+ -
+
+
+
+ 105
+ 25
+
+
+
+ Qt::StrongFocus
+
+
+ Extended ASCII
+
+
+ ExtendedASCII
+
+
+ true
+
+
+ optionButtons
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 25
+
+
+
+ Switch to simple mode
+
+
+ Simple
+
+
+ optionButtons
+
+
+
+
+
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 25
+
+
+
+ Character set to exclude from generated password
+
+
+ true
+
+
+
+ -
+
+
+ Do not include:
+
+
+
+ -
+
+
+
+ 0
+ 25
+
+
+
+ Add non-hex letters to "do not include" list
+
+
+ Hex
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 0
+
+
+
-
+
+ Excluded characters: "0", "1", "l", "I", "O", "|", "﹒"
+
Exclude look-alike characters
@@ -534,6 +971,19 @@ QProgressBar::chunk {
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
@@ -585,9 +1035,6 @@ QProgressBar::chunk {
- -
-
-
@@ -607,7 +1054,29 @@ QProgressBar::chunk {
checkBoxLower
checkBoxNumbers
checkBoxSpecialChars
+ checkBoxExtASCII
+ buttonAdvancedMode
+ checkBoxUpperAdv
+ checkBoxNumbersAdv
+ checkBoxPunctuation
+ checkBoxMath
+ checkBoxLogograms
+ checkBoxBraces
+ checkBoxQuotes
+ checkBoxDashes
+ checkBoxExtASCIIAdv
+ editExcludedChars
+ buttonSimpleMode
checkBoxExcludeAlike
+ checkBoxEnsureEvery
+ tabWidget
+ comboBoxWordList
+ sliderWordCount
+ spinBoxWordCount
+ editWordSeparator
+ buttonGenerate
+ buttonCopy
+ buttonApply
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index df5c37f83..261c9627e 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -190,6 +190,9 @@ add_unit_test(NAME testykchallengeresponsekey
add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
LIBS ${TEST_LIBRARIES})
+add_unit_test(NAME testtools SOURCES TestTools.cpp
+ LIBS ${TEST_LIBRARIES})
+
if(WITH_GUI_TESTS)
add_subdirectory(gui)
endif(WITH_GUI_TESTS)
diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp
index e5cc75c2c..f04aabcb5 100644
--- a/tests/TestKeys.cpp
+++ b/tests/TestKeys.cpp
@@ -24,7 +24,6 @@
#include "config-keepassx-tests.h"
#include "core/Metadata.h"
-#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp
index 0a9f0cfa2..60ea68e5f 100644
--- a/tests/TestModified.cpp
+++ b/tests/TestModified.cpp
@@ -23,7 +23,6 @@
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
-#include "core/Tools.h"
#include "crypto/Crypto.h"
QTEST_GUILESS_MAIN(TestModified)
diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp
new file mode 100644
index 000000000..c3acd3c6d
--- /dev/null
+++ b/tests/TestTools.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "TestTools.h"
+
+#include
+#include
+
+QTEST_GUILESS_MAIN(TestTools)
+
+namespace
+{
+ QString createDecimal(QString wholes, QString fractions, QString unit)
+ {
+ return wholes + QLocale().decimalPoint() + fractions + " " + unit;
+ }
+}
+
+void TestTools::testHumanReadableFileSize()
+{
+ constexpr auto kibibyte = 1024u;
+ using namespace Tools;
+
+ QCOMPARE(createDecimal("1", "00", "B"), humanReadableFileSize(1));
+ QCOMPARE(createDecimal("1", "00", "KiB"), humanReadableFileSize(kibibyte));
+ QCOMPARE(createDecimal("1", "00", "MiB"), humanReadableFileSize(kibibyte * kibibyte));
+ QCOMPARE(createDecimal("1", "00", "GiB"), humanReadableFileSize(kibibyte * kibibyte * kibibyte));
+
+ QCOMPARE(QString("100 B"), humanReadableFileSize(100, 0));
+ QCOMPARE(createDecimal("1", "10", "KiB"), humanReadableFileSize(kibibyte + 100));
+ QCOMPARE(createDecimal("1", "001", "KiB"), humanReadableFileSize(kibibyte + 1, 3));
+ QCOMPARE(createDecimal("15", "00", "KiB"), humanReadableFileSize(kibibyte * 15));
+}
+
+void TestTools::testIsHex()
+{
+ QVERIFY(Tools::isHex("0123456789abcdefABCDEF"));
+ QVERIFY(not Tools::isHex(QByteArray("0xnothex")));
+}
+
+void TestTools::testIsBase64()
+{
+ QVERIFY(Tools::isBase64(QByteArray("1234")));
+ QVERIFY(Tools::isBase64(QByteArray("123=")));
+ QVERIFY(Tools::isBase64(QByteArray("12==")));
+ QVERIFY(Tools::isBase64(QByteArray("abcd9876MN==")));
+ QVERIFY(Tools::isBase64(QByteArray("abcd9876DEFGhijkMNO=")));
+ QVERIFY(not Tools::isBase64(QByteArray("abcd123==")));
+ QVERIFY(not Tools::isBase64(QByteArray("abc_")));
+ QVERIFY(not Tools::isBase64(QByteArray("123")));
+}
diff --git a/tests/TestTools.h b/tests/TestTools.h
new file mode 100644
index 000000000..56d354eca
--- /dev/null
+++ b/tests/TestTools.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef KEEPASSX_TESTTOOLS_H
+#define KEEPASSX_TESTTOOLS_H
+
+#include "core/Tools.h"
+
+class TestTools : public QObject
+{
+ Q_OBJECT
+private slots:
+ void testHumanReadableFileSize();
+ void testIsHex();
+ void testIsBase64();
+};
+
+#endif // KEEPASSX_TESTTOOLS_H