File indexing completed on 2024-04-14 15:32:42

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include "sentrybeacon.h"
0005 
0006 #include "backtracegenerator.h"
0007 #include "crashedapplication.h"
0008 #include "debuggermanager.h"
0009 #include "drkonqi.h"
0010 #include "drkonqi_debug.h"
0011 #include <QFile>
0012 #include <QJsonDocument>
0013 #include <QJsonObject>
0014 #include <QNetworkReply>
0015 #include <QTemporaryDir>
0016 
0017 void SentryBeacon::sendEvent()
0018 {
0019     if (m_started) {
0020         return;
0021     }
0022     m_started = true;
0023 
0024     // We grab the payload here to prevent data races, it could change between now and when the actual submission happens.
0025     m_eventPayload = DrKonqi::debuggerManager()->backtraceGenerator()->sentryPayload();
0026     m_manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
0027     getDSNs();
0028 }
0029 
0030 void SentryBeacon::sendUserFeedback(const QString &feedback)
0031 {
0032     m_userFeedback = feedback;
0033     if (!m_eventID.isNull() && !m_userFeedback.isEmpty()) {
0034         postUserFeedback();
0035     } else {
0036         Q_EMIT userFeedbackSent();
0037     }
0038 }
0039 
0040 void SentryBeacon::getDSNs()
0041 {
0042     QNetworkRequest request(QUrl::fromUserInput(QStringLiteral("https://errors-eval.kde.org/_drkonqi_static/0/dsns.json")));
0043     auto reply = m_manager->get(request);
0044     connect(reply, &QNetworkReply::finished, this, &SentryBeacon::onDSNsReceived);
0045 }
0046 
0047 void SentryBeacon::onDSNsReceived()
0048 {
0049     auto reply = qobject_cast<QNetworkReply *>(sender());
0050     reply->deleteLater();
0051     const auto application = DrKonqi::crashedApplication()->fakeExecutableBaseName();
0052     const auto document = QJsonDocument::fromJson(reply->readAll());
0053     const auto object = document.object();
0054 
0055     qDebug() << document << document.isObject() << object;
0056     if (maybePostStore(object.value(application))) {
0057         return;
0058     }
0059     qCWarning(DRKONQI_LOG) << "Failed to post to application" << application;
0060     if (maybePostStore(object.value(QStringLiteral("fallthrough")))) {
0061         return;
0062     }
0063     qCWarning(DRKONQI_LOG) << "Failed to post to dynamic fallthrough";
0064     // final fallback is a hardcoded fallthrough, this isn't ideal because we can't change this after releases
0065     postStore({QStringLiteral("fallthrough"), QStringLiteral("456f53a71a074438bbb786d6add63241"), QStringLiteral("11")});
0066 }
0067 
0068 bool SentryBeacon::maybePostStore(const QJsonValue &value)
0069 {
0070     if (value.isObject()) {
0071         const auto object = value.toObject();
0072         postStore({
0073             object.value(QStringLiteral("project")).toString(),
0074             object.value(QStringLiteral("key")).toString(),
0075             object.value(QStringLiteral("index")).toString(),
0076         });
0077         return true;
0078     }
0079     return false;
0080 }
0081 
0082 void SentryBeacon::postStore(const SentryDSNContext &context)
0083 {
0084     m_context = context;
0085 
0086     // https://develop.sentry.dev/sdk/store/
0087     QNetworkRequest request(QUrl::fromUserInput(QStringLiteral("https://%1@errors-eval.kde.org/api/%2/store/").arg(context.key, context.index)));
0088     request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0089     request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("DrKonqi"));
0090     request.setRawHeader(QByteArrayLiteral("X-Sentry-Auth"),
0091                          QStringLiteral("Sentry sentry_version=7,sentry_timestamp=%1,sentry_client=sentry-curl/1.0,sentry_key=%2")
0092                              .arg(QString::number(std::time(nullptr)), context.key)
0093                              .toUtf8());
0094 
0095     auto reply = m_manager->post(request, m_eventPayload);
0096     connect(reply, &QNetworkReply::finished, this, &SentryBeacon::onStoreSent);
0097 }
0098 
0099 void SentryBeacon::onStoreSent()
0100 {
0101     auto reply = qobject_cast<QNetworkReply *>(sender());
0102     reply->deleteLater();
0103 
0104     const auto replyBlob = QJsonDocument::fromJson(reply->readAll());
0105     m_eventID = replyBlob.object().value(QStringLiteral("id"));
0106 
0107     Q_EMIT eventSent();
0108     if (!m_userFeedback.isEmpty()) { // a feedback was set in the meantime, apply it; otherwise we wait for sendFeedback()
0109         postUserFeedback();
0110     } else {
0111         Q_EMIT userFeedbackSent();
0112     }
0113 }
0114 
0115 void SentryBeacon::postUserFeedback()
0116 {
0117     qDebug() << m_context.key << m_context.index;
0118     const QJsonObject feedbackObject = {
0119         {QStringLiteral("event_id"), QJsonValue::fromVariant(m_eventID)},
0120         {QStringLiteral("name"), QStringLiteral("Anonymous")},
0121         {QStringLiteral("email"), QStringLiteral("anonymous@kde.org")},
0122         {QStringLiteral("comments"), m_userFeedback},
0123     };
0124     // TODO we could back reference the bug report, but that needs some reshuffling
0125 
0126     // https://docs.sentry.io/api/projects/submit-user-feedback/
0127     QNetworkRequest request(QUrl::fromUserInput(QStringLiteral("https://errors-eval.kde.org/api/0/projects/kde/%1/user-feedback/").arg(m_context.project)));
0128     request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0129     request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("DrKonqi"));
0130     request.setRawHeader(QByteArrayLiteral("Authorization"),
0131                          QStringLiteral("DSN https://%1@errors-eval.kde.org/%2").arg(m_context.key, m_context.index).toUtf8());
0132     auto feedbackReply = m_manager->post(request, QJsonDocument(feedbackObject).toJson());
0133     connect(feedbackReply, &QNetworkReply::finished, this, &SentryBeacon::onUserFeedbackSent);
0134 }
0135 
0136 void SentryBeacon::onUserFeedbackSent()
0137 {
0138     auto reply = qobject_cast<QNetworkReply *>(sender());
0139     reply->deleteLater();
0140     qDebug() << reply->readAll();
0141     Q_EMIT userFeedbackSent();
0142 }