aboutsummaryrefslogtreecommitdiff
path: root/lib/csapi/redaction.cpp
blob: acf1b0e4e7e767c7431818867a6626a9d4e0cdcf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/******************************************************************************
 * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN
 */

#include "redaction.h"

using namespace Quotient;

RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId,
                               const QString& txnId, const QString& reason)
    : BaseJob(HttpVerb::Put, QStringLiteral("RedactEventJob"),
              makePath("/_matrix/client/r0", "/rooms/", roomId, "/redact/",
                       eventId, "/", txnId))
{
    QJsonObject _data;
    addParam<IfNotEmpty>(_data, QStringLiteral("reason"), reason);
    setRequestData(std::move(_data));
}
272' href='#n272'>272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
// SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de>
// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
// SPDX-License-Identifier: LGPL-2.1-or-later

#pragma once

#include "requestdata.h"
#include "logging.h"
#include "converters.h" // Common for csapi/ headers even though not used here
#include "quotient_common.h" // For DECL_DEPRECATED_ENUMERATOR

#include <QtCore/QObject>
#include <QtCore/QStringBuilder>

class QNetworkReply;
class QSslError;

namespace Quotient {
class ConnectionData;

enum class HttpVerb { Get, Put, Post, Delete };

class QUOTIENT_API BaseJob : public QObject {
    Q_OBJECT
    Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT)
    Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries)
    Q_PROPERTY(int statusCode READ error NOTIFY statusChanged)

    static QByteArray encodeIfParam(const QString& paramPart);
    template <int N>
    static auto encodeIfParam(const char (&constPart)[N])
    {
        return constPart;
    }

