diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 01b8d6137..67b0e1746 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,31 +1,32 @@ -# Contributing to KeePassX Reboot +# Contributing to KeePassXC :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: -The following is a set of guidelines for contributing to KeePassX Reboot on GitHub. +The following is a set of guidelines for contributing to KeePassXC on GitHub. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. -#### Table Of Contents +#### Table of contents [What should I know before I get started?](#what-should-i-know-before-i-get-started) * [Open Source Contribution Policy](#open-source-contribution-policy) -[How Can I Contribute?](#how-can-i-contribute) - * [Feature Requests](#feature-requests) - * [Bug Reports](#bug-reports) - * [Your First Code Contribution](#your-first-code-contribution) - * [Pull Requests](#pull-requests) +[How can I contribute?](#how-can-i-contribute) + * [Feature requests](#feature-requests) + * [Bug reports](#bug-reports) + * [Discuss with the team](#discuss-with-the-team) + * [Your first code contribution](#your-first-code-contribution) + * [Pull requests](#pull-requests) * [Translations](#translations) [Styleguides](#styleguides) - * [Git Branch Strategy](#git_branch_strategy) - * [Git Commit Messages](#git-commit-messages) - * [Coding Styleguide](#coding-styleguide) + * [Git branch strategy](#git-branch-strategy) + * [Git commit messages](#git-commit-messages) + * [Coding styleguide](#coding-styleguide) ## What should I know before I get started? ### Open Source Contribution Policy -[Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad) +**Source**: [Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad) #### Policy @@ -49,35 +50,35 @@ If we reject your contribution, it means only that we do not consider it suitabl * 0.3, 2011–11–19: Added “irrevocably” to “we can use” and changed “it” to “your contribution” in the “if rejected” section. Thanks to Patrick Maupin. -## How Can I Contribute? -### Feature Requests +## How can I contribute? +### Feature requests -We're always looking for suggestions to improve our application. If you have a suggestion for improving an existing feature, or would like to suggest a completely new feature for KeePassX Reboot, please use the Issues section or our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum. +We're always looking for suggestions to improve our application. If you have a suggestion to improve an existing feature, or would like to suggest a completely new feature for KeePassXC, please use the [issue tracker on GitHub][issues-section]. For more general discussion, try using our [Google Groups][google-groups] forum. -### Bug Reports +### Bug reports -Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the Issues section. +Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the issue tracker. -Before submitting a Bug Report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to highlight a deficiency on an existing issue, simply add a comment. +Before submitting a bug report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to add further information to an existing issue, simply add a comment on that issue. -### Discuss with the Team +### Discuss with the team -You can talk to the KeePassX Reboot Team about Bugs, new feature, Issue and PullRequests at our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum +As with feature requests, you can talk to the KeePassXC team about bugs, new features, other issues and pull requests on the dedicated issue tracker, using the [Google Groups][google-groups] forum, or in the IRC channel on Freenode (`#keepassxc-dev` on `irc.freenode.net`, or use a [webchat link](https://webchat.freenode.net/?channels=%23keepassxc-dev)). -### Your First Code Contribution +### Your first code contribution -Unsure where to begin contributing to KeePassX Reboot? You can start by looking through these `beginner` and `help-wanted` issues: +Unsure where to begin contributing to KeePassXC? You can start by looking through these `beginner` and `help-wanted` issues: -* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two. -* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. +* [Beginner issues][beginner] – issues which should only require a few lines of code, and a test or two. +* ['Help wanted' issues][help-wanted] – issues which should be a bit more involved than `beginner` issues. -Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. +Both issue lists are sorted by total number of comments. While not perfect, looking at the number of comments on an issue can give a general idea of how much an impact a given change will have. -### Pull Requests +### Pull requests Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code. -All pull requests must comply with the above requirements and with the [Styleguides](#styleguides). +All pull requests must comply with the above requirements and with the [styleguides](#styleguides). ### Translations @@ -86,19 +87,20 @@ Please join an existing language team or request a new one if there is none. ## Styleguides -### Git Branch Strategy +### Git branch strategy The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successful-git-branching-model/). -* **master** -> always points to the last release published -* **develop** -> points to the next planned release, tested and reviewed code -* **feature/**[name] -> points to brand new feature in codebase, candidate for merge into develop (subject to rebase) +* **master** – points to the latest public release +* **develop** – points to the development of the next release, contains tested and reviewed code +* **feature/**[name] – points to a branch with a new feature, one which is candidate for merge into develop (subject to rebase) +* **hotfix/**[id]-[description] – points to a branch with a fix for a particular issue ID -### Git Commit Messages +### Git commit messages * Use the present tense ("Add feature" not "Added feature") -* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +* Use the imperative mood ("Move cursor to…" not "Moves cursor to…") * Limit the first line to 72 characters or less * Reference issues and pull requests liberally * When only changing documentation, include `[ci skip]` in the commit description @@ -114,21 +116,21 @@ The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successf * :lock: `:lock:` when dealing with security -### Coding Styleguide +### Coding styleguide This project follows the [Qt Coding Style](https://wiki.qt.io/Qt_Coding_Style). All submissions are expected to follow this style. -In particular Code must follow the following specific rules: +In particular, code must stick to the following rules: -#### Naming Convention +#### Naming convention `lowerCamelCase` -For names made of only one word, the fist letter is lowercase. -For names made of multiple concatenated words, the first letter is lowercase and each subsequent concatenated word is capitalized. +For names made of only one word, the first letter should be lowercase. +For names made of multiple concatenated words, the first letter of the whole is lowercase, and the first letter of each subsequent word is capitalized. #### Indention -For C++ files (.cpp .h): 4 spaces -For Qt-UI files (.ui): 2 spaces +For **C++ files** (*.cpp .h*): 4 spaces +For **Qt-UI files** (*.ui*): 2 spaces #### Pointers ```c @@ -165,9 +167,8 @@ Use prefix: `m_*` Example: `m_variable` -#### GUI Widget names -Widget names must be related to the desired program behaviour. -Preferably end the name with the Widget Classname +#### GUI widget names +Widget names must be related to the desired program behavior, and preferably end with the widget's classname. Example: `` @@ -175,3 +176,5 @@ Example: `` [beginner]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3A%22help+wanted%22+sort%3Acomments-desc [help-wanted]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+sort%3Acomments-desc +[issues-section]:https://github.com/keepassxreboot/keepassxc/issues +[google-groups]:https://groups.google.com/forum/#!forum/keepassx-reboot diff --git a/src/autotype/mac/AutoTypeMac.cpp b/src/autotype/mac/AutoTypeMac.cpp index 90563a23a..e55c336cb 100644 --- a/src/autotype/mac/AutoTypeMac.cpp +++ b/src/autotype/mac/AutoTypeMac.cpp @@ -98,7 +98,9 @@ QString AutoTypePlatformMac::activeWindowTitle() if (windowLayer(window) == 0) { // First toplevel window in list (front to back order) title = windowTitle(window); - break; + if (!title.isEmpty()) { + break; + } } } diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index b2b06e7c8..46e2670ac 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -353,6 +353,12 @@ void Entry::setTitle(const QString& title) void Entry::setUrl(const QString& url) { + bool remove = url != m_attributes->value(EntryAttributes::URLKey) && + (m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "1" || + m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "0"); + if (remove) { + m_attributes->remove(EntryAttributes::RememberCmdExecAttr); + } m_attributes->set(EntryAttributes::URLKey, url, m_attributes->isProtected(EntryAttributes::URLKey)); } @@ -508,7 +514,8 @@ Entry* Entry::clone(CloneFlags flags) const entry->m_data.timeInfo.setLocationChanged(now); } - + if (flags & CloneRenameTitle) + entry->setTitle(entry->title() + tr(" - Clone")); return entry; } diff --git a/src/core/Entry.h b/src/core/Entry.h index 66b9362a6..ae60b596c 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -115,7 +115,8 @@ public: CloneNoFlags = 0, CloneNewUuid = 1, // generate a random uuid for the clone CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time - CloneIncludeHistory = 4 // clone the history items + CloneIncludeHistory = 4, // clone the history items + CloneRenameTitle = 8 // add "-Clone" after the original title }; Q_DECLARE_FLAGS(CloneFlags, CloneFlag) diff --git a/src/core/EntryAttributes.cpp b/src/core/EntryAttributes.cpp index 195a8f14a..b633cae32 100644 --- a/src/core/EntryAttributes.cpp +++ b/src/core/EntryAttributes.cpp @@ -24,6 +24,7 @@ const QString EntryAttributes::URLKey = "URL"; const QString EntryAttributes::NotesKey = "Notes"; const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey << PasswordKey << URLKey << NotesKey); +const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD"; EntryAttributes::EntryAttributes(QObject* parent) : QObject(parent) diff --git a/src/core/EntryAttributes.h b/src/core/EntryAttributes.h index 1c0ddaaeb..211b6d483 100644 --- a/src/core/EntryAttributes.h +++ b/src/core/EntryAttributes.h @@ -52,6 +52,7 @@ public: static const QString URLKey; static const QString NotesKey; static const QStringList DefaultAttributes; + static const QString RememberCmdExecAttr; static bool isDefaultAttribute(const QString& key); Q_SIGNALS: diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index c0360a36c..01e152e2a 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -42,7 +42,11 @@ QList EntrySearcher::searchEntries(const QString& searchTerm, const Grou const QList children = group->children(); for (Group* childGroup : children) { if (childGroup->searchingEnabled() != Group::Disable) { - searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity)); + if (matchGroup(searchTerm, childGroup, caseSensitivity)) { + searchResult.append(childGroup->entriesRecursive()); + } else { + searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity)); + } } } @@ -69,3 +73,21 @@ bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensiti entry->url().contains(word, caseSensitivity) || entry->notes().contains(word, caseSensitivity); } + +bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity) +{ + const QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts); + for (const QString& word : wordList) { + if (!wordMatch(word, group, caseSensitivity)) { + return false; + } + } + + return true; +} + +bool EntrySearcher::wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity) +{ + return group->name().contains(word, caseSensitivity) || + group->notes().contains(word, caseSensitivity); +} diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h index c7075dc9b..4e8d4eabe 100644 --- a/src/core/EntrySearcher.h +++ b/src/core/EntrySearcher.h @@ -33,6 +33,8 @@ private: QList searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity); QList matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity); bool wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity); + bool matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity); + bool wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity); }; #endif // KEEPASSX_ENTRYSEARCHER_H diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index dfb03bd06..dca387b19 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -388,7 +388,7 @@ void KeePass2XmlReader::parseBinaries() QString id = attr.value("ID").toString(); QByteArray data; - if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) { + if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { data = readCompressedBinary(); } else { diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index f08b432fe..f1ab04109 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -312,8 +313,10 @@ void DatabaseWidget::cloneEntry() return; } - Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo); + Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle); entry->setGroup(currentEntry->group()); + if (isInSearchMode()) + search(m_lastSearchText); m_entryView->setFocus(); m_entryView->setCurrentEntry(entry); } @@ -494,8 +497,46 @@ void DatabaseWidget::openUrlForEntry(Entry* entry) } if (urlString.startsWith("cmd://")) { + // check if decision to execute command was stored + if (entry->attributes()->hasKey(EntryAttributes::RememberCmdExecAttr)) { + if (entry->attributes()->value(EntryAttributes::RememberCmdExecAttr) == "1") { + QProcess::startDetached(urlString.mid(6)); + } + return; + } + + // otherwise ask user if (urlString.length() > 6) { - QProcess::startDetached(urlString.mid(6)); + QString cmdTruncated = urlString.mid(6); + if (cmdTruncated.length() > 400) + cmdTruncated = cmdTruncated.left(400) + " […]"; + QMessageBox msgbox(QMessageBox::Icon::Question, + tr("Execute command?"), + tr("Do you really want to execute the following command?

