diff --git a/core/KGpgGroupNode.cpp b/core/KGpgGroupNode.cpp index 33f8e21f..cffe020c 100644 --- a/core/KGpgGroupNode.cpp +++ b/core/KGpgGroupNode.cpp @@ -1,257 +1,257 @@ /* * Copyright (C) 2008,2009,2010,2012,2016,2017 * Rolf Eike Beer * * 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 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of 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 "KGpgGroupNode.h" #include "kgpg_general_debug.h" #include "KGpgGroupMemberNode.h" #include "KGpgRootNode.h" #include "kgpgsettings.h" #include #include #include #include class KGpgGroupNodePrivate { public: KGpgGroupNodePrivate(const QString &name); QString m_name; /** * @brief find the line that defines this group in the configuration * @param conffile file object (will be initialized) * @param stream text stream (will be initialized and connected to conffile) * @param lines the lines found in conffile (will be filled) * @return the index in lines of the entry defining this group * @retval -1 no entry defining this group was found * * stream will be positioned at the beginning. */ int findGroupEntry(QFile &conffile, QTextStream &stream, QStringList &lines); static const QRegExp &groupPattern(); static const QString &groupTag(); }; KGpgGroupNodePrivate::KGpgGroupNodePrivate(const QString &name) : m_name(name) { } int KGpgGroupNodePrivate::findGroupEntry(QFile &conffile, QTextStream &stream, QStringList &lines) { conffile.setFileName(KGpgSettings::gpgConfigPath()); if (!conffile.exists()) return -1; if (!conffile.open(QIODevice::ReadWrite)) return -1; stream.setDevice(&conffile); int index = -1; int i = -1; while (!stream.atEnd()) { const QString rawLine = stream.readLine(); i++; QString parsedLine = rawLine.simplified().section(QLatin1Char('#'), 0, 0); if (groupPattern().exactMatch(parsedLine)) { // remove "group " parsedLine = parsedLine.remove(0, 6); if (parsedLine.startsWith(m_name)) { - if (parsedLine.mid(m_name.length()).simplified().startsWith(QLatin1Char('='))) { + if (parsedLine.midRef(m_name.length()).trimmed().startsWith(QLatin1Char('='))) { if (index >= 0) { // multiple definitions of the same group, drop the second one continue; } else { index = i; } } } } lines << rawLine; } stream.seek(0); return index; } const QRegExp & KGpgGroupNodePrivate::groupPattern() { static const QRegExp groupre(QLatin1String("^group [^ ]+ ?= ?([0-9a-fA-F]{8,} ?)*$")); return groupre; } const QString & KGpgGroupNodePrivate::groupTag() { static const QString grouptag(QLatin1String("group ")); return grouptag; } KGpgGroupNode::KGpgGroupNode(KGpgRootNode *parent, const QString &name, const QStringList &members) : KGpgExpandableNode(parent), d_ptr(new KGpgGroupNodePrivate(name)) { for (const QString &id : members) if (id.startsWith(QLatin1String("0x"))) new KGpgGroupMemberNode(this, id.mid(2)); else new KGpgGroupMemberNode(this, id); parent->m_groups++; } KGpgGroupNode::KGpgGroupNode(KGpgRootNode *parent, const QString &name, const KGpgKeyNode::List &members) : KGpgExpandableNode(parent), d_ptr(new KGpgGroupNodePrivate(name)) { Q_ASSERT(!members.isEmpty()); for (KGpgKeyNode *nd : members) new KGpgGroupMemberNode(this, nd); parent->m_groups++; } KGpgGroupNode::~KGpgGroupNode() { if (parent() != nullptr) m_parent->toRootNode()->m_groups--; } KgpgCore::KgpgItemType KGpgGroupNode::getType() const { return ITYPE_GROUP; } QString KGpgGroupNode::getName() const { const Q_D(KGpgGroupNode); return d->m_name; } QString KGpgGroupNode::getSize() const { return i18np("1 key", "%1 keys", children.count()); } void KGpgGroupNode::readChildren() { } void KGpgGroupNode::rename(const QString &newName) { Q_D(KGpgGroupNode); QFile conffile; QTextStream t; QStringList lines; int index = d->findGroupEntry(conffile, t, lines); // check if file opening failed if (!t.device()) return; if (index < 0) { qCDebug(KGPG_LOG_GENERAL) << "Group " << d->m_name << " not renamed, group does not exist"; return; } // 6 = groupTag().length() const QString values = lines[index].simplified().mid(6 + d->m_name.length()); lines[index] = d->groupTag() + newName + QLatin1Char(' ') + values; conffile.resize(0); t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); d->m_name = newName; } void KGpgGroupNode::saveMembers() { Q_D(KGpgGroupNode); QFile conffile; QTextStream t; QStringList lines; int index = d->findGroupEntry(conffile, t, lines); // check if file opening failed if (!t.device()) return; QStringList memberIds; for (int j = getChildCount() - 1; j >= 0; j--) memberIds << getChild(j)->toGroupMemberNode()->getId(); const QString groupEntry = d->groupTag() + d->m_name + QLatin1String(" = ") + memberIds.join(QLatin1String(" ")); if (index >= 0) lines[index] = groupEntry; else lines << groupEntry; conffile.resize(0); t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); } void KGpgGroupNode::remove() { Q_D(KGpgGroupNode); QFile conffile; QTextStream t; QStringList lines; int index = d->findGroupEntry(conffile, t, lines); // check if file opening failed if (!t.device()) return; if (index < 0) return; lines.removeAt(index); conffile.resize(0); t << lines.join(QLatin1String("\n")) + QLatin1Char('\n'); } diff --git a/transactions/kgpgtextorfiletransaction.cpp b/transactions/kgpgtextorfiletransaction.cpp index 020fa14e..ce9b662a 100644 --- a/transactions/kgpgtextorfiletransaction.cpp +++ b/transactions/kgpgtextorfiletransaction.cpp @@ -1,184 +1,184 @@ /* * Copyright (C) 2008,2009,2010,2011,2012,2013 Rolf Eike Beer */ /*************************************************************************** * * * 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 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgtextorfiletransaction.h" #include "kgpg_general_debug.h" #include "gpgproc.h" #include #include #include KGpgTextOrFileTransaction::KGpgTextOrFileTransaction(QObject *parent, const QString &text, const bool allowChaining) : KGpgTransaction(parent, allowChaining) { setText(text); } KGpgTextOrFileTransaction::KGpgTextOrFileTransaction(QObject *parent, const QList &files, const bool allowChaining) : KGpgTransaction(parent, allowChaining) { setUrls(files); } KGpgTextOrFileTransaction::~KGpgTextOrFileTransaction() { cleanUrls(); } void KGpgTextOrFileTransaction::setText(const QString &text) { m_text = text; cleanUrls(); int begin = text.indexOf(QRegExp(QLatin1String("^(.*\n)?-----BEGIN PGP [A-Z ]*-----\r?\n"))); if (begin < 0) return; // find the end of the BEGIN PGP ... line static const QChar lf = QLatin1Char('\n'); begin = text.indexOf(lf, begin); Q_ASSERT(begin > 0); // now loop until either an empty line is found (end of header) or // a line beginning with Charset is found. If the latter, use the // charset found there as hint for the following operation int nextlf; begin++; while ((nextlf = text.indexOf(lf, begin)) > 0) { static const QChar cr = QLatin1Char('\r'); if ((nextlf == begin) || ((nextlf == begin + 1) && (text[begin] == cr))) break; const QString charset = QLatin1String("Charset: "); - if (text.mid(begin, charset.length()) == charset) { + if (text.midRef(begin, charset.length()) == charset) { QString cs = text.mid(begin + charset.length(), nextlf - begin - charset.length()); if (!getProcess()->setCodec(cs.toLatin1())) qCDebug(KGPG_LOG_GENERAL) << "unsupported charset found in header" << cs; break; } begin = nextlf + 1; } } void KGpgTextOrFileTransaction::setUrls(const QList &files) { m_text.clear(); m_inpfiles = files; } bool KGpgTextOrFileTransaction::preStart() { QStringList locfiles; for (const QUrl &url : qAsConst(m_inpfiles)) { if (url.isLocalFile()) { locfiles.append(url.toLocalFile()); } else { QTemporaryFile tmpFile; tmpFile.open(); auto copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName())); copyJob->exec(); if (!copyJob->error()) { tmpFile.setAutoRemove(false); m_tempfiles.append(tmpFile.fileName()); } else { m_messages.append(copyJob->errorString()); cleanUrls(); setSuccess(TS_KIO_FAILED); return false; } } } if (locfiles.isEmpty() && m_tempfiles.isEmpty() && m_text.isEmpty() && !hasInputTransaction()) { setSuccess(TS_MSG_SEQUENCE); return false; } QStringList args(QLatin1String("--status-fd=1")); args << command(); // if the input is not stdin set command-fd so GnuPG // can ask if e.g. the file already exists m_closeInput = !args.contains(QLatin1String("--command-fd=0")); if (m_closeInput && (!locfiles.isEmpty() || !m_tempfiles.isEmpty())) { args << QLatin1String("--command-fd=0"); m_closeInput = false; } if (locfiles.count() + m_tempfiles.count() > 1) args << QLatin1String("--multifile"); args << locfiles << m_tempfiles; addArguments(args); return true; } void KGpgTextOrFileTransaction::postStart() { if (!m_text.isEmpty()){ GPGProc *proc = getProcess(); proc->write(m_text.toUtf8()); if (m_closeInput) proc->closeWriteChannel(); } } bool KGpgTextOrFileTransaction::nextLine(const QString &line) { m_messages.append(line); return false; } void KGpgTextOrFileTransaction::finish() { if (getProcess()->exitCode() != 0) { setSuccess(TS_MSG_SEQUENCE); } } const QStringList & KGpgTextOrFileTransaction::getMessages() const { return m_messages; } void KGpgTextOrFileTransaction::cleanUrls() { for (const QString &u : qAsConst(m_tempfiles)) QFile::remove(u); m_tempfiles.clear(); m_locfiles.clear(); m_inpfiles.clear(); } const QList & KGpgTextOrFileTransaction::getInputFiles() const { return m_inpfiles; } diff --git a/transactions/kgpgtransactionprivate.cpp b/transactions/kgpgtransactionprivate.cpp index 73daa7b0..8f13e246 100644 --- a/transactions/kgpgtransactionprivate.cpp +++ b/transactions/kgpgtransactionprivate.cpp @@ -1,336 +1,335 @@ /* * Copyright (C) 2009,2010,2012,2014,2016,2018 Rolf Eike Beer */ /*************************************************************************** * * * 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 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgpgtransactionprivate.h" #include "kgpgtransaction.h" #include "kgpg_transactions_debug.h" #include "kgpg_general_debug.h" #include #include #include #include KGpgTransactionPrivate::KGpgTransactionPrivate(KGpgTransaction *parent, bool allowChaining) : m_parent(parent), m_process(new GPGProc()), m_inputTransaction(nullptr), m_newPasswordDialog(nullptr), m_passwordDialog(nullptr), m_success(KGpgTransaction::TS_OK), m_tries(3), m_chainingAllowed(allowChaining), m_inputProcessDone(false), m_inputProcessResult(KGpgTransaction::TS_OK), m_ownProcessFinished(false), m_quitTries(0) { connect(m_process, &GPGProc::readReady, this, &KGpgTransactionPrivate::slotReadReady); connect(m_process, &GPGProc::processExited, this, &KGpgTransactionPrivate::slotProcessExited); connect(m_process, &GPGProc::started, this, &KGpgTransactionPrivate::slotProcessStarted); } KGpgTransactionPrivate::~KGpgTransactionPrivate() { if (m_newPasswordDialog) { m_newPasswordDialog->close(); m_newPasswordDialog->deleteLater(); } if (m_process->state() == QProcess::Running) { m_process->closeWriteChannel(); m_process->terminate(); } delete m_inputTransaction; delete m_process; } void KGpgTransactionPrivate::slotReadReady() { QString line; QPointer process(m_process); QPointer par(m_parent); while (!process.isNull() && (m_process->readln(line, true) >= 0)) { if (m_quitTries) m_quitLines << line; #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << line; #endif /* KGPG_DEBUG_TRANSACTIONS */ static const QString getBool = QLatin1String("[GNUPG:] GET_BOOL "); if (keyConsidered(line)) { // already handled by keyConsidered - skip the line } else if (line.startsWith(QLatin1String("[GNUPG:] USERID_HINT "))) { m_parent->addIdHint(line); } else if (line.startsWith(QLatin1String("[GNUPG:] BAD_PASSPHRASE "))) { // the MISSING_PASSPHRASE line comes first, in that case ignore a // following BAD_PASSPHRASE if (m_success != KGpgTransaction::TS_USER_ABORTED) m_success = KGpgTransaction::TS_BAD_PASSPHRASE; } else if (line.startsWith(QLatin1String("[GNUPG:] GET_HIDDEN passphrase.enter"))) { const bool goOn = m_parent->passphraseRequested(); // Check if the object was deleted while waiting for the result if (!goOn || par.isNull()) return; } else if (line.startsWith(QLatin1String("[GNUPG:] GOOD_PASSPHRASE"))) { emit m_parent->statusMessage(i18n("Got Passphrase")); if (m_passwordDialog != nullptr) { m_passwordDialog->close(); m_passwordDialog->deleteLater(); m_passwordDialog = nullptr; } if (m_parent->passphraseReceived()) { // signal GnuPG that there will be no further input and it can // begin sending output. m_process->closeWriteChannel(); } } else if (line.startsWith(getBool)) { - static const QString overwrite = QLatin1String("openfile.overwrite.okay"); const QString question = line.mid(getBool.length()); KGpgTransaction::ts_boolanswer answer; - if (question.startsWith(overwrite)) { + if (question.startsWith(QLatin1String("openfile.overwrite.okay"))) { m_overwriteUrl.clear(); answer = m_parent->confirmOverwrite(m_overwriteUrl); if ((answer == KGpgTransaction::BA_UNKNOWN) && !m_overwriteUrl.isEmpty()) { QPointer over = new KIO::RenameDialog(qobject_cast(m_parent->parent()), i18n("File Already Exists"), QUrl(), m_overwriteUrl, KIO::RenameDialog_Overwrite); m_overwriteUrl.clear(); switch (over->exec()) { case KIO::R_OVERWRITE: answer = KGpgTransaction::BA_YES; break; case KIO::R_RENAME: answer = KGpgTransaction::BA_NO; m_overwriteUrl = over->newDestUrl(); break; default: answer = KGpgTransaction::BA_UNKNOWN; m_parent->setSuccess(KGpgTransaction::TS_USER_ABORTED); // Close the pipes, otherwise GnuPG will try to answer // further questions about this file. m_process->closeWriteChannel(); m_process->closeReadChannel(QProcess::StandardOutput); break; } delete over; if (answer == KGpgTransaction::BA_UNKNOWN) continue; } } else { answer = m_parent->boolQuestion(question); } switch (answer) { case KGpgTransaction::BA_YES: write("YES\n"); break; case KGpgTransaction::BA_NO: write("NO\n"); break; case KGpgTransaction::BA_UNKNOWN: m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); m_parent->unexpectedLine(line); sendQuit(); } } else if (!m_overwriteUrl.isEmpty() && line.startsWith(QLatin1String("[GNUPG:] GET_LINE openfile.askoutname"))) { write(m_overwriteUrl.toLocalFile().toUtf8() + '\n'); m_overwriteUrl.clear(); } else if (line.startsWith(QLatin1String("[GNUPG:] MISSING_PASSPHRASE"))) { m_success = KGpgTransaction::TS_USER_ABORTED; } else if (line.startsWith(QLatin1String("[GNUPG:] CARDCTRL "))) { // just ignore them, pinentry should handle that } else { // all known hints int i = 0; bool matched = false; for (const QString &hintName : hintNames()) { const KGpgTransaction::ts_hintType h = static_cast(i++); if (!line.startsWith(hintName)) continue; matched = true; bool r; const int skip = hintName.length(); if (line.length() == skip) { r = m_parent->hintLine(h, QString()); } else { r = m_parent->hintLine(h, line.mid(skip + 1).trimmed()); } if (!r) { m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); sendQuit(); } break; } if (!matched) { if (m_parent->nextLine(line)) sendQuit(); } } } } void KGpgTransactionPrivate::slotProcessExited() { Q_ASSERT(sender() == m_process); m_ownProcessFinished = true; if (m_inputProcessDone) processDone(); } void KGpgTransactionPrivate::slotProcessStarted() { m_parent->postStart(); } void KGpgTransactionPrivate::sendQuit(void) { write("quit\n"); #ifdef KGPG_DEBUG_TRANSACTIONS if (m_quitTries == 0) qCDebug(KGPG_LOG_TRANSACTIONS) << "sending quit"; #endif /* KGPG_DEBUG_TRANSACTIONS */ if (m_quitTries++ >= 3) { qCDebug(KGPG_LOG_GENERAL) << "tried" << m_quitTries << "times to quit the GnuPG session"; qCDebug(KGPG_LOG_GENERAL) << "last input was" << m_quitLines; qCDebug(KGPG_LOG_GENERAL) << "please file a bug report at https://bugs.kde.org"; m_process->closeWriteChannel(); m_success = KGpgTransaction::TS_MSG_SEQUENCE; } } void KGpgTransactionPrivate::slotInputTransactionDone(int result) { Q_ASSERT(sender() == m_inputTransaction); m_inputProcessDone = true; m_inputProcessResult = result; if (m_ownProcessFinished) processDone(); } void KGpgTransactionPrivate::slotPassphraseEntered(const QString &passphrase) { // not calling KGpgTransactionPrivate::write() here for obvious privacy reasons m_process->write(passphrase.toUtf8() + '\n'); if (sender() == m_newPasswordDialog) { m_newPasswordDialog->deleteLater(); m_newPasswordDialog = nullptr; m_parent->newPassphraseEntered(); } else { Q_ASSERT(sender() == m_passwordDialog); } } void KGpgTransactionPrivate::slotPassphraseAborted() { Q_ASSERT((sender() == m_passwordDialog) ^ (sender() == m_newPasswordDialog)); sender()->deleteLater(); m_newPasswordDialog = nullptr; m_passwordDialog = nullptr; handlePassphraseAborted(); } void KGpgTransactionPrivate::handlePassphraseAborted() { // sending "quit" here is useless as it would be interpreted as the passphrase m_process->closeWriteChannel(); m_success = KGpgTransaction::TS_USER_ABORTED; } void KGpgTransactionPrivate::write(const QByteArray &a) { m_process->write(a); #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << m_parent << a; #endif /* KGPG_DEBUG_TRANSACTIONS */ } const QStringList & KGpgTransactionPrivate::hintNames (void) { static QStringList hints; if (hints.isEmpty()) { hints.insert(KGpgTransaction::HT_KEYEXPIRED, QLatin1String("[GNUPG:] KEYEXPIRED")); hints.insert(KGpgTransaction::HT_SIGEXPIRED, QLatin1String("[GNUPG:] SIGEXPIRED")); hints.insert(KGpgTransaction::HT_NOSECKEY, QLatin1String("[GNUPG:] NO_SECKEY")); hints.insert(KGpgTransaction::HT_ENCTO, QLatin1String("[GNUPG:] ENC_TO")); hints.insert(KGpgTransaction::HT_PINENTRY_LAUNCHED, QLatin1String("[GNUPG:] PINENTRY_LAUNCHED")); } return hints; } void KGpgTransactionPrivate::processDone() { m_parent->finish(); emit m_parent->infoProgress(100, 100); emit m_parent->done(m_success); #ifdef KGPG_DEBUG_TRANSACTIONS qCDebug(KGPG_LOG_TRANSACTIONS) << this << "result:" << m_success; #endif /* KGPG_DEBUG_TRANSACTIONS */ } bool KGpgTransactionPrivate::keyConsidered(const QString& line) { if (!line.startsWith(QLatin1String("[GNUPG:] KEY_CONSIDERED "))) return false; const QStringList &parts = line.split(QLatin1Char(' '), QString::SkipEmptyParts); if (parts.count() < 3) m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); else if (!m_expectedFingerprints.isEmpty() && !m_expectedFingerprints.contains(parts[2], Qt::CaseInsensitive)) m_parent->setSuccess(KGpgTransaction::TS_MSG_SEQUENCE); return true; }