public:
#define WITH_DEPRECATED_ERROR_VERSION(Recommended) \
    Recommended, DECL_DEPRECATED_ENUMERATOR(Recommended##Error, Recommended)

    /*! The status code of a job
     *
     * Every job is created in Unprepared status; upon calling prepare()
     * from Connection (if things are fine) it go to Pending status. After
     * that, the next transition comes after the reply arrives and its contents
     * are analysed. At any point in time the job can be abandon()ed, causing
     * it to switch to status Abandoned for a brief period before deletion.
     */
    enum StatusCode {
        Success = 0,
        NoError = Success,
        Pending = 1,
        WarningLevel = 20, //< Warnings have codes starting from this
        UnexpectedResponseType = 21,
        UnexpectedResponseTypeWarning = UnexpectedResponseType,
        Unprepared = 25, //< Initial job state is incomplete, hence warning level
        Abandoned = 50, //< A tiny period between abandoning and object deletion
        ErrorLevel = 100, //< Errors have codes starting from this
        NetworkError = 101,
        WITH_DEPRECATED_ERROR_VERSION(Timeout),
        Unauthorised,
        ContentAccessError,
        WITH_DEPRECATED_ERROR_VERSION(NotFound),
        WITH_DEPRECATED_ERROR_VERSION(IncorrectRequest),
        WITH_DEPRECATED_ERROR_VERSION(IncorrectResponse),
        WITH_DEPRECATED_ERROR_VERSION(TooManyRequests),
        RateLimited = TooManyRequests,
        WITH_DEPRECATED_ERROR_VERSION(RequestNotImplemented),
        WITH_DEPRECATED_ERROR_VERSION(UnsupportedRoomVersion),
        WITH_DEPRECATED_ERROR_VERSION(NetworkAuthRequired),
        WITH_DEPRECATED_ERROR_VERSION(UserConsentRequired),
        CannotLeaveRoom,
        UserDeactivated,
        FileError,
        UserDefinedError = 256
    };
    Q_ENUM(StatusCode)

#undef WITH_DEPRECATED_ERROR_VERSION

    template <typename... StrTs>
    static QByteArray makePath(StrTs&&... parts)
    {
        return (QByteArray() % ... % encodeIfParam(parts));
    }

    using Data
#ifndef Q_CC_MSVC
        Q_DECL_DEPRECATED_X("Use Quotient::RequestData instead")
#endif
        = RequestData;

    /*!
     * This structure stores the status of a server call job. The status
     * consists of a code, that is described (but not delimited) by the
     * respective enum, and a freeform message.
     *
     * To extend the list of error codes, define an (anonymous) enum
     * along the lines of StatusCode, with additional values
     * starting at UserDefinedError
     */
    struct Status {
        Status(StatusCode c) : code(c) {}
        Status(int c, QString m) : code(c), message(std::move(m)) {}

        static StatusCode fromHttpCode(int httpCode);
        static Status fromHttpCode(int httpCode, QString msg)
        {
            return { fromHttpCode(httpCode), std::move(msg) };
        }

        bool good() const { return code < ErrorLevel; }
        QDebug dumpToLog(QDebug dbg) const;
        friend QDebug operator<<(const QDebug& dbg, const Status& s)
        {
            return s.dumpToLog(dbg);
        }

        bool operator==(const Status& other) const
        {
            return code == other.code && message == other.message;
        }
        bool operator!=(const Status& other) const
        {
            return !operator==(other);
        }
        bool operator==(int otherCode) const
        {
            return code == otherCode;
        }
        bool operator!=(int otherCode) const
        {
            return !operator==(otherCode);
        }

        int code;
        QString message;
    };

public:
    BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint,
            bool needsToken = true);
    BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint,
            const QUrlQuery& query, RequestData&& data = {},
            bool needsToken = true);

    QUrl requestUrl() const;
    bool isBackground() const;

    /** Current status of the job */
    Status status() const;

    /** Short human-friendly message on the job status */
    QString statusCaption() const;

    /*! Get first bytes of the raw response body as received from the server
     *
     * \param bytesAtMost the number of leftmost bytes to return
     *
     * \sa rawDataSample
     */
    QByteArray rawData(int bytesAtMost) const;

    /*! Access the whole response body as received from the server */
    const QByteArray& rawData() const;

    /** Get UI-friendly sample of raw data
     *
     * This is almost the same as rawData but appends the "truncated"
     * suffix if not all data fit in bytesAtMost. This call is
     * recommended to present a sample of raw data as "details" next to
     * error messages. Note that the default \p bytesAtMost value is
     * also tailored to UI cases.
     *
     * \sa rawData
     */
    QString rawDataSample(int bytesAtMost = 65535) const;

    /** Get the response body as a JSON object
     *
     * If the job's returned content type is not `application/json`
     * or if the top-level JSON entity is not an object, an empty object
     * is returned.
     */
    QJsonObject jsonData() const;

    /** Get the response body as a JSON array
     *
     * If the job's returned content type is not `application/json`
     * or if the top-level JSON entity is not an array, an empty array
     * is returned.
     */
    QJsonArray jsonItems() const;

    /** Load the property from the JSON response assuming a given C++ type
     *
     * If there's no top-level JSON object in the response or if there's
     * no node with the key \p keyName, \p defaultValue is returned.
     */
    template <typename T, typename StrT>
    T loadFromJson(const StrT& keyName, T&& defaultValue = {}) const
    {
        const auto& jv = jsonData().value(keyName);
        return jv.isUndefined() ? std::forward<T>(defaultValue)
                                : fromJson<T>(jv);
    }

    /** Load the property from the JSON response and delete it from JSON
     *
     * If there's no top-level JSON object in the response or if there's
     * no node with the key \p keyName, \p defaultValue is returned.
     */
    template <typename T>
    T takeFromJson(const QString& key, T&& defaultValue = {})
    {
        if (const auto& jv = takeValueFromJson(key); !jv.isUndefined())
            return fromJson<T>(jv);

        return std::forward<T>(defaultValue);
    }

    /** Error (more generally, status) code
     * Equivalent to status().code
     * \sa status
     */
    int error() const;

    /** Error-specific message, as returned by the server */
    virtual QString errorString() const;

    /** A URL to help/clarify the error, if provided by the server */
    QUrl errorUrl() const;

    int maxRetries() const;
    void setMaxRetries(int newMaxRetries);

    using duration_ms_t = std::chrono::milliseconds::rep; // normally int64_t

    std::chrono::seconds getCurrentTimeout() const;
    Q_INVOKABLE Quotient::BaseJob::duration_ms_t getCurrentTimeoutMs() const;
    std::chrono::seconds getNextRetryInterval() const;
    Q_INVOKABLE Quotient::BaseJob::duration_ms_t getNextRetryMs() const;
    std::chrono::milliseconds timeToRetry() const;
    Q_INVOKABLE Quotient::BaseJob::duration_ms_t millisToRetry() const;

    friend QDebug operator<<(QDebug dbg, const BaseJob* j)
    {
        return dbg << j->objectName();
    }

