mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-28 08:49:42 -05:00
Merge branch 'develop' into feature/window-modified-indicator
This commit is contained in:
commit
d50fe46f0e
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -3,11 +3,11 @@
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
## Motivation and context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
## How has this been tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
@ -29,5 +29,6 @@
|
||||
- ✅ I have read the **CONTRIBUTING** document. **[REQUIRED]**
|
||||
- ✅ My code follows the code style of this project. **[REQUIRED]**
|
||||
- ✅ All new and existing tests passed. **[REQUIRED]**
|
||||
- ✅ I have compiled and verified my code with `-DWITH_ASAN=ON`. **[REQUIRED]**
|
||||
- ✅ My change requires a change to the documentation and I have updated it accordingly.
|
||||
- ✅ I have added tests to cover my changes.
|
||||
|
@ -13,15 +13,15 @@ compiler:
|
||||
- gcc
|
||||
|
||||
env:
|
||||
- CONFIG=Release
|
||||
- CONFIG=Debug
|
||||
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
|
||||
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1:leak_check_at_exit=0
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libmicrohttpd10 libmicrohttpd-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libclang-common-3.5-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi
|
||||
@ -32,7 +32,7 @@ before_script:
|
||||
- mkdir build && pushd build
|
||||
|
||||
script:
|
||||
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_ASAN=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- make -j2
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
|
@ -32,7 +32,8 @@ include(CheckCXXSourceCompiles)
|
||||
option(WITH_TESTS "Enable building of unit tests" ON)
|
||||
option(WITH_GUI_TESTS "Enable building of GUI tests" OFF)
|
||||
option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF)
|
||||
option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF)
|
||||
option(WITH_ASAN "Enable address sanitizer checks (Linux only)" OFF)
|
||||
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
|
||||
|
||||
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
|
||||
@ -83,6 +84,17 @@ endif()
|
||||
add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti")
|
||||
add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual")
|
||||
add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings")
|
||||
if(WITH_ASAN)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
message(FATAL_ERROR "WITH_ASAN is only supported on Linux at the moment.")
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN")
|
||||
|
||||
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()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||
@ -154,16 +166,19 @@ if(APPLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local")
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
set(CLI_INSTALL_DIR ".")
|
||||
set(BIN_INSTALL_DIR ".")
|
||||
set(PLUGIN_INSTALL_DIR ".")
|
||||
set(DATA_INSTALL_DIR "share")
|
||||
elseif(APPLE)
|
||||
set(CLI_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)
|
||||
|
||||
set(CLI_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")
|
||||
@ -242,7 +257,6 @@ include(FeatureSummary)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(share)
|
||||
add_subdirectory(utils)
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif(WITH_TESTS)
|
||||
|
15
README.md
15
README.md
@ -10,7 +10,7 @@ KeePass Cross-platform Community Edition
|
||||
- Auto-Type on all three major platforms (Linux, Windows, OS X)
|
||||
- Stand-alone password generator
|
||||
- Password strength meter
|
||||
- Yubikey 2FA support for unlocking databases
|
||||
- YubiKey HMAC-SHA1 authentication for unlocking databases
|
||||
- Using website favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
@ -55,8 +55,19 @@ make -j8
|
||||
sudo make install
|
||||
```
|
||||
|
||||
To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. If these options are not specified, KeePassXC will be built without these plugins.
|
||||
cmake accepts the following options:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
|
||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||
|
||||
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
|
||||
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
|
||||
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
||||
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux only) (default: OFF)
|
||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
|
@ -31,6 +31,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
|
||||
set(keepassx_SOURCES
|
||||
core/AutoTypeAssociations.cpp
|
||||
core/Config.cpp
|
||||
core/CsvParser.cpp
|
||||
core/Database.cpp
|
||||
core/DatabaseIcons.cpp
|
||||
core/Endian.cpp
|
||||
@ -102,6 +103,9 @@ set(keepassx_SOURCES
|
||||
gui/UnlockDatabaseWidget.cpp
|
||||
gui/UnlockDatabaseDialog.cpp
|
||||
gui/WelcomeWidget.cpp
|
||||
gui/csvImport/CsvImportWidget.cpp
|
||||
gui/csvImport/CsvImportWizard.cpp
|
||||
gui/csvImport/CsvParserModel.cpp
|
||||
gui/entry/AutoTypeAssociationsModel.cpp
|
||||
gui/entry/EditEntryWidget.cpp
|
||||
gui/entry/EditEntryWidget_p.h
|
||||
@ -135,6 +139,7 @@ set(keepassx_FORMS
|
||||
gui/AboutDialog.ui
|
||||
gui/ChangeMasterKeyWidget.ui
|
||||
gui/CloneDialog.ui
|
||||
gui/csvImport/CsvImportWidget.ui
|
||||
gui/DatabaseOpenWidget.ui
|
||||
gui/DatabaseSettingsWidget.ui
|
||||
gui/CategoryListWidget.ui
|
||||
@ -164,6 +169,7 @@ if(WITH_XC_HTTP)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
add_subdirectory(cli)
|
||||
|
||||
set(autotype_SOURCES
|
||||
core/Tools.cpp
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
# 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
|
||||
@ -13,24 +13,31 @@
|
||||
# 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(../src)
|
||||
set(cli_SOURCES
|
||||
EntropyMeter.cpp
|
||||
EntropyMeter.h
|
||||
Extract.cpp
|
||||
Extract.h
|
||||
List.cpp
|
||||
List.h
|
||||
Merge.cpp
|
||||
Merge.h
|
||||
Show.cpp
|
||||
Show.h)
|
||||
|
||||
add_executable(kdbx-extract kdbx-extract.cpp)
|
||||
target_link_libraries(kdbx-extract
|
||||
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}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
${ZLIB_LIBRARIES}
|
||||
zxcvbn)
|
||||
|
||||
add_executable(kdbx-merge kdbx-merge.cpp)
|
||||
target_link_libraries(kdbx-merge
|
||||
keepassx_core
|
||||
Qt5::Core
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
|
||||
add_executable(entropy-meter entropy-meter.cpp)
|
||||
target_link_libraries(entropy-meter zxcvbn)
|
||||
install(TARGETS keepassxc-cli
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${CLI_INSTALL_DIR} COMPONENT Runtime)
|
@ -6,6 +6,8 @@ Copyright (c) 2016, KeePassXC Team
|
||||
See zxcvbn/zxcvbn.cpp for complete COPYRIGHT Notice
|
||||
*/
|
||||
|
||||
#include "EntropyMeter.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@ -76,7 +78,7 @@ static void calculate(const char *pwd, int advanced)
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int EntropyMeter::execute(int argc, char **argv)
|
||||
{
|
||||
printf("KeePassXC Entropy Meter, based on zxcvbn-c.\nEnter your password below or pass it as argv\n");
|
||||
printf(" Usage: entropy-meter [-a] [pwd1 pwd2 ...]\n> ");
|
27
src/cli/EntropyMeter.h
Normal file
27
src/cli/EntropyMeter.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_ENTROPYMETER_H
|
||||
#define KEEPASSXC_ENTROPYMETER_H
|
||||
|
||||
class EntropyMeter
|
||||
{
|
||||
public:
|
||||
static int execute(int argc, char** argv);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_ENTROPYMETER_H
|
@ -15,8 +15,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Extract.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
@ -24,32 +27,28 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int Extract::execute(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
QTextStream out(stdout);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main",
|
||||
"Extract and print a KeePassXC database file."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract."));
|
||||
parser.addHelpOption();
|
||||
"Extract and print the content of a database."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract."));
|
||||
parser.process(app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 1) {
|
||||
parser.showHelp();
|
||||
return 1;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!Crypto::init()) {
|
||||
qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString()));
|
||||
}
|
||||
out << "Insert the database password\n> ";
|
||||
out.flush();
|
||||
|
||||
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QString line = inputTextStream.readLine();
|
||||
@ -59,11 +58,11 @@ int main(int argc, char **argv)
|
||||
QFile dbFile(databaseFilename);
|
||||
if (!dbFile.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(databaseFilename));
|
||||
return 1;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!dbFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(databaseFilename));
|
||||
return 1;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
@ -76,15 +75,14 @@ int main(int argc, char **argv)
|
||||
if (reader.hasError()) {
|
||||
if (xmlData.isEmpty()) {
|
||||
qCritical("Error while reading the database:\n%s", qPrintable(reader.errorString()));
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
qWarning("Error while parsing the database:\n%s\n", qPrintable(reader.errorString()));
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QTextStream out(stdout);
|
||||
out << xmlData.constData() << "\n";
|
||||
|
||||
return 0;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
27
src/cli/Extract.h
Normal file
27
src/cli/Extract.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_EXTRACT_H
|
||||
#define KEEPASSXC_EXTRACT_H
|
||||
|
||||
class Extract
|
||||
{
|
||||
public:
|
||||
static int execute(int argc, char** argv);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_EXTRACT_H
|
89
src/cli/List.cpp
Normal file
89
src/cli/List.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "List.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
void printGroup(Group* group, QString baseName, int depth) {
|
||||
|
||||
QTextStream out(stdout);
|
||||
|
||||
QString groupName = baseName + group->name() + "/";
|
||||
QString indentation = QString(" ").repeated(depth);
|
||||
|
||||
out << indentation << groupName << " " << group->uuid().toHex() << "\n";
|
||||
out.flush();
|
||||
|
||||
if (group->entries().isEmpty() && group->children().isEmpty()) {
|
||||
out << indentation << " [empty]\n";
|
||||
return;
|
||||
}
|
||||
|
||||
for (Entry* entry : group->entries()) {
|
||||
out << indentation << " " << entry->title() << " " << entry->uuid().toHex() << "\n";
|
||||
}
|
||||
|
||||
for (Group* innerGroup : group->children()) {
|
||||
printGroup(innerGroup, groupName, depth + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int List::execute(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
QTextStream out(stdout);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main",
|
||||
"List database entries."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
|
||||
parser.process(app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 1) {
|
||||
parser.showHelp();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << "Insert the database password\n> ";
|
||||
out.flush();
|
||||
|
||||
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QString line = inputTextStream.readLine();
|
||||
CompositeKey key = CompositeKey::readFromLine(line);
|
||||
|
||||
Database* db = Database::openDatabaseFile(args.at(0), key);
|
||||
if (db == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
printGroup(db->rootGroup(), QString(""), 0);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
27
src/cli/List.h
Normal file
27
src/cli/List.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_LIST_H
|
||||
#define KEEPASSXC_LIST_H
|
||||
|
||||
class List
|
||||
{
|
||||
public:
|
||||
static int execute(int argc, char** argv);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_LIST_H
|
@ -15,50 +15,47 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "Merge.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
int Merge::execute(int argc, char** argv)
|
||||
{
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
QTextStream out(stdout);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main", "Merge 2 KeePassXC database files."));
|
||||
parser.addPositionalArgument("database1", QCoreApplication::translate("main", "path of the database to merge into."));
|
||||
parser.addPositionalArgument("database2", QCoreApplication::translate("main", "path of the database to merge from."));
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases."));
|
||||
parser.addPositionalArgument("database1", QCoreApplication::translate("main", "Path of the database to merge into."));
|
||||
parser.addPositionalArgument("database2", QCoreApplication::translate("main", "Path of the database to merge from."));
|
||||
|
||||
QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password",
|
||||
QCoreApplication::translate("main", "use the same password for both database files."));
|
||||
QCoreApplication::translate("main", "Use the same password for both database files."));
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.addOption(samePasswordOption);
|
||||
parser.process(app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
parser.showHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Crypto::init()) {
|
||||
qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString()));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << "Insert the first database password\n> ";
|
||||
out.flush();
|
||||
|
||||
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
|
||||
QString line1 = inputTextStream.readLine();
|
||||
CompositeKey key1 = CompositeKey::readFromLine(line1);
|
||||
|
||||
@ -67,56 +64,29 @@ int main(int argc, char **argv)
|
||||
key2 = *key1.clone();
|
||||
}
|
||||
else {
|
||||
out << "Insert the second database password\n> ";
|
||||
out.flush();
|
||||
QString line2 = inputTextStream.readLine();
|
||||
key2 = CompositeKey::readFromLine(line2);
|
||||
}
|
||||
|
||||
|
||||
QString databaseFilename1 = args.at(0);
|
||||
QFile dbFile1(databaseFilename1);
|
||||
if (!dbFile1.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(databaseFilename1));
|
||||
return 1;
|
||||
}
|
||||
if (!dbFile1.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(databaseFilename1));
|
||||
return 1;
|
||||
Database* db1 = Database::openDatabaseFile(args.at(0), key1);
|
||||
if (db1 == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
KeePass2Reader reader1;
|
||||
Database* db1 = reader1.readDatabase(&dbFile1, key1);
|
||||
|
||||
if (reader1.hasError()) {
|
||||
qCritical("Error while parsing the database:\n%s\n", qPrintable(reader1.errorString()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
QString databaseFilename2 = args.at(1);
|
||||
QFile dbFile2(databaseFilename2);
|
||||
if (!dbFile2.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(databaseFilename2));
|
||||
return 1;
|
||||
}
|
||||
if (!dbFile2.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(databaseFilename2));
|
||||
return 1;
|
||||
}
|
||||
|
||||
KeePass2Reader reader2;
|
||||
Database* db2 = reader2.readDatabase(&dbFile2, key2);
|
||||
|
||||
if (reader2.hasError()) {
|
||||
qCritical("Error while parsing the database:\n%s\n", qPrintable(reader2.errorString()));
|
||||
return 1;
|
||||
Database* db2 = Database::openDatabaseFile(args.at(1), key2);
|
||||
if (db2 == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
db1->merge(db2);
|
||||
|
||||
QSaveFile saveFile(databaseFilename1);
|
||||
QSaveFile saveFile(args.at(0));
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
qCritical("Unable to open file %s for writing.", qPrintable(databaseFilename1));
|
||||
return 1;
|
||||
qCritical("Unable to open file %s for writing.", qPrintable(args.at(0)));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
KeePass2Writer writer;
|
||||
@ -124,15 +94,15 @@ int main(int argc, char **argv)
|
||||
|
||||
if (writer.hasError()) {
|
||||
qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString()));
|
||||
return 1;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!saveFile.commit()) {
|
||||
qCritical("Error while updating the database:\n%s\n", qPrintable(writer.errorString()));
|
||||
return 0;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
qDebug("Successfully merged the database files.\n");
|
||||
return 1;
|
||||
out << "Successfully merged the database files.\n";
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
}
|
27
src/cli/Merge.h
Normal file
27
src/cli/Merge.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_MERGE_H
|
||||
#define KEEPASSXC_MERGE_H
|
||||
|
||||
class Merge
|
||||
{
|
||||
public:
|
||||
static int execute(int argc, char** argv);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_MERGE_H
|
72
src/cli/Show.cpp
Normal file
72
src/cli/Show.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Show.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
int Show::execute(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
QTextStream out(stdout);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main",
|
||||
"Show a password."));
|
||||
parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database."));
|
||||
parser.addPositionalArgument("uuid", QCoreApplication::translate("main", "Uuid of the entry to show"));
|
||||
parser.process(app);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (args.size() != 2) {
|
||||
parser.showHelp();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << "Insert the database password\n> ";
|
||||
out.flush();
|
||||
|
||||
static QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QString line = inputTextStream.readLine();
|
||||
CompositeKey key = CompositeKey::readFromLine(line);
|
||||
|
||||
Database* db = Database::openDatabaseFile(args.at(0), key);
|
||||
if (db == nullptr) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Uuid uuid = Uuid::fromHex(args.at(1));
|
||||
Entry* entry = db->resolveEntry(uuid);
|
||||
if (entry == nullptr) {
|
||||
qCritical("No entry found with uuid %s", qPrintable(uuid.toHex()));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << entry->password() << "\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
27
src/cli/Show.h
Normal file
27
src/cli/Show.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_SHOW_H
|
||||
#define KEEPASSXC_SHOW_H
|
||||
|
||||
class Show
|
||||
{
|
||||
public:
|
||||
static int execute(int argc, char** argv);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SHOW_H
|
114
src/cli/keepassxc-cli.cpp
Normal file
114
src/cli/keepassxc-cli.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
|
||||
#include <cli/EntropyMeter.h>
|
||||
#include <cli/Extract.h>
|
||||
#include <cli/List.h>
|
||||
#include <cli/Merge.h>
|
||||
#include <cli/Show.h>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
QCommandLineParser parser;
|
||||
|
||||
QString description("KeePassXC command line interface.");
|
||||
description = description.append(QString("\n\nAvailable commands:"));
|
||||
description = description.append(QString("\n extract\tExtract and print the content of a database."));
|
||||
description = description.append(QString("\n entropy-meter\tCalculate password entropy."));
|
||||
description = description.append(QString("\n list\t\tList database entries."));
|
||||
description = description.append(QString("\n merge\t\tMerge two databases."));
|
||||
description = description.append(QString("\n show\t\tShow a password."));
|
||||
parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description)));
|
||||
|
||||
parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute."));
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
// TODO : use process once the setOptionsAfterPositionalArgumentsMode (Qt 5.6)
|
||||
// is available. Until then, options passed to sub-commands won't be
|
||||
// recognized by this parser.
|
||||
// parser.process(app);
|
||||
|
||||
if (argc < 2) {
|
||||
parser.showHelp();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString commandName = argv[1];
|
||||
|
||||
// Removing the first cli argument before dispatching.
|
||||
++argv;
|
||||
--argc;
|
||||
|
||||
int exitCode = EXIT_FAILURE;
|
||||
|
||||
if (commandName == "entropy-meter") {
|
||||
argv[0] = const_cast<char*>("keepassxc-cli entropy-meter");
|
||||
exitCode = EntropyMeter::execute(argc, argv);
|
||||
} else if (commandName == "extract") {
|
||||
argv[0] = const_cast<char*>("keepassxc-cli extract");
|
||||
exitCode = Extract::execute(argc, argv);
|
||||
} else if (commandName == "list") {
|
||||
argv[0] = const_cast<char*>("keepassxc-cli list");
|
||||
exitCode = List::execute(argc, argv);
|
||||
} else if (commandName == "merge") {
|
||||
argv[0] = const_cast<char*>("keepassxc-cli merge");
|
||||
exitCode = Merge::execute(argc, argv);
|
||||
} else if (commandName == "show") {
|
||||
argv[0] = const_cast<char*>("keepassxc-cli show");
|
||||
exitCode = Show::execute(argc, argv);
|
||||
} else {
|
||||
qCritical("Invalid command %s.", qPrintable(commandName));
|
||||
parser.showHelp();
|
||||
exitCode = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#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
|
||||
__lsan_do_leak_check();
|
||||
__lsan_disable();
|
||||
#endif
|
||||
|
||||
return exitCode;
|
||||
|
||||
}
|
@ -35,6 +35,16 @@ QVariant Config::get(const QString& key, const QVariant& defaultValue)
|
||||
return m_settings->value(key, defaultValue);
|
||||
}
|
||||
|
||||
bool Config::hasAccessError()
|
||||
{
|
||||
return m_settings->status() & QSettings::AccessError;
|
||||
}
|
||||
|
||||
QString Config::getFileName()
|
||||
{
|
||||
return m_settings->fileName();
|
||||
}
|
||||
|
||||
void Config::set(const QString& key, const QVariant& value)
|
||||
{
|
||||
m_settings->setValue(key, value);
|
||||
|
@ -31,7 +31,9 @@ public:
|
||||
~Config();
|
||||
QVariant get(const QString& key);
|
||||
QVariant get(const QString& key, const QVariant& defaultValue);
|
||||
QString getFileName();
|
||||
void set(const QString& key, const QVariant& value);
|
||||
bool hasAccessError();
|
||||
|
||||
static Config* instance();
|
||||
static void createConfigFromFile(const QString& file);
|
||||
|
383
src/core/CsvParser.cpp
Normal file
383
src/core/CsvParser.cpp
Normal file
@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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 "CsvParser.h"
|
||||
|
||||
#include <QTextCodec>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/Tools.h"
|
||||
|
||||
CsvParser::CsvParser()
|
||||
: m_ch(0)
|
||||
, m_comment('#')
|
||||
, m_currCol(1)
|
||||
, m_currRow(1)
|
||||
, m_isBackslashSyntax(false)
|
||||
, m_isEof(false)
|
||||
, m_isFileLoaded(false)
|
||||
, m_isGood(true)
|
||||
, m_lastPos(-1)
|
||||
, m_maxCols(0)
|
||||
, m_qualifier('"')
|
||||
, m_separator(',')
|
||||
, m_statusMsg("")
|
||||
{
|
||||
m_csv.setBuffer(&m_array);
|
||||
m_ts.setDevice(&m_csv);
|
||||
m_csv.open(QIODevice::ReadOnly);
|
||||
m_ts.setCodec("UTF-8");
|
||||
}
|
||||
|
||||
CsvParser::~CsvParser() {
|
||||
m_csv.close();
|
||||
}
|
||||
|
||||
bool CsvParser::isFileLoaded() {
|
||||
return m_isFileLoaded;
|
||||
}
|
||||
|
||||
bool CsvParser::reparse() {
|
||||
reset();
|
||||
return parseFile();
|
||||
}
|
||||
|
||||
|
||||
bool CsvParser::parse(QFile *device) {
|
||||
clear();
|
||||
if (nullptr == device) {
|
||||
appendStatusMsg(QObject::tr("NULL device"), true);
|
||||
return false;
|
||||
}
|
||||
if (!readFile(device))
|
||||
return false;
|
||||
return parseFile();
|
||||
}
|
||||
|
||||
bool CsvParser::readFile(QFile *device) {
|
||||
if (device->isOpen())
|
||||
device->close();
|
||||
|
||||
device->open(QIODevice::ReadOnly);
|
||||
if (!Tools::readAllFromDevice(device, m_array)) {
|
||||
appendStatusMsg(QObject::tr("error reading from device"), true);
|
||||
m_isFileLoaded = false;
|
||||
}
|
||||
else {
|
||||
device->close();
|
||||
|
||||
m_array.replace("\r\n", "\n");
|
||||
m_array.replace("\r", "\n");
|
||||
if (0 == m_array.size())
|
||||
appendStatusMsg(QObject::tr("file empty !\n"));
|
||||
m_isFileLoaded = true;
|
||||
}
|
||||
return m_isFileLoaded;
|
||||
}
|
||||
|
||||
void CsvParser::reset() {
|
||||
m_ch = 0;
|
||||
m_currCol = 1;
|
||||
m_currRow = 1;
|
||||
m_isEof = false;
|
||||
m_isGood = true;
|
||||
m_lastPos = -1;
|
||||
m_maxCols = 0;
|
||||
m_statusMsg = "";
|
||||
m_ts.seek(0);
|
||||
m_table.clear();
|
||||
//the following are users' concern :)
|
||||
//m_comment = '#';
|
||||
//m_backslashSyntax = false;
|
||||
//m_comment = '#';
|
||||
//m_qualifier = '"';
|
||||
//m_separator = ',';
|
||||
}
|
||||
|
||||
void CsvParser::clear() {
|
||||
reset();
|
||||
m_isFileLoaded = false;
|
||||
m_array.clear();
|
||||
}
|
||||
|
||||
bool CsvParser::parseFile() {
|
||||
parseRecord();
|
||||
while (!m_isEof) {
|
||||
if (!skipEndline())
|
||||
appendStatusMsg(QObject::tr("malformed string"), true);
|
||||
m_currRow++;
|
||||
m_currCol = 1;
|
||||
parseRecord();
|
||||
}
|
||||
fillColumns();
|
||||
return m_isGood;
|
||||
}
|
||||
|
||||
void CsvParser::parseRecord() {
|
||||
CsvRow row;
|
||||
if (isComment()) {
|
||||
skipLine();
|
||||
return;
|
||||
}
|
||||
do {
|
||||
parseField(row);
|
||||
getChar(m_ch);
|
||||
} while (isSeparator(m_ch) && !m_isEof);
|
||||
|
||||
if (!m_isEof)
|
||||
ungetChar();
|
||||
if (isEmptyRow(row)) {
|
||||
row.clear();
|
||||
return;
|
||||
}
|
||||
m_table.push_back(row);
|
||||
if (m_maxCols < row.size())
|
||||
m_maxCols = row.size();
|
||||
m_currCol++;
|
||||
}
|
||||
|
||||
void CsvParser::parseField(CsvRow& row) {
|
||||
QString field;
|
||||
peek(m_ch);
|
||||
if (!isTerminator(m_ch)) {
|
||||
if (isQualifier(m_ch))
|
||||
parseQuoted(field);
|
||||
else
|
||||
parseSimple(field);
|
||||
}
|
||||
row.push_back(field);
|
||||
}
|
||||
|
||||
void CsvParser::parseSimple(QString &s) {
|
||||
QChar c;
|
||||
getChar(c);
|
||||
while ((isText(c)) && (!m_isEof)) {
|
||||
s.append(c);
|
||||
getChar(c);
|
||||
}
|
||||
if (!m_isEof)
|
||||
ungetChar();
|
||||
}
|
||||
|
||||
void CsvParser::parseQuoted(QString &s) {
|
||||
//read and discard initial qualifier (e.g. quote)
|
||||
getChar(m_ch);
|
||||
parseEscaped(s);
|
||||
//getChar(m_ch);
|
||||
if (!isQualifier(m_ch))
|
||||
appendStatusMsg(QObject::tr("missing closing quote"), true);
|
||||
}
|
||||
|
||||
void CsvParser::parseEscaped(QString &s) {
|
||||
parseEscapedText(s);
|
||||
while (processEscapeMark(s, m_ch))
|
||||
parseEscapedText(s);
|
||||
if (!m_isEof)
|
||||
ungetChar();
|
||||
}
|
||||
|
||||
void CsvParser::parseEscapedText(QString &s) {
|
||||
getChar(m_ch);
|
||||
while ((!isQualifier(m_ch)) && !m_isEof) {
|
||||
s.append(m_ch);
|
||||
getChar(m_ch);
|
||||
}
|
||||
}
|
||||
|
||||
bool CsvParser::processEscapeMark(QString &s, QChar c) {
|
||||
QChar buf;
|
||||
peek(buf);
|
||||
QChar c2;
|
||||
if (true == m_isBackslashSyntax) {
|
||||
//escape-character syntax, e.g. \"
|
||||
if (c != '\\') {
|
||||
return false;
|
||||
}
|
||||
//consume (and append) second qualifier
|
||||
getChar(c2);
|
||||
if (m_isEof) {
|
||||
c2='\\';
|
||||
s.append('\\');
|
||||
return false;
|
||||
} else {
|
||||
s.append(c2);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
//double quote syntax, e.g. ""
|
||||
if (!isQualifier(c))
|
||||
return false;
|
||||
peek(c2);
|
||||
if (!m_isEof) { //not EOF, can read one char
|
||||
if (isQualifier(c2)) {
|
||||
s.append(c2);
|
||||
getChar(c2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CsvParser::fillColumns() {
|
||||
//fill shorter rows with empty placeholder columns
|
||||
for (int i = 0; i < m_table.size(); ++i) {
|
||||
int gap = m_maxCols-m_table.at(i).size();
|
||||
if (gap > 0) {
|
||||
CsvRow r = m_table.at(i);
|
||||
for (int j = 0; j < gap; ++j) {
|
||||
r.append(QString(""));
|
||||
}
|
||||
m_table.replace(i, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CsvParser::skipLine() {
|
||||
m_ts.readLine();
|
||||
m_ts.seek(m_ts.pos() - 1);
|
||||
}
|
||||
|
||||
bool CsvParser::skipEndline() {
|
||||
getChar(m_ch);
|
||||
return (m_ch == '\n');
|
||||
}
|
||||
|
||||
|
||||
void CsvParser::getChar(QChar& c) {
|
||||
m_isEof = m_ts.atEnd();
|
||||
if (!m_isEof) {
|
||||
m_lastPos = m_ts.pos();
|
||||
m_ts >> c;
|
||||
}
|
||||
}
|
||||
|
||||
void CsvParser::ungetChar() {
|
||||
if (!m_ts.seek(m_lastPos))
|
||||
appendStatusMsg(QObject::tr("INTERNAL - unget lower bound exceeded"), true);
|
||||
}
|
||||
|
||||
void CsvParser::peek(QChar& c) {
|
||||
getChar(c);
|
||||
if (!m_isEof)
|
||||
ungetChar();
|
||||
}
|
||||
|
||||
bool CsvParser::isQualifier(const QChar &c) const {
|
||||
if (true == m_isBackslashSyntax && (c != m_qualifier))
|
||||
return (c == '\\');
|
||||
else
|
||||
return (c == m_qualifier);
|
||||
}
|
||||
|
||||
bool CsvParser::isComment() {
|
||||
bool result = false;
|
||||
QChar c2;
|
||||
qint64 pos = m_ts.pos();
|
||||
|
||||
do getChar(c2);
|
||||
while ((isSpace(c2) || isTab(c2)) && (!m_isEof));
|
||||
|
||||
if (c2 == m_comment)
|
||||
result = true;
|
||||
m_ts.seek(pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CsvParser::isText(QChar c) const {
|
||||
return !( (isCRLF(c)) || (isSeparator(c)) );
|
||||
}
|
||||
|
||||
bool CsvParser::isEmptyRow(CsvRow row) const {
|
||||
CsvRow::const_iterator it = row.constBegin();
|
||||
for (; it != row.constEnd(); ++it)
|
||||
if ( ((*it) != "\n") && ((*it) != "") )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CsvParser::isCRLF(const QChar &c) const {
|
||||
return (c == '\n');
|
||||
}
|
||||
|
||||
bool CsvParser::isSpace(const QChar &c) const {
|
||||
return (c == ' ');
|
||||
}
|
||||
|
||||
bool CsvParser::isTab(const QChar &c) const {
|
||||
return (c == '\t');
|
||||
}
|
||||
|
||||
bool CsvParser::isSeparator(const QChar &c) const {
|
||||
return (c == m_separator);
|
||||
}
|
||||
|
||||
bool CsvParser::isTerminator(const QChar &c) const {
|
||||
return (isSeparator(c) || (c == '\n') || (c == '\r'));
|
||||
}
|
||||
|
||||
void CsvParser::setBackslashSyntax(bool set) {
|
||||
m_isBackslashSyntax = set;
|
||||
}
|
||||
|
||||
void CsvParser::setComment(const QChar &c) {
|
||||
m_comment = c.unicode();
|
||||
}
|
||||
|
||||
void CsvParser::setCodec(const QString &s) {
|
||||
m_ts.setCodec(QTextCodec::codecForName(s.toLocal8Bit()));
|
||||
}
|
||||
|
||||
void CsvParser::setFieldSeparator(const QChar &c) {
|
||||
m_separator = c.unicode();
|
||||
}
|
||||
|
||||
void CsvParser::setTextQualifier(const QChar &c) {
|
||||
m_qualifier = c.unicode();
|
||||
}
|
||||
|
||||
int CsvParser::getFileSize() const {
|
||||
return m_csv.size();
|
||||
}
|
||||
|
||||
const CsvTable CsvParser::getCsvTable() const {
|
||||
return m_table;
|
||||
}
|
||||
|
||||
QString CsvParser::getStatus() const {
|
||||
return m_statusMsg;
|
||||
}
|
||||
|
||||
int CsvParser::getCsvCols() const {
|
||||
if ((m_table.size() > 0) && (m_table.at(0).size() > 0))
|
||||
return m_table.at(0).size();
|
||||
else return 0;
|
||||
}
|
||||
|
||||
int CsvParser::getCsvRows() const {
|
||||
return m_table.size();
|
||||
}
|
||||
|
||||
|
||||
void CsvParser::appendStatusMsg(QString s, bool isCritical) {
|
||||
m_statusMsg += s
|
||||
.append(": (row,col) " + QString::number(m_currRow))
|
||||
.append(",")
|
||||
.append(QString::number(m_currCol))
|
||||
.append("\n");
|
||||
m_isGood = not isCritical;
|
||||
}
|
101
src/core/CsvParser.h
Normal file
101
src/core/CsvParser.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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_CSVPARSER_H
|
||||
#define KEEPASSX_CSVPARSER_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QQueue>
|
||||
#include <QTextStream>
|
||||
|
||||
typedef QStringList CsvRow;
|
||||
typedef QList<CsvRow> CsvTable;
|
||||
|
||||
class CsvParser {
|
||||
|
||||
public:
|
||||
CsvParser();
|
||||
~CsvParser();
|
||||
//read data from device and parse it
|
||||
bool parse(QFile *device);
|
||||
bool isFileLoaded();
|
||||
//reparse the same buffer (device is not opened again)
|
||||
bool reparse();
|
||||
void setCodec(const QString &s);
|
||||
void setComment(const QChar &c);
|
||||
void setFieldSeparator(const QChar &c);
|
||||
void setTextQualifier(const QChar &c);
|
||||
void setBackslashSyntax(bool set);
|
||||
int getFileSize() const;
|
||||
int getCsvRows() const;
|
||||
int getCsvCols() const;
|
||||
QString getStatus() const;
|
||||
const CsvTable getCsvTable() const;
|
||||
|
||||
protected:
|
||||
CsvTable m_table;
|
||||
|
||||
private:
|
||||
QByteArray m_array;
|
||||
QBuffer m_csv;
|
||||
QChar m_ch;
|
||||
QChar m_comment;
|
||||
unsigned int m_currCol;
|
||||
unsigned int m_currRow;
|
||||
bool m_isBackslashSyntax;
|
||||
bool m_isEof;
|
||||
bool m_isFileLoaded;
|
||||
bool m_isGood;
|
||||
qint64 m_lastPos;
|
||||
int m_maxCols;
|
||||
QChar m_qualifier;
|
||||
QChar m_separator;
|
||||
QString m_statusMsg;
|
||||
QTextStream m_ts;
|
||||
|
||||
void getChar(QChar &c);
|
||||
void ungetChar();
|
||||
void peek(QChar &c);
|
||||
void fillColumns();
|
||||
bool isTerminator(const QChar &c) const;
|
||||
bool isSeparator(const QChar &c) const;
|
||||
bool isQualifier(const QChar &c) const;
|
||||
bool processEscapeMark(QString &s, QChar c);
|
||||
bool isText(QChar c) const;
|
||||
bool isComment();
|
||||
bool isCRLF(const QChar &c) const;
|
||||
bool isSpace(const QChar &c) const;
|
||||
bool isTab(const QChar &c) const;
|
||||
bool isEmptyRow(CsvRow row) const;
|
||||
bool parseFile();
|
||||
void parseRecord();
|
||||
void parseField(CsvRow &row);
|
||||
void parseSimple(QString &s);
|
||||
void parseQuoted(QString &s);
|
||||
void parseEscaped(QString &s);
|
||||
void parseEscapedText(QString &s);
|
||||
bool readFile(QFile *device);
|
||||
void reset();
|
||||
void clear();
|
||||
bool skipEndline();
|
||||
void skipLine();
|
||||
void appendStatusMsg(QString s, bool isCritical = false);
|
||||
};
|
||||
|
||||
#endif //CSVPARSER_H
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
|
||||
QHash<Uuid, Database*> Database::m_uuidMap;
|
||||
|
||||
@ -355,3 +356,27 @@ const CompositeKey & Database::key() const
|
||||
return m_data.key;
|
||||
}
|
||||
|
||||
Database* Database::openDatabaseFile(QString fileName, CompositeKey key)
|
||||
{
|
||||
|
||||
QFile dbFile(fileName);
|
||||
if (!dbFile.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(fileName));
|
||||
return nullptr;
|
||||
}
|
||||
if (!dbFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(fileName));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KeePass2Reader reader;
|
||||
Database* db = reader.readDatabase(&dbFile, key);
|
||||
|
||||
if (reader.hasError()) {
|
||||
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ public:
|
||||
Uuid uuid();
|
||||
|
||||
static Database* databaseByUuid(const Uuid& uuid);
|
||||
static Database* openDatabaseFile(QString fileName, CompositeKey key);
|
||||
|
||||
signals:
|
||||
void groupDataChanged(Group* group);
|
||||
|
@ -649,7 +649,8 @@ const Database* Entry::database() const
|
||||
QString Entry::resolveMultiplePlaceholders(const QString& str) const
|
||||
{
|
||||
QString result = str;
|
||||
QRegExp tmplRegEx("({.*})", Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
QRegExp tmplRegEx("(\\{.*\\})", Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
tmplRegEx.setMinimal(true);
|
||||
QStringList tmplList;
|
||||
int pos = 0;
|
||||
|
||||
|
@ -160,7 +160,7 @@
|
||||
<li>debfx (KeePassX)</li>
|
||||
<li>droidmonkey</li>
|
||||
<li>louib</li>
|
||||
<li>phoerious<li>
|
||||
<li>phoerious</li>
|
||||
<li>thezero</li>
|
||||
</ul></string>
|
||||
</property>
|
||||
|
@ -212,6 +212,23 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
emit messageDismissGlobal();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::importCsv()
|
||||
{
|
||||
QString fileName = fileDialog()->getOpenFileName(this, tr("Open CSV file"), QString(),
|
||||
tr("CSV file") + " (*.csv);;" + tr("All files (*)"));
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Database* db = new Database();
|
||||
DatabaseManagerStruct dbStruct;
|
||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
dbStruct.dbWidget->switchToImportCsv(fileName);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::mergeDatabase()
|
||||
{
|
||||
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
|
||||
|
@ -66,6 +66,7 @@ public:
|
||||
public slots:
|
||||
void newDatabase();
|
||||
void openDatabase();
|
||||
void importCsv();
|
||||
void mergeDatabase();
|
||||
void importKeePass1Database();
|
||||
bool saveDatabase(int index = -1);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -122,6 +122,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
m_editGroupWidget->setObjectName("editGroupWidget");
|
||||
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
|
||||
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
|
||||
m_csvImportWizard = new CsvImportWizard();
|
||||
m_csvImportWizard->setObjectName("csvImportWizard");
|
||||
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
|
||||
headlineLabelFont.setBold(true);
|
||||
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
|
||||
@ -145,6 +147,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
addWidget(m_databaseSettingsWidget);
|
||||
addWidget(m_historyEditEntryWidget);
|
||||
addWidget(m_databaseOpenWidget);
|
||||
addWidget(m_csvImportWizard);
|
||||
addWidget(m_databaseOpenMergeWidget);
|
||||
addWidget(m_keepass1OpenWidget);
|
||||
addWidget(m_unlockDatabaseWidget);
|
||||
@ -165,6 +168,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
|
||||
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
|
||||
@ -192,6 +196,9 @@ DatabaseWidget::Mode DatabaseWidget::currentMode() const
|
||||
if (currentWidget() == nullptr) {
|
||||
return DatabaseWidget::None;
|
||||
}
|
||||
else if (currentWidget() == m_csvImportWizard) {
|
||||
return DatabaseWidget::ImportMode;
|
||||
}
|
||||
else if (currentWidget() == m_mainWidget) {
|
||||
return DatabaseWidget::ViewMode;
|
||||
}
|
||||
@ -407,7 +414,7 @@ void DatabaseWidget::copyTitle()
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->title()));
|
||||
}
|
||||
|
||||
void DatabaseWidget::copyUsername()
|
||||
@ -418,7 +425,7 @@ void DatabaseWidget::copyUsername()
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->username()));
|
||||
}
|
||||
|
||||
void DatabaseWidget::copyPassword()
|
||||
@ -429,7 +436,7 @@ void DatabaseWidget::copyPassword()
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password()));
|
||||
}
|
||||
|
||||
void DatabaseWidget::copyURL()
|
||||
@ -440,7 +447,7 @@ void DatabaseWidget::copyURL()
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->url()));
|
||||
}
|
||||
|
||||
void DatabaseWidget::copyNotes()
|
||||
@ -451,7 +458,7 @@ void DatabaseWidget::copyNotes()
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->notes()));
|
||||
}
|
||||
|
||||
void DatabaseWidget::copyAttribute(QAction* action)
|
||||
@ -462,7 +469,7 @@ void DatabaseWidget::copyAttribute(QAction* action)
|
||||
return;
|
||||
}
|
||||
|
||||
setClipboardTextAndMinimize(currentEntry->attributes()->value(action->text()));
|
||||
setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->attributes()->value(action->text())));
|
||||
}
|
||||
|
||||
void DatabaseWidget::setClipboardTextAndMinimize(const QString& text)
|
||||
@ -624,6 +631,16 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget)
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void DatabaseWidget::csvImportFinished(bool accepted)
|
||||
{
|
||||
if (!accepted) {
|
||||
emit closeRequest();
|
||||
}
|
||||
else {
|
||||
setCurrentWidget(m_mainWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToView(bool accepted)
|
||||
{
|
||||
if (m_newGroup) {
|
||||
@ -844,12 +861,21 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToImportCsv(const QString& fileName)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
switchToMasterKeyChange();
|
||||
m_csvImportWizard->load(fileName, m_db);
|
||||
setCurrentWidget(m_csvImportWizard);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
|
||||
{
|
||||
m_databaseOpenMergeWidget->load(fileName);
|
||||
setCurrentWidget(m_databaseOpenMergeWidget);
|
||||
}
|
||||
|
||||
|
||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName, const QString& password,
|
||||
const QString& keyFile)
|
||||
{
|
||||
@ -1176,7 +1202,7 @@ bool DatabaseWidget::currentEntryHasUsername()
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->resolvePlaceholder(currentEntry->username()).isEmpty();
|
||||
return !currentEntry->resolveMultiplePlaceholders(currentEntry->username()).isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasPassword()
|
||||
@ -1186,7 +1212,7 @@ bool DatabaseWidget::currentEntryHasPassword()
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->resolvePlaceholder(currentEntry->password()).isEmpty();
|
||||
return !currentEntry->resolveMultiplePlaceholders(currentEntry->password()).isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasUrl()
|
||||
@ -1196,7 +1222,7 @@ bool DatabaseWidget::currentEntryHasUrl()
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->resolvePlaceholder(currentEntry->url()).isEmpty();
|
||||
return !currentEntry->resolveMultiplePlaceholders(currentEntry->url()).isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasNotes()
|
||||
@ -1206,7 +1232,7 @@ bool DatabaseWidget::currentEntryHasNotes()
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty();
|
||||
return !currentEntry->resolveMultiplePlaceholders(currentEntry->notes()).isEmpty();
|
||||
}
|
||||
|
||||
GroupView* DatabaseWidget::groupView() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -27,6 +27,7 @@
|
||||
|
||||
#include "gui/entry/EntryModel.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
#include "gui/csvImport/CsvImportWizard.h"
|
||||
|
||||
class ChangeMasterKeyWidget;
|
||||
class DatabaseOpenWidget;
|
||||
@ -60,6 +61,7 @@ public:
|
||||
enum Mode
|
||||
{
|
||||
None,
|
||||
ImportMode,
|
||||
ViewMode,
|
||||
EditMode,
|
||||
LockedMode
|
||||
@ -143,6 +145,8 @@ public slots:
|
||||
void switchToDatabaseSettings();
|
||||
void switchToOpenDatabase(const QString& fileName);
|
||||
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||
void switchToImportCsv(const QString& fileName);
|
||||
void csvImportFinished(bool accepted);
|
||||
void switchToOpenMergeDatabase(const QString& fileName);
|
||||
void switchToOpenMergeDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||
void switchToImportKeepass1(const QString& fileName);
|
||||
@ -188,6 +192,7 @@ private:
|
||||
EditEntryWidget* m_historyEditEntryWidget;
|
||||
EditGroupWidget* m_editGroupWidget;
|
||||
ChangeMasterKeyWidget* m_changeMasterKeyWidget;
|
||||
CsvImportWizard* m_csvImportWizard;
|
||||
DatabaseSettingsWidget* m_databaseSettingsWidget;
|
||||
DatabaseOpenWidget* m_databaseOpenWidget;
|
||||
DatabaseOpenWidget* m_databaseOpenMergeWidget;
|
||||
|
@ -250,6 +250,8 @@ MainWindow::MainWindow()
|
||||
SLOT(changeMasterKey()));
|
||||
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget,
|
||||
SLOT(changeDatabaseSettings()));
|
||||
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget,
|
||||
SLOT(importCsv()));
|
||||
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget,
|
||||
SLOT(importKeePass1Database()));
|
||||
connect(m_ui->actionRepairDatabase, SIGNAL(triggered()), this,
|
||||
@ -312,6 +314,12 @@ MainWindow::MainWindow()
|
||||
connect(m_ui->tabWidget, SIGNAL(messageDismissTab()), this, SLOT(hideTabMessage()));
|
||||
|
||||
updateTrayIcon();
|
||||
|
||||
if (config()->hasAccessError()) {
|
||||
m_ui->globalMessageWidget->showMessage(
|
||||
tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -423,6 +431,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
break;
|
||||
}
|
||||
case DatabaseWidget::EditMode:
|
||||
case DatabaseWidget::ImportMode:
|
||||
case DatabaseWidget::LockedMode: {
|
||||
const QList<QAction*> entryActions = m_ui->menuEntries->actions();
|
||||
for (QAction* action : entryActions) {
|
||||
@ -487,7 +496,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
m_ui->actionImportKeePass1->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
|
||||
m_ui->actionRepairDatabase->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||
|
||||
|
@ -174,6 +174,13 @@
|
||||
<string>&Recent databases</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuImport">
|
||||
<property name="title">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
<addaction name="actionImportKeePass1"/>
|
||||
<addaction name="actionImportCsv"/>
|
||||
</widget>
|
||||
<addaction name="actionDatabaseNew"/>
|
||||
<addaction name="actionDatabaseOpen"/>
|
||||
<addaction name="menuRecentDatabases"/>
|
||||
@ -185,7 +192,7 @@
|
||||
<addaction name="actionChangeDatabaseSettings"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDatabaseMerge"/>
|
||||
<addaction name="actionImportKeePass1"/>
|
||||
<addaction name="menuImport"/>
|
||||
<addaction name="actionExportCsv"/>
|
||||
<addaction name="actionRepairDatabase"/>
|
||||
<addaction name="separator"/>
|
||||
@ -392,11 +399,6 @@
|
||||
<string>Database settings</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportKeePass1">
|
||||
<property name="text">
|
||||
<string>&Import KeePass 1 database</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEntryClone">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
@ -504,6 +506,16 @@
|
||||
<string>&Export to CSV file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportKeePass1">
|
||||
<property name="text">
|
||||
<string>Import KeePass 1 database</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportCsv">
|
||||
<property name="text">
|
||||
<string>Import CSV file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRepairDatabase">
|
||||
<property name="text">
|
||||
<string>Re&pair database</string>
|
||||
|
@ -68,7 +68,7 @@ SettingsWidget::SettingsWidget(QWidget* parent)
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// systray not useful on OS X
|
||||
m_generalUi->systraySettings->setVisible(false);
|
||||
m_generalUi->systraySettings->setVisible(false);
|
||||
#endif
|
||||
|
||||
connect(this, SIGNAL(accepted()), SLOT(saveSettings()));
|
||||
@ -99,6 +99,12 @@ void SettingsWidget::addSettingsPage(ISettingsPage* page)
|
||||
|
||||
void SettingsWidget::loadSettings()
|
||||
{
|
||||
|
||||
if (config()->hasAccessError()) {
|
||||
showMessage(
|
||||
tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error);
|
||||
}
|
||||
|
||||
m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool());
|
||||
m_generalUi->rememberLastKeyFilesCheckBox->setChecked(config()->get("RememberLastKeyFiles").toBool());
|
||||
m_generalUi->openPreviousDatabasesOnStartupCheckBox->setChecked(
|
||||
@ -154,6 +160,15 @@ void SettingsWidget::loadSettings()
|
||||
|
||||
void SettingsWidget::saveSettings()
|
||||
{
|
||||
|
||||
if (config()->hasAccessError()) {
|
||||
showMessage(
|
||||
tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error);
|
||||
// We prevent closing the settings page if we could not write to
|
||||
// the config file.
|
||||
return;
|
||||
}
|
||||
|
||||
config()->set("RememberLastDatabases", m_generalUi->rememberLastDatabasesCheckBox->isChecked());
|
||||
config()->set("RememberLastKeyFiles", m_generalUi->rememberLastKeyFilesCheckBox->isChecked());
|
||||
config()->set("OpenPreviousDatabasesOnStartup",
|
||||
|
297
src/gui/csvImport/CsvImportWidget.cpp
Normal file
297
src/gui/csvImport/CsvImportWidget.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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 "CsvImportWidget.h"
|
||||
#include "ui_CsvImportWidget.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSpacerItem>
|
||||
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
|
||||
//I wanted to make the CSV import GUI future-proof, so if one day you need entries
|
||||
//to have a new field, all you have to do is uncomment a row or two here, and the GUI will follow:
|
||||
//dynamic generation of comboBoxes, labels, placement and so on. Try it for immense fun!
|
||||
const QStringList CsvImportWidget::m_columnHeader = QStringList()
|
||||
<< QObject::tr("Group")
|
||||
<< QObject::tr("Title")
|
||||
<< QObject::tr("Username")
|
||||
<< QObject::tr("Password")
|
||||
<< QObject::tr("URL")
|
||||
<< QObject::tr("Notes")
|
||||
// << QObject::tr("Future field1")
|
||||
// << QObject::tr("Future field2")
|
||||
// << QObject::tr("Future field3")
|
||||
;
|
||||
|
||||
CsvImportWidget::CsvImportWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::CsvImportWidget())
|
||||
, m_parserModel(new CsvParserModel(this))
|
||||
, m_comboModel(new QStringListModel(this))
|
||||
, m_comboMapper(new QSignalMapper(this))
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE");
|
||||
m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<".");
|
||||
m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|");
|
||||
m_ui->comboBoxComment->addItems(QStringList() <<"#" <<";" <<":" <<"@");
|
||||
m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus);
|
||||
m_ui->messageWidget->setHidden(true);
|
||||
|
||||
for (int i = 0; i < m_columnHeader.count(); ++i) {
|
||||
QLabel* label = new QLabel(m_columnHeader.at(i), this);
|
||||
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
QFont font = label->font();
|
||||
font.setBold(false);
|
||||
label->setFont(font);
|
||||
|
||||
QComboBox* combo = new QComboBox(this);
|
||||
font = combo->font();
|
||||
font.setBold(false);
|
||||
combo->setFont(font);
|
||||
m_combos.append(combo);
|
||||
combo->setModel(m_comboModel);
|
||||
m_comboMapper->setMapping(combo, i);
|
||||
connect(combo, SIGNAL(currentIndexChanged(int)), m_comboMapper, SLOT(map()));
|
||||
|
||||
//layout labels and combo fields in column-first order
|
||||
int combo_rows = 1 + (m_columnHeader.count() - 1) / 2;
|
||||
int x = i % combo_rows;
|
||||
int y = 2 * (i / combo_rows);
|
||||
m_ui->gridLayout_combos->addWidget(label, x, y);
|
||||
m_ui->gridLayout_combos->addWidget(combo, x, y+1);
|
||||
QSpacerItem *item = new QSpacerItem(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
m_ui->gridLayout_combos->addItem(item, x, y+2);
|
||||
}
|
||||
|
||||
m_parserModel->setHeaderLabels(m_columnHeader);
|
||||
m_ui->tableViewFields->setModel(m_parserModel);
|
||||
|
||||
connect(m_ui->spinBoxSkip, SIGNAL(valueChanged(int)), SLOT(skippedChanged(int)));
|
||||
connect(m_ui->comboBoxCodec, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||
connect(m_ui->comboBoxTextQualifier, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||
connect(m_ui->comboBoxComment, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||
connect(m_ui->comboBoxFieldSeparator, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||
connect(m_ui->checkBoxBackslash, SIGNAL(toggled(bool)), SLOT(parse()));
|
||||
connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int)));
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase()));
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
void CsvImportWidget::comboChanged(int comboId) {
|
||||
QComboBox* currentSender = qobject_cast<QComboBox*>(m_comboMapper->mapping(comboId));
|
||||
if (currentSender->currentIndex() != -1)
|
||||
//this line is the one that actually updates GUI table
|
||||
m_parserModel->mapColumns(currentSender->currentIndex(), comboId);
|
||||
updateTableview();
|
||||
}
|
||||
|
||||
void CsvImportWidget::skippedChanged(int rows) {
|
||||
m_parserModel->setSkippedRows(rows);
|
||||
updateTableview();
|
||||
}
|
||||
|
||||
CsvImportWidget::~CsvImportWidget() {}
|
||||
|
||||
void CsvImportWidget::configParser() {
|
||||
m_parserModel->setBackslashSyntax(m_ui->checkBoxBackslash->isChecked());
|
||||
m_parserModel->setComment(m_ui->comboBoxComment->currentText().at(0));
|
||||
m_parserModel->setTextQualifier(m_ui->comboBoxTextQualifier->currentText().at(0));
|
||||
m_parserModel->setCodec(m_ui->comboBoxCodec->currentText());
|
||||
m_parserModel->setFieldSeparator(m_ui->comboBoxFieldSeparator->currentText().at(0));
|
||||
}
|
||||
|
||||
void CsvImportWidget::updateTableview() {
|
||||
m_ui->tableViewFields->resizeRowsToContents();
|
||||
m_ui->tableViewFields->resizeColumnsToContents();
|
||||
|
||||
for (int c = 0; c < m_ui->tableViewFields->horizontalHeader()->count(); ++c)
|
||||
m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
|
||||
}
|
||||
|
||||
void CsvImportWidget::updatePreview() {
|
||||
|
||||
m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo());
|
||||
m_ui->spinBoxSkip->setValue(0);
|
||||
m_ui->spinBoxSkip->setMaximum(qMax(0, m_parserModel->rowCount() - 1));
|
||||
|
||||
int i;
|
||||
QStringList list(tr("Not present in CSV file"));
|
||||
|
||||
for (i = 1; i < m_parserModel->getCsvCols(); ++i) {
|
||||
QString s = QString(tr("Column ")) + QString::number(i);
|
||||
list << s;
|
||||
}
|
||||
m_comboModel->setStringList(list);
|
||||
|
||||
i=1;
|
||||
for (QComboBox* b : m_combos) {
|
||||
if (i < m_parserModel->getCsvCols())
|
||||
b->setCurrentIndex(i);
|
||||
else
|
||||
b->setCurrentIndex(0);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void CsvImportWidget::load(const QString& filename, Database* const db) {
|
||||
//QApplication::processEvents();
|
||||
m_db = db;
|
||||
m_parserModel->setFilename(filename);
|
||||
m_ui->labelFilename->setText(filename);
|
||||
Group* group = m_db->rootGroup();
|
||||
group->setUuid(Uuid::random());
|
||||
group->setNotes(tr("Imported from CSV file").append("\n").append(tr("Original data: ")) + filename);
|
||||
parse();
|
||||
}
|
||||
|
||||
void CsvImportWidget::parse() {
|
||||
configParser();
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
//QApplication::processEvents();
|
||||
bool good = m_parserModel->parse();
|
||||
updatePreview();
|
||||
QApplication::restoreOverrideCursor();
|
||||
if (!good)
|
||||
m_ui->messageWidget->showMessage(tr("Error(s) detected in CSV file !").append("\n")
|
||||
.append(formatStatusText()), MessageWidget::Warning);
|
||||
else
|
||||
m_ui->messageWidget->setHidden(true);
|
||||
QWidget::adjustSize();
|
||||
}
|
||||
|
||||
|
||||
QString CsvImportWidget::formatStatusText() const {
|
||||
QString text = m_parserModel->getStatus();
|
||||
int items = text.count('\n');
|
||||
if (items > 2) {
|
||||
return text.section('\n', 0, 1)
|
||||
.append("\n[").append(QString::number(items - 2))
|
||||
.append(tr(" more messages skipped]"));
|
||||
}
|
||||
if (items == 1) {
|
||||
text.append(QString("\n"));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void CsvImportWidget::writeDatabase() {
|
||||
|
||||
setRootGroup();
|
||||
for (int r = 0; r < m_parserModel->rowCount(); ++r) {
|
||||
//use validity of second column as a GO/NOGO for all others fields
|
||||
if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid())
|
||||
continue;
|
||||
Entry* entry = new Entry();
|
||||
entry->setUuid(Uuid::random());
|
||||
entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString()));
|
||||
entry->setTitle(m_parserModel->data(m_parserModel->index(r, 1)).toString());
|
||||
entry->setUsername(m_parserModel->data(m_parserModel->index(r, 2)).toString());
|
||||
entry->setPassword(m_parserModel->data(m_parserModel->index(r, 3)).toString());
|
||||
entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString());
|
||||
entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString());
|
||||
}
|
||||
QBuffer buffer;
|
||||
buffer.open(QBuffer::ReadWrite);
|
||||
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(&buffer, m_db);
|
||||
if (writer.hasError())
|
||||
MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n")
|
||||
.append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok);
|
||||
emit editFinished(true);
|
||||
}
|
||||
|
||||
|
||||
void CsvImportWidget::setRootGroup() {
|
||||
QString groupLabel;
|
||||
QStringList groupList;
|
||||
bool is_root = false;
|
||||
bool is_empty = false;
|
||||
bool is_label = false;
|
||||
|
||||
for (int r = 0; r < m_parserModel->rowCount(); ++r) {
|
||||
//use validity of second column as a GO/NOGO for all others fields
|
||||
if (not m_parserModel->data(m_parserModel->index(r, 1)).isValid())
|
||||
continue;
|
||||
groupLabel = m_parserModel->data(m_parserModel->index(r, 0)).toString();
|
||||
//check if group name is either "root", "" (empty) or some other label
|
||||
groupList = groupLabel.split("/", QString::SkipEmptyParts);
|
||||
if (groupList.isEmpty())
|
||||
is_empty = true;
|
||||
else
|
||||
if (not groupList.first().compare("Root", Qt::CaseSensitive))
|
||||
is_root = true;
|
||||
else if (not groupLabel.compare(""))
|
||||
is_empty = true;
|
||||
else
|
||||
is_label = true;
|
||||
|
||||
groupList.clear();
|
||||
}
|
||||
|
||||
if ((is_empty and is_root) or (is_label and not is_empty and is_root))
|
||||
m_db->rootGroup()->setName("CSV IMPORTED");
|
||||
else
|
||||
m_db->rootGroup()->setName("Root");
|
||||
}
|
||||
|
||||
Group *CsvImportWidget::splitGroups(QString label) {
|
||||
//extract group names from nested path provided in "label"
|
||||
Group *current = m_db->rootGroup();
|
||||
if (label.isEmpty())
|
||||
return current;
|
||||
|
||||
QStringList groupList = label.split("/", QString::SkipEmptyParts);
|
||||
//avoid the creation of a subgroup with the same name as Root
|
||||
if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root")
|
||||
groupList.removeFirst();
|
||||
|
||||
for (const QString& groupName : groupList) {
|
||||
Group *children = hasChildren(current, groupName);
|
||||
if (children == nullptr) {
|
||||
Group *brandNew = new Group();
|
||||
brandNew->setParent(current);
|
||||
brandNew->setName(groupName);
|
||||
brandNew->setUuid(Uuid::random());
|
||||
current = brandNew;
|
||||
} else {
|
||||
Q_ASSERT(children != nullptr);
|
||||
current = children;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
Group* CsvImportWidget::hasChildren(Group* current, QString groupName) {
|
||||
//returns the group whose name is "groupName" and is child of "current" group
|
||||
for (Group * group : current->children()) {
|
||||
if (group->name() == groupName)
|
||||
return group;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CsvImportWidget::reject() {
|
||||
emit editFinished(false);
|
||||
}
|
78
src/gui/csvImport/CsvImportWidget.h
Normal file
78
src/gui/csvImport/CsvImportWidget.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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_CSVIMPORTWIDGET_H
|
||||
#define KEEPASSX_CSVIMPORTWIDGET_H
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QPushButton>
|
||||
#include <QStringListModel>
|
||||
#include <QSignalMapper>
|
||||
#include <QList>
|
||||
#include <QComboBox>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "core/Metadata.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "gui/csvImport/CsvParserModel.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class CsvImportWidget;
|
||||
}
|
||||
|
||||
class CsvImportWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CsvImportWidget(QWidget *parent = nullptr);
|
||||
~CsvImportWidget();
|
||||
void load(const QString& filename, Database* const db);
|
||||
|
||||
signals:
|
||||
void editFinished(bool accepted);
|
||||
|
||||
private slots:
|
||||
void parse();
|
||||
void comboChanged(int comboId);
|
||||
void skippedChanged(int rows);
|
||||
void writeDatabase();
|
||||
void setRootGroup();
|
||||
void reject();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(CsvImportWidget)
|
||||
const QScopedPointer<Ui::CsvImportWidget> m_ui;
|
||||
CsvParserModel* const m_parserModel;
|
||||
QStringListModel* const m_comboModel;
|
||||
QSignalMapper* m_comboMapper;
|
||||
QList<QComboBox*> m_combos;
|
||||
Database *m_db;
|
||||
|
||||
KeePass2Writer m_writer;
|
||||
static const QStringList m_columnHeader;
|
||||
void configParser();
|
||||
void updatePreview();
|
||||
void updateTableview();
|
||||
Group* splitGroups(QString label);
|
||||
Group* hasChildren(Group* current, QString groupName);
|
||||
QString formatStatusText() const;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_CSVIMPORTWIDGET_H
|
584
src/gui/csvImport/CsvImportWidget.ui
Normal file
584
src/gui/csvImport/CsvImportWidget.ui
Normal file
@ -0,0 +1,584 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CsvImportWidget</class>
|
||||
<widget class="QWidget" name="CsvImportWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>779</width>
|
||||
<height>691</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelHeadline">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Import CSV fields</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelFilename">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>filename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSizeRowsCols">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>size, rows, columns</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="Encoding">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Encoding</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="4">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="checkBoxBackslash">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Consider '\' an escape character</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelCodec">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Codec</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QLabel" name="labelWarnings">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelFieldSeparator">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Fields are separated by</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBoxFieldSeparator">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelComments">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Comments start with</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_10">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBoxTextQualifier">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<spacer name="horizontalSpacer_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelTextQualifier">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Text is qualified by</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBoxCodec">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<spacer name="horizontalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSkipRows">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Skip first</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxSkip">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSkipRows_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>rows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="comboBoxComment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>114</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBoxColumnAssociations">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Column layout</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_combos">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>758</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBoxPreview">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTableView" name="tableViewFields">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
77
src/gui/csvImport/CsvImportWizard.cpp
Normal file
77
src/gui/csvImport/CsvImportWizard.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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 "CsvImportWizard.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
|
||||
CsvImportWizard::CsvImportWizard(QWidget *parent)
|
||||
: DialogyWidget(parent)
|
||||
{
|
||||
m_layout = new QGridLayout(this);
|
||||
m_pages = new QStackedWidget(parent);
|
||||
m_layout->addWidget(m_pages, 0, 0);
|
||||
|
||||
m_pages->addWidget(key = new ChangeMasterKeyWidget(m_pages));
|
||||
m_pages->addWidget(parse = new CsvImportWidget(m_pages));
|
||||
key->headlineLabel()->setText(tr("Import CSV file"));
|
||||
QFont headLineFont = key->headlineLabel()->font();
|
||||
headLineFont.setBold(true);
|
||||
headLineFont.setPointSize(headLineFont.pointSize() + 2);
|
||||
key->headlineLabel()->setFont(headLineFont);
|
||||
|
||||
connect(key, SIGNAL(editFinished(bool)), this, SLOT(keyFinished(bool)));
|
||||
connect(parse, SIGNAL(editFinished(bool)), this, SLOT(parseFinished(bool)));
|
||||
}
|
||||
|
||||
CsvImportWizard::~CsvImportWizard()
|
||||
{}
|
||||
|
||||
void CsvImportWizard::load(const QString& filename, Database* database)
|
||||
{
|
||||
m_db = database;
|
||||
parse->load(filename, database);
|
||||
key->clearForms();
|
||||
}
|
||||
|
||||
void CsvImportWizard::keyFinished(bool accepted)
|
||||
{
|
||||
if (!accepted) {
|
||||
emit(importFinished(false));
|
||||
return;
|
||||
}
|
||||
|
||||
m_pages->setCurrentIndex(m_pages->currentIndex()+1);
|
||||
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
bool result = m_db->setKey(key->newMasterKey());
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!result) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||
emit(importFinished(false));
|
||||
}
|
||||
}
|
||||
|
||||
void CsvImportWizard::parseFinished(bool accepted)
|
||||
{
|
||||
emit(importFinished(accepted));
|
||||
}
|
56
src/gui/csvImport/CsvImportWizard.h
Normal file
56
src/gui/csvImport/CsvImportWizard.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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_CSVIMPORTWIZARD_H
|
||||
#define KEEPASSX_CSVIMPORTWIZARD_H
|
||||
|
||||
#include "CsvImportWidget.h"
|
||||
|
||||
#include <QStackedWidget>
|
||||
#include <QGridLayout>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "gui/ChangeMasterKeyWidget.h"
|
||||
#include "gui/DialogyWidget.h"
|
||||
|
||||
class CsvImportWidget;
|
||||
|
||||
class CsvImportWizard : public DialogyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CsvImportWizard(QWidget *parent = nullptr);
|
||||
~CsvImportWizard();
|
||||
void load(const QString& filename, Database *database);
|
||||
|
||||
signals:
|
||||
void importFinished(bool accepted);
|
||||
|
||||
private slots:
|
||||
void keyFinished(bool accepted);
|
||||
void parseFinished(bool accepted);
|
||||
|
||||
private:
|
||||
Database* m_db;
|
||||
CsvImportWidget* parse;
|
||||
ChangeMasterKeyWidget* key;
|
||||
QStackedWidget *m_pages;
|
||||
QGridLayout *m_layout;
|
||||
};
|
||||
|
||||
#endif //KEEPASSX_CSVIMPORTWIZARD_H
|
125
src/gui/csvImport/CsvParserModel.cpp
Normal file
125
src/gui/csvImport/CsvParserModel.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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 "CsvParserModel.h"
|
||||
|
||||
CsvParserModel::CsvParserModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_skipped(0)
|
||||
{}
|
||||
|
||||
CsvParserModel::~CsvParserModel()
|
||||
{}
|
||||
|
||||
void CsvParserModel::setFilename(const QString& filename) {
|
||||
m_filename = filename;
|
||||
}
|
||||
|
||||
QString CsvParserModel::getFileInfo(){
|
||||
QString a(QString::number(getFileSize()).append(tr(" byte, ")));
|
||||
a.append(QString::number(getCsvRows())).append(tr(" rows, "));
|
||||
a.append(QString::number(qMax(0, getCsvCols()-1))).append(tr(" columns"));
|
||||
return a;
|
||||
}
|
||||
|
||||
bool CsvParserModel::parse() {
|
||||
bool r;
|
||||
beginResetModel();
|
||||
m_columnMap.clear();
|
||||
if (CsvParser::isFileLoaded()) {
|
||||
r = CsvParser::reparse();
|
||||
} else {
|
||||
QFile csv(m_filename);
|
||||
r = CsvParser::parse(&csv);
|
||||
}
|
||||
for (int i = 0; i < columnCount(); ++i)
|
||||
m_columnMap.insert(i,0);
|
||||
addEmptyColumn();
|
||||
endResetModel();
|
||||
return r;
|
||||
}
|
||||
|
||||
void CsvParserModel::addEmptyColumn() {
|
||||
for (int i = 0; i < m_table.size(); ++i) {
|
||||
CsvRow r = m_table.at(i);
|
||||
r.prepend(QString(""));
|
||||
m_table.replace(i, r);
|
||||
}
|
||||
}
|
||||
|
||||
void CsvParserModel::mapColumns(int csvColumn, int dbColumn) {
|
||||
if ((csvColumn < 0) || (dbColumn < 0))
|
||||
return;
|
||||
beginResetModel();
|
||||
if (csvColumn >= getCsvCols())
|
||||
m_columnMap[dbColumn] = 0; //map to the empty column
|
||||
else
|
||||
m_columnMap[dbColumn] = csvColumn;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void CsvParserModel::setSkippedRows(int skipped) {
|
||||
m_skipped = skipped;
|
||||
QModelIndex topLeft = createIndex(skipped,0);
|
||||
QModelIndex bottomRight = createIndex(m_skipped+rowCount(), columnCount());
|
||||
emit dataChanged(topLeft, bottomRight);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void CsvParserModel::setHeaderLabels(QStringList l) {
|
||||
m_columnHeader = l;
|
||||
}
|
||||
|
||||
int CsvParserModel::rowCount(const QModelIndex &parent) const {
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return getCsvRows();
|
||||
}
|
||||
|
||||
int CsvParserModel::columnCount(const QModelIndex &parent) const {
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return m_columnHeader.size();
|
||||
}
|
||||
|
||||
QVariant CsvParserModel::data(const QModelIndex &index, int role) const {
|
||||
if ((index.column() >= m_columnHeader.size())
|
||||
|| (index.row()+m_skipped >= rowCount())
|
||||
|| !index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
if (role == Qt::DisplayRole)
|
||||
return m_table.at(index.row()+m_skipped).at(m_columnMap[index.column()]);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant CsvParserModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (orientation == Qt::Horizontal) {
|
||||
if ((section < 0) || (section >= m_columnHeader.size()))
|
||||
return QVariant();
|
||||
return m_columnHeader.at(section);
|
||||
} else if (orientation == Qt::Vertical) {
|
||||
if (section+m_skipped >= rowCount())
|
||||
return QVariant();
|
||||
return QString::number(section+1);
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
60
src/gui/csvImport/CsvParserModel.h
Normal file
60
src/gui/csvImport/CsvParserModel.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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_CSVPARSERMODEL_H
|
||||
#define KEEPASSX_CSVPARSERMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QMap>
|
||||
|
||||
#include "core/CsvParser.h"
|
||||
#include "core/Group.h"
|
||||
|
||||
class CsvParserModel : public QAbstractTableModel, public CsvParser
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CsvParserModel(QObject *parent = nullptr);
|
||||
~CsvParserModel();
|
||||
void setFilename(const QString& filename);
|
||||
QString getFileInfo();
|
||||
bool parse();
|
||||
|
||||
void setHeaderLabels(QStringList l);
|
||||
void mapColumns(int csvColumn, int dbColumn);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
public slots:
|
||||
void setSkippedRows(int skipped);
|
||||
|
||||
private:
|
||||
int m_skipped;
|
||||
QString m_filename;
|
||||
QStringList m_columnHeader;
|
||||
//first column of model must be empty (aka combobox row "Not present in CSV file")
|
||||
void addEmptyColumn();
|
||||
//mapping CSV columns to keepassx columns
|
||||
QMap<int, int> m_columnMap;
|
||||
};
|
||||
|
||||
#endif //KEEPASSX_CSVPARSERMODEL_H
|
||||
|
@ -139,19 +139,19 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
break;
|
||||
case Title:
|
||||
result = entry->resolvePlaceholder(entry->title());
|
||||
result = entry->resolveMultiplePlaceholders(entry->title());
|
||||
if (attr->isReference(EntryAttributes::TitleKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Username:
|
||||
result = entry->resolvePlaceholder(entry->username());
|
||||
result = entry->resolveMultiplePlaceholders(entry->username());
|
||||
if (attr->isReference(EntryAttributes::UserNameKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
}
|
||||
return result;
|
||||
case Url:
|
||||
result = entry->resolvePlaceholder(entry->url());
|
||||
result = entry->resolveMultiplePlaceholders(entry->url());
|
||||
if (attr->isReference(EntryAttributes::URLKey)) {
|
||||
result.prepend(tr("Ref: ","Reference abbreviation"));
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public:
|
||||
*/
|
||||
void detect();
|
||||
|
||||
Q_SIGNALS:
|
||||
signals:
|
||||
/** Emitted in response to detect() when a device is found
|
||||
*
|
||||
* @slot is the slot number detected
|
||||
|
17
src/main.cpp
17
src/main.cpp
@ -26,8 +26,13 @@
|
||||
#include "crypto/Crypto.h"
|
||||
#include "gui/Application.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/csvImport/CsvImportWizard.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
#endif
|
||||
|
||||
#ifdef QT_STATIC
|
||||
#include <QtPlugin>
|
||||
|
||||
@ -130,6 +135,14 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
|
||||
int exitCode = app.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
|
||||
__lsan_do_leak_check();
|
||||
__lsan_disable();
|
||||
#endif
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
@ -158,6 +158,9 @@ endif()
|
||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
336
tests/TestCsvParser.cpp
Normal file
336
tests/TestCsvParser.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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 "TestCsvParser.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestCsvParser)
|
||||
|
||||
void TestCsvParser::initTestCase()
|
||||
{
|
||||
parser = new CsvParser();
|
||||
}
|
||||
|
||||
void TestCsvParser::cleanupTestCase()
|
||||
{
|
||||
delete parser;
|
||||
}
|
||||
|
||||
void TestCsvParser::init()
|
||||
{
|
||||
file = new QTemporaryFile();
|
||||
if (not file->open())
|
||||
QFAIL("Cannot open file!");
|
||||
parser->setBackslashSyntax(false);
|
||||
parser->setComment('#');
|
||||
parser->setFieldSeparator(',');
|
||||
parser->setTextQualifier(QChar('"'));
|
||||
}
|
||||
|
||||
void TestCsvParser::cleanup()
|
||||
{
|
||||
file->remove();
|
||||
}
|
||||
|
||||
/****************** TEST CASES ******************/
|
||||
void TestCsvParser::testMissingQuote() {
|
||||
parser->setTextQualifier(':');
|
||||
QTextStream out(file);
|
||||
out << "A,B\n:BM,1";
|
||||
QEXPECT_FAIL("", "Bad format", Continue);
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QWARN(parser->getStatus().toLatin1());
|
||||
}
|
||||
|
||||
void TestCsvParser::testMalformed() {
|
||||
parser->setTextQualifier(':');
|
||||
QTextStream out(file);
|
||||
out << "A,B,C\n:BM::,1,:2:";
|
||||
QEXPECT_FAIL("", "Bad format", Continue);
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QWARN(parser->getStatus().toLatin1());
|
||||
}
|
||||
|
||||
void TestCsvParser::testBackslashSyntax() {
|
||||
parser->setBackslashSyntax(true);
|
||||
parser->setTextQualifier(QChar('X'));
|
||||
QTextStream out(file);
|
||||
//attended result: one"\t\"wo
|
||||
out << "Xone\\\"\\\\t\\\\\\\"w\noX\n"
|
||||
<< "X13X,X2\\X,X,\"\"3\"X\r"
|
||||
<< "3,X\"4\"X,,\n"
|
||||
<< "XX\n"
|
||||
<< "\\";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no");
|
||||
QVERIFY(t.at(1).at(0) == "13");
|
||||
QVERIFY(t.at(1).at(1) == "2X,");
|
||||
QVERIFY(t.at(1).at(2) == "\"\"3\"X");
|
||||
QVERIFY(t.at(2).at(0) == "3");
|
||||
QVERIFY(t.at(2).at(1) == "\"4\"");
|
||||
QVERIFY(t.at(2).at(2) == "");
|
||||
QVERIFY(t.at(2).at(3) == "");
|
||||
QVERIFY(t.at(3).at(0) == "\\");
|
||||
QVERIFY(t.size() == 4);
|
||||
}
|
||||
|
||||
void TestCsvParser::testQuoted() {
|
||||
QTextStream out(file);
|
||||
out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n"
|
||||
<< "2\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.at(0).at(0) == "ro");
|
||||
QVERIFY(t.at(0).at(1) == "w");
|
||||
QVERIFY(t.at(0).at(2) == "end, of \"\"\"row\"\"");
|
||||
QVERIFY(t.at(1).at(0) == "2");
|
||||
QVERIFY(t.size() == 2);
|
||||
}
|
||||
|
||||
void TestCsvParser::testEmptySimple() {
|
||||
QTextStream out(file);
|
||||
out <<"";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 0);
|
||||
}
|
||||
|
||||
void TestCsvParser::testEmptyQuoted() {
|
||||
QTextStream out(file);
|
||||
out <<"\"\"";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 0);
|
||||
}
|
||||
|
||||
void TestCsvParser::testEmptyNewline() {
|
||||
QTextStream out(file);
|
||||
out <<"\"\n\"";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 0);
|
||||
}
|
||||
|
||||
void TestCsvParser::testEmptyFile()
|
||||
{
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 0);
|
||||
}
|
||||
|
||||
void TestCsvParser::testNewline()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << "1,2\n\n\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 1);
|
||||
QVERIFY(t.at(0).at(0) == "1");
|
||||
QVERIFY(t.at(0).at(1) == "2");
|
||||
}
|
||||
|
||||
void TestCsvParser::testCR()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << "1,2\r3,4";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 2);
|
||||
QVERIFY(t.at(0).at(0) == "1");
|
||||
QVERIFY(t.at(0).at(1) == "2");
|
||||
QVERIFY(t.at(1).at(0) == "3");
|
||||
QVERIFY(t.at(1).at(1) == "4");
|
||||
}
|
||||
|
||||
void TestCsvParser::testLF()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << "1,2\n3,4";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 2);
|
||||
QVERIFY(t.at(0).at(0) == "1");
|
||||
QVERIFY(t.at(0).at(1) == "2");
|
||||
QVERIFY(t.at(1).at(0) == "3");
|
||||
QVERIFY(t.at(1).at(1) == "4");
|
||||
}
|
||||
|
||||
void TestCsvParser::testCRLF()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << "1,2\r\n3,4";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 2);
|
||||
QVERIFY(t.at(0).at(0) == "1");
|
||||
QVERIFY(t.at(0).at(1) == "2");
|
||||
QVERIFY(t.at(1).at(0) == "3");
|
||||
QVERIFY(t.at(1).at(1) == "4");
|
||||
}
|
||||
|
||||
void TestCsvParser::testComments()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << " #one\n"
|
||||
<< " \t # two, three \r\n"
|
||||
<< " #, sing\t with\r"
|
||||
<< " #\t me!\n"
|
||||
<< "useful,text #1!";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 1);
|
||||
QVERIFY(t.at(0).at(0) == "useful");
|
||||
QVERIFY(t.at(0).at(1) == "text #1!");
|
||||
}
|
||||
|
||||
void TestCsvParser::testColumns() {
|
||||
QTextStream out(file);
|
||||
out << "1,2\n"
|
||||
<< ",,,,,,,,,a\n"
|
||||
<< "a,b,c,d\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(parser->getCsvCols() == 10);
|
||||
}
|
||||
|
||||
void TestCsvParser::testSimple() {
|
||||
QTextStream out(file);
|
||||
out << ",,2\r,2,3\n"
|
||||
<< "A,,B\"\n"
|
||||
<< " ,,\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 4);
|
||||
QVERIFY(t.at(0).at(0) == "");
|
||||
QVERIFY(t.at(0).at(1) == "");
|
||||
QVERIFY(t.at(0).at(2) == "2");
|
||||
QVERIFY(t.at(1).at(0) == "");
|
||||
QVERIFY(t.at(1).at(1) == "2");
|
||||
QVERIFY(t.at(1).at(2) == "3");
|
||||
QVERIFY(t.at(2).at(0) == "A");
|
||||
QVERIFY(t.at(2).at(1) == "");
|
||||
QVERIFY(t.at(2).at(2) == "B\"");
|
||||
QVERIFY(t.at(3).at(0) == " ");
|
||||
QVERIFY(t.at(3).at(1) == "");
|
||||
QVERIFY(t.at(3).at(2) == "");
|
||||
}
|
||||
|
||||
void TestCsvParser::testSeparator() {
|
||||
parser->setFieldSeparator('\t');
|
||||
QTextStream out(file);
|
||||
out << "\t\t2\r\t2\t3\n"
|
||||
<< "A\t\tB\"\n"
|
||||
<< " \t\t\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 4);
|
||||
QVERIFY(t.at(0).at(0) == "");
|
||||
QVERIFY(t.at(0).at(1) == "");
|
||||
QVERIFY(t.at(0).at(2) == "2");
|
||||
QVERIFY(t.at(1).at(0) == "");
|
||||
QVERIFY(t.at(1).at(1) == "2");
|
||||
QVERIFY(t.at(1).at(2) == "3");
|
||||
QVERIFY(t.at(2).at(0) == "A");
|
||||
QVERIFY(t.at(2).at(1) == "");
|
||||
QVERIFY(t.at(2).at(2) == "B\"");
|
||||
QVERIFY(t.at(3).at(0) == " ");
|
||||
QVERIFY(t.at(3).at(1) == "");
|
||||
QVERIFY(t.at(3).at(2) == "");
|
||||
}
|
||||
|
||||
void TestCsvParser::testMultiline()
|
||||
{
|
||||
parser->setTextQualifier(QChar(':'));
|
||||
QTextStream out(file);
|
||||
out << ":1\r\n2a::b:,:3\r4:\n"
|
||||
<< "2\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.at(0).at(0) == "1\n2a:b");
|
||||
QVERIFY(t.at(0).at(1) == "3\n4");
|
||||
QVERIFY(t.at(1).at(0) == "2");
|
||||
QVERIFY(t.size() == 2);
|
||||
}
|
||||
|
||||
void TestCsvParser::testEmptyReparsing()
|
||||
{
|
||||
parser->parse(nullptr);
|
||||
QVERIFY(parser->reparse());
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 0);
|
||||
}
|
||||
|
||||
void TestCsvParser::testReparsing()
|
||||
{
|
||||
QTextStream out(file);
|
||||
out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n"
|
||||
<< "2\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
|
||||
QEXPECT_FAIL("", "Wrong qualifier", Continue);
|
||||
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
||||
|
||||
parser->setTextQualifier(QChar(':'));
|
||||
|
||||
QVERIFY(parser->reparse());
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
||||
QVERIFY(t.at(0).at(1) == "te\nxt2");
|
||||
QVERIFY(t.at(0).at(2) == "end of \"this\n string\"");
|
||||
QVERIFY(t.at(1).at(0) == "2");
|
||||
QVERIFY(t.size() == 2);
|
||||
}
|
||||
|
||||
void TestCsvParser::testQualifier() {
|
||||
parser->setTextQualifier(QChar('X'));
|
||||
QTextStream out(file);
|
||||
out << "X1X,X2XX,X,\"\"3\"\"\"X\r"
|
||||
<< "3,X\"4\"X,,\n";
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 2);
|
||||
QVERIFY(t.at(0).at(0) == "1");
|
||||
QVERIFY(t.at(0).at(1) == "2X,");
|
||||
QVERIFY(t.at(0).at(2) == "\"\"3\"\"\"X");
|
||||
QVERIFY(t.at(1).at(0) == "3");
|
||||
QVERIFY(t.at(1).at(1) == "\"4\"");
|
||||
QVERIFY(t.at(1).at(2) == "");
|
||||
QVERIFY(t.at(1).at(3) == "");
|
||||
}
|
||||
|
||||
void TestCsvParser::testUnicode() {
|
||||
//QString m("Texte en fran\u00e7ais");
|
||||
//CORRECT QString g("\u20AC");
|
||||
//CORRECT QChar g(0x20AC);
|
||||
//ERROR QChar g("\u20AC");
|
||||
parser->setFieldSeparator(QChar('A'));
|
||||
QTextStream out(file);
|
||||
out << QString("€1A2śA\"3śAż\"Ażac");
|
||||
|
||||
QVERIFY(parser->parse(file));
|
||||
t = parser->getCsvTable();
|
||||
QVERIFY(t.size() == 1);
|
||||
QVERIFY(t.at(0).at(0) == "€1");
|
||||
QVERIFY(t.at(0).at(1) == "2ś");
|
||||
QVERIFY(t.at(0).at(2) == "3śAż");
|
||||
QVERIFY(t.at(0).at(3) == "żac");
|
||||
}
|
70
tests/TestCsvParser.h
Normal file
70
tests/TestCsvParser.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||
*
|
||||
* 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_TESTCSVPARSER_H
|
||||
#define KEEPASSX_TESTCSVPARSER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "core/CsvParser.h"
|
||||
|
||||
class CsvParser;
|
||||
|
||||
class TestCsvParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
private slots:
|
||||
void init();
|
||||
void cleanup();
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void testUnicode();
|
||||
void testLF();
|
||||
void testEmptyReparsing();
|
||||
void testSimple();
|
||||
void testEmptyQuoted();
|
||||
void testEmptyNewline();
|
||||
void testSeparator();
|
||||
void testCR();
|
||||
void testCRLF();
|
||||
void testMalformed();
|
||||
void testQualifier();
|
||||
void testNewline();
|
||||
void testEmptySimple();
|
||||
void testMissingQuote();
|
||||
void testComments();
|
||||
void testBackslashSyntax();
|
||||
void testReparsing();
|
||||
void testEmptyFile();
|
||||
void testQuoted();
|
||||
void testMultiline();
|
||||
void testColumns();
|
||||
|
||||
private:
|
||||
QTemporaryFile* file;
|
||||
CsvParser* parser;
|
||||
CsvTable t;
|
||||
void dumpRow(CsvTable table, int row);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTCSVPARSER_H
|
@ -26,7 +26,7 @@ class TestYubiKeyChalResp: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
|
@ -316,6 +316,8 @@ void TestGui::testAddEntry()
|
||||
QTest::keyClicks(titleEdit, "something 3");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QApplication::processEvents();
|
||||
|
||||
// Confirm that 4 entries now exist
|
||||
QTRY_COMPARE(entryView->model()->rowCount(), 4);
|
||||
}
|
||||
@ -588,6 +590,50 @@ void TestGui::testCloneEntry()
|
||||
QCOMPARE(entryClone->title(), entryOrg->title() + QString(" - Clone"));
|
||||
}
|
||||
|
||||
void TestGui::testEntryPlaceholders()
|
||||
{
|
||||
QToolBar* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
||||
// Find the new entry action
|
||||
QAction* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
|
||||
QVERIFY(entryNewAction->isEnabled());
|
||||
|
||||
// Find the button associated with the new entry action
|
||||
QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction);
|
||||
QVERIFY(entryNewWidget->isVisible());
|
||||
QVERIFY(entryNewWidget->isEnabled());
|
||||
|
||||
// Click the new entry button and check that we enter edit mode
|
||||
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
|
||||
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");
|
||||
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");
|
||||
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
|
||||
|
||||
QCOMPARE(entryView->model()->rowCount(), 2);
|
||||
|
||||
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode);
|
||||
QModelIndex item = entryView->model()->index(1, 1);
|
||||
Entry* entry = entryView->entryFromIndex(item);
|
||||
|
||||
QCOMPARE(entry->title(), QString("test"));
|
||||
QCOMPARE(entry->url(), QString("{TITLE}.{USERNAME}"));
|
||||
|
||||
// Test password copy
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
m_dbWidget->copyURL();
|
||||
QTRY_COMPARE(clipboard->text(), QString("test.john"));
|
||||
}
|
||||
|
||||
void TestGui::testDragAndDropEntry()
|
||||
{
|
||||
EntryView* entryView = m_dbWidget->findChild<EntryView*>("entryView");
|
||||
|
@ -48,6 +48,7 @@ private slots:
|
||||
void testSearch();
|
||||
void testDeleteEntry();
|
||||
void testCloneEntry();
|
||||
void testEntryPlaceholders();
|
||||
void testDragAndDropEntry();
|
||||
void testDragAndDropGroup();
|
||||
void testSaveAs();
|
||||
|
Loading…
Reference in New Issue
Block a user