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 }