diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 1f7ab25d9..f9958e5b2 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -24,6 +24,8 @@ set(cli_SOURCES Extract.h List.cpp List.h + Locate.cpp + Locate.h Merge.cpp Merge.h Show.cpp diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index a7b6bab0a..721710776 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -26,6 +26,7 @@ #include "EntropyMeter.h" #include "Extract.h" #include "List.h" +#include "Locate.h" #include "Merge.h" #include "Show.h" @@ -58,6 +59,7 @@ void populateCommands() commands.insert(QString("clip"), new Clip()); commands.insert(QString("entropy-meter"), new EntropyMeter()); commands.insert(QString("extract"), new Extract()); + commands.insert(QString("locate"), new Locate()); commands.insert(QString("ls"), new List()); commands.insert(QString("merge"), new Merge()); commands.insert(QString("show"), new Show()); diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp new file mode 100644 index 000000000..bb9831be1 --- /dev/null +++ b/src/cli/Locate.cpp @@ -0,0 +1,87 @@ +/* + * 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 "Locate.h" + +#include +#include +#include + +#include "cli/Utils.h" +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" + +Locate::Locate() +{ + this->name = QString("locate"); + this->description = QObject::tr("Find entries quickly."); +} + +Locate::~Locate() +{ +} + +int Locate::execute(QStringList arguments) +{ + + QTextStream out(stdout); + + QCommandLineParser parser; + parser.setApplicationDescription(this->description); + parser.addPositionalArgument("database", QObject::tr("Path of the database.")); + parser.addPositionalArgument("term", QObject::tr("Search term.")); + QCommandLineOption keyFile(QStringList() << "k" + << "key-file", + QObject::tr("Key file of the database."), + QObject::tr("path")); + parser.addOption(keyFile); + parser.process(arguments); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 2) { + out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli locate"); + return EXIT_FAILURE; + } + + Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile)); + if (!db) { + return EXIT_FAILURE; + } + + return this->locateEntry(db, args.at(1)); +} + +int Locate::locateEntry(Database* database, QString searchTerm) +{ + + QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + QStringList results = database->rootGroup()->locate(searchTerm); + if (results.isEmpty()) { + outputTextStream << "No results for that search term" << endl; + return EXIT_SUCCESS; + } + + for (QString result : results) { + outputTextStream << result << endl; + } + return EXIT_SUCCESS; + +} diff --git a/src/cli/Locate.h b/src/cli/Locate.h new file mode 100644 index 000000000..c919b0cb3 --- /dev/null +++ b/src/cli/Locate.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_LOCATE_H +#define KEEPASSXC_LOCATE_H + +#include "Command.h" + +class Locate : public Command +{ +public: + Locate(); + ~Locate(); + int execute(QStringList arguments); + int locateEntry(Database* database, QString searchTerm); +}; + +#endif // KEEPASSXC_LOCATE_H diff --git a/src/core/Group.cpp b/src/core/Group.cpp index 008755510..e199ae43f 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -908,3 +908,25 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry) break; } } + +QStringList Group::locate(QString locateTerm, QString currentPath) +{ + Q_ASSERT(!locateTerm.isNull()); + QStringList response; + + for (Entry* entry : asConst(m_entries)) { + QString entryPath = currentPath + entry->title(); + if (entryPath.toLower().contains(locateTerm.toLower())) { + response << entryPath; + } + } + + for (Group* group : asConst(m_children)) { + for (QString path : group->locate(locateTerm, currentPath + group->name() + QString("/"))) { + response << path; + } + } + + return response; + +} diff --git a/src/core/Group.h b/src/core/Group.h index 10313b488..4e76538e0 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -84,6 +84,7 @@ public: Entry* findEntryByUuid(const Uuid& uuid); Entry* findEntryByPath(QString entryPath, QString basePath = QString("")); Group* findGroupByPath(QString groupPath, QString basePath = QString("/")); + QStringList locate(QString locateTerm, QString currentPath = QString("/")); void setUuid(const Uuid& uuid); void setName(const QString& name); void setNotes(const QString& notes); diff --git a/tests/TestGroup.cpp b/tests/TestGroup.cpp index 14956618e..5553feae8 100644 --- a/tests/TestGroup.cpp +++ b/tests/TestGroup.cpp @@ -755,3 +755,63 @@ void TestGroup::testPrint() delete db; } + +void TestGroup::testLocate() +{ + Database* db = new Database(); + + Entry* entry1 = new Entry(); + entry1->setTitle("entry1"); + entry1->setGroup(db->rootGroup()); + + Entry* entry2 = new Entry(); + entry2->setTitle("entry2"); + entry2->setGroup(db->rootGroup()); + + Group* group1 = new Group(); + group1->setName("group1"); + group1->setParent(db->rootGroup()); + + Group* group2 = new Group(); + group2->setName("group2"); + group2->setParent(group1); + + Entry* entry3 = new Entry(); + entry3->setTitle("entry3"); + entry3->setGroup(group1); + + Entry* entry43 = new Entry(); + entry43->setTitle("entry43"); + entry43->setGroup(group1); + + Entry* google = new Entry(); + google->setTitle("Google"); + google->setGroup(group2); + + QStringList results = db->rootGroup()->locate("entry"); + QVERIFY(results.size() == 4); + QVERIFY(results.contains("/group1/entry43")); + + results = db->rootGroup()->locate("entry1"); + QVERIFY(results.size() == 1); + QVERIFY(results.contains("/entry1")); + + results = db->rootGroup()->locate("Entry1"); + QVERIFY(results.size() == 1); + QVERIFY(results.contains("/entry1")); + + results = db->rootGroup()->locate("invalid"); + QVERIFY(results.size() == 0); + + results = db->rootGroup()->locate("google"); + QVERIFY(results.size() == 1); + QVERIFY(results.contains("/group1/group2/Google")); + + results = db->rootGroup()->locate("group1"); + QVERIFY(results.size() == 3); + QVERIFY(results.contains("/group1/entry3")); + QVERIFY(results.contains("/group1/entry43")); + QVERIFY(results.contains("/group1/group2/Google")); + + delete db; +} diff --git a/tests/TestGroup.h b/tests/TestGroup.h index 16bb42acd..16f4906ee 100644 --- a/tests/TestGroup.h +++ b/tests/TestGroup.h @@ -42,6 +42,7 @@ private slots: void testFindEntry(); void testFindGroupByPath(); void testPrint(); + void testLocate(); private: Database* createMergeTestDatabase();