public Q_SLOTS:
    void initiate(Quotient::ConnectionData* connData, bool inBackground);

    /**
     * Abandons the result of this job, arrived or unarrived.
     *
     * This aborts waiting for a reply from the server (if there was
     * any pending) and deletes the job object. No result signals
     * (result, success, failure) are emitted.
     */
    void abandon();

Q_SIGNALS:
    /** The job is about to send a network request */
    void aboutToSendRequest();

    /** The job has sent a network request */
    void sentRequest();

    /** The job has changed its status */
    void statusChanged(Quotient::BaseJob::Status newStatus);

    /**
     * The previous network request has failed; the next attempt will
     * be done in the specified time
     * @param nextAttempt the 1-based number of attempt (will always be more
     * than 1)
     * @param inMilliseconds the interval after which the next attempt will be
     * taken
     */
    void retryScheduled(int nextAttempt,
                        Quotient::BaseJob::duration_ms_t inMilliseconds);

    /**
     * The previous network request has been rate-limited; the next attempt
     * will be queued and run sometime later. Since other jobs may already
     * wait in the queue, it's not possible to predict the wait time.
     */
    void rateLimited();

    /**
     * Emitted when the job is finished, in any case. It is used to notify
     * observers that the job is terminated and that progress can be hidden.
     *
     * This should not be emitted directly by subclasses;
     * use finishJob() instead.
     *
     * In general, to be notified of a job's completion, client code
     * should connect to result(), success(), or failure()
     * rather than finished(). However if you need to track the job's
     * lifecycle you should connect to this instead of result();
     * in particular, only this signal will be emitted on abandoning.
     *
     * @param job the job that emitted this signal
     *
     * @see result, success, failure
     */
    void finished(Quotient::BaseJob* job);

    /**
     * Emitted when the job is finished (except when abandoned).
     *
     * Use error() to know if the job was finished with error.
     *
     * @param job the job that emitted this signal
     *
     * @see success, failure
     */
    void result(Quotient::BaseJob* job);

    /**
     * Emitted together with result() in case there's no error.
     *
     * @see result, failure
     */
    void success(Quotient::BaseJob*);

    /**
     * Emitted together with result() if there's an error.
     * Similar to result(), this won't be emitted in case of abandon().
     *
     * @see result, success
     */
    void failure(Quotient::BaseJob*);

    void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    void uploadProgress(qint64 bytesSent, qint64 bytesTotal);