%1
") + .arg(cmdTruncated.toHtmlEscaped()), + QMessageBox::Yes | QMessageBox::No, + this + ); + msgbox.setDefaultButton(QMessageBox::No); + + QCheckBox* checkbox = new QCheckBox(tr("Remember my choice"), &msgbox); + msgbox.setCheckBox(checkbox); + bool remember = false; + QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) { + if (static_cast(state) == Qt::CheckState::Checked) { + remember = true; + } + }); + + int result = msgbox.exec(); + if (result == QMessageBox::Yes) { + QProcess::startDetached(urlString.mid(6)); + } + + if (remember) { + entry->attributes()->set(EntryAttributes::RememberCmdExecAttr, + result == QMessageBox::Yes ? "1" : "0"); + } } } else { @@ -750,7 +791,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod void DatabaseWidget::switchToEntryEdit() { Entry* entry = m_entryView->currentEntry(); - Q_ASSERT(entry); + if (!entry) { return; } @@ -761,7 +802,7 @@ void DatabaseWidget::switchToEntryEdit() void DatabaseWidget::switchToGroupEdit() { Group* group = m_groupView->currentGroup(); - Q_ASSERT(group); + if (!group) { return; } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index cc94ca9a9..819bda5dc 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -364,7 +364,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) bool groupSelected = dbWidget->isGroupSelected(); m_ui->actionEntryNew->setEnabled(!inSearch); - m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch); + m_ui->actionEntryClone->setEnabled(singleEntrySelected); m_ui->actionEntryEdit->setEnabled(singleEntrySelected); m_ui->actionEntryDelete->setEnabled(entriesSelected); m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle()); diff --git a/src/gui/SearchWidget.cpp b/src/gui/SearchWidget.cpp index 4ac01b3fc..933686dfa 100644 --- a/src/gui/SearchWidget.cpp +++ b/src/gui/SearchWidget.cpp @@ -41,6 +41,7 @@ SearchWidget::SearchWidget(QWidget *parent) connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear())); new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut); + new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut); m_ui->searchEdit->installEventFilter(this); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index d9ba5bd83..f3535f9b0 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -89,6 +89,7 @@ void EditEntryWidget::setupMain() add(tr("Entry"), m_mainWidget); m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show")); + m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false)); connect(m_mainUi->togglePasswordButton, SIGNAL(toggled(bool)), m_mainUi->passwordEdit, SLOT(setShowPassword(bool))); connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool))); connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool))); @@ -433,6 +434,9 @@ void EditEntryWidget::saveEntry() void EditEntryWidget::updateEntryData(Entry* entry) const { + entry->attributes()->copyCustomKeysFrom(m_entryAttributes); + entry->attachments()->copyDataFrom(m_entryAttachments); + entry->setTitle(m_mainUi->titleEdit->text()); entry->setUsername(m_mainUi->usernameEdit->text()); entry->setUrl(m_mainUi->urlEdit->text()); @@ -442,9 +446,6 @@ void EditEntryWidget::updateEntryData(Entry* entry) const entry->setNotes(m_mainUi->notesEdit->toPlainText()); - entry->attributes()->copyCustomKeysFrom(m_entryAttributes); - entry->attachments()->copyDataFrom(m_entryAttachments); - IconStruct iconStruct = m_iconsWidget->state(); if (iconStruct.number < 0) { diff --git a/src/gui/entry/EditEntryWidgetMain.ui b/src/gui/entry/EditEntryWidgetMain.ui index 083f1c033..b896963c0 100644 --- a/src/gui/entry/EditEntryWidgetMain.ui +++ b/src/gui/entry/EditEntryWidgetMain.ui @@ -77,9 +77,6 @@ - - Generate - true diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index c23226a28..0c776e021 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -481,8 +481,7 @@ void TestGui::testSearch() QCOMPARE(entry->title(), origTitle.append("_edited")); // Cancel search, should return to normal view - QTest::mouseClick(searchTextEdit, Qt::LeftButton); - QTest::keyClick(searchTextEdit, Qt::Key_Escape); + QTest::keyClick(m_mainWindow, Qt::Key_Escape); QTRY_COMPARE(m_dbWidget->currentMode(), DatabaseWidget::ViewMode); } @@ -567,7 +566,7 @@ void TestGui::testCloneEntry() QCOMPARE(entryView->model()->rowCount(), 2); Entry* entryClone = entryView->entryFromIndex(entryView->model()->index(1, 1)); QVERIFY(entryOrg->uuid() != entryClone->uuid()); - QCOMPARE(entryClone->title(), entryOrg->title()); + QCOMPARE(entryClone->title(), entryOrg->title() + QString(" - Clone")); } void TestGui::testDragAndDropEntry()