File indexing completed on 2024-04-28 16:44:07

0001 /*
0002     SPDX-FileCopyrightText: 2019 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "apijob.h"
0008 
0009 #include <QMetaMethod>
0010 #include <QTimer>
0011 
0012 #include <KIO/TransferJob>
0013 
0014 #include "bugzilla_debug.h"
0015 #include "exceptions.h"
0016 
0017 namespace Bugzilla
0018 {
0019 TransferAPIJob::TransferAPIJob(KIO::TransferJob *transferJob, QObject *parent)
0020     : APIJob(parent)
0021     , m_transferJob(transferJob)
0022 {
0023     // Required for every request type.
0024     addMetaData(QStringLiteral("content-type"), QStringLiteral("application/json"));
0025     addMetaData(QStringLiteral("accept"), QStringLiteral("application/json"));
0026     addMetaData(QStringLiteral("UserAgent"), QStringLiteral("DrKonqi"));
0027     // We don't want HTML blobs but proper job errors + text!
0028     addMetaData(QStringLiteral("errorPage"), QStringLiteral("false"));
0029     // Disable automatic cookie injection. We don't need cookies but they
0030     // can mess up requests (supposedly by being unexpected or invalid or outdated ...)
0031     // https://bugs.kde.org/show_bug.cgi?id=419646
0032     addMetaData(QStringLiteral("cookies"), QStringLiteral("none"));
0033 
0034     connect(m_transferJob, &KIO::TransferJob::data, this, [this](KIO::Job *, const QByteArray &data) {
0035         m_data += data;
0036     });
0037 
0038     connect(m_transferJob, &KIO::TransferJob::finished, this, [this](KJob *job) {
0039         // Set errors, they are read by document() when the consumer reads
0040         // the data and possibly raised as exception.
0041         setError(job->error());
0042         setErrorText(job->errorText());
0043 
0044         Q_ASSERT(!((KIO::TransferJob *)job)->isErrorPage());
0045 
0046         // Force a delay on all API actions if configured. This allows
0047         // simulation of slow connections.
0048         static int delay = qEnvironmentVariableIntValue("DRKONQI_HTTP_DELAY_MS");
0049         if (delay > 0) {
0050             QTimer::singleShot(delay, this, [this] {
0051                 emitResult();
0052             });
0053             return;
0054         }
0055 
0056         emitResult();
0057     });
0058 }
0059 
0060 void TransferAPIJob::addMetaData(const QString &key, const QString &value)
0061 {
0062     m_transferJob->addMetaData(key, value);
0063 }
0064 
0065 void TransferAPIJob::setPutData(const QByteArray &data)
0066 {
0067     m_putData = data;
0068 
0069     // This is really awkward, does it need to be this way? Why can't we just
0070     // push the entire array in?
0071 
0072     // dataReq says we shouldn't send data >1mb, so segment the incoming data
0073     // accordingly and generate QBAs wrapping the raw data (zero-copy).
0074     int segmentSize = 1024 * 1024; // 1 mb per segment maximum
0075     int segments = qMax(data.size() / segmentSize, 1);
0076     m_dataSegments.reserve(segments);
0077     for (int i = 0; i < segments; ++i) {
0078         int offset = i * segmentSize;
0079         const char *buf = data.constData() + offset;
0080         int segmentLength = qMin(offset + segmentSize, data.size());
0081         m_dataSegments.append(QByteArray::fromRawData(buf, segmentLength));
0082     }
0083 
0084     // TODO: throw away, only here to make sure I don't mess up the
0085     // segmentation.
0086     int allLengths = 0;
0087     for (const auto &a : qAsConst(m_dataSegments)) {
0088         allLengths += a.size();
0089     }
0090     Q_ASSERT(allLengths == data.size());
0091 
0092     connect(m_transferJob, &KIO::TransferJob::dataReq, this, [this](KIO::Job *, QByteArray &dataForSending) {
0093         if (m_dataSegments.isEmpty()) {
0094             return;
0095         }
0096         dataForSending = m_dataSegments.takeFirst();
0097     });
0098 }
0099 
0100 QJsonDocument APIJob::document() const
0101 {
0102     ProtocolException::maybeThrow(this);
0103     Q_ASSERT(error() == KJob::NoError);
0104 
0105     auto document = QJsonDocument::fromJson(data());
0106     APIException::maybeThrow(document);
0107     return document;
0108 }
0109 
0110 QJsonObject APIJob::object() const
0111 {
0112     return document().object();
0113 }
0114 
0115 void APIJob::setAutoStart(bool start)
0116 {
0117     m_autostart = start;
0118 }
0119 
0120 void APIJob::connectNotify(const QMetaMethod &signal)
0121 {
0122     if (m_autostart && signal == QMetaMethod::fromSignal(&KJob::finished)) {
0123         qCDebug(BUGZILLA_LOG) << "auto starting";
0124         start();
0125     }
0126     KJob::connectNotify(signal);
0127 }
0128 
0129 } // namespace Bugzilla