protected:
    using headers_t = QHash<QByteArray, QByteArray>;

    Q_DECL_DEPRECATED_X("Deprecated due to being unused")
    const QString& apiEndpoint() const;
    Q_DECL_DEPRECATED_X("Deprecated due to being unused")
    void setApiEndpoint(const QString& apiEndpoint);
    const headers_t& requestHeaders() const;
    void setRequestHeader(const headers_t::key_type& headerName,
                          const headers_t::mapped_type& headerValue);
    void setRequestHeaders(const headers_t& headers);
    QUrlQuery query() const;
    void setRequestQuery(const QUrlQuery& query);
    const RequestData& requestData() const;
    void setRequestData(RequestData&& data);
    const QByteArrayList& expectedContentTypes() const;
    void addExpectedContentType(const QByteArray& contentType);
    void setExpectedContentTypes(const QByteArrayList& contentTypes);
    QByteArrayList expectedKeys() const;
    void addExpectedKey(const QByteArray &key);
    void setExpectedKeys(const QByteArrayList &keys);

    const QNetworkReply* reply() const;
    QNetworkReply* reply();

    /** Construct a URL out of baseUrl, path and query
     *
     * The function ensures exactly one '/' between the path component of
     * \p baseUrl and \p path. The query component of \p baseUrl is ignored.
     */
    static QUrl makeRequestUrl(QUrl baseUrl, const QByteArray &encodedPath,
                               const QUrlQuery& query = {});

    /*! Prepares the job for execution
     *
     * This method is called no more than once per job lifecycle,
     * when it's first scheduled for execution; in particular, it is not called
     * on retries.
     */
    virtual void doPrepare();

    /*! Postprocessing after the network request has been sent
     *
     * This method is called every time the job receives a running
     * QNetworkReply object from NetworkAccessManager - basically, after
     * successfully sending a network request (including retries).
     */
    virtual void onSentRequest(QNetworkReply*);
    virtual void beforeAbandon();

    /*! \brief An extension point for additional reply processing.
     *
     * The base implementation does nothing and returns Success.
     *
     * \sa gotReply
     */
    virtual Status prepareResult();

    /*! \brief Process details of the error
     *
     * The function processes the reply in case when status from checkReply()
     * was not good (usually because of an unsuccessful HTTP code).
     * The base implementation assumes Matrix JSON error object in the body;
     * overrides are strongly recommended to call it for all stock Matrix
     * responses as early as possible and only then process custom errors,
     * with JSON or non-JSON payload.
     *
     * \return updated (if necessary) job status
     */
    virtual Status prepareError(Status currentStatus);

    /*! \brief Get direct access to the JSON response object in the job
     *
     * This allows to implement deserialisation with "move" semantics for parts
     * of the response. Assuming that the response body is a valid JSON object,
     * the function calls QJsonObject::take(key) on it and returns the result.
     *
     * \return QJsonValue::Null, if the response content type is not
     *                           advertised as `application/json`;
     *         QJsonValue::Undefined, if the response is a JSON object but
     *                                doesn't have \p key;
     *         the value for \p key otherwise.
     *
     * \sa takeFromJson
     */
    QJsonValue takeValueFromJson(const QString& key);

    void setStatus(Status s);
    void setStatus(int code, QString message);

    // Q_DECLARE_LOGGING_CATEGORY return different function types
    // in different versions
    using LoggingCategory = decltype(JOBS)*;
    void setLoggingCategory(LoggingCategory lcf);

    // Job objects should only be deleted via QObject::deleteLater
    ~BaseJob() override;

protected Q_SLOTS:
    void timeout();

    /*! \brief Check the pending or received reply for upfront issues
     *
     * This is invoked when headers are first received and also once
     * the complete reply is obtained; the base implementation checks the HTTP
     * headers to detect general issues such as network errors or access denial
     * and it's strongly recommended to call it from overrides,
     * as early as possible.
     * This slot is const and cannot read the response body. If you need to read
     * the body on the fly, override onSentRequest() and connect in it
     * to reply->readyRead(); and if you only need to validate the body after
     * it fully arrived, use prepareResult() for that). Returning anything
     * except NoError/Success switches further processing from prepareResult()
     * to prepareError().
     *
     * @return the result of checking the reply
     *
     * @see gotReply
     */
    virtual Status checkReply(const QNetworkReply *reply) const;

private Q_SLOTS:
    void sendRequest();
    void gotReply();

    friend class ConnectionData; // to provide access to sendRequest()

private:
    void stop();
    void finishJob();

    class Private;
    ImplPtr<Private> d;
};

inline bool QUOTIENT_API isJobPending(BaseJob* job)
{
    return job && job->error() == BaseJob::Pending;
}
} // namespace Quotient