diff --git a/docs/man/keepassxc-cli.1.adoc b/docs/man/keepassxc-cli.1.adoc index c0b0deaa7..0efddfe51 100644 --- a/docs/man/keepassxc-cli.1.adoc +++ b/docs/man/keepassxc-cli.1.adoc @@ -176,6 +176,9 @@ The same password generation options as documented for the generate command can *--url* <__url__>:: Specifies the URL of the entry. +*-n*, *--notes* <__notes__>:: + Specifies the notes of the entry. + *-p*, *--password-prompt*:: Uses a password prompt for the entry's password. diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index b218df7e2..62f2c6058 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -36,6 +36,11 @@ const QCommandLineOption Add::UsernameOption = QCommandLineOption(QStringList() const QCommandLineOption Add::UrlOption = QCommandLineOption(QStringList() << "url", QObject::tr("URL for the entry."), QObject::tr("URL")); +const QCommandLineOption Add::NotesOption = QCommandLineOption(QStringList() << "n" + << "notes", + QObject::tr("Notes for the entry."), + QObject::tr("Notes")); + const QCommandLineOption Add::PasswordPromptOption = QCommandLineOption(QStringList() << "p" << "password-prompt", @@ -51,6 +56,7 @@ Add::Add() description = QObject::tr("Add a new entry to a database."); options.append(Add::UsernameOption); options.append(Add::UrlOption); + options.append(Add::NotesOption); options.append(Add::PasswordPromptOption); positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to add."), QString("")}); @@ -105,6 +111,10 @@ int Add::executeWithDatabase(QSharedPointer database, QSharedPointersetUrl(parser->value(Add::UrlOption)); } + if (!parser->value(Add::NotesOption).isEmpty()) { + entry->setNotes(parser->value(Add::NotesOption).replace("\\n", "\n")); + } + if (parser->isSet(Add::PasswordPromptOption)) { if (!parser->isSet(Command::QuietOption)) { out << QObject::tr("Enter password for new entry: ") << flush; diff --git a/src/cli/Add.h b/src/cli/Add.h index 2d9cf9710..8c5e5ab5b 100644 --- a/src/cli/Add.h +++ b/src/cli/Add.h @@ -29,6 +29,7 @@ public: static const QCommandLineOption UsernameOption; static const QCommandLineOption UrlOption; + static const QCommandLineOption NotesOption; static const QCommandLineOption PasswordPromptOption; static const QCommandLineOption GenerateOption; static const QCommandLineOption PasswordLengthOption; diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 6fd1b0369..60c7d5893 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -41,6 +41,7 @@ Edit::Edit() // Using some of the options from the Add command since they are the same. options.append(Add::UsernameOption); options.append(Add::UrlOption); + options.append(Add::NotesOption); options.append(Add::PasswordPromptOption); options.append(Edit::TitleOption); positionalArguments.append({QString("entry"), QObject::tr("Path of the entry to edit."), QString("")}); @@ -91,9 +92,10 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< QString username = parser->value(Add::UsernameOption); QString url = parser->value(Add::UrlOption); + QString notes = parser->value(Add::NotesOption); QString title = parser->value(Edit::TitleOption); bool prompt = parser->isSet(Add::PasswordPromptOption); - if (username.isEmpty() && url.isEmpty() && title.isEmpty() && !prompt && !generate) { + if (username.isEmpty() && url.isEmpty() && notes.isEmpty() && title.isEmpty() && !prompt && !generate) { err << QObject::tr("Not changing any field for entry %1.").arg(entryPath) << endl; return EXIT_FAILURE; } @@ -108,6 +110,10 @@ int Edit::executeWithDatabase(QSharedPointer database, QSharedPointer< entry->setUsername(username); } + if (!notes.isEmpty()) { + entry->setNotes(notes.replace("\\n", "\n")); + } + if (!url.isEmpty()) { entry->setUrl(url); } diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp index 693c67b58..d944910f4 100644 --- a/tests/TestCli.cpp +++ b/tests/TestCli.cpp @@ -282,6 +282,8 @@ void TestCli::testAdd() "-g", "-L", "20", + "-n", + "some notes", m_dbFile->fileName(), "/newuser-entry"}); m_stderr->readLine(); // Skip password prompt @@ -294,6 +296,7 @@ void TestCli::testAdd() QCOMPARE(entry->username(), QString("newuser")); QCOMPARE(entry->url(), QString("https://example.com/")); QCOMPARE(entry->password().size(), 20); + QCOMPARE(entry->notes(), QString("some notes")); // Quiet option setInput("a"); @@ -353,6 +356,18 @@ void TestCli::testAdd() QCOMPARE(entry->username(), QString("newuser4")); QCOMPARE(entry->password().size(), 20); QVERIFY(!defaultPasswordClassesRegex.match(entry->password()).hasMatch()); + + setInput("a"); + execCmd(addCmd, {"add", "-u", "newuser5", "-n", "test\\nnew line", m_dbFile->fileName(), "/newuser-entry5"}); + m_stderr->readLine(); // skip password prompt + QCOMPARE(m_stderr->readAll(), QByteArray("")); + QCOMPARE(m_stdout->readAll(), QByteArray("Successfully added entry newuser-entry5.\n")); + + db = readDatabase(); + entry = db->rootGroup()->findEntryByPath("/newuser-entry5"); + QVERIFY(entry); + QCOMPARE(entry->username(), QString("newuser5")); + QCOMPARE(entry->notes(), QString("test\nnew line")); } void TestCli::testAddGroup() @@ -734,6 +749,8 @@ void TestCli::testEdit() "newuser", "--url", "https://otherurl.example.com/", + "-n", + "newnotes", "-t", "newtitle", m_dbFile->fileName(), @@ -746,6 +763,7 @@ void TestCli::testEdit() QCOMPARE(entry->username(), QString("newuser")); QCOMPARE(entry->url(), QString("https://otherurl.example.com/")); QCOMPARE(entry->password(), QString("Password")); + QCOMPARE(entry->notes(), QString("newnotes")); // Quiet option setInput("a"); @@ -803,6 +821,14 @@ void TestCli::testEdit() entry = db->rootGroup()->findEntryByPath("/evennewertitle"); QVERIFY(entry); QCOMPARE(entry->password(), QString("newpassword")); + + // with line break in notes + setInput("a"); + execCmd(editCmd, {"edit", m_dbFile->fileName(), "-n", "testing\\nline breaks", "/evennewertitle"}); + db = readDatabase(); + entry = db->rootGroup()->findEntryByPath("/evennewertitle"); + QVERIFY(entry); + QCOMPARE(entry->notes(), QString("testing\nline breaks")); } void TestCli::testEstimate_data()