From e37c2e34ce13a42db60aeb1ede222381c0f4592e Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Sun, 21 Jan 2018 15:12:21 +0200 Subject: [PATCH] Use the Accept-Content header to enable response compression fixes #86 fixes #89 --- CMakeLists.txt | 8 ++++- include/MatrixClient.h | 1 + src/MatrixClient.cc | 74 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 876409b6..b8450a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,12 @@ find_package(Qt5LinguistTools REQUIRED) find_package(Qt5Concurrent REQUIRED) find_package(Qt5Multimedia REQUIRED) +find_package(ZLIB) + +if (ZLIB_FOUND) + include_directories(${ZLIB_INCLUDE_DIRS}) +endif() + if (APPLE) find_package(Qt5MacExtras REQUIRED) endif(APPLE) @@ -321,7 +327,7 @@ add_subdirectory(libs/matrix-structs) include_directories(${matrix_structs_SOURCE_DIR}/include) include_directories(${matrix_structs_SOURCE_DIR}/deps) -set(COMMON_LIBS matrix_structs Qt5::Widgets Qt5::Network Qt5::Concurrent) +set(COMMON_LIBS matrix_structs Qt5::Widgets Qt5::Network Qt5::Concurrent ${ZLIB_LIBRARIES}) if(APPVEYOR_BUILD) set(NHEKO_LIBS ${COMMON_LIBS} lmdb) diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 826b4e18..6847ab22 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -142,6 +142,7 @@ signals: private: QNetworkReply *makeUploadRequest(QSharedPointer iodev); + QByteArray uncompress(const QByteArray &data); // Client API prefix. QString clientApiUrl_; diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 70a9bbf5..44257172 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -28,6 +28,8 @@ #include #include +#include + #include "Login.h" #include "MatrixClient.h" #include "Register.h" @@ -252,6 +254,7 @@ MatrixClient::sync() noexcept endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); + request.setRawHeader(QByteArray("Accept-Encoding"), QByteArray("gzip, deflate")); auto reply = get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { @@ -264,8 +267,13 @@ MatrixClient::sync() noexcept return; } + auto data = reply->readAll(); + + if (reply->hasRawHeader(QByteArray("Content-Encoding"))) + data = uncompress(data); + try { - mtx::responses::Sync response = nlohmann::json::parse(reply->readAll()); + mtx::responses::Sync response = nlohmann::json::parse(data); emit syncCompleted(response); } catch (std::exception &e) { qWarning() << "Sync malformed response: " << e.what(); @@ -374,6 +382,7 @@ MatrixClient::initialSync() noexcept endpoint.setQuery(query); QNetworkRequest request(QString(endpoint.toEncoded())); + request.setRawHeader(QByteArray("Accept-Encoding"), QByteArray("gzip, deflate")); auto reply = get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { @@ -386,8 +395,13 @@ MatrixClient::initialSync() noexcept return; } + auto data = reply->readAll(); + + if (reply->hasRawHeader(QByteArray("Content-Encoding"))) + data = uncompress(data); + try { - mtx::responses::Sync response = nlohmann::json::parse(reply->readAll()); + mtx::responses::Sync response = nlohmann::json::parse(data); emit initialSyncCompleted(response); } catch (std::exception &e) { qWarning() << "Sync malformed response" << e.what(); @@ -1268,3 +1282,59 @@ MatrixClient::makeUploadRequest(QSharedPointer iodev) return reply; } + +QByteArray +MatrixClient::uncompress(const QByteArray &data) +{ + /* + * https://stackoverflow.com/questions/2690328/qt-quncompress-gzip-data/7351507#7351507 + */ + + if (data.size() <= 4) { + qWarning("uncompress: Input data is truncated"); + return QByteArray(); + } + + QByteArray result; + + int ret; + z_stream strm; + static const int CHUNK_SIZE = 1024; + char out[CHUNK_SIZE]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = data.size(); + strm.next_in = (Bytef *)(data.data()); + + ret = inflateInit2(&strm, 15 + 32); // gzip decoding + if (ret != Z_OK) + return QByteArray(); + + // run inflate() + do { + strm.avail_out = CHUNK_SIZE; + strm.next_out = (Bytef *)(out); + + ret = inflate(&strm, Z_NO_FLUSH); + Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + // fall through + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return QByteArray(); + } + + result.append(out, CHUNK_SIZE - strm.avail_out); + } while (strm.avail_out == 0); + + // clean up and return + inflateEnd(&strm); + return result; +}