From afdf02b4bea88d001a74048cd065143530b28c3a Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 9 Jan 2017 01:33:21 +0100 Subject: [PATCH 01/50] Implement import of databases in CSV (Comma Separated Values) format (i.e. from other password managers) --- src/CMakeLists.txt | 5 + src/core/CsvParser.cpp | 411 ++++++++++++++++++++ src/core/CsvParser.h | 101 +++++ src/gui/DatabaseTabWidget.cpp | 17 + src/gui/DatabaseTabWidget.h | 1 + src/gui/DatabaseWidget.cpp | 25 +- src/gui/DatabaseWidget.h | 6 +- src/gui/MainWindow.cpp | 4 +- src/gui/MainWindow.ui | 24 +- src/gui/csvImport/CsvImportWidget.cpp | 289 ++++++++++++++ src/gui/csvImport/CsvImportWidget.h | 78 ++++ src/gui/csvImport/CsvImportWidget.ui | 524 ++++++++++++++++++++++++++ src/gui/csvImport/CsvImportWizard.cpp | 72 ++++ src/gui/csvImport/CsvImportWizard.h | 55 +++ src/gui/csvImport/CsvParserModel.cpp | 139 +++++++ src/gui/csvImport/CsvParserModel.h | 59 +++ src/main.cpp | 1 + tests/CMakeLists.txt | 3 + tests/TestCsvParser.cpp | 335 ++++++++++++++++ tests/TestCsvParser.h | 69 ++++ 20 files changed, 2209 insertions(+), 9 deletions(-) create mode 100644 src/core/CsvParser.cpp create mode 100644 src/core/CsvParser.h create mode 100644 src/gui/csvImport/CsvImportWidget.cpp create mode 100644 src/gui/csvImport/CsvImportWidget.h create mode 100644 src/gui/csvImport/CsvImportWidget.ui create mode 100644 src/gui/csvImport/CsvImportWizard.cpp create mode 100644 src/gui/csvImport/CsvImportWizard.h create mode 100644 src/gui/csvImport/CsvParserModel.cpp create mode 100644 src/gui/csvImport/CsvParserModel.h create mode 100644 tests/TestCsvParser.cpp create mode 100644 tests/TestCsvParser.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94a685737..f0b293a06 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 @@ -133,6 +137,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 diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp new file mode 100644 index 000000000..87b3f3403 --- /dev/null +++ b/src/core/CsvParser.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "core/Tools.h" +#include "CsvParser.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) { + m_statusMsg += QObject::tr("NULL device\n"); + 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)) { + m_statusMsg += QObject::tr("Error reading from device\n"); + m_isFileLoaded = false; + } + else { + device->close(); + + m_array.replace("\r\n", "\n"); + m_array.replace("\r", "\n"); + if (0 == m_array.size()) { + m_statusMsg += 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")); + } + m_currRow++; + m_currCol = 1; + parseRecord(); + } + fillColumns(); + return m_isGood; +} + +void CsvParser::parseRecord() { + csvrow row; + if (isComment()) { + skipLine(); + return; + } + else { + 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")); + } +} + +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; + //escape-character syntax, e.g. \" + if (true == m_isBackslashSyntax) + { + 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; + } + } + //double quote syntax, e.g. "" + else + { + 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 the rows with lesser columns with empty fields + + for (int i=0; i 0) { + csvrow r = m_table.at(i); + for (int j=0; j> c; + } +} + +void CsvParser::ungetChar() { + if (!m_ts.seek(m_lastPos)) + m_statusMsg += QObject::tr("Internal: unget lower bound exceeded"); +} + +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 == 0x20); +} + +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) { + m_statusMsg += s + .append(" @" + QString::number(m_currRow)) + .append(",") + .append(QString::number(m_currCol)) + .append("\n"); + m_isGood = false; +} diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h new file mode 100644 index 000000000..77c6d36e1 --- /dev/null +++ b/src/core/CsvParser.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_CSVPARSER_H +#define KEEPASSX_CSVPARSER_H + +#include +#include +#include +#include + +typedef QStringList csvrow; +typedef QList 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); +}; + +#endif //CSVPARSER_H + diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index 910e94899..e3f4bc188 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -212,6 +212,23 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, Q_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")); diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index 8f01a987d..d48e0e9d0 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -66,6 +66,7 @@ public: public Q_SLOTS: void newDatabase(); void openDatabase(); + void importCsv(); void mergeDatabase(); void importKeePass1Database(); bool saveDatabase(int index = -1); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1cb1882d3..2a88d8a1a 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2010 Felix Geyer * * 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())); @@ -624,6 +628,16 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget) adjustSize(); } +void DatabaseWidget::csvImportFinished(bool accepted) +{ + if (!accepted) { + Q_EMIT closeRequest(); + } + else { + setCurrentWidget(m_mainWidget); + } +} + void DatabaseWidget::switchToView(bool accepted) { if (m_newGroup) { @@ -844,12 +858,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) { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 66ece0537..87d14a182 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2010 Felix Geyer * * 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; @@ -143,6 +144,8 @@ public Q_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); @@ -187,6 +190,7 @@ private: EditEntryWidget* m_historyEditEntryWidget; EditGroupWidget* m_editGroupWidget; ChangeMasterKeyWidget* m_changeMasterKeyWidget; + CsvImportWizard* m_csvImportWizard; DatabaseSettingsWidget* m_databaseSettingsWidget; DatabaseOpenWidget* m_databaseOpenWidget; DatabaseOpenWidget* m_databaseOpenMergeWidget; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 763ada1ae..aa862465d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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, @@ -487,7 +489,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); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index 6e3ecf684..7a029eeef 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -176,6 +176,13 @@ &Recent databases + + + Import + + + + @@ -187,7 +194,7 @@ - + @@ -394,11 +401,6 @@ Database settings - - - &Import KeePass 1 database - - false @@ -506,6 +508,16 @@ &Export to CSV file + + + Import KeePass 1 database + + + + + Import CSV file + + Re&pair database diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp new file mode 100644 index 000000000..9e53d5af3 --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CsvImportWidget.h" +#include "ui_CsvImportWidget.h" +#include "gui/MessageBox.h" + +#include +#include + +//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); + + QFont font = m_ui->labelHeadline->font(); + font.setBold(true); + font.setPointSize(font.pointSize() + 2); + m_ui->labelHeadline->setFont(font); + + 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); + + for (int i=0; isetFixedWidth(label->minimumSizeHint().width()); + 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); + } + + 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_ui->pushButtonWarnings, SIGNAL(clicked()), this, SLOT(showReport())); + 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(m_comboMapper->mapping(comboId)); + if (currentSender->currentIndex() != -1) { + //here is the line that actually updates the 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; ctableViewFields->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(m_parserModel->rowCount()-1); + + int i; + QStringList list(tr("Not present in CSV file")); + + for (i=1; igetCsvCols(); i++) { + QString s = QString(tr("Column ")) + QString::number(i); + list << s; + } + m_comboModel->setStringList(list); + + i=1; + Q_FOREACH (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) { + 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\nOriginal data: ") + filename); + + parse(); +} + +void CsvImportWidget::parse() { + configParser(); + QApplication::setOverrideCursor(Qt::WaitCursor); + bool good = m_parserModel->parse(); + QApplication::restoreOverrideCursor(); + updatePreview(); + m_ui->pushButtonWarnings->setEnabled(!good); +} + +void CsvImportWidget::showReport() { + MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") + .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); +} + +void CsvImportWidget::writeDatabase() { + + checkGroupNames(); + for (int r=0; rrowCount(); r++) { + //use the validity of second column as a GO/NOGO hint for all others fields + if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { + 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); + } + Q_EMIT editFinished(true); +} + + +void CsvImportWidget::checkGroupNames() { + QString groupLabel; + QStringList groupList; + bool is_root = false + , is_empty = false + , is_label = false; + for (int r=0; rrowCount(); r++) { + 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 (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 ((not is_label and (is_empty xor is_root)) + or (is_label and not is_root)) { + m_db->rootGroup()->setName("Root"); + } + else if ((is_empty and is_root) + or (is_label and not is_empty and is_root)) { + m_db->rootGroup()->setName("CSV IMPORTED"); + } + else { + //SHOULD NEVER GET HERE + m_db->rootGroup()->setName("ROOT_FALLBACK"); + } +} + +Group *CsvImportWidget::splitGroups(QString label) { + //extract group names from nested path provided in "label" + Group *current = m_db->rootGroup(); + QStringList groupList = label.split("/", QString::SkipEmptyParts); + + //skip the creation of a subgroup of Root with the same name + 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); + 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() { + Q_EMIT editFinished(false); +} diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h new file mode 100644 index 000000000..f8798b560 --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_CSVIMPORTWIDGET_H +#define KEEPASSX_CSVIMPORTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +#include "format/KeePass2Writer.h" +#include "gui/csvImport/CsvParserModel.h" +#include "keys/PasswordKey.h" +#include "core/Metadata.h" + + +namespace Ui { + class CsvImportWidget; +} + +class CsvImportWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CsvImportWidget(QWidget *parent = nullptr); + virtual ~CsvImportWidget(); + void load(const QString& filename, Database* const db); + +Q_SIGNALS: + void editFinished(bool accepted); + +private Q_SLOTS: + void parse(); + void showReport(); + void comboChanged(int comboId); + void skippedChanged(int rows); + void writeDatabase(); + void checkGroupNames(); + void reject(); + +private: + Q_DISABLE_COPY(CsvImportWidget) + const QScopedPointer m_ui; + CsvParserModel* const m_parserModel; + QStringListModel* const m_comboModel; + QSignalMapper* m_comboMapper; + QList 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); +}; + +#endif // KEEPASSX_CSVIMPORTWIDGET_H diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui new file mode 100644 index 000000000..5df2aa1af --- /dev/null +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -0,0 +1,524 @@ + + + CsvImportWidget + + + + 0 + 0 + 779 + 691 + + + + + + + + + + + 0 + 137 + + + + + 75 + true + + + + Encoding + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Text is qualified by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + + 50 + false + + + + Show parser warnings + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Codec + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 50 + false + + + + Fields are separated by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + + + + Comments start with + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Treat '\' as escape character + + + + + + + + + + 50 + false + + + + Skip first + + + + + + + + 50 + false + + + + + + + + + 50 + false + + + + rows + + + + + + + + + + 50 + false + true + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 75 + true + + + + Column layout + + + + + + 0 + + + + + + + + + + + + + 0 + 0 + + + + Import CSV fields + + + + + + + + 0 + 0 + + + + filename + + + + + + + + 0 + 0 + + + + size, rows, columns + + + + + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + 75 + true + + + + Preview + + + false + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 27 + + + + + + + + + diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp new file mode 100644 index 000000000..f84a22959 --- /dev/null +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CsvImportWizard.h" +#include +#include +#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")); + + 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) { + Q_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")); + Q_EMIT importFinished(false); + return; + } +} + +void CsvImportWizard::parseFinished(bool accepted) +{ + Q_EMIT(importFinished(accepted)); +} diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h new file mode 100644 index 000000000..5a83a19d8 --- /dev/null +++ b/src/gui/csvImport/CsvImportWizard.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_CSVIMPORTWIZARD_H +#define KEEPASSX_CSVIMPORTWIZARD_H + +#include +#include + +#include "CsvImportWidget.h" +#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); + virtual ~CsvImportWizard(); + void load(const QString& filename, Database *database); + +Q_SIGNALS: + void importFinished(bool accepted); + +private Q_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 diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp new file mode 100644 index 000000000..3bc6c834c --- /dev/null +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "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((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= 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()); + Q_EMIT dataChanged(topLeft, bottomRight); + Q_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(); +} + + diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h new file mode 100644 index 000000000..ae7bf6e20 --- /dev/null +++ b/src/gui/csvImport/CsvParserModel.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_CSVPARSERMODEL_H +#define KEEPASSX_CSVPARSERMODEL_H + +#include +#include +#include "core/Group.h" +#include "core/CsvParser.h" + +class CsvParserModel : public QAbstractTableModel, public CsvParser +{ + Q_OBJECT + +public: + explicit CsvParserModel(QObject *parent = nullptr); + virtual ~CsvParserModel(); + void setFilename(const QString& filename); + QString getFileInfo(); + bool parse(); + + void setHeaderLabels(QStringList l); + void mapColumns(int csvColumn, int dbColumn); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + +public Q_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 m_columnMap; +}; + +#endif //KEEPASSX_CSVPARSERMODEL_H + diff --git a/src/main.cpp b/src/main.cpp index 0618cefae..ab8177f74 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" +#include "gui/csvImport/CsvImportWizard.h" #include "gui/MessageBox.h" #ifdef QT_STATIC diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5840a5b4b..4f64f4c65 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -154,6 +154,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}) diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp new file mode 100644 index 000000000..9ff93f025 --- /dev/null +++ b/tests/TestCsvParser.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2015 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TestCsvParser.h" +#include + +QTEST_GUILESS_MAIN(TestCsvParser) + +void TestCsvParser::initTestCase() +{ + parser = new CsvParser(); +} + +void TestCsvParser::cleanupTestCase() +{ + delete parser; +} + +void TestCsvParser::init() +{ + file.setFileName("/tmp/keepassXn94do1x.csv"); + if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) + QFAIL("Cannot open file!"); + parser->setBackslashSyntax(false); + parser->setComment('#'); + parser->setFieldSeparator(','); + parser->setTextQualifier(QChar('"')); +} + +void TestCsvParser::cleanup() +{ + file.close(); +} + +/****************** 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"); +} diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h new file mode 100644 index 000000000..efdd96ab0 --- /dev/null +++ b/tests/TestCsvParser.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Enrico Mariotti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_TESTCSVPARSER_H +#define KEEPASSX_TESTCSVPARSER_H + +#include +#include + +#include "core/CsvParser.h" + +class CsvParser; + +class TestCsvParser : public QObject +{ + Q_OBJECT + +public: + +private Q_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: + QFile file; + CsvParser* parser; + csvtable t; + void dumpRow(csvtable table, int row); +}; + +#endif // KEEPASSX_TESTCSVPARSER_H From a7e358c27dedb31ccf01e53df9d4277583f6a4a7 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Sat, 18 Feb 2017 01:51:31 +0100 Subject: [PATCH 02/50] Syntax style, spaces and pretty indentation --- src/core/CsvParser.cpp | 174 +++++++++++--------------- src/core/CsvParser.h | 44 +++---- src/gui/DatabaseWidget.cpp | 3 + src/gui/DatabaseWidget.h | 1 + src/gui/MainWindow.cpp | 1 + src/gui/csvImport/CsvImportWidget.cpp | 99 +++++++-------- src/gui/csvImport/CsvImportWidget.h | 8 +- src/gui/csvImport/CsvImportWidget.ui | 160 ++++++++++++----------- src/gui/csvImport/CsvImportWizard.cpp | 8 +- src/gui/csvImport/CsvImportWizard.h | 5 +- src/gui/csvImport/CsvParserModel.cpp | 48 +++---- src/gui/csvImport/CsvParserModel.h | 13 +- tests/TestCsvParser.h | 4 +- 13 files changed, 273 insertions(+), 295 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 87b3f3403..288cd0404 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -15,10 +15,12 @@ * along with this program. If not, see . */ +#include "CsvParser.h" + #include #include + #include "core/Tools.h" -#include "CsvParser.h" CsvParser::CsvParser() : m_ch(0) @@ -61,16 +63,14 @@ bool CsvParser::parse(QFile *device) { m_statusMsg += QObject::tr("NULL device\n"); return false; } - if (!readFile(device)) { + if (!readFile(device)) return false; - } return parseFile(); } bool CsvParser::readFile(QFile *device) { - if (device->isOpen()) { + if (device->isOpen()) device->close(); - } device->open(QIODevice::ReadOnly); if (!Tools::readAllFromDevice(device, m_array)) { @@ -78,14 +78,14 @@ bool CsvParser::readFile(QFile *device) { m_isFileLoaded = false; } else { - device->close(); + device->close(); - m_array.replace("\r\n", "\n"); - m_array.replace("\r", "\n"); - if (0 == m_array.size()) { - m_statusMsg += QObject::tr("File empty\n"); - } - m_isFileLoaded = true; + m_array.replace("\r\n", "\n"); + m_array.replace("\r", "\n"); + if (0 == m_array.size()) { + m_statusMsg += QObject::tr("File empty\n"); + } + m_isFileLoaded = true; } return m_isFileLoaded; } @@ -117,11 +117,9 @@ void CsvParser::clear() { bool CsvParser::parseFile() { parseRecord(); - while (!m_isEof) - { - if (!skipEndline()) { + while (!m_isEof) { + if (!skipEndline()) appendStatusMsg(QObject::tr("malformed string")); - } m_currRow++; m_currCol = 1; parseRecord(); @@ -131,43 +129,36 @@ bool CsvParser::parseFile() { } void CsvParser::parseRecord() { - csvrow row; + CsvRow row; if (isComment()) { skipLine(); return; } - else { - do { - parseField(row); - getChar(m_ch); - } while (isSeparator(m_ch) && !m_isEof); + 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++; + 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) { +void CsvParser::parseField(CsvRow& row) { QString field; peek(m_ch); - if (!isTerminator(m_ch)) - { - if (isQualifier(m_ch)) { + if (!isTerminator(m_ch)) { + if (isQualifier(m_ch)) parseQuoted(field); - } - else { - parseSimple(field); - } + else + parseSimple(field); } row.push_back(field); } @@ -175,14 +166,12 @@ void CsvParser::parseField(csvrow& row) { void CsvParser::parseSimple(QString &s) { QChar c; getChar(c); - while ((isText(c)) && (!m_isEof)) - { + while ((isText(c)) && (!m_isEof)) { s.append(c); getChar(c); } - if (!m_isEof) { + if (!m_isEof) ungetChar(); - } } void CsvParser::parseQuoted(QString &s) { @@ -190,25 +179,21 @@ void CsvParser::parseQuoted(QString &s) { getChar(m_ch); parseEscaped(s); //getChar(m_ch); - if (!isQualifier(m_ch)) { + if (!isQualifier(m_ch)) appendStatusMsg(QObject::tr("missing closing quote")); - } } void CsvParser::parseEscaped(QString &s) { parseEscapedText(s); - while (processEscapeMark(s, m_ch)) { + while (processEscapeMark(s, m_ch)) parseEscapedText(s); - } - if (!m_isEof) { + if (!m_isEof) ungetChar(); - } } void CsvParser::parseEscapedText(QString &s) { getChar(m_ch); - while ((!isQualifier(m_ch)) && !m_isEof) - { + while ((!isQualifier(m_ch)) && !m_isEof) { s.append(m_ch); getChar(m_ch); } @@ -218,30 +203,25 @@ bool CsvParser::processEscapeMark(QString &s, QChar c) { QChar buf; peek(buf); QChar c2; - //escape-character syntax, e.g. \" - if (true == m_isBackslashSyntax) - { + if (true == m_isBackslashSyntax) { + //escape-character syntax, e.g. \" if (c != '\\') { return false; } //consume (and append) second qualifier getChar(c2); - if (m_isEof){ + if (m_isEof) { c2='\\'; s.append('\\'); return false; - } - else { + } else { s.append(c2); return true; } - } - //double quote syntax, e.g. "" - else - { - if (!isQualifier(c)) { + } 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)) { @@ -255,13 +235,12 @@ bool CsvParser::processEscapeMark(QString &s, QChar c) { } void CsvParser::fillColumns() { - //fill the rows with lesser columns with empty fields - - for (int i=0; i 0) { - csvrow r = m_table.at(i); - for (int j=0; j 100) + return m_statusMsg.section('\n', 0, 4) + .append("\n[...]\n").append(QObject::tr("More messages, skipped!")); return m_statusMsg; } diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h index 77c6d36e1..f7c043a30 100644 --- a/src/core/CsvParser.h +++ b/src/core/CsvParser.h @@ -23,8 +23,8 @@ #include #include -typedef QStringList csvrow; -typedef QList csvtable; +typedef QStringList CsvRow; +typedef QList CsvTable; class CsvParser { @@ -36,19 +36,19 @@ public: 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 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; + int getFileSize() const; + int getCsvRows() const; + int getCsvCols() const; QString getStatus() const; - const csvtable getCsvTable() const; + const CsvTable getCsvTable() const; protected: - csvtable m_table; + CsvTable m_table; private: QByteArray m_array; @@ -72,22 +72,22 @@ private: 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 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 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 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(); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 2a88d8a1a..86319e910 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -196,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; } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 87d14a182..651b9d34f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -61,6 +61,7 @@ public: enum Mode { None, + ImportMode, ViewMode, EditMode, LockedMode diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index aa862465d..47edaf482 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -425,6 +425,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) break; } case DatabaseWidget::EditMode: + case DatabaseWidget::ImportMode: case DatabaseWidget::LockedMode: { const QList entryActions = m_ui->menuEntries->actions(); for (QAction* action : entryActions) { diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 9e53d5af3..1e3c81a86 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -17,15 +17,17 @@ #include "CsvImportWidget.h" #include "ui_CsvImportWidget.h" -#include "gui/MessageBox.h" #include #include +#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() +const QStringList CsvImportWidget::m_columnHeader = QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username") @@ -46,10 +48,7 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) { m_ui->setupUi(this); - QFont font = m_ui->labelHeadline->font(); - font.setBold(true); - font.setPointSize(font.pointSize() + 2); - m_ui->labelHeadline->setFont(font); + m_ui->messageWidget->setHidden(true); m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE"); m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); @@ -59,10 +58,10 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection); m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus); - for (int i=0; isetFixedWidth(label->minimumSizeHint().width()); - font = label->font(); + QFont font = label->font(); font.setBold(false); label->setFont(font); @@ -76,14 +75,14 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) 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); + 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); } - m_parserModel->setHeaderLabels(m_columnheader); + m_parserModel->setHeaderLabels(m_columnHeader); m_ui->tableViewFields->setModel(m_parserModel); connect(m_ui->spinBoxSkip, SIGNAL(valueChanged(int)), SLOT(skippedChanged(int))); @@ -101,10 +100,9 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) void CsvImportWidget::comboChanged(int comboId) { QComboBox* currentSender = qobject_cast(m_comboMapper->mapping(comboId)); - if (currentSender->currentIndex() != -1) { - //here is the line that actually updates the GUI table + if (currentSender->currentIndex() != -1) + //this line is the one that actually updates GUI table m_parserModel->mapColumns(currentSender->currentIndex(), comboId); - } updateTableview(); } @@ -127,7 +125,7 @@ void CsvImportWidget::updateTableview() { m_ui->tableViewFields->resizeRowsToContents(); m_ui->tableViewFields->resizeColumnsToContents(); - for (int c=0; ctableViewFields->horizontalHeader()->count(); ++c) { + for (int c = 0; c < m_ui->tableViewFields->horizontalHeader()->count(); ++c) { m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode( c, QHeaderView::Stretch); } @@ -137,12 +135,12 @@ void CsvImportWidget::updatePreview() { m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo()); m_ui->spinBoxSkip->setValue(0); - m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount()-1); + m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount() - 1); int i; QStringList list(tr("Not present in CSV file")); - for (i=1; igetCsvCols(); i++) { + for (i = 1; i < m_parserModel->getCsvCols(); i++) { QString s = QString(tr("Column ")) + QString::number(i); list << s; } @@ -150,12 +148,10 @@ void CsvImportWidget::updatePreview() { i=1; Q_FOREACH (QComboBox* b, m_combos) { - if (i < m_parserModel->getCsvCols()) { + if (i < m_parserModel->getCsvCols()) b->setCurrentIndex(i); - } - else { + else b->setCurrentIndex(0); - } ++i; } } @@ -166,7 +162,7 @@ void CsvImportWidget::load(const QString& filename, Database* const db) { m_ui->labelFilename->setText(filename); Group* group = m_db->rootGroup(); group->setUuid(Uuid::random()); - group->setNotes(tr("Imported from CSV file\nOriginal data: ") + filename); + group->setNotes(tr("Imported from CSV file").append("\n").append(tr("Original data: ")) + filename); parse(); } @@ -181,47 +177,48 @@ void CsvImportWidget::parse() { } void CsvImportWidget::showReport() { - MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") - .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); +// MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") +// .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); + m_ui->messageWidget->showMessage(tr("Syntax error while parsing file.").append("\n") + .append(m_parserModel->getStatus()), MessageWidget::Warning); } void CsvImportWidget::writeDatabase() { - checkGroupNames(); - for (int r=0; rrowCount(); r++) { + setRootGroup(); + for (int r = 0; r < m_parserModel->rowCount(); r++) //use the validity of second column as a GO/NOGO hint for all others fields if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { 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()); + 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()) { + if (writer.hasError()) MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n") .append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok); - } Q_EMIT editFinished(true); } -void CsvImportWidget::checkGroupNames() { +void CsvImportWidget::setRootGroup() { QString groupLabel; QStringList groupList; - bool is_root = false - , is_empty = false - , is_label = false; - for (int r=0; rrowCount(); r++) { + bool is_root = false; + bool is_empty = false; + bool is_label = false; + + for (int r = 0; r < m_parserModel->rowCount(); r++) { 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); @@ -234,18 +231,13 @@ void CsvImportWidget::checkGroupNames() { groupList.clear(); } - if ((not is_label and (is_empty xor is_root)) - or (is_label and not is_root)) { + if ((not is_label and (is_empty xor is_root)) or (is_label and not is_root)) m_db->rootGroup()->setName("Root"); - } - else if ((is_empty and is_root) - or (is_label and not is_empty and is_root)) { + else if ((is_empty and is_root) or (is_label and not is_empty and is_root)) m_db->rootGroup()->setName("CSV IMPORTED"); - } - else { + else //SHOULD NEVER GET HERE m_db->rootGroup()->setName("ROOT_FALLBACK"); - } } Group *CsvImportWidget::splitGroups(QString label) { @@ -254,9 +246,8 @@ Group *CsvImportWidget::splitGroups(QString label) { QStringList groupList = label.split("/", QString::SkipEmptyParts); //skip the creation of a subgroup of Root with the same name - if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") { + if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") groupList.removeFirst(); - } for (const QString& groupName : groupList) { Group *children = hasChildren(current, groupName); @@ -265,8 +256,7 @@ Group *CsvImportWidget::splitGroups(QString label) { brandNew->setParent(current); brandNew->setName(groupName); current = brandNew; - } - else { + } else { Q_ASSERT(children != nullptr); current = children; } @@ -277,9 +267,8 @@ Group *CsvImportWidget::splitGroups(QString label) { 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) { + if (group->name() == groupName) return group; - } } return nullptr; } diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index f8798b560..1a71924ea 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -26,10 +26,10 @@ #include #include +#include "core/Metadata.h" #include "format/KeePass2Writer.h" #include "gui/csvImport/CsvParserModel.h" #include "keys/PasswordKey.h" -#include "core/Metadata.h" namespace Ui { @@ -42,7 +42,7 @@ class CsvImportWidget : public QWidget public: explicit CsvImportWidget(QWidget *parent = nullptr); - virtual ~CsvImportWidget(); + ~CsvImportWidget(); void load(const QString& filename, Database* const db); Q_SIGNALS: @@ -54,7 +54,7 @@ private Q_SLOTS: void comboChanged(int comboId); void skippedChanged(int rows); void writeDatabase(); - void checkGroupNames(); + void setRootGroup(); void reject(); private: @@ -67,7 +67,7 @@ private: Database *m_db; KeePass2Writer m_writer; - static const QStringList m_columnheader; + static const QStringList m_columnHeader; void configParser(); void updatePreview(); void updateTableview(); diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 5df2aa1af..8816c577c 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -14,7 +14,33 @@ - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 758 + 24 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + @@ -377,17 +403,7 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - + @@ -409,50 +425,7 @@ - - - - - - - 0 - 0 - - - - Import CSV fields - - - - - - - - 0 - 0 - - - - filename - - - - - - - - 0 - 0 - - - - size, rows, columns - - - - - - + @@ -501,24 +474,69 @@ - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 27 - - - + + + + + + + + + + 0 + 0 + + + + + 11 + 75 + true + + + + Import CSV fields + + + + + + + + 0 + 0 + + + + filename + + + + + + + + 0 + 0 + + + + size, rows, columns + + + + + + + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
+
diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index f84a22959..0114fcf32 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -16,8 +16,10 @@ */ #include "CsvImportWizard.h" + #include #include + #include "gui/MessageBox.h" @@ -49,7 +51,7 @@ void CsvImportWizard::load(const QString& filename, Database* database) void CsvImportWizard::keyFinished(bool accepted) { if (!accepted) { - Q_EMIT(importFinished(false)); + emit(importFinished(false)); return; } @@ -61,12 +63,12 @@ void CsvImportWizard::keyFinished(bool accepted) if (!result) { MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); - Q_EMIT importFinished(false); + emit(importFinished(false)); return; } } void CsvImportWizard::parseFinished(bool accepted) { - Q_EMIT(importFinished(accepted)); + emit(importFinished(accepted)); } diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 5a83a19d8..75d10bb9d 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -18,10 +18,11 @@ #ifndef KEEPASSX_CSVIMPORTWIZARD_H #define KEEPASSX_CSVIMPORTWIZARD_H +#include "CsvImportWidget.h" + #include #include -#include "CsvImportWidget.h" #include "core/Database.h" #include "gui/ChangeMasterKeyWidget.h" #include "gui/DialogyWidget.h" @@ -34,7 +35,7 @@ class CsvImportWizard : public DialogyWidget public: explicit CsvImportWizard(QWidget *parent = nullptr); - virtual ~CsvImportWizard(); + ~CsvImportWizard(); void load(const QString& filename, Database *database); Q_SIGNALS: diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index 3bc6c834c..ba5d20d92 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -42,39 +42,33 @@ bool CsvParserModel::parse() { m_columnMap.clear(); if (CsvParser::isFileLoaded()) { r = CsvParser::reparse(); - } - else { + } else { QFile csv(m_filename); r = CsvParser::parse(&csv); } - for (int i=0; i= getCsvCols()) { + if (csvColumn >= getCsvCols()) m_columnMap[dbColumn] = 0; //map to the empty column - } - else { + else m_columnMap[dbColumn] = csvColumn; - } endResetModel(); } @@ -82,8 +76,8 @@ void CsvParserModel::setSkippedRows(int skipped) { m_skipped = skipped; QModelIndex topLeft = createIndex(skipped,0); QModelIndex bottomRight = createIndex(m_skipped+rowCount(), columnCount()); - Q_EMIT dataChanged(topLeft, bottomRight); - Q_EMIT layoutChanged(); + emit dataChanged(topLeft, bottomRight); + emit layoutChanged(); } void CsvParserModel::setHeaderLabels(QStringList l) { @@ -91,45 +85,37 @@ void CsvParserModel::setHeaderLabels(QStringList l) { } int CsvParserModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) { + if (parent.isValid()) return 0; - } return getCsvRows(); } int CsvParserModel::columnCount(const QModelIndex &parent) const { - if (parent.isValid()) { + if (parent.isValid()) return 0; - } return m_columnHeader.size(); } QVariant CsvParserModel::data(const QModelIndex &index, int role) const { - if ( (index.column() >= m_columnHeader.size()) + if ((index.column() >= m_columnHeader.size()) || (index.row()+m_skipped >= rowCount()) - || !index.isValid() ) - { + || !index.isValid()) { return QVariant(); } - - if (role == Qt::DisplayRole) { + 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())) { + if ((section < 0) || (section >= m_columnHeader.size())) return QVariant(); - } return m_columnHeader.at(section); - } - else if (orientation == Qt::Vertical) { - if (section+m_skipped >= rowCount()) { + } else if (orientation == Qt::Vertical) { + if (section+m_skipped >= rowCount()) return QVariant(); - } return QString::number(section+1); } } diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h index ae7bf6e20..ff4f410d7 100644 --- a/src/gui/csvImport/CsvParserModel.h +++ b/src/gui/csvImport/CsvParserModel.h @@ -20,8 +20,9 @@ #include #include -#include "core/Group.h" + #include "core/CsvParser.h" +#include "core/Group.h" class CsvParserModel : public QAbstractTableModel, public CsvParser { @@ -29,7 +30,7 @@ class CsvParserModel : public QAbstractTableModel, public CsvParser public: explicit CsvParserModel(QObject *parent = nullptr); - virtual ~CsvParserModel(); + ~CsvParserModel(); void setFilename(const QString& filename); QString getFileInfo(); bool parse(); @@ -37,10 +38,10 @@ public: void setHeaderLabels(QStringList l); void mapColumns(int csvColumn, int dbColumn); - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + 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 Q_SLOTS: void setSkippedRows(int skipped); diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index efdd96ab0..975d86a92 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -62,8 +62,8 @@ private Q_SLOTS: private: QFile file; CsvParser* parser; - csvtable t; - void dumpRow(csvtable table, int row); + CsvTable t; + void dumpRow(CsvTable table, int row); }; #endif // KEEPASSX_TESTCSVPARSER_H From 41f9c3d2a1dac4c009dee2c53bcdb5bb7ccbd76e Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Wed, 22 Feb 2017 01:03:22 +0100 Subject: [PATCH 03/50] Better handle of parser status messages (critical/not critical) Use of messageWidget for displaying parser status messages setRootGroup assigns the right label to the root db folder test uses portable QTemporaryFile instead of hardcoded file --- src/core/CsvParser.cpp | 24 +++--- src/core/CsvParser.h | 2 +- src/gui/AboutDialog.ui | 3 +- src/gui/csvImport/CsvImportWidget.cpp | 106 +++++++++++++++----------- src/gui/csvImport/CsvImportWidget.h | 2 +- src/gui/csvImport/CsvImportWidget.ui | 42 +++++----- src/gui/csvImport/CsvImportWizard.cpp | 5 +- src/gui/csvImport/CsvParserModel.cpp | 2 +- tests/TestCsvParser.cpp | 85 +++++++++++---------- tests/TestCsvParser.h | 3 +- 10 files changed, 150 insertions(+), 124 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 288cd0404..0ab7f28cc 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -60,7 +60,7 @@ bool CsvParser::reparse() { bool CsvParser::parse(QFile *device) { clear(); if (nullptr == device) { - m_statusMsg += QObject::tr("NULL device\n"); + appendStatusMsg(QObject::tr("NULL device"), true); return false; } if (!readFile(device)) @@ -74,7 +74,7 @@ bool CsvParser::readFile(QFile *device) { device->open(QIODevice::ReadOnly); if (!Tools::readAllFromDevice(device, m_array)) { - m_statusMsg += QObject::tr("Error reading from device\n"); + appendStatusMsg(QObject::tr("error reading from device"), true); m_isFileLoaded = false; } else { @@ -82,9 +82,8 @@ bool CsvParser::readFile(QFile *device) { m_array.replace("\r\n", "\n"); m_array.replace("\r", "\n"); - if (0 == m_array.size()) { - m_statusMsg += QObject::tr("File empty\n"); - } + if (0 == m_array.size()) + appendStatusMsg(QObject::tr("file empty !\n")); m_isFileLoaded = true; } return m_isFileLoaded; @@ -119,7 +118,7 @@ bool CsvParser::parseFile() { parseRecord(); while (!m_isEof) { if (!skipEndline()) - appendStatusMsg(QObject::tr("malformed string")); + appendStatusMsg(QObject::tr("malformed string"), true); m_currRow++; m_currCol = 1; parseRecord(); @@ -180,7 +179,7 @@ void CsvParser::parseQuoted(QString &s) { parseEscaped(s); //getChar(m_ch); if (!isQualifier(m_ch)) - appendStatusMsg(QObject::tr("missing closing quote")); + appendStatusMsg(QObject::tr("missing closing quote"), true); } void CsvParser::parseEscaped(QString &s) { @@ -269,7 +268,7 @@ void CsvParser::getChar(QChar& c) { void CsvParser::ungetChar() { if (!m_ts.seek(m_lastPos)) - m_statusMsg += QObject::tr("Internal: unget lower bound exceeded"); + appendStatusMsg(QObject::tr("INTERNAL - unget lower bound exceeded"), true); } void CsvParser::peek(QChar& c) { @@ -360,9 +359,6 @@ const CsvTable CsvParser::getCsvTable() const { } QString CsvParser::getStatus() const { - if (m_statusMsg.size() > 100) - return m_statusMsg.section('\n', 0, 4) - .append("\n[...]\n").append(QObject::tr("More messages, skipped!")); return m_statusMsg; } @@ -377,11 +373,11 @@ int CsvParser::getCsvRows() const { } -void CsvParser::appendStatusMsg(QString s) { +void CsvParser::appendStatusMsg(QString s, bool isCritical) { m_statusMsg += s - .append(" @" + QString::number(m_currRow)) + .append(": (row,col) " + QString::number(m_currRow)) .append(",") .append(QString::number(m_currCol)) .append("\n"); - m_isGood = false; + m_isGood = not isCritical; } diff --git a/src/core/CsvParser.h b/src/core/CsvParser.h index f7c043a30..48be05584 100644 --- a/src/core/CsvParser.h +++ b/src/core/CsvParser.h @@ -94,7 +94,7 @@ private: void clear(); bool skipEndline(); void skipLine(); - void appendStatusMsg(QString s); + void appendStatusMsg(QString s, bool isCritical = false); }; #endif //CSVPARSER_H diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index e1e706808..bfa27689a 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -160,8 +160,9 @@ <li>debfx (KeePassX)</li> <li>droidmonkey</li> <li>louib</li> -<li>phoerious<li> +<li>phoerious</li> <li>thezero</li> +<li>seatedscribe</li> </ul> diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 1e3c81a86..2c782b89f 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -48,8 +48,6 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) { m_ui->setupUi(this); - m_ui->messageWidget->setHidden(true); - m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE"); m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<"."); m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|"); @@ -75,9 +73,9 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) 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); + 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); } @@ -91,7 +89,6 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) 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_ui->pushButtonWarnings, SIGNAL(clicked()), this, SLOT(showReport())); connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int))); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase())); @@ -125,17 +122,15 @@ 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); - } + 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(m_parserModel->rowCount() - 1); + m_ui->spinBoxSkip->setMaximum(qMax(0, m_parserModel->rowCount() - 1)); int i; QStringList list(tr("Not present in CSV file")); @@ -157,48 +152,63 @@ void CsvImportWidget::updatePreview() { } 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(); - QApplication::restoreOverrideCursor(); updatePreview(); - m_ui->pushButtonWarnings->setEnabled(!good); + QApplication::restoreOverrideCursor(); + if (good) + //four newline are provided to avoid resizing at every Positive/Warning switch + m_ui->messageWidget->showMessage(QString("\n\n").append(tr("CSV syntax seems in good shape !")) + .append("\n\n"), MessageWidget::Positive); + else + m_ui->messageWidget->showMessage(tr("Error(s) detected in CSV file !").append("\n") + .append(formatStatusText()), MessageWidget::Warning); + QWidget::adjustSize(); } -void CsvImportWidget::showReport() { -// MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n") -// .append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok); - m_ui->messageWidget->showMessage(tr("Syntax error while parsing file.").append("\n") - .append(m_parserModel->getStatus()), MessageWidget::Warning); + +QString CsvImportWidget::formatStatusText() const { + QString text = m_parserModel->getStatus(); + int items = text.count('\n'); + if (items > 3) + return text.section('\n', 0, 2) + .append("\n[").append(QString::number(items - 3)) + .append(tr(" more messages skipped]")); + else + for (int i = 0; i < 3 - items; i++) + text.append(QString("\n")); + return text; } void CsvImportWidget::writeDatabase() { setRootGroup(); - for (int r = 0; r < m_parserModel->rowCount(); r++) - //use the validity of second column as a GO/NOGO hint for all others fields - if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) { - 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()); - } - + 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); @@ -207,7 +217,7 @@ void CsvImportWidget::writeDatabase() { if (writer.hasError()) MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n") .append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok); - Q_EMIT editFinished(true); + emit editFinished(true); } @@ -219,33 +229,39 @@ void CsvImportWidget::setRootGroup() { 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 (not groupList.first().compare("Root", Qt::CaseSensitive)) - is_root = true; - else if (not groupLabel.compare("")) + if (groupList.isEmpty()) is_empty = true; else - is_label = true; + 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 ((not is_label and (is_empty xor is_root)) or (is_label and not is_root)) - m_db->rootGroup()->setName("Root"); - else if ((is_empty and is_root) or (is_label and not is_empty and is_root)) + if ((is_empty and is_root) or (is_label and not is_empty and is_root)) m_db->rootGroup()->setName("CSV IMPORTED"); else - //SHOULD NEVER GET HERE - m_db->rootGroup()->setName("ROOT_FALLBACK"); + m_db->rootGroup()->setName("Root"); } Group *CsvImportWidget::splitGroups(QString label) { //extract group names from nested path provided in "label" Group *current = m_db->rootGroup(); - QStringList groupList = label.split("/", QString::SkipEmptyParts); + if (label.isEmpty()) + return current; - //skip the creation of a subgroup of Root with the same name + 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(); @@ -274,5 +290,5 @@ Group* CsvImportWidget::hasChildren(Group* current, QString groupName) { } void CsvImportWidget::reject() { - Q_EMIT editFinished(false); + emit editFinished(false); } diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 1a71924ea..2079e9f96 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -50,7 +50,6 @@ Q_SIGNALS: private Q_SLOTS: void parse(); - void showReport(); void comboChanged(int comboId); void skippedChanged(int rows); void writeDatabase(); @@ -73,6 +72,7 @@ private: void updateTableview(); Group* splitGroups(QString label); Group* hasChildren(Group* current, QString groupName); + QString formatStatusText() const; }; #endif // KEEPASSX_CSVIMPORTWIDGET_H diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 8816c577c..82ee68dd2 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -122,22 +122,6 @@
- - - - false - - - - 50 - false - - - - Show parser warnings - - - @@ -162,6 +146,12 @@ 0 + + + 0 + 25 + + 50 @@ -239,6 +229,12 @@ 0 + + + 0 + 25 + + 50 @@ -290,6 +286,12 @@ 0 + + + 0 + 25 + + 50 @@ -309,6 +311,12 @@ 0 + + + 0 + 25 + + 50 @@ -342,7 +350,7 @@ - Treat '\' as escape character + Consider '\' as escape character diff --git a/src/gui/csvImport/CsvImportWizard.cpp b/src/gui/csvImport/CsvImportWizard.cpp index 0114fcf32..a1e1757bb 100644 --- a/src/gui/csvImport/CsvImportWizard.cpp +++ b/src/gui/csvImport/CsvImportWizard.cpp @@ -33,6 +33,10 @@ CsvImportWizard::CsvImportWizard(QWidget *parent) 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))); @@ -64,7 +68,6 @@ void CsvImportWizard::keyFinished(bool accepted) if (!result) { MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); emit(importFinished(false)); - return; } } diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index ba5d20d92..d315f1ad2 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -32,7 +32,7 @@ void CsvParserModel::setFilename(const QString& filename) { QString CsvParserModel::getFileInfo(){ QString a(QString::number(getFileSize()).append(tr(" byte, "))); a.append(QString::number(getCsvRows())).append(tr(" rows, ")); - a.append(QString::number((getCsvCols()-1))).append(tr(" columns")); + a.append(QString::number(qMax(0, getCsvCols()-1))).append(tr(" columns")); return a; } diff --git a/tests/TestCsvParser.cpp b/tests/TestCsvParser.cpp index 9ff93f025..93bcf0060 100644 --- a/tests/TestCsvParser.cpp +++ b/tests/TestCsvParser.cpp @@ -16,6 +16,7 @@ */ #include "TestCsvParser.h" + #include QTEST_GUILESS_MAIN(TestCsvParser) @@ -32,8 +33,8 @@ void TestCsvParser::cleanupTestCase() void TestCsvParser::init() { - file.setFileName("/tmp/keepassXn94do1x.csv"); - if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) + file = new QTemporaryFile(); + if (not file->open()) QFAIL("Cannot open file!"); parser->setBackslashSyntax(false); parser->setComment('#'); @@ -43,26 +44,26 @@ void TestCsvParser::init() void TestCsvParser::cleanup() { - file.close(); + file->remove(); } /****************** TEST CASES ******************/ void TestCsvParser::testMissingQuote() { parser->setTextQualifier(':'); - QTextStream out(&file); + QTextStream out(file); out << "A,B\n:BM,1"; QEXPECT_FAIL("", "Bad format", Continue); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QWARN(parser->getStatus().toLatin1()); } void TestCsvParser::testMalformed() { parser->setTextQualifier(':'); - QTextStream out(&file); + QTextStream out(file); out << "A,B,C\n:BM::,1,:2:"; QEXPECT_FAIL("", "Bad format", Continue); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QWARN(parser->getStatus().toLatin1()); } @@ -70,14 +71,14 @@ void TestCsvParser::testMalformed() { void TestCsvParser::testBackslashSyntax() { parser->setBackslashSyntax(true); parser->setTextQualifier(QChar('X')); - QTextStream out(&file); + 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)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no"); QVERIFY(t.at(1).at(0) == "13"); @@ -92,10 +93,10 @@ void TestCsvParser::testBackslashSyntax() { } void TestCsvParser::testQuoted() { - QTextStream out(&file); + QTextStream out(file); out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "ro"); QVERIFY(t.at(0).at(1) == "w"); @@ -105,41 +106,41 @@ void TestCsvParser::testQuoted() { } void TestCsvParser::testEmptySimple() { - QTextStream out(&file); + QTextStream out(file); out <<""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyQuoted() { - QTextStream out(&file); + QTextStream out(file); out <<"\"\""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyNewline() { - QTextStream out(&file); + QTextStream out(file); out <<"\"\n\""; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testEmptyFile() { - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 0); } void TestCsvParser::testNewline() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n\n\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "1"); @@ -148,9 +149,9 @@ void TestCsvParser::testNewline() void TestCsvParser::testCR() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\r3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -161,9 +162,9 @@ void TestCsvParser::testCR() void TestCsvParser::testLF() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -174,9 +175,9 @@ void TestCsvParser::testLF() void TestCsvParser::testCRLF() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\r\n3,4"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -187,13 +188,13 @@ void TestCsvParser::testCRLF() void TestCsvParser::testComments() { - QTextStream out(&file); + 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)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "useful"); @@ -201,21 +202,21 @@ void TestCsvParser::testComments() } void TestCsvParser::testColumns() { - QTextStream out(&file); + QTextStream out(file); out << "1,2\n" << ",,,,,,,,,a\n" << "a,b,c,d\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(parser->getCsvCols() == 10); } void TestCsvParser::testSimple() { - QTextStream out(&file); + QTextStream out(file); out << ",,2\r,2,3\n" << "A,,B\"\n" << " ,,\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 4); QVERIFY(t.at(0).at(0) == ""); @@ -234,11 +235,11 @@ void TestCsvParser::testSimple() { void TestCsvParser::testSeparator() { parser->setFieldSeparator('\t'); - QTextStream out(&file); + QTextStream out(file); out << "\t\t2\r\t2\t3\n" << "A\t\tB\"\n" << " \t\t\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 4); QVERIFY(t.at(0).at(0) == ""); @@ -258,10 +259,10 @@ void TestCsvParser::testSeparator() { void TestCsvParser::testMultiline() { parser->setTextQualifier(QChar(':')); - QTextStream out(&file); + QTextStream out(file); out << ":1\r\n2a::b:,:3\r4:\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.at(0).at(0) == "1\n2a:b"); QVERIFY(t.at(0).at(1) == "3\n4"); @@ -279,10 +280,10 @@ void TestCsvParser::testEmptyReparsing() void TestCsvParser::testReparsing() { - QTextStream out(&file); + QTextStream out(file); out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n" << "2\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QEXPECT_FAIL("", "Wrong qualifier", Continue); @@ -301,10 +302,10 @@ void TestCsvParser::testReparsing() void TestCsvParser::testQualifier() { parser->setTextQualifier(QChar('X')); - QTextStream out(&file); + QTextStream out(file); out << "X1X,X2XX,X,\"\"3\"\"\"X\r" << "3,X\"4\"X,,\n"; - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 2); QVERIFY(t.at(0).at(0) == "1"); @@ -322,10 +323,10 @@ void TestCsvParser::testUnicode() { //CORRECT QChar g(0x20AC); //ERROR QChar g("\u20AC"); parser->setFieldSeparator(QChar('A')); - QTextStream out(&file); + QTextStream out(file); out << QString("€1A2śA\"3śAż\"Ażac"); - QVERIFY(parser->parse(&file)); + QVERIFY(parser->parse(file)); t = parser->getCsvTable(); QVERIFY(t.size() == 1); QVERIFY(t.at(0).at(0) == "€1"); diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index 975d86a92..928ac4201 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/CsvParser.h" @@ -60,7 +61,7 @@ private Q_SLOTS: void testColumns(); private: - QFile file; + QTemporaryFile* file; CsvParser* parser; CsvTable t; void dumpRow(CsvTable table, int row); From 39057a6aa085e603fcd0f24063bc101f7a77f0b4 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 6 Mar 2017 00:47:49 +0100 Subject: [PATCH 04/50] Better widget positions, removed futile message when no errors shows up --- src/core/CsvParser.cpp | 2 +- src/gui/AboutDialog.ui | 1 - src/gui/csvImport/CsvImportWidget.cpp | 23 +- src/gui/csvImport/CsvImportWidget.ui | 970 +++++++++++++------------- 4 files changed, 515 insertions(+), 481 deletions(-) diff --git a/src/core/CsvParser.cpp b/src/core/CsvParser.cpp index 0ab7f28cc..7f0443aac 100644 --- a/src/core/CsvParser.cpp +++ b/src/core/CsvParser.cpp @@ -315,7 +315,7 @@ bool CsvParser::isCRLF(const QChar &c) const { } bool CsvParser::isSpace(const QChar &c) const { - return (c == 0x20); + return (c == ' '); } bool CsvParser::isTab(const QChar &c) const { diff --git a/src/gui/AboutDialog.ui b/src/gui/AboutDialog.ui index bfa27689a..a853c0413 100644 --- a/src/gui/AboutDialog.ui +++ b/src/gui/AboutDialog.ui @@ -162,7 +162,6 @@ <li>louib</li> <li>phoerious</li> <li>thezero</li> -<li>seatedscribe</li> </ul> diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 2c782b89f..38eb09b93 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "gui/MessageBox.h" #include "gui/MessageWidget.h" @@ -52,13 +53,13 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) 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->setFixedWidth(label->minimumSizeHint().width()); + label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); QFont font = label->font(); font.setBold(false); label->setFont(font); @@ -78,6 +79,8 @@ CsvImportWidget::CsvImportWidget(QWidget *parent) 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); @@ -169,13 +172,11 @@ void CsvImportWidget::parse() { bool good = m_parserModel->parse(); updatePreview(); QApplication::restoreOverrideCursor(); - if (good) - //four newline are provided to avoid resizing at every Positive/Warning switch - m_ui->messageWidget->showMessage(QString("\n\n").append(tr("CSV syntax seems in good shape !")) - .append("\n\n"), MessageWidget::Positive); - else + 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(); } @@ -183,12 +184,12 @@ void CsvImportWidget::parse() { QString CsvImportWidget::formatStatusText() const { QString text = m_parserModel->getStatus(); int items = text.count('\n'); - if (items > 3) - return text.section('\n', 0, 2) - .append("\n[").append(QString::number(items - 3)) + if (items > 2) + return text.section('\n', 0, 1) + .append("\n[").append(QString::number(items - 2)) .append(tr(" more messages skipped]")); else - for (int i = 0; i < 3 - items; i++) + for (int i = 0; i < 2 - items; i++) text.append(QString("\n")); return text; } diff --git a/src/gui/csvImport/CsvImportWidget.ui b/src/gui/csvImport/CsvImportWidget.ui index 82ee68dd2..1b4bed729 100644 --- a/src/gui/csvImport/CsvImportWidget.ui +++ b/src/gui/csvImport/CsvImportWidget.ui @@ -14,474 +14,6 @@ - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 758 - 24 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - - - - 0 - 137 - - - - - 75 - true - - - - Encoding - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Text is qualified by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 114 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 114 - 20 - - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Codec - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - - 50 - false - - - - Fields are separated by - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 50 - false - - - - Comments start with - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 50 - false - - - - false - - - - - - - Qt::Horizontal - - - - 114 - 20 - - - - - - - - - 50 - false - - - - Consider '\' as escape character - - - - - - - - - - 50 - false - - - - Skip first - - - - - - - - 50 - false - - - - - - - - - 50 - false - - - - rows - - - - - - - - - - 50 - false - true - - - - - - - - - - - - - - - 75 - true - - - - Column layout - - - - - - 0 - - - - - - - - - - - 0 - 0 - - - - - 0 - 200 - - - - - 75 - true - - - - Preview - - - false - - - - - - - 0 - 0 - - - - - 50 - false - - - - true - - - - - - @@ -535,6 +67,508 @@ + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 75 + true + + + + Encoding + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Consider '\' an escape character + + + + + + + + 50 + false + + + + Codec + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + true + + + + + + + + + + + + 50 + false + + + + Fields are separated by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + + 50 + false + + + + Comments start with + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + 50 + false + + + + Text is qualified by + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 114 + 20 + + + + + + + + + + + 50 + false + + + + Skip first + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + false + + + + + + + + + 50 + false + + + + rows + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 50 + false + + + + false + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + Qt::Horizontal + + + + 114 + 20 + + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Column layout + + + + + + 6 + + + 6 + + + 0 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 758 + 24 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + 75 + true + + + + Preview + + + false + + + + + + + 0 + 0 + + + + + 50 + false + + + + true + + + + + + From f4791c19e18b219809197512a8a7a2cca68bb444 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Mon, 6 Mar 2017 23:05:06 +0100 Subject: [PATCH 05/50] Assign uuid to newborn groups --- src/gui/csvImport/CsvImportWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 38eb09b93..4d64296e9 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -272,6 +272,7 @@ Group *CsvImportWidget::splitGroups(QString label) { Group *brandNew = new Group(); brandNew->setParent(current); brandNew->setName(groupName); + brandNew->setUuid(Uuid::random()); current = brandNew; } else { Q_ASSERT(children != nullptr); From 984602b7a0b6b79cb2c74f1e0b47f9e069945cd4 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Wed, 8 Mar 2017 22:58:46 +0100 Subject: [PATCH 06/50] Enhance FormatStatusText(), other minor cosmetics --- src/gui/csvImport/CsvImportWidget.cpp | 19 ++++++++++--------- src/gui/csvImport/CsvParserModel.cpp | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index 4d64296e9..ed00790f5 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -138,14 +138,14 @@ void CsvImportWidget::updatePreview() { int i; QStringList list(tr("Not present in CSV file")); - for (i = 1; i < m_parserModel->getCsvCols(); i++) { + for (i = 1; i < m_parserModel->getCsvCols(); ++i) { QString s = QString(tr("Column ")) + QString::number(i); list << s; } m_comboModel->setStringList(list); i=1; - Q_FOREACH (QComboBox* b, m_combos) { + for (QComboBox* b : m_combos) { if (i < m_parserModel->getCsvCols()) b->setCurrentIndex(i); else @@ -184,20 +184,21 @@ void CsvImportWidget::parse() { QString CsvImportWidget::formatStatusText() const { QString text = m_parserModel->getStatus(); int items = text.count('\n'); - if (items > 2) + if (items > 2) { return text.section('\n', 0, 1) .append("\n[").append(QString::number(items - 2)) .append(tr(" more messages skipped]")); - else - for (int i = 0; i < 2 - items; i++) - text.append(QString("\n")); - return text; + } + if (items == 1) { + text.append(QString("\n")); + } + return text; } void CsvImportWidget::writeDatabase() { setRootGroup(); - for (int r = 0; r < m_parserModel->rowCount(); r++) { + 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; @@ -229,7 +230,7 @@ void CsvImportWidget::setRootGroup() { bool is_empty = false; bool is_label = false; - for (int r = 0; r < m_parserModel->rowCount(); r++) { + 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; diff --git a/src/gui/csvImport/CsvParserModel.cpp b/src/gui/csvImport/CsvParserModel.cpp index d315f1ad2..efffda552 100644 --- a/src/gui/csvImport/CsvParserModel.cpp +++ b/src/gui/csvImport/CsvParserModel.cpp @@ -46,7 +46,7 @@ bool CsvParserModel::parse() { QFile csv(m_filename); r = CsvParser::parse(&csv); } - for (int i = 0; i < columnCount(); i++) + for (int i = 0; i < columnCount(); ++i) m_columnMap.insert(i,0); addEmptyColumn(); endResetModel(); From f12c6bf748e133addc4bcd7e20fc3065164645c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Tue, 14 Mar 2017 14:55:25 +0100 Subject: [PATCH 07/50] Update feature and build instructions for Yubikey --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 93da44c6c..aced5afb7 100644 --- a/README.md +++ b/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 +- YukiKey HMAC-SHA1 authentication for unlocking databases - Using website favicons as entry icons - Merging of databases - Automatic reload when the database changed on disk @@ -55,7 +55,7 @@ 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. +To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. Yubikey HMAC-SHA1 authentication support is compiled in by adding `-DWITH_XC_YUBIKEY=ON`. If these options are not specified, KeePassXC will be built without these plugins. ### Contributing From 51b7ec2b26916abff475b878f562dfbdf961901b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Wed, 15 Mar 2017 15:04:43 +0100 Subject: [PATCH 08/50] List all cmake build options --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aced5afb7..e49512455 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,18 @@ 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`. Yubikey HMAC-SHA1 authentication support is compiled in by adding `-DWITH_XC_YUBIKEY=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_COVERAGE=[ON|OFF] Enable/Disable coverage tests. (GCC ONLY) (default: OFF) +``` ### Contributing From 65d4a0a8cd5c59ebdd36a20f9f70f174acf5867d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:29:09 +0100 Subject: [PATCH 09/50] Add ASAN option to CMake --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef1743df7..2ea0fc8f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ 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_ASAN "Enable address sanitizer checks" OFF) option(WITH_COVERAGE "Use to build with coverage tests. (GCC ONLY)." OFF) option(WITH_XC_AUTOTYPE "Include Auto-Type." ON) @@ -83,6 +84,9 @@ 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) + add_gcc_compiler_flags("-fsanitize=address") +endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") From 28ec015ef4ef2f142f56983e089dd7197d7245df Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:34:16 +0100 Subject: [PATCH 10/50] Add -DWITH_ASAN=ON requirement to pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c83ca4e53..b9852f3c9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,11 @@ ## Description -## Motivation and Context +## Motivation and context -## How Has This Been Tested? +## How has this been tested? @@ -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. From 504bd402630ad0eacdd59b3f884e269c48c027c3 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 15:32:48 +0100 Subject: [PATCH 11/50] Prevent massive end-of-process leak sanitizer dump --- CMakeLists.txt | 2 +- src/main.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ea0fc8f3..86dedbc11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ 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) - add_gcc_compiler_flags("-fsanitize=address") + add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN") endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) diff --git a/src/main.cpp b/src/main.cpp index 0618cefae..2563c843b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,10 @@ #include "gui/MainWindow.h" #include "gui/MessageBox.h" +#ifdef WITH_ASAN +#include +#endif + #ifdef QT_STATIC #include @@ -130,6 +134,14 @@ int main(int argc, char** argv) } } } - - return app.exec(); + + int exitCode = app.exec(); + +#ifdef WITH_ASAN + // 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; } From 2587bac30024f5e2c59a1c33bd25609e2466e000 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 14:53:29 +0100 Subject: [PATCH 12/50] Enable ASAN option in Travis build --- .travis.yml | 8 ++++---- CMakeLists.txt | 4 ++++ src/main.cpp | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index be05d6e47..e24d1d178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 86dedbc11..7b41ac2fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,10 @@ add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virt add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings") if(WITH_ASAN) 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) diff --git a/src/main.cpp b/src/main.cpp index 2563c843b..8ed4ee14c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,7 @@ #include "gui/MainWindow.h" #include "gui/MessageBox.h" -#ifdef WITH_ASAN +#if defined(WITH_ASAN) && defined(WITH_LSAN) #include #endif @@ -137,7 +137,7 @@ int main(int argc, char** argv) int exitCode = app.exec(); -#ifdef WITH_ASAN +#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(); From 9608464ed12b412d0a270d9baf34a647b22a2a49 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Tue, 14 Mar 2017 21:24:32 +0100 Subject: [PATCH 13/50] Show error message when trying to use WITH_ASAN on Windows or OS X --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b41ac2fb..9448763fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,10 @@ 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)) From 8b04040d7e1ce0b7c6e7184b1efcebb6d1426dcf Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 15 Mar 2017 15:26:40 +0100 Subject: [PATCH 14/50] Add WITH_ASAN option to README --- CMakeLists.txt | 4 ++-- README.md | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9448763fb..e7d22312f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,8 +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_ASAN "Enable address sanitizer checks" 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) diff --git a/README.md b/README.md index e49512455..fd439ded4 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,15 @@ sudo make install 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_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_COVERAGE=[ON|OFF] Enable/Disable coverage tests. (GCC ONLY) (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 From 52991f3d66eb5e7d7a5098dfe3b9bb7106466f52 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 30 Jan 2017 19:18:35 -0500 Subject: [PATCH 15/50] Add first keepassxc-cli version. --- src/CMakeLists.txt | 1 + {utils => src/cli}/CMakeLists.txt | 29 +++---- utils/kdbx-extract.cpp => src/cli/Extract.cpp | 9 +-- src/cli/Extract.h | 27 +++++++ utils/kdbx-merge.cpp => src/cli/Merge.cpp | 12 +-- src/cli/Merge.h | 27 +++++++ src/cli/keepassxc-cli.cpp | 77 +++++++++++++++++++ 7 files changed, 150 insertions(+), 32 deletions(-) rename {utils => src/cli}/CMakeLists.txt (58%) rename utils/kdbx-extract.cpp => src/cli/Extract.cpp (92%) create mode 100644 src/cli/Extract.h rename utils/kdbx-merge.cpp => src/cli/Merge.cpp (93%) create mode 100644 src/cli/Merge.h create mode 100644 src/cli/keepassxc-cli.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e221b916..1d9984460 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -164,6 +164,7 @@ if(WITH_XC_HTTP) endif() add_subdirectory(autotype) +add_subdirectory(cli) set(autotype_SOURCES core/Tools.cpp diff --git a/utils/CMakeLists.txt b/src/cli/CMakeLists.txt similarity index 58% rename from utils/CMakeLists.txt rename to src/cli/CMakeLists.txt index 83f00b4bc..4456cbeaa 100644 --- a/utils/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2010 Felix Geyer +# 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,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -include_directories(../src) +set(cli_SOURCES + Merge.cpp + Merge.h + Extract.cpp + Extract.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}) - -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) diff --git a/utils/kdbx-extract.cpp b/src/cli/Extract.cpp similarity index 92% rename from utils/kdbx-extract.cpp rename to src/cli/Extract.cpp index 255f5d003..4ad658784 100644 --- a/utils/kdbx-extract.cpp +++ b/src/cli/Extract.cpp @@ -17,6 +17,8 @@ #include +#include "Extract.h" + #include #include #include @@ -30,7 +32,7 @@ #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); @@ -38,7 +40,6 @@ int main(int argc, char **argv) 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(); parser.process(app); const QStringList args = parser.positionalArguments(); @@ -47,10 +48,6 @@ int main(int argc, char **argv) return 1; } - if (!Crypto::init()) { - qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); diff --git a/src/cli/Extract.h b/src/cli/Extract.h new file mode 100644 index 000000000..9a6638e4b --- /dev/null +++ b/src/cli/Extract.h @@ -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 . + */ + +#ifndef KEEPASSXC_EXTRACT_H +#define KEEPASSXC_EXTRACT_H + +class Extract +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_EXTRACT_H diff --git a/utils/kdbx-merge.cpp b/src/cli/Merge.cpp similarity index 93% rename from utils/kdbx-merge.cpp rename to src/cli/Merge.cpp index da780ea1b..bac8f4bd8 100644 --- a/utils/kdbx-merge.cpp +++ b/src/cli/Merge.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include +#include "Merge.h" #include #include @@ -25,12 +25,11 @@ #include #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); @@ -43,7 +42,6 @@ int main(int argc, char **argv) QCommandLineOption samePasswordOption(QStringList() << "s" << "same-password", QCoreApplication::translate("main", "use the same password for both database files.")); - parser.addHelpOption(); parser.addOption(samePasswordOption); parser.process(app); @@ -53,10 +51,6 @@ int main(int argc, char **argv) return 1; } - if (!Crypto::init()) { - qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - } - static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QString line1 = inputTextStream.readLine(); @@ -133,6 +127,6 @@ int main(int argc, char **argv) } qDebug("Successfully merged the database files.\n"); - return 1; + return 0; } diff --git a/src/cli/Merge.h b/src/cli/Merge.h new file mode 100644 index 000000000..dd9b8a4c0 --- /dev/null +++ b/src/cli/Merge.h @@ -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 . + */ + +#ifndef KEEPASSXC_MERGE_H +#define KEEPASSXC_MERGE_H + +class Merge +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_MERGE_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp new file mode 100644 index 000000000..086e544a6 --- /dev/null +++ b/src/cli/keepassxc-cli.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include "config-keepassx.h" +#include "crypto/Crypto.h" + +int main(int argc, char **argv) +{ + + if (!Crypto::init()) { + qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); + return 1; + } + + QCoreApplication app(argc, argv); + app.setApplicationVersion(KEEPASSX_VERSION); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", "KeepassXC command line interface.")); + parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); + + parser.addHelpOption(); + parser.addVersionOption(); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() < 1) { + parser.showHelp(); + return 1; + } + + QString commandName = args.at(0); + + for (int i = 1; i < argc - 1; ++i) { + argv[i] = argv[i+1]; + } + argv[argc - 1] = nullptr; + argc--; + + if (commandName == "merge") + { + argv[0] = const_cast("keepassxc-cli merge"); + return Merge::execute(argc, argv); + } + + if (commandName == "extract") + { + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); + } + + qCritical("Invalid command %s.", qPrintable(commandName)); + parser.showHelp(); + return 1; + +} From 9cfc862b0787567a06c330c5f72ccd7bccc3e5bd Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 10:33:50 -0500 Subject: [PATCH 16/50] Disable core dumps (keepassxc-cli). --- src/cli/keepassxc-cli.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 086e544a6..78b111647 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -23,10 +23,14 @@ #include #include "config-keepassx.h" +#include "core/Tools.h" #include "crypto/Crypto.h" 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())); From bf9b23539e172f1bd3aa9e2c4d3ff44726a2115c Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 10:51:33 -0500 Subject: [PATCH 17/50] Add dependency + adjust styling. --- src/cli/CMakeLists.txt | 1 + src/cli/keepassxc-cli.cpp | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 4456cbeaa..70dd420d5 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -28,4 +28,5 @@ target_link_libraries(keepassxc-cli keepassx_core Qt5::Core ${GCRYPT_LIBRARIES} + ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 78b111647..7df4d7d87 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -57,20 +57,18 @@ int main(int argc, char **argv) QString commandName = args.at(0); for (int i = 1; i < argc - 1; ++i) { - argv[i] = argv[i+1]; + argv[i] = argv[i + 1]; } argv[argc - 1] = nullptr; - argc--; + --argc; - if (commandName == "merge") - { - argv[0] = const_cast("keepassxc-cli merge"); + if (commandName == "merge") { + argv[0] = const_cast("keepassxc-cli merge"); return Merge::execute(argc, argv); } - if (commandName == "extract") - { - argv[0] = const_cast("keepassxc-cli extract"); + if (commandName == "extract") { + argv[0] = const_cast("keepassxc-cli extract"); return Extract::execute(argc, argv); } From 9b92e7f8e8fe6438f7916832ede22bdb9b22bdae Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:30:54 -0500 Subject: [PATCH 18/50] Use EXIT_FAILURE/SUCCESS --- src/cli/Extract.cpp | 11 ++++++----- src/cli/Merge.cpp | 24 +++++++++++++----------- src/cli/keepassxc-cli.cpp | 11 ++++++----- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 4ad658784..73581d8f1 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include #include #include "Extract.h" @@ -45,7 +46,7 @@ int Extract::execute(int argc, char **argv) const QStringList args = parser.positionalArguments(); if (args.size() != 1) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -56,11 +57,11 @@ int Extract::execute(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; @@ -73,15 +74,15 @@ int Extract::execute(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; } diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index bac8f4bd8..caa8d19c1 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +#include + #include "Merge.h" #include @@ -48,7 +50,7 @@ int Merge::execute(int argc, char** argv) const QStringList args = parser.positionalArguments(); if (args.size() != 2) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -70,11 +72,11 @@ int Merge::execute(int argc, char** argv) QFile dbFile1(databaseFilename1); if (!dbFile1.exists()) { qCritical("File %s does not exist.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } if (!dbFile1.open(QIODevice::ReadOnly)) { qCritical("Unable to open file %s.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } KeePass2Reader reader1; @@ -82,7 +84,7 @@ int Merge::execute(int argc, char** argv) if (reader1.hasError()) { qCritical("Error while parsing the database:\n%s\n", qPrintable(reader1.errorString())); - return 1; + return EXIT_FAILURE; } @@ -90,11 +92,11 @@ int Merge::execute(int argc, char** argv) QFile dbFile2(databaseFilename2); if (!dbFile2.exists()) { qCritical("File %s does not exist.", qPrintable(databaseFilename2)); - return 1; + return EXIT_FAILURE; } if (!dbFile2.open(QIODevice::ReadOnly)) { qCritical("Unable to open file %s.", qPrintable(databaseFilename2)); - return 1; + return EXIT_FAILURE; } KeePass2Reader reader2; @@ -102,7 +104,7 @@ int Merge::execute(int argc, char** argv) if (reader2.hasError()) { qCritical("Error while parsing the database:\n%s\n", qPrintable(reader2.errorString())); - return 1; + return EXIT_FAILURE; } db1->merge(db2); @@ -110,7 +112,7 @@ int Merge::execute(int argc, char** argv) QSaveFile saveFile(databaseFilename1); if (!saveFile.open(QIODevice::WriteOnly)) { qCritical("Unable to open file %s for writing.", qPrintable(databaseFilename1)); - return 1; + return EXIT_FAILURE; } KeePass2Writer writer; @@ -118,15 +120,15 @@ int Merge::execute(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 0; + return EXIT_SUCCESS; } diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 7df4d7d87..e745dd09f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -15,13 +15,14 @@ * along with this program. If not, see . */ -#include -#include +#include #include #include #include +#include +#include #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -34,7 +35,7 @@ int main(int argc, char **argv) if (!Crypto::init()) { qFatal("Fatal error while testing the cryptographic functions:\n%s", qPrintable(Crypto::errorString())); - return 1; + return EXIT_FAILURE; } QCoreApplication app(argc, argv); @@ -51,7 +52,7 @@ int main(int argc, char **argv) const QStringList args = parser.positionalArguments(); if (args.size() < 1) { parser.showHelp(); - return 1; + return EXIT_FAILURE; } QString commandName = args.at(0); @@ -74,6 +75,6 @@ int main(int argc, char **argv) qCritical("Invalid command %s.", qPrintable(commandName)); parser.showHelp(); - return 1; + return EXIT_FAILURE; } From 992d8a90c746f31ba77ada572d1e1c88ab299d79 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:54:39 -0500 Subject: [PATCH 19/50] Migrate entropy-meter to keepassxc-cli --- CMakeLists.txt | 1 - src/cli/CMakeLists.txt | 2 ++ .../cli/EntropyMeter.cpp | 4 ++- src/cli/EntropyMeter.h | 27 +++++++++++++++++++ src/cli/keepassxc-cli.cpp | 16 ++++++++--- 5 files changed, 44 insertions(+), 6 deletions(-) rename utils/entropy-meter.cpp => src/cli/EntropyMeter.cpp (98%) create mode 100644 src/cli/EntropyMeter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d22312f..3dd435553 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,6 @@ include(FeatureSummary) add_subdirectory(src) add_subdirectory(share) -add_subdirectory(utils) if(WITH_TESTS) add_subdirectory(tests) endif(WITH_TESTS) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 70dd420d5..d678ff395 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -16,6 +16,8 @@ set(cli_SOURCES Merge.cpp Merge.h + EntropyMeter.cpp + EntropyMeter.h Extract.cpp Extract.h) diff --git a/utils/entropy-meter.cpp b/src/cli/EntropyMeter.cpp similarity index 98% rename from utils/entropy-meter.cpp rename to src/cli/EntropyMeter.cpp index 74f6bc11a..ffaecc8e6 100644 --- a/utils/entropy-meter.cpp +++ b/src/cli/EntropyMeter.cpp @@ -6,6 +6,8 @@ Copyright (c) 2016, KeePassXC Team See zxcvbn/zxcvbn.cpp for complete COPYRIGHT Notice */ +#include "EntropyMeter.h" + #include #include #include @@ -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> "); diff --git a/src/cli/EntropyMeter.h b/src/cli/EntropyMeter.h new file mode 100644 index 000000000..5034b9660 --- /dev/null +++ b/src/cli/EntropyMeter.h @@ -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 . + */ + +#ifndef KEEPASSXC_ENTROPYMETER_H +#define KEEPASSXC_ENTROPYMETER_H + +class EntropyMeter +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_ENTROPYMETER_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e745dd09f..e8719d9c2 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -47,15 +48,17 @@ int main(int argc, char **argv) parser.addHelpOption(); parser.addVersionOption(); - parser.process(app); + // 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); - const QStringList args = parser.positionalArguments(); - if (args.size() < 1) { + if (argc < 2) { parser.showHelp(); return EXIT_FAILURE; } - QString commandName = args.at(0); + QString commandName = argv[1]; for (int i = 1; i < argc - 1; ++i) { argv[i] = argv[i + 1]; @@ -73,6 +76,11 @@ int main(int argc, char **argv) return Extract::execute(argc, argv); } + if (commandName == "entropy-meter") { + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); + } + qCritical("Invalid command %s.", qPrintable(commandName)); parser.showHelp(); return EXIT_FAILURE; From 342d49d05048330d07f7062bcf989df5f9c8b21a Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 17:59:06 -0500 Subject: [PATCH 20/50] Missing zxcvbn dependency. --- src/cli/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index d678ff395..8b547636a 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -31,4 +31,5 @@ target_link_libraries(keepassxc-cli Qt5::Core ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} - ${ZLIB_LIBRARIES}) + ${ZLIB_LIBRARIES} + zxcvbn) From 805600ad448bb129f745bfded33d18adc86a714e Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 2 Feb 2017 18:29:31 -0500 Subject: [PATCH 21/50] Installing keepassxc-cli executable. --- src/cli/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 8b547636a..b8dce663c 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -33,3 +33,7 @@ target_link_libraries(keepassxc-cli ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES} zxcvbn) + +install(TARGETS keepassxc-cli + BUNDLE DESTINATION . COMPONENT Runtime + RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) From e1e8f33f6776a82302f4fb64b7d69851407c0873 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 4 Feb 2017 14:52:43 -0500 Subject: [PATCH 22/50] Install path on Mac. --- CMakeLists.txt | 3 +++ src/cli/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dd435553..56baa8f81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,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") diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index b8dce663c..4f0386e36 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -36,4 +36,4 @@ target_link_libraries(keepassxc-cli install(TARGETS keepassxc-cli BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime) + RUNTIME DESTINATION ${CLI_INSTALL_DIR} COMPONENT Runtime) From 782d1f17d1d4c57ac940655ab48481de2ede5ed1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 5 Feb 2017 12:56:44 -0500 Subject: [PATCH 23/50] Using ++argv --- src/cli/keepassxc-cli.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e8719d9c2..e532e95f6 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -60,10 +60,8 @@ int main(int argc, char **argv) QString commandName = argv[1]; - for (int i = 1; i < argc - 1; ++i) { - argv[i] = argv[i + 1]; - } - argv[argc - 1] = nullptr; + // Removing the first cli argument before dispatching. + ++argv; --argc; if (commandName == "merge") { From b85941531df219cdc5547445df7e91318c026c3f Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 5 Feb 2017 13:40:40 -0500 Subject: [PATCH 24/50] Keepass -> KeePass. --- src/cli/keepassxc-cli.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index e532e95f6..a7e7c275c 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -43,7 +43,7 @@ int main(int argc, char **argv) app.setApplicationVersion(KEEPASSX_VERSION); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "KeepassXC command line interface.")); + parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC command line interface.")); parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); parser.addHelpOption(); From 7636a559f9408ee765665c9e94527bf9c63c0d21 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Feb 2017 22:21:19 -0500 Subject: [PATCH 25/50] Remove unused imports. --- src/cli/Extract.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 73581d8f1..b08039aee 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -27,11 +27,8 @@ #include #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 Extract::execute(int argc, char **argv) { From 7ca475f968deba1a659c0b03235afe806a2016bf Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Feb 2017 22:29:20 -0500 Subject: [PATCH 26/50] Add list to keepassxc-cli --- src/cli/CMakeLists.txt | 2 + src/cli/List.cpp | 101 ++++++++++++++++++++++++++++++++++++++ src/cli/List.h | 27 ++++++++++ src/cli/keepassxc-cli.cpp | 6 +++ 4 files changed, 136 insertions(+) create mode 100644 src/cli/List.cpp create mode 100644 src/cli/List.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 4f0386e36..0ed5d991d 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,6 +14,8 @@ # along with this program. If not, see . set(cli_SOURCES + List.cpp + List.h Merge.cpp Merge.h EntropyMeter.cpp diff --git a/src/cli/List.cpp b/src/cli/List.cpp new file mode 100644 index 000000000..2049cc1d4 --- /dev/null +++ b/src/cli/List.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "List.h" + +#include +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "format/KeePass2Reader.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 << "\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); + + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", + "List the passwords in the database.")); + 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; + } + + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QString line = inputTextStream.readLine(); + CompositeKey key = CompositeKey::readFromLine(line); + + QString databaseFilename = args.at(0); + QFile dbFile(databaseFilename); + if (!dbFile.exists()) { + qCritical("File %s does not exist.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + if (!dbFile.open(QIODevice::ReadOnly)) { + qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + + KeePass2Reader reader; + Database* db = reader.readDatabase(&dbFile, key); + + if (reader.hasError()) { + qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + return EXIT_FAILURE; + } + + printGroup(db->rootGroup(), QString(""), 0); + return EXIT_SUCCESS; +} diff --git a/src/cli/List.h b/src/cli/List.h new file mode 100644 index 000000000..76f086c63 --- /dev/null +++ b/src/cli/List.h @@ -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 . + */ + +#ifndef KEEPASSXC_LIST_H +#define KEEPASSXC_LIST_H + +class List +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_LIST_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index a7e7c275c..67eb8e8ad 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,11 @@ int main(int argc, char **argv) ++argv; --argc; + if (commandName == "list") { + argv[0] = const_cast("keepassxc-cli list"); + return List::execute(argc, argv); + } + if (commandName == "merge") { argv[0] = const_cast("keepassxc-cli merge"); return Merge::execute(argc, argv); From 64dfada038f0f72465e2c6eebcb7ce42ace340b5 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 15 Feb 2017 21:01:50 -0500 Subject: [PATCH 27/50] Adding available commands. --- src/cli/keepassxc-cli.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 67eb8e8ad..c96da4d18 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -44,7 +44,15 @@ int main(int argc, char **argv) app.setApplicationVersion(KEEPASSX_VERSION); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassXC command line interface.")); + + QString description("KeePassXC command line interface."); + description = description.append(QString("\n\nAvailable commands:")); + description = description.append(QString("\n extract\tExtract and print a KeePassXC database file.")); + 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 2 KeePassXC database files.")); + parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description))); + parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); parser.addHelpOption(); From f579345059815f55a88042ddb16e49a72a671b33 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Wed, 15 Feb 2017 21:05:40 -0500 Subject: [PATCH 28/50] Change cli commands description. --- src/cli/Extract.cpp | 2 +- src/cli/List.cpp | 2 +- src/cli/Merge.cpp | 2 +- src/cli/keepassxc-cli.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index b08039aee..cbdb2a394 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -36,7 +36,7 @@ int Extract::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", - "Extract and print a KeePassXC database file.")); + "Extract and print the content of a database.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract.")); parser.process(app); diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 2049cc1d4..0c1389d20 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -63,7 +63,7 @@ int List::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", - "List the passwords in the database.")); + "List database entries.")); parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database.")); parser.process(app); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index caa8d19c1..fdc81b9f6 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -37,7 +37,7 @@ int Merge::execute(int argc, char** argv) QCoreApplication app(argc, argv); QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::translate("main", "Merge 2 KeePassXC database files.")); + 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.")); diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index c96da4d18..700f1263f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -47,10 +47,10 @@ int main(int argc, char **argv) QString description("KeePassXC command line interface."); description = description.append(QString("\n\nAvailable commands:")); - description = description.append(QString("\n extract\tExtract and print a KeePassXC database file.")); + 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 2 KeePassXC database files.")); + description = description.append(QString("\n merge\t\tMerge two databases.")); parser.setApplicationDescription(QCoreApplication::translate("main", qPrintable(description))); parser.addPositionalArgument("command", QCoreApplication::translate("main", "Name of the command to execute.")); From 98911af39646997250bfe9c0d68b34bfe9ae4542 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Thu, 16 Feb 2017 12:53:34 -0500 Subject: [PATCH 29/50] Fixed indentation. --- src/cli/keepassxc-cli.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 700f1263f..85b679bd7 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -74,23 +74,23 @@ int main(int argc, char **argv) --argc; if (commandName == "list") { - argv[0] = const_cast("keepassxc-cli list"); - return List::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli list"); + return List::execute(argc, argv); } if (commandName == "merge") { - argv[0] = const_cast("keepassxc-cli merge"); - return Merge::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli merge"); + return Merge::execute(argc, argv); } if (commandName == "extract") { - argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); } if (commandName == "entropy-meter") { - argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); } qCritical("Invalid command %s.", qPrintable(commandName)); From e01e9715b925da073c79a92e9c882416ca84706c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Feb 2017 20:26:51 +0100 Subject: [PATCH 30/50] text for inserting password --- src/cli/Extract.cpp | 5 ++++- src/cli/List.cpp | 4 ++++ src/cli/Merge.cpp | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index cbdb2a394..5bfb1185f 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -33,6 +33,7 @@ int Extract::execute(int argc, char **argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", @@ -46,6 +47,9 @@ int Extract::execute(int argc, char **argv) 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); @@ -78,7 +82,6 @@ int Extract::execute(int argc, char **argv) return EXIT_FAILURE; } - QTextStream out(stdout); out << xmlData.constData() << "\n"; return EXIT_SUCCESS; diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 0c1389d20..1702a469f 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -60,6 +60,7 @@ void printGroup(Group* group, QString baseName, int depth) { int List::execute(int argc, char **argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", @@ -73,6 +74,9 @@ int List::execute(int argc, char **argv) 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); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index fdc81b9f6..0f4a90117 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -35,6 +35,7 @@ int Merge::execute(int argc, char** argv) { QCoreApplication app(argc, argv); + QTextStream out(stdout); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Merge two databases.")); @@ -53,8 +54,10 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } + out << "Insert the database password\n> "; + out.flush(); + static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); - QString line1 = inputTextStream.readLine(); CompositeKey key1 = CompositeKey::readFromLine(line1); @@ -128,7 +131,7 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } - qDebug("Successfully merged the database files.\n"); + out << "Successfully merged the database files.\n"; return EXIT_SUCCESS; } From 15c2727a1d57980684218c60a36b1f29cf8e95a2 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:34:56 -0400 Subject: [PATCH 31/50] Adding the show command. --- src/cli/CMakeLists.txt | 10 +++-- src/cli/Extract.cpp | 2 +- src/cli/List.cpp | 2 +- src/cli/Merge.cpp | 6 +-- src/cli/Show.cpp | 83 +++++++++++++++++++++++++++++++++++++++ src/cli/Show.h | 27 +++++++++++++ src/cli/keepassxc-cli.cpp | 28 ++++++++----- 7 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 src/cli/Show.cpp create mode 100644 src/cli/Show.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 0ed5d991d..e090ad1d8 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -14,14 +14,16 @@ # along with this program. If not, see . set(cli_SOURCES + EntropyMeter.cpp + EntropyMeter.h + Extract.cpp + Extract.h List.cpp List.h Merge.cpp Merge.h - EntropyMeter.cpp - EntropyMeter.h - Extract.cpp - Extract.h) + Show.cpp + Show.h) add_library(cli STATIC ${cli_SOURCES}) target_link_libraries(cli Qt5::Core Qt5::Widgets) diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 5bfb1185f..81a9ddf07 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -38,7 +38,7 @@ int Extract::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "Extract and print the content of a database.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database to extract.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database to extract.")); parser.process(app); const QStringList args = parser.positionalArguments(); diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 1702a469f..cfeba3ceb 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -65,7 +65,7 @@ int List::execute(int argc, char **argv) QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("main", "List database entries.")); - parser.addPositionalArgument("database", QCoreApplication::translate("main", "path of the database.")); + parser.addPositionalArgument("database", QCoreApplication::translate("main", "Path of the database.")); parser.process(app); const QStringList args = parser.positionalArguments(); diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 0f4a90117..404892f7b 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -39,11 +39,11 @@ int Merge::execute(int argc, char** argv) QCommandLineParser parser; 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.")); + 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.addOption(samePasswordOption); parser.process(app); diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp new file mode 100644 index 000000000..c4a3b1acd --- /dev/null +++ b/src/cli/Show.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "Show.h" + +#include +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "format/KeePass2Reader.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); + + QString databaseFilename = args.at(0); + QFile dbFile(databaseFilename); + if (!dbFile.exists()) { + qCritical("File %s does not exist.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + if (!dbFile.open(QIODevice::ReadOnly)) { + qCritical("Unable to open file %s.", qPrintable(databaseFilename)); + return EXIT_FAILURE; + } + + KeePass2Reader reader; + Database* db = reader.readDatabase(&dbFile, key); + + if (reader.hasError()) { + qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + return EXIT_FAILURE; + } + + Uuid uuid = Uuid::fromHex(args.at(1)); + Entry* entry = db->resolveEntry(uuid); + out << entry->password() << "\n"; + return EXIT_SUCCESS; +} diff --git a/src/cli/Show.h b/src/cli/Show.h new file mode 100644 index 000000000..aa06b5c9a --- /dev/null +++ b/src/cli/Show.h @@ -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 . + */ + +#ifndef KEEPASSXC_SHOW_H +#define KEEPASSXC_SHOW_H + +class Show +{ +public: + static int execute(int argc, char** argv); +}; + +#endif // KEEPASSXC_SHOW_H diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 85b679bd7..1802870e3 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -21,10 +21,12 @@ #include #include +#include +#include #include #include -#include -#include +#include + #include "config-keepassx.h" #include "core/Tools.h" #include "crypto/Crypto.h" @@ -51,6 +53,7 @@ int main(int argc, char **argv) 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.")); @@ -73,6 +76,16 @@ int main(int argc, char **argv) ++argv; --argc; + if (commandName == "entropy-meter") { + argv[0] = const_cast("keepassxc-cli entropy-meter"); + return EntropyMeter::execute(argc, argv); + } + + if (commandName == "extract") { + argv[0] = const_cast("keepassxc-cli extract"); + return Extract::execute(argc, argv); + } + if (commandName == "list") { argv[0] = const_cast("keepassxc-cli list"); return List::execute(argc, argv); @@ -83,14 +96,9 @@ int main(int argc, char **argv) return Merge::execute(argc, argv); } - if (commandName == "extract") { - argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); - } - - if (commandName == "entropy-meter") { - argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); + if (commandName == "show") { + argv[0] = const_cast("keepassxc-cli show"); + return Show::execute(argc, argv); } qCritical("Invalid command %s.", qPrintable(commandName)); From db1bf889347d3327bcd6cd0a175780746f9dcee7 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:37:20 -0400 Subject: [PATCH 32/50] Handling entry not found. --- src/cli/Show.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index c4a3b1acd..b596e521b 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -78,6 +78,11 @@ int Show::execute(int argc, char **argv) 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; } From 993f90cb2cfcc71da99ea6dfef092fab9463169a Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:47:05 -0400 Subject: [PATCH 33/50] Extracting openDatabaseFile. --- src/cli/Show.cpp | 20 ++------------------ src/core/Database.cpp | 25 +++++++++++++++++++++++++ src/core/Database.h | 1 + 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index b596e521b..9222a093d 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -22,14 +22,12 @@ #include #include -#include #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" int Show::execute(int argc, char **argv) @@ -57,22 +55,8 @@ int Show::execute(int argc, char **argv) QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); - QString databaseFilename = args.at(0); - QFile dbFile(databaseFilename); - if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - - KeePass2Reader reader; - Database* db = reader.readDatabase(&dbFile, key); - - if (reader.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + Database* db = Database::openDatabaseFile(args.at(0), key); + if (db == nullptr) { return EXIT_FAILURE; } diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 23b564143..4aa9e3f5b 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -25,6 +25,7 @@ #include "core/Metadata.h" #include "crypto/Random.h" #include "format/KeePass2.h" +#include "format/KeePass2Reader.h" QHash 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; + +} diff --git a/src/core/Database.h b/src/core/Database.h index 3767bb30d..16c149608 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -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); From 780e23301b5bf5e27e06ac8f2397f633ce7a3424 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:56:30 -0400 Subject: [PATCH 34/50] Using openDatabaseFile in Merge. --- src/cli/Merge.cpp | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 404892f7b..118213b80 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -21,13 +21,11 @@ #include #include -#include #include #include #include #include "core/Database.h" -#include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" #include "keys/CompositeKey.h" @@ -71,50 +69,21 @@ int Merge::execute(int argc, char** argv) } - QString databaseFilename1 = args.at(0); - QFile dbFile1(databaseFilename1); - if (!dbFile1.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename1)); - return EXIT_FAILURE; - } - if (!dbFile1.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename1)); + 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 EXIT_FAILURE; - } - - - QString databaseFilename2 = args.at(1); - QFile dbFile2(databaseFilename2); - if (!dbFile2.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename2)); - return EXIT_FAILURE; - } - if (!dbFile2.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename2)); - return EXIT_FAILURE; - } - - KeePass2Reader reader2; - Database* db2 = reader2.readDatabase(&dbFile2, key2); - - if (reader2.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader2.errorString())); + 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)); + qCritical("Unable to open file %s for writing.", qPrintable(args.at(0))); return EXIT_FAILURE; } From cf2334391110d5443c37c00241bd53beee06d7db Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 13:57:44 -0400 Subject: [PATCH 35/50] Using openDatabaseFile in List. --- src/cli/List.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index cfeba3ceb..b2d673f51 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -22,14 +22,12 @@ #include #include -#include #include #include #include "core/Database.h" #include "core/Entry.h" #include "core/Group.h" -#include "format/KeePass2Reader.h" #include "keys/CompositeKey.h" void printGroup(Group* group, QString baseName, int depth) { @@ -81,22 +79,8 @@ int List::execute(int argc, char **argv) QString line = inputTextStream.readLine(); CompositeKey key = CompositeKey::readFromLine(line); - QString databaseFilename = args.at(0); - QFile dbFile(databaseFilename); - if (!dbFile.exists()) { - qCritical("File %s does not exist.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - if (!dbFile.open(QIODevice::ReadOnly)) { - qCritical("Unable to open file %s.", qPrintable(databaseFilename)); - return EXIT_FAILURE; - } - - KeePass2Reader reader; - Database* db = reader.readDatabase(&dbFile, key); - - if (reader.hasError()) { - qCritical("Error while parsing the database:\n%s\n", qPrintable(reader.errorString())); + Database* db = Database::openDatabaseFile(args.at(0), key); + if (db == nullptr) { return EXIT_FAILURE; } From fd9d372e6ab9780cb433d8ec334fcedd6550290c Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 14:17:03 -0400 Subject: [PATCH 36/50] Adding second prompt for merge. --- src/cli/Merge.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 118213b80..aa399dd5b 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -52,7 +52,7 @@ int Merge::execute(int argc, char** argv) return EXIT_FAILURE; } - out << "Insert the database password\n> "; + out << "Insert the first database password\n> "; out.flush(); static QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -64,6 +64,8 @@ int Merge::execute(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); } From a661c17eca4659303880b174ba9850b5b0c05c6e Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 12 Mar 2017 14:24:22 -0400 Subject: [PATCH 37/50] Adding group uuid to list. --- src/cli/List.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/List.cpp b/src/cli/List.cpp index b2d673f51..1e3488106 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -37,7 +37,7 @@ void printGroup(Group* group, QString baseName, int depth) { QString groupName = baseName + group->name() + "/"; QString indentation = QString(" ").repeated(depth); - out << indentation << groupName << "\n"; + out << indentation << groupName << " " << group->uuid().toHex() << "\n"; out.flush(); if (group->entries().isEmpty() && group->children().isEmpty()) { From 558c75a452ccafa925c658db48a9e68e875c664b Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 18:41:12 +0100 Subject: [PATCH 38/50] Add ASAN to keepassxc-cli --- src/cli/keepassxc-cli.cpp | 46 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/cli/keepassxc-cli.cpp b/src/cli/keepassxc-cli.cpp index 1802870e3..b27b7483f 100644 --- a/src/cli/keepassxc-cli.cpp +++ b/src/cli/keepassxc-cli.cpp @@ -31,6 +31,10 @@ #include "core/Tools.h" #include "crypto/Crypto.h" +#if defined(WITH_ASAN) && defined(WITH_LSAN) +#include +#endif + int main(int argc, char **argv) { #ifdef QT_NO_DEBUG @@ -76,33 +80,35 @@ int main(int argc, char **argv) ++argv; --argc; + int exitCode = EXIT_FAILURE; + if (commandName == "entropy-meter") { argv[0] = const_cast("keepassxc-cli entropy-meter"); - return EntropyMeter::execute(argc, argv); - } - - if (commandName == "extract") { + exitCode = EntropyMeter::execute(argc, argv); + } else if (commandName == "extract") { argv[0] = const_cast("keepassxc-cli extract"); - return Extract::execute(argc, argv); - } - - if (commandName == "list") { + exitCode = Extract::execute(argc, argv); + } else if (commandName == "list") { argv[0] = const_cast("keepassxc-cli list"); - return List::execute(argc, argv); - } - - if (commandName == "merge") { + exitCode = List::execute(argc, argv); + } else if (commandName == "merge") { argv[0] = const_cast("keepassxc-cli merge"); - return Merge::execute(argc, argv); - } - - if (commandName == "show") { + exitCode = Merge::execute(argc, argv); + } else if (commandName == "show") { argv[0] = const_cast("keepassxc-cli show"); - return Show::execute(argc, argv); + exitCode = Show::execute(argc, argv); + } else { + qCritical("Invalid command %s.", qPrintable(commandName)); + parser.showHelp(); + exitCode = EXIT_FAILURE; } - qCritical("Invalid command %s.", qPrintable(commandName)); - parser.showHelp(); - return 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; } From e3602e3c75489fc39f752d05147ab189a3070555 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 20:31:14 +0100 Subject: [PATCH 39/50] fix regex for placeholders, fix #402, add regression test --- src/core/Entry.cpp | 3 ++- src/gui/DatabaseWidget.cpp | 18 +++++++-------- src/gui/entry/EntryModel.cpp | 6 ++--- tests/gui/TestGui.cpp | 44 ++++++++++++++++++++++++++++++++++++ tests/gui/TestGui.h | 1 + 5 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 7edf7e788..d1672c5b1 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -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; diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 8a40b6364..ca3257d0c 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -407,7 +407,7 @@ void DatabaseWidget::copyTitle() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->title())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->title())); } void DatabaseWidget::copyUsername() @@ -418,7 +418,7 @@ void DatabaseWidget::copyUsername() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->username())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->username())); } void DatabaseWidget::copyPassword() @@ -429,7 +429,7 @@ void DatabaseWidget::copyPassword() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->password())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->password())); } void DatabaseWidget::copyURL() @@ -440,7 +440,7 @@ void DatabaseWidget::copyURL() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->url())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->url())); } void DatabaseWidget::copyNotes() @@ -451,7 +451,7 @@ void DatabaseWidget::copyNotes() return; } - setClipboardTextAndMinimize(currentEntry->resolvePlaceholder(currentEntry->notes())); + setClipboardTextAndMinimize(currentEntry->resolveMultiplePlaceholders(currentEntry->notes())); } void DatabaseWidget::copyAttribute(QAction* action) @@ -1176,7 +1176,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 +1186,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 +1196,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 +1206,7 @@ bool DatabaseWidget::currentEntryHasNotes() Q_ASSERT(false); return false; } - return !currentEntry->resolvePlaceholder(currentEntry->notes()).isEmpty(); + return !currentEntry->resolveMultiplePlaceholders(currentEntry->notes()).isEmpty(); } GroupView* DatabaseWidget::groupView() { diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index b28eaed46..6bc10376f 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -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")); } diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 7df5942e8..1cb3d6ada 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -588,6 +588,50 @@ void TestGui::testCloneEntry() QCOMPARE(entryClone->title(), entryOrg->title() + QString(" - Clone")); } +void TestGui::testEntryPlaceholders() +{ + QToolBar* toolBar = m_mainWindow->findChild("toolBar"); + EntryView* entryView = m_dbWidget->findChild("entryView"); + + // Find the new entry action + QAction* entryNewAction = m_mainWindow->findChild("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"); + QLineEdit* titleEdit = editEntryWidget->findChild("titleEdit"); + QTest::keyClicks(titleEdit, "test"); + QLineEdit* usernameEdit = editEntryWidget->findChild("usernameEdit"); + QTest::keyClicks(usernameEdit, "john"); + QLineEdit* urlEdit = editEntryWidget->findChild("urlEdit"); + QTest::keyClicks(urlEdit, "{TITLE}.{USERNAME}"); + QDialogButtonBox* editEntryWidgetButtonBox = editEntryWidget->findChild("buttonBox"); + 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"); diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 1ae297005..d05ab8f58 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -48,6 +48,7 @@ private slots: void testSearch(); void testDeleteEntry(); void testCloneEntry(); + void testEntryPlaceholders(); void testDragAndDropEntry(); void testDragAndDropGroup(); void testSaveAs(); From 0e5a1cc8e479db1d03cc8e13228a526e6c558655 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 16 Mar 2017 20:38:56 +0100 Subject: [PATCH 40/50] resolve placeholders for custom attributes --- src/gui/DatabaseWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index ca3257d0c..5caf662ce 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -462,7 +462,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) From 7e515d9d5b91db0001b8c19fad0eaba0460b9900 Mon Sep 17 00:00:00 2001 From: Florin Andrei Date: Thu, 16 Mar 2017 11:58:16 -0700 Subject: [PATCH 41/50] Update README.md typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd439ded4..a277e5b5e 100644 --- a/README.md +++ b/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 -- YukiKey HMAC-SHA1 authentication 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 From 06bbd6e066857e0a18cf2e703fb94d6cb8e788ff Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Thu, 16 Mar 2017 21:46:53 +0100 Subject: [PATCH 42/50] Get rid of Q_{EMIT,SLOTS,SIGNALS} --- src/gui/DatabaseWidget.cpp | 2 +- src/gui/csvImport/CsvImportWidget.h | 4 ++-- src/gui/csvImport/CsvImportWizard.h | 4 ++-- src/gui/csvImport/CsvParserModel.h | 2 +- src/http/OptionDialog.ui | 2 +- src/keys/drivers/YubiKey.h | 2 +- tests/TestCsvParser.h | 2 +- tests/TestYkChallengeResponseKey.h | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 1b4c7cc66..b4f623155 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -634,7 +634,7 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget) void DatabaseWidget::csvImportFinished(bool accepted) { if (!accepted) { - Q_EMIT closeRequest(); + emit closeRequest(); } else { setCurrentWidget(m_mainWidget); diff --git a/src/gui/csvImport/CsvImportWidget.h b/src/gui/csvImport/CsvImportWidget.h index 2079e9f96..aa463c08b 100644 --- a/src/gui/csvImport/CsvImportWidget.h +++ b/src/gui/csvImport/CsvImportWidget.h @@ -45,10 +45,10 @@ public: ~CsvImportWidget(); void load(const QString& filename, Database* const db); -Q_SIGNALS: +signals: void editFinished(bool accepted); -private Q_SLOTS: +private slots: void parse(); void comboChanged(int comboId); void skippedChanged(int rows); diff --git a/src/gui/csvImport/CsvImportWizard.h b/src/gui/csvImport/CsvImportWizard.h index 75d10bb9d..1c99259cd 100644 --- a/src/gui/csvImport/CsvImportWizard.h +++ b/src/gui/csvImport/CsvImportWizard.h @@ -38,10 +38,10 @@ public: ~CsvImportWizard(); void load(const QString& filename, Database *database); -Q_SIGNALS: +signals: void importFinished(bool accepted); -private Q_SLOTS: +private slots: void keyFinished(bool accepted); void parseFinished(bool accepted); diff --git a/src/gui/csvImport/CsvParserModel.h b/src/gui/csvImport/CsvParserModel.h index ff4f410d7..8cab4d4ff 100644 --- a/src/gui/csvImport/CsvParserModel.h +++ b/src/gui/csvImport/CsvParserModel.h @@ -43,7 +43,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; -public Q_SLOTS: +public slots: void setSkippedRows(int skipped); private: diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index abe994772..1959f8052 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -39,7 +39,7 @@ - 0 + 2 diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h index 47341f9a2..8a7552136 100644 --- a/src/keys/drivers/YubiKey.h +++ b/src/keys/drivers/YubiKey.h @@ -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 diff --git a/tests/TestCsvParser.h b/tests/TestCsvParser.h index 928ac4201..0aa3d01ef 100644 --- a/tests/TestCsvParser.h +++ b/tests/TestCsvParser.h @@ -32,7 +32,7 @@ class TestCsvParser : public QObject public: -private Q_SLOTS: +private slots: void init(); void cleanup(); void initTestCase(); diff --git a/tests/TestYkChallengeResponseKey.h b/tests/TestYkChallengeResponseKey.h index 4699b9101..309e014d5 100644 --- a/tests/TestYkChallengeResponseKey.h +++ b/tests/TestYkChallengeResponseKey.h @@ -26,7 +26,7 @@ class TestYubiKeyChalResp: public QObject { Q_OBJECT -private Q_SLOTS: +private slots: void initTestCase(); void cleanupTestCase(); From 506a2b99c53ee8013e47bffaef8f88877b180361 Mon Sep 17 00:00:00 2001 From: seatedscribe Date: Thu, 16 Mar 2017 22:55:26 +0100 Subject: [PATCH 43/50] Revert dialog index back to zero --- src/http/OptionDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/OptionDialog.ui b/src/http/OptionDialog.ui index 1959f8052..abe994772 100644 --- a/src/http/OptionDialog.ui +++ b/src/http/OptionDialog.ui @@ -39,7 +39,7 @@ - 2 + 0 From c4d6fa855c399a347b5be6159eb3599deea00507 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Fri, 17 Mar 2017 02:08:09 +0100 Subject: [PATCH 44/50] Force event processing after adding entries to prevent test failure --- tests/gui/TestGui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 1cb3d6ada..90f873197 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -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); } From 15a288aa5bb5477ab01304a8660daff9862b4df5 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 6 Mar 2017 17:12:07 -0500 Subject: [PATCH 45/50] Adding warning messages when config access error. --- src/core/Config.cpp | 14 ++++++++++++++ src/core/Config.h | 2 ++ src/gui/SettingsWidget.cpp | 5 +++++ src/gui/SettingsWidgetGeneral.ui | 9 +++++++++ 4 files changed, 30 insertions(+) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index 28b17536f..e3a1cb633 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -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); @@ -92,6 +102,10 @@ void Config::init(const QString& fileName) { m_settings.reset(new QSettings(fileName, QSettings::IniFormat)); + if (hasAccessError()) { + qWarning("Access error with config file %s", qPrintable(fileName)); + } + m_defaults.insert("RememberLastDatabases", true); m_defaults.insert("RememberLastKeyFiles", true); m_defaults.insert("OpenPreviousDatabasesOnStartup", true); diff --git a/src/core/Config.h b/src/core/Config.h index 09aa02fb1..1fb937cf9 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -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); diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 62af276e8..2002bc733 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,6 +62,11 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); + if (config()->hasAccessError()) { + m_generalUi->messageWidget->showMessage( + tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); + } + if (!autoType()->isAvailable()) { m_generalUi->generalSettingsTabWidget->removeTab(1); } diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index 88d7cad45..fb9fb1709 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -23,6 +23,9 @@ 0 + + + @@ -368,6 +371,12 @@ QLineEdit
autotype/ShortcutWidget.h
+ + MessageWidget + QWidget +
gui/MessageWidget.h
+ 1 +
From da85252347c8fdc7332af2b38678df09048b305b Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 11 Mar 2017 22:04:38 -0500 Subject: [PATCH 46/50] Hide config errors by default. --- src/gui/SettingsWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 2002bc733..d68a11e98 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,6 +62,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); + m_generalUi->messageWidget->setVisible(false); if (config()->hasAccessError()) { m_generalUi->messageWidget->showMessage( tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); From 55a32c58a972760d5861163ccc3403c6dd69a3e2 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Mon, 13 Mar 2017 22:12:00 -0400 Subject: [PATCH 47/50] Moving access error to MainWindow. --- src/core/Config.cpp | 4 ---- src/gui/MainWindow.cpp | 6 ++++++ src/gui/SettingsWidget.cpp | 6 ------ src/gui/SettingsWidgetGeneral.ui | 9 --------- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/core/Config.cpp b/src/core/Config.cpp index e3a1cb633..e074df6cb 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -102,10 +102,6 @@ void Config::init(const QString& fileName) { m_settings.reset(new QSettings(fileName, QSettings::IniFormat)); - if (hasAccessError()) { - qWarning("Access error with config file %s", qPrintable(fileName)); - } - m_defaults.insert("RememberLastDatabases", true); m_defaults.insert("RememberLastKeyFiles", true); m_defaults.insert("OpenPreviousDatabasesOnStartup", true); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 442c04735..adb1ae297 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -314,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 ") + config()->getFileName(), MessageWidget::Error); + } + } MainWindow::~MainWindow() diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index d68a11e98..62af276e8 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -62,12 +62,6 @@ SettingsWidget::SettingsWidget(QWidget* parent) addPage(tr("General"), FilePath::instance()->icon("categories", "preferences-other"), m_generalWidget); addPage(tr("Security"), FilePath::instance()->icon("status", "security-high"), m_secWidget); - m_generalUi->messageWidget->setVisible(false); - if (config()->hasAccessError()) { - m_generalUi->messageWidget->showMessage( - tr("Access error with config file ") + config()->getFileName(), MessageWidget::Error); - } - if (!autoType()->isAvailable()) { m_generalUi->generalSettingsTabWidget->removeTab(1); } diff --git a/src/gui/SettingsWidgetGeneral.ui b/src/gui/SettingsWidgetGeneral.ui index fb9fb1709..88d7cad45 100644 --- a/src/gui/SettingsWidgetGeneral.ui +++ b/src/gui/SettingsWidgetGeneral.ui @@ -23,9 +23,6 @@ 0 - - - @@ -371,12 +368,6 @@ QLineEdit
autotype/ShortcutWidget.h
- - MessageWidget - QWidget -
gui/MessageWidget.h
- 1 -
From e6b452802808028033531c0ab55f29e6556925b1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 18 Mar 2017 13:31:15 -0400 Subject: [PATCH 48/50] Adjust indentation. --- src/gui/SettingsWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 62af276e8..ff4c124e6 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -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())); From a3840963e1b650baf89489ed0a92f34385a473b1 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sat, 18 Mar 2017 14:00:31 -0400 Subject: [PATCH 49/50] Checking config access errors in settings. --- src/gui/SettingsWidget.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index ff4c124e6..caf9919c9 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -99,6 +99,12 @@ void SettingsWidget::addSettingsPage(ISettingsPage* page) void SettingsWidget::loadSettings() { + + if (config()->hasAccessError()) { + showMessage( + tr("Access error for config file ") + 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 ") + 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", From d8ad360b383fb9841b19997457c29efac5adae26 Mon Sep 17 00:00:00 2001 From: Louis-Bertrand Varin Date: Sun, 19 Mar 2017 16:05:52 -0400 Subject: [PATCH 50/50] Using format strings. --- src/gui/MainWindow.cpp | 2 +- src/gui/SettingsWidget.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index adb1ae297..d8dd7dcd9 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -317,7 +317,7 @@ MainWindow::MainWindow() if (config()->hasAccessError()) { m_ui->globalMessageWidget->showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error); } } diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index caf9919c9..19dae371d 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -102,7 +102,7 @@ void SettingsWidget::loadSettings() if (config()->hasAccessError()) { showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error); } m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool()); @@ -163,7 +163,7 @@ void SettingsWidget::saveSettings() if (config()->hasAccessError()) { showMessage( - tr("Access error for config file ") + config()->getFileName(), MessageWidget::Error); + 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;