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"