File indexing completed on 2024-04-28 05:26:49

0001 /*******************************************************************
0002  * bugzillalib.cpp
0003  * SPDX-FileCopyrightText: 2009, 2011 Dario Andres Rodriguez <andresbajotierra@gmail.com>
0004  * SPDX-FileCopyrightText: 2012 George Kiagiadakis <kiagiadakis.george@gmail.com>
0005  * SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
0006  *
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  *
0009  ******************************************************************/
0010 
0011 #include "bugzillalib.h"
0012 
0013 #include <QReadWriteLock>
0014 #include <QRegularExpression>
0015 
0016 #include "drkonqi_debug.h"
0017 #include "drkonqi_globals.h"
0018 #include "libbugzilla/bugzilla.h"
0019 #include "libbugzilla/clients/attachmentclient.h"
0020 #include "libbugzilla/clients/bugclient.h"
0021 #include "libbugzilla/clients/productclient.h"
0022 #include "libbugzilla/connection.h"
0023 
0024 static const char showBugUrl[] = "show_bug.cgi?id=%1";
0025 
0026 // Extra filter rigging. We don't want to leak secrets via qdebug, so install
0027 // a message handler which does nothing more than replace secrets in debug
0028 // messages with placeholders.
0029 // This is used as a global static (since message handlers are meant to be
0030 // static) and is slightly synchronizing across threads WRT the filter hash.
0031 struct QMessageFilterContainer {
0032     QMessageFilterContainer();
0033     ~QMessageFilterContainer();
0034     void insert(const QString &needle, const QString &replace);
0035     void clear();
0036 
0037     QString filter(const QString &msg);
0038 
0039     // Message handler is called across threads. Synchronize for good measure.
0040     QReadWriteLock lock;
0041     QtMessageHandler handler;
0042 
0043 private:
0044     QHash<QString, QString> filters;
0045 };
0046 
0047 Q_GLOBAL_STATIC(QMessageFilterContainer, s_messageFilter)
0048 
0049 QMessageFilterContainer::QMessageFilterContainer()
0050 {
0051     handler = qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
0052         s_messageFilter->handler(type, context, s_messageFilter->filter(msg));
0053     });
0054 }
0055 
0056 QMessageFilterContainer::~QMessageFilterContainer()
0057 {
0058     qInstallMessageHandler(handler);
0059 }
0060 
0061 void QMessageFilterContainer::insert(const QString &needle, const QString &replace)
0062 {
0063     if (needle.isEmpty()) {
0064         return;
0065     }
0066 
0067     QWriteLocker locker(&lock);
0068     filters[needle] = replace;
0069 }
0070 
0071 QString QMessageFilterContainer::filter(const QString &msg)
0072 {
0073     QReadLocker locker(&lock);
0074     QString filteredMsg = msg;
0075     for (auto it = filters.constBegin(); it != filters.constEnd(); ++it) {
0076         filteredMsg.replace(it.key(), it.value());
0077     }
0078     return filteredMsg;
0079 }
0080 
0081 void QMessageFilterContainer::clear()
0082 {
0083     QWriteLocker locker(&lock);
0084     filters.clear();
0085 }
0086 
0087 BugzillaManager::BugzillaManager(const QString &bugTrackerUrl, QObject *parent)
0088     : QObject(parent)
0089     , m_bugTrackerUrl(bugTrackerUrl.isEmpty() ? KDE_BUGZILLA_URL : bugTrackerUrl)
0090 {
0091     Q_ASSERT(bugTrackerUrl.endsWith(QLatin1Char('/')));
0092     Bugzilla::setConnection(new Bugzilla::HTTPConnection(QUrl(m_bugTrackerUrl + QStringLiteral("rest"))));
0093 }
0094 
0095 void BugzillaManager::lookupVersion()
0096 {
0097     KJob *job = Bugzilla::version();
0098     connect(job, &KJob::finished, this, [this](KJob *job) {
0099         try {
0100             QString version = Bugzilla::version(job);
0101             setFeaturesForVersion(version);
0102             Q_EMIT bugzillaVersionFound();
0103         } catch (Bugzilla::Exception &e) {
0104             // Version detection problems simply mean we'll not mark the version
0105             // found and the UI will not allow reporting.
0106             qCWarning(DRKONQI_LOG) << e.whatString();
0107             Q_EMIT bugzillaVersionError(e.whatString());
0108         }
0109     });
0110 }
0111 
0112 void BugzillaManager::setFeaturesForVersion(const QString &version)
0113 {
0114     // A procedure to change Dr Konqi behaviour automatically when Bugzilla
0115     // software versions change.
0116     //
0117     // Changes should be added to Dr Konqi AHEAD of when the corresponding
0118     // Bugzilla software changes are released into bugs.kde.org, so that
0119     // Dr Konqi can continue to operate smoothly, without bug reports and a
0120     // reactive KDE software release.
0121     //
0122     // If Bugzilla announces a change to its software that affects Dr Konqi,
0123     // add executable code to implement the change automatically when the
0124     // Bugzilla software version changes. It goes at the end of this procedure
0125     // and elsewhere in this class (BugzillaManager) and/or other classes where
0126     // the change should actually be implemented.
0127 
0128     const int nVersionParts = 3;
0129     QStringList digits = version.split(QRegularExpression(QStringLiteral("[._-]")), Qt::SkipEmptyParts);
0130     while (digits.count() < nVersionParts) {
0131         digits << QLatin1String("0");
0132     }
0133     if (digits.count() > nVersionParts) {
0134         qCWarning(DRKONQI_LOG)
0135             << QStringLiteral("Current Bugzilla version %1 has more than %2 parts. Check that this is not a problem.").arg(version).arg(nVersionParts);
0136     }
0137 
0138     qCDebug(DRKONQI_LOG) << "VERSION" << version;
0139 }
0140 
0141 void BugzillaManager::tryLogin(const QString &username, const QString &password)
0142 {
0143     m_username = username;
0144     m_password = password;
0145     refreshToken();
0146 }
0147 
0148 void BugzillaManager::refreshToken()
0149 {
0150     Q_ASSERT(!m_username.isEmpty());
0151     Q_ASSERT(!m_password.isEmpty());
0152     m_logged = false;
0153 
0154     // Rest token and qdebug filters
0155     Bugzilla::connection().setToken(QString());
0156     s_messageFilter->clear();
0157     s_messageFilter->insert(m_password, QStringLiteral("PASSWORD"));
0158 
0159     KJob *job = Bugzilla::login(m_username, m_password);
0160     connect(job, &KJob::finished, this, [this](KJob *job) {
0161         try {
0162             auto details = Bugzilla::login(job);
0163             m_token = details.token;
0164             if (m_token.isEmpty()) {
0165                 throw Bugzilla::RuntimeException(QStringLiteral("Did not receive a token"));
0166             }
0167 
0168             s_messageFilter->insert(m_token, QStringLiteral("TOKEN"));
0169             Bugzilla::connection().setToken(m_token);
0170             m_logged = true;
0171 
0172             Q_EMIT loginFinished(true);
0173         } catch (Bugzilla::Exception &e) {
0174             qCWarning(DRKONQI_LOG) << e.whatString();
0175             // Version detection problems simply mean we'll not mark the version
0176             // found and the UI will not allow reporting.
0177             Q_EMIT loginError(e.whatString());
0178         }
0179     });
0180 }
0181 
0182 bool BugzillaManager::getLogged() const
0183 {
0184     return m_logged;
0185 }
0186 
0187 QString BugzillaManager::getUsername() const
0188 {
0189     return m_username;
0190 }
0191 
0192 void BugzillaManager::sendReport(const Bugzilla::NewBug &bug)
0193 {
0194     auto job = Bugzilla::BugClient().create(bug);
0195     connect(job, &KJob::finished, this, [this](KJob *job) {
0196         try {
0197             int id = Bugzilla::BugClient().create(job);
0198             Q_ASSERT(id > 0);
0199             Q_EMIT reportSent(id);
0200         } catch (Bugzilla::Exception &e) {
0201             qCWarning(DRKONQI_LOG) << e.whatString();
0202             Q_EMIT sendReportError(e.whatString());
0203         }
0204     });
0205 }
0206 
0207 void BugzillaManager::attachTextToReport(const QString &text, const QString &filename, const QString &summary, int bugId, const QString &comment)
0208 {
0209     Bugzilla::NewAttachment attachment;
0210     attachment.ids = QList<int>{bugId};
0211     attachment.data = text;
0212     attachment.file_name = filename;
0213     attachment.summary = summary;
0214     attachment.comment = comment;
0215     attachment.content_type = QLatin1String("text/plain");
0216 
0217     auto job = Bugzilla::AttachmentClient().createAttachment(bugId, attachment);
0218     connect(job, &KJob::finished, this, [this, bugId](KJob *job) {
0219         try {
0220             const QList<int> attachmentIds = Bugzilla::AttachmentClient().createAttachment(job);
0221             Q_ASSERT(attachmentIds.size() == 1); // NB: attachmentIds are not bug ids!
0222             Q_EMIT attachToReportSent(bugId);
0223         } catch (Bugzilla::Exception &e) {
0224             qCWarning(DRKONQI_LOG) << e.whatString();
0225             Q_EMIT attachToReportError(e.whatString());
0226         }
0227     });
0228 }
0229 
0230 void BugzillaManager::fetchProductInfo(const QString &product)
0231 {
0232     auto job = Bugzilla::ProductClient().get(product);
0233     connect(job, &KJob::finished, this, [this](KJob *job) {
0234         try {
0235             auto ptr = Bugzilla::ProductClient().get(job);
0236             Q_ASSERT(ptr);
0237             Q_EMIT productInfoFetched(ptr);
0238         } catch (Bugzilla::Exception &e) {
0239             qCWarning(DRKONQI_LOG) << e.whatString();
0240             // This doesn't have a string because it is actually not used for
0241             // anything...
0242             Q_EMIT productInfoError();
0243         }
0244     });
0245 }
0246 
0247 QString BugzillaManager::urlForBug(int bug_number) const
0248 {
0249     return QString(m_bugTrackerUrl) + QString::fromLatin1(showBugUrl).arg(bug_number);
0250 }
0251 
0252 #include "moc_bugzillalib.cpp"