diff --git a/.travis.yml b/.travis.yml index dcf373bf5..dd319995b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,16 +33,15 @@ notifications: on_success: change on_failure: change -# container-based builds -#sudo: false cache: ccache: true directories: - $HOME/hombebrew_cache # opt-in Ubuntu Trusty -sudo: required dist: trusty +# container-based builds +sudo: false addons: coverity_scan: @@ -62,7 +61,7 @@ addons: - sourceline: 'ppa:beineri/opt-qt551-trusty' - sourceline: 'ppa:adrozdoff/cmake' packages: - # packages list: https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise + # packages list: https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise - autoconf - automake - cmake @@ -74,6 +73,7 @@ addons: # Qt 5.5.1 - qt55base - qt55tools + before_install: # only allow specific build for coverity scan, others will stop - if [ "$TRAVIS_BRANCH" = "$coverity_branch" ] && ! [ "$TRAVIS_OS_NAME" = "linux" -a "$lt_branch" = "RC_1_0" -a "$gui" = true -a "$build_system" = "qmake" ]; then exit ; fi @@ -102,54 +102,51 @@ before_install: - echo $build_system - echo $ltconf - echo $qbtconf + install: - - | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then + #- | + #if [ "$TRAVIS_OS_NAME" = "linux" ]; then # build libtorrent from source #if [ "$lt_branch" != "dist" ]; then - #cd "$HOME" && pwd && git clone --depth 1 https://github.com/arvidn/libtorrent.git --branch $lt_branch ; - #cd libtorrent && ./autotool.sh && ./configure $ltconf && make install ; - #fi ; - - # ccache - if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then - dpkg-query -L ccache && export use_ccache=true ; - ccache -V && ccache --show-stats && ccache --zero-stats ; - fi ; - fi + #cd "$HOME" && pwd && git clone --depth 1 https://github.com/arvidn/libtorrent.git --branch $lt_branch + #cd libtorrent && ./autotool.sh && ./configure $ltconf && make install + #fi + #fi - | if [ "$TRAVIS_OS_NAME" = "osx" ]; then - wget https://builds.shiki.hu/homebrew/version ; + # dependencies + brew update > /dev/null + brew install colormake ccache zlib + PATH="/usr/local/opt/ccache/libexec:$PATH" + brew link --force zlib + brew outdated "pkg-config" || brew upgrade "pkg-config" + + wget https://builds.shiki.hu/homebrew/version if ! cmp --quiet "version" "$HOME/hombebrew_cache/version" ; then - echo "Cached files are different from server. Downloading new ones." ; + echo "Cached files are different from server. Downloading new ones." # First delete old files - rm -r "$HOME/hombebrew_cache" ; - mkdir "$HOME/hombebrew_cache"; - cp "version" $HOME/hombebrew_cache ; - cd "$HOME/hombebrew_cache" ; - wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb ; - wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz ; - wget https://builds.shiki.hu/homebrew/qt5.rb ; - wget https://builds.shiki.hu/homebrew/qt5-5.7.1_1.el_capitan.bottle.tar.gz ; + rm -r "$HOME/hombebrew_cache" + mkdir "$HOME/hombebrew_cache" + cp "version" $HOME/hombebrew_cache + cd "$HOME/hombebrew_cache" + wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb + wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz + wget https://builds.shiki.hu/homebrew/qt5.rb + wget https://builds.shiki.hu/homebrew/qt5-5.7.1_1.el_capitan.bottle.tar.gz fi - # dependencies - brew update > /dev/null ; - brew install colormake ccache ; - if [ "$build_system" = "cmake" ]; then - brew unlink cmake - brew install cmake; - fi - brew outdated "pkg-config" || brew upgrade "pkg-config" ; # Copy custom libtorrent bottle to homebrew's cache so it can find and install it # Also install our custom libtorrent formula by passing the local path to it # These 2 files are restored from Travis' cache. - cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz" "$(brew --cache)" ; - brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb" ; + cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz" "$(brew --cache)" + brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb" if [ "$build_system" = "cmake" ]; then - brew install qt5 ; - brew link --force qt5 ; + brew unlink cmake + brew install cmake + + brew install qt5 + brew link --force qt5 ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs ln -s /usr/local/opt/qt/plugins /usr/local/plugins else @@ -157,18 +154,17 @@ install: # Copy custom qt5 bottle to homebrew's cache so it can find and install it # Also install our custom qt5 formula by passing the local path to it # These 2 files are restored from Travis' cache. - cp "$HOME/hombebrew_cache/qt5-5.7.1_1.el_capitan.bottle.tar.gz" "$(brew --cache)" ; - brew install "$HOME/hombebrew_cache/qt5.rb" ; - brew link --force qt5 ; + cp "$HOME/hombebrew_cache/qt5-5.7.1_1.el_capitan.bottle.tar.gz" "$(brew --cache)" + brew install "$HOME/hombebrew_cache/qt5.rb" + brew link --force qt5 fi MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/" - - # ccache - if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then - export PATH="/usr/local/opt/ccache/libexec:$PATH" && export use_ccache=true ; - ccache -V && ccache --show-stats && ccache --zero-stats ; - fi ; + fi + - | + if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then + export use_ccache=true + ccache -V && ccache --show-stats && ccache --zero-stats fi script: @@ -184,9 +180,9 @@ script: if [ "$build_system" = "qmake" ]; then ./bootstrap.sh && ./configure $qbtconf if [ "$TRAVIS_OS_NAME" = "osx" ]; then - sed -i "" -e "s/^\(CC.*&&\).*$/\1 $CC/" src/Makefile ; # workaround for Qt & ccache: https://bugreports.qt.io/browse/QTBUG-31034 - sed -i "" -e "s/^\(CXX.*&&\).*$/\1 $CXX/" src/Makefile ; - sed -i "" -e 's/^\(CXXFLAGS.*\)$/\1 -Wno-unused-local-typedefs -Wno-inconsistent-missing-override/' src/Makefile ; + sed -i "" -e "s/^\(CC.*&&\).*$/\1 $CC/" src/Makefile # workaround for Qt & ccache: https://bugreports.qt.io/browse/QTBUG-31034 + sed -i "" -e "s/^\(CXX.*&&\).*$/\1 $CXX/" src/Makefile + sed -i "" -e 's/^\(CXXFLAGS.*\)$/\1 -Wno-unused-local-typedefs -Wno-inconsistent-missing-override/' src/Makefile fi fi - make && make install @@ -196,8 +192,8 @@ after_success: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then cd "$qbt_path/bin" ; fi - | if [ "$TRAVIS_OS_NAME" = "osx" ]; then - macdeployqt "$TRAVIS_BUILD_DIR/src/$qbt_exe.app" ; - cd "$TRAVIS_BUILD_DIR/src/$qbt_exe.app/Contents/MacOS" ; + macdeployqt "$TRAVIS_BUILD_DIR/src/$qbt_exe.app" + cd "$TRAVIS_BUILD_DIR/src/$qbt_exe.app/Contents/MacOS" fi - ./$qbt_exe --version diff --git a/configure b/configure index 192dc9ed1..685aa7cff 100755 --- a/configure +++ b/configure @@ -5188,12 +5188,12 @@ if test -n "$zlib_CFLAGS"; then pkg_cv_zlib_CFLAGS="$zlib_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib\""; } >&5 - ($PKG_CONFIG --exists --print-errors "zlib") 2>&5 + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib >= 1.2.5.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "zlib >= 1.2.5.2") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_zlib_CFLAGS=`$PKG_CONFIG --cflags "zlib" 2>/dev/null` + pkg_cv_zlib_CFLAGS=`$PKG_CONFIG --cflags "zlib >= 1.2.5.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5205,12 +5205,12 @@ if test -n "$zlib_LIBS"; then pkg_cv_zlib_LIBS="$zlib_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ - { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib\""; } >&5 - ($PKG_CONFIG --exists --print-errors "zlib") 2>&5 + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib >= 1.2.5.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "zlib >= 1.2.5.2") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then - pkg_cv_zlib_LIBS=`$PKG_CONFIG --libs "zlib" 2>/dev/null` + pkg_cv_zlib_LIBS=`$PKG_CONFIG --libs "zlib >= 1.2.5.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes @@ -5231,14 +5231,14 @@ else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then - zlib_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "zlib" 2>&1` + zlib_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "zlib >= 1.2.5.2" 2>&1` else - zlib_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "zlib" 2>&1` + zlib_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "zlib >= 1.2.5.2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$zlib_PKG_ERRORS" >&5 - as_fn_error $? "Package requirements (zlib) were not met: + as_fn_error $? "Package requirements (zlib >= 1.2.5.2) were not met: $zlib_PKG_ERRORS diff --git a/configure.ac b/configure.ac index cc1347241..342d9550f 100644 --- a/configure.ac +++ b/configure.ac @@ -166,7 +166,7 @@ PKG_CHECK_MODULES(libtorrent, LIBS="$libtorrent_LIBS $LIBS"]) PKG_CHECK_MODULES(zlib, - [zlib], + [zlib >= 1.2.5.2], [CPPFLAGS="$zlib_CFLAGS $CPPFLAGS" LIBS="$zlib_LIBS $LIBS"]) diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 1a953f406..76b8de174 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(ZLIB REQUIRED) +find_package(ZLIB 1.2.5.2 REQUIRED) set(QBT_BASE_HEADERS bittorrent/addtorrentparams.h diff --git a/src/base/http/connection.cpp b/src/base/http/connection.cpp index ad757817c..4a8709232 100644 --- a/src/base/http/connection.cpp +++ b/src/base/http/connection.cpp @@ -29,14 +29,14 @@ * Contact : chris@qbittorrent.org */ -#include -#include +#include "connection.h" + #include -#include "types.h" +#include + +#include "irequesthandler.h" #include "requestparser.h" #include "responsegenerator.h" -#include "irequesthandler.h" -#include "connection.h" using namespace Http; @@ -46,27 +46,33 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj , m_requestHandler(requestHandler) { m_socket->setParent(this); + m_idleTimer.start(); connect(m_socket, SIGNAL(readyRead()), SLOT(read())); - connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater())); } Connection::~Connection() { + m_socket->close(); } void Connection::read() { - m_receivedData.append(m_socket->readAll()); + m_idleTimer.restart(); + m_receivedData.append(m_socket->readAll()); Request request; - RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request); + RequestParser::ErrorCode err = RequestParser::parse(m_receivedData, request); // TODO: transform request headers to lowercase + switch (err) { case RequestParser::IncompleteRequest: // Partial request waiting for the rest break; + case RequestParser::BadRequest: sendResponse(Response(400, "Bad Request")); + m_receivedData.clear(); break; + case RequestParser::NoError: Environment env; env.clientAddress = m_socket->peerAddress(); @@ -74,25 +80,65 @@ void Connection::read() if (acceptsGzipEncoding(request.headers["accept-encoding"])) response.headers[HEADER_CONTENT_ENCODING] = "gzip"; sendResponse(response); + m_receivedData.clear(); break; } } void Connection::sendResponse(const Response &response) { - m_socket->write(ResponseGenerator::generate(response)); - m_socket->disconnectFromHost(); + m_socket->write(toByteArray(response)); + m_socket->close(); // TODO: remove when HTTP pipelining is supported +} + +bool Connection::hasExpired(const qint64 timeout) const +{ + return m_idleTimer.hasExpired(timeout); +} + +bool Connection::isClosed() const +{ + return (m_socket->state() == QAbstractSocket::UnconnectedState); } -bool Connection::acceptsGzipEncoding(const QString &encoding) +bool Connection::acceptsGzipEncoding(QString codings) { - QRegExp rx("(gzip)(;q=([^,]+))?"); - if (rx.indexIn(encoding) >= 0) { - if (rx.cap(2).size() > 0) - // check if quality factor > 0 - return (rx.cap(3).toDouble() > 0); - // if quality factor is not specified, then it's 1 + // [rfc7231] 5.3.4. Accept-Encoding + + const auto isCodingAvailable = [](const QStringList &list, const QString &encoding) -> bool + { + foreach (const QString &str, list) { + if (!str.startsWith(encoding)) + continue; + + // without quality values + if (str == encoding) + return true; + + // [rfc7231] 5.3.1. Quality Values + const QStringRef substr = str.midRef(encoding.size() + 3); // ex. skip over "gzip;q=" + + bool ok = false; + const double qvalue = substr.toDouble(&ok); + if (!ok || (qvalue <= 0.0)) + return false; + + return true; + } + return false; + }; + + const QStringList list = codings.remove(' ').remove('\t').split(',', QString::SkipEmptyParts); + if (list.isEmpty()) + return false; + + const bool canGzip = isCodingAvailable(list, QLatin1String("gzip")); + if (canGzip) return true; - } + + const bool canAny = isCodingAvailable(list, QLatin1String("*")); + if (canAny) + return true; + return false; } diff --git a/src/base/http/connection.h b/src/base/http/connection.h index 95498b1e6..81ec3ab80 100644 --- a/src/base/http/connection.h +++ b/src/base/http/connection.h @@ -33,12 +33,12 @@ #ifndef HTTP_CONNECTION_H #define HTTP_CONNECTION_H +#include #include + #include "types.h" -QT_BEGIN_NAMESPACE class QTcpSocket; -QT_END_NAMESPACE namespace Http { @@ -53,16 +53,20 @@ namespace Http Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0); ~Connection(); + bool hasExpired(qint64 timeout) const; + bool isClosed() const; + private slots: void read(); private: - static bool acceptsGzipEncoding(const QString &encoding); + static bool acceptsGzipEncoding(QString codings); void sendResponse(const Response &response); QTcpSocket *m_socket; IRequestHandler *m_requestHandler; QByteArray m_receivedData; + QElapsedTimer m_idleTimer; }; } diff --git a/src/base/http/responsegenerator.cpp b/src/base/http/responsegenerator.cpp index 2e070ee86..4ac6cba61 100644 --- a/src/base/http/responsegenerator.cpp +++ b/src/base/http/responsegenerator.cpp @@ -29,39 +29,79 @@ * Contact : chris@qbittorrent.org */ -#include "base/utils/gzip.h" #include "responsegenerator.h" -using namespace Http; +#include + +#include "base/utils/gzip.h" -QByteArray ResponseGenerator::generate(Response response) +QByteArray Http::toByteArray(Response response) { - if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") { - // A gzip seems to have 23 bytes overhead. - // Also "Content-Encoding: gzip\r\n" is 26 bytes long - // So we only benefit from gzip if the message is bigger than 23+26 = 49 - // If the message is smaller than 49 bytes we actually send MORE data if we gzip - QByteArray dest_buf; - if ((response.content.size() > 49) && (Utils::Gzip::compress(response.content, dest_buf))) - response.content = dest_buf; - else - response.headers.remove(HEADER_CONTENT_ENCODING); - } - - if (response.content.length() > 0) - response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length()); - - QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n")); - - QString header; - foreach (const QString& key, response.headers.keys()) - header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]); - - ret = ret.arg(response.status.code).arg(response.status.text).arg(header); - - // qDebug() << Q_FUNC_INFO; - // qDebug() << "HTTP Response header:"; - // qDebug() << ret; - - return ret.toUtf8() + response.content; + compressContent(response); + + response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length()); + response.headers[HEADER_DATE] = httpDate(); + + QByteArray buf; + buf.reserve(10 * 1024); + + // Status Line + buf += QString("HTTP/%1 %2 %3") + .arg("1.1", // TODO: depends on request + QString::number(response.status.code), + response.status.text) + .toLatin1() + .append(CRLF); + + // Header Fields + for (auto i = response.headers.constBegin(); i != response.headers.constEnd(); ++i) + buf += QString("%1: %2").arg(i.key(), i.value()).toLatin1().append(CRLF); + + // the first empty line + buf += CRLF; + + // message body // TODO: support HEAD request + buf += response.content; + + return buf; +} + +QString Http::httpDate() +{ + // [RFC 7231] 7.1.1.1. Date/Time Formats + // example: "Sun, 06 Nov 1994 08:49:37 GMT" + + return QLocale::c().toString(QDateTime::currentDateTimeUtc(), QLatin1String("ddd, dd MMM yyyy HH:mm:ss")) + .append(QLatin1String(" GMT")); +} + +void Http::compressContent(Response &response) +{ + if (response.headers.value(HEADER_CONTENT_ENCODING) != QLatin1String("gzip")) + return; + + response.headers.remove(HEADER_CONTENT_ENCODING); + + // for very small files, compressing them only wastes cpu cycles + const int contentSize = response.content.size(); + if (contentSize <= 1024) // 1 kb + return; + + // filter out known hard-to-compress types + const QString contentType = response.headers[HEADER_CONTENT_TYPE]; + if ((contentType == CONTENT_TYPE_GIF) || (contentType == CONTENT_TYPE_PNG)) + return; + + // try compressing + bool ok = false; + const QByteArray compressedData = Utils::Gzip::compress(response.content, 6, &ok); + if (!ok) + return; + + // "Content-Encoding: gzip\r\n" is 24 bytes long + if ((compressedData.size() + 24) >= contentSize) + return; + + response.content = compressedData; + response.headers[HEADER_CONTENT_ENCODING] = QLatin1String("gzip"); } diff --git a/src/base/http/responsegenerator.h b/src/base/http/responsegenerator.h index e12b5c344..7c714e050 100644 --- a/src/base/http/responsegenerator.h +++ b/src/base/http/responsegenerator.h @@ -37,11 +37,9 @@ namespace Http { - class ResponseGenerator - { - public: - static QByteArray generate(Response response); - }; + QByteArray toByteArray(Response response); + QString httpDate(); + void compressContent(Response &response); } #endif // HTTP_RESPONSEGENERATOR_H diff --git a/src/base/http/server.cpp b/src/base/http/server.cpp index 35be36d98..b3cd02168 100644 --- a/src/base/http/server.cpp +++ b/src/base/http/server.cpp @@ -30,8 +30,10 @@ #include "server.h" +#include #include #include +#include #ifndef QT_NO_OPENSSL #include @@ -41,6 +43,10 @@ #include "connection.h" +static const int KEEP_ALIVE_DURATION = 7; // seconds +static const int CONNECTIONS_LIMIT = 500; +static const int CONNECTIONS_SCAN_INTERVAL = 2; // seconds + using namespace Http; Server::Server(IRequestHandler *requestHandler, QObject *parent) @@ -54,6 +60,10 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent) #ifndef QT_NO_OPENSSL QSslSocket::setDefaultCiphers(safeCipherList()); #endif + + QTimer *dropConnectionTimer = new QTimer(this); + connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection); + dropConnectionTimer->start(CONNECTIONS_SCAN_INTERVAL * 1000); } Server::~Server() @@ -62,6 +72,8 @@ Server::~Server() void Server::incomingConnection(qintptr socketDescriptor) { + if (m_connections.size() >= CONNECTIONS_LIMIT) return; + QTcpSocket *serverSocket; #ifndef QT_NO_OPENSSL if (m_https) @@ -70,20 +82,34 @@ void Server::incomingConnection(qintptr socketDescriptor) #endif serverSocket = new QTcpSocket(this); - if (serverSocket->setSocketDescriptor(socketDescriptor)) { + if (!serverSocket->setSocketDescriptor(socketDescriptor)) { + delete serverSocket; + return; + } + #ifndef QT_NO_OPENSSL - if (m_https) { - static_cast(serverSocket)->setProtocol(QSsl::SecureProtocols); - static_cast(serverSocket)->setPrivateKey(m_key); - static_cast(serverSocket)->setLocalCertificateChain(m_certificates); - static_cast(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone); - static_cast(serverSocket)->startServerEncryption(); - } -#endif - new Connection(serverSocket, m_requestHandler, this); + if (m_https) { + static_cast(serverSocket)->setProtocol(QSsl::SecureProtocols); + static_cast(serverSocket)->setPrivateKey(m_key); + static_cast(serverSocket)->setLocalCertificateChain(m_certificates); + static_cast(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone); + static_cast(serverSocket)->startServerEncryption(); } - else { - serverSocket->deleteLater(); +#endif + + Connection *c = new Connection(serverSocket, m_requestHandler, this); + m_connections.append(c); +} + +void Server::dropTimedOutConnection() +{ + QMutableListIterator i(m_connections); + while (i.hasNext()) { + auto connection = i.next(); + if (connection->isClosed() || connection->hasExpired(KEEP_ALIVE_DURATION)) { + delete connection; + i.remove(); + } } } diff --git a/src/base/http/server.h b/src/base/http/server.h index 31f33b735..d9b9ae19e 100644 --- a/src/base/http/server.h +++ b/src/base/http/server.h @@ -60,10 +60,14 @@ namespace Http void disableHttps(); #endif + private slots: + void dropTimedOutConnection(); + private: void incomingConnection(qintptr socketDescriptor); IRequestHandler *m_requestHandler; + QList m_connections; // for tracking persistence connections #ifndef QT_NO_OPENSSL QList safeCipherList() const; diff --git a/src/base/http/types.h b/src/base/http/types.h index a8dc9a898..1fb52fd32 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -29,32 +29,35 @@ #ifndef HTTP_TYPES_H #define HTTP_TYPES_H -#include -#include #include +#include #include #include "base/types.h" namespace Http { - const QString HEADER_SET_COOKIE = "Set-Cookie"; - const QString HEADER_CONTENT_TYPE = "Content-Type"; - const QString HEADER_CONTENT_ENCODING = "Content-Encoding"; - const QString HEADER_CONTENT_LENGTH = "Content-Length"; - const QString HEADER_CACHE_CONTROL = "Cache-Control"; - const QString HEADER_X_FRAME_OPTIONS = "X-Frame-Options"; - const QString HEADER_X_XSS_PROTECTION = "X-XSS-Protection"; - const QString HEADER_X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; - const QString HEADER_CONTENT_SECURITY_POLICY = "Content-Security-Policy"; + const char HEADER_CACHE_CONTROL[] = "Cache-Control"; + const char HEADER_CONTENT_ENCODING[] = "Content-Encoding"; + const char HEADER_CONTENT_LENGTH[] = "Content-Length"; + const char HEADER_CONTENT_SECURITY_POLICY[] = "Content-Security-Policy"; + const char HEADER_CONTENT_TYPE[] = "Content-Type"; + const char HEADER_DATE[] = "Date"; + const char HEADER_SET_COOKIE[] = "Set-Cookie"; + const char HEADER_X_CONTENT_TYPE_OPTIONS[] = "X-Content-Type-Options"; + const char HEADER_X_FRAME_OPTIONS[] = "X-Frame-Options"; + const char HEADER_X_XSS_PROTECTION[] = "X-XSS-Protection"; + + const char CONTENT_TYPE_CSS[] = "text/css; charset=UTF-8"; + const char CONTENT_TYPE_GIF[] = "image/gif"; + const char CONTENT_TYPE_HTML[] = "text/html; charset=UTF-8"; + const char CONTENT_TYPE_JS[] = "application/javascript; charset=UTF-8"; + const char CONTENT_TYPE_JSON[] = "application/json"; + const char CONTENT_TYPE_PNG[] = "image/png"; + const char CONTENT_TYPE_TXT[] = "text/plain; charset=UTF-8"; - const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8"; - const QString CONTENT_TYPE_GIF = "image/gif"; - const QString CONTENT_TYPE_HTML = "text/html; charset=UTF-8"; - const QString CONTENT_TYPE_JS = "application/javascript; charset=UTF-8"; - const QString CONTENT_TYPE_JSON = "application/json"; - const QString CONTENT_TYPE_PNG = "image/png"; - const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8"; + // portability: "\r\n" doesn't guarantee mapping to the correct value + const char CRLF[] = {0x0D, 0x0A, '\0'}; struct Environment { diff --git a/src/base/net/downloadhandler.cpp b/src/base/net/downloadhandler.cpp index 13142775c..96a5068bf 100644 --- a/src/base/net/downloadhandler.cpp +++ b/src/base/net/downloadhandler.cpp @@ -92,8 +92,8 @@ void DownloadHandler::processFinishedDownload() // Success QByteArray replyData = m_reply->readAll(); if (m_reply->rawHeader("Content-Encoding") == "gzip") { - // uncompress gzip reply - Utils::Gzip::uncompress(replyData, replyData); + // decompress gzip reply + replyData = Utils::Gzip::decompress(replyData); } if (m_saveToFile) { diff --git a/src/base/net/geoipmanager.cpp b/src/base/net/geoipmanager.cpp index 19c74455d..efa150de9 100644 --- a/src/base/net/geoipmanager.cpp +++ b/src/base/net/geoipmanager.cpp @@ -416,8 +416,10 @@ void GeoIPManager::downloadFinished(const QString &url, QByteArray data) { Q_UNUSED(url); - if (!Utils::Gzip::uncompress(data, data)) { - Logger::instance()->addMessage(tr("Could not uncompress GeoIP database file."), Log::WARNING); + bool ok = false; + data = Utils::Gzip::decompress(data, &ok); + if (!ok) { + Logger::instance()->addMessage(tr("Could not decompress GeoIP database file."), Log::WARNING); return; } diff --git a/src/base/utils/gzip.cpp b/src/base/utils/gzip.cpp index 89dc16500..a3ec5d8de 100644 --- a/src/base/utils/gzip.cpp +++ b/src/base/utils/gzip.cpp @@ -27,116 +27,123 @@ * exception statement from your version. */ +#include "gzip.h" + #include -#include -#include "gzip.h" +#ifndef ZLIB_CONST +#define ZLIB_CONST // make z_stream.next_in const +#endif +#include -bool Utils::Gzip::compress(QByteArray src, QByteArray &dest) +QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *ok) { - static const int BUFSIZE = 128 * 1024; - char tmpBuf[BUFSIZE]; - int ret; + if (ok) *ok = false; + + if (data.isEmpty()) + return {}; - dest.clear(); + const int BUFSIZE = 128 * 1024; + char tmpBuf[BUFSIZE] = {0}; z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; - strm.next_in = reinterpret_cast(src.data()); - strm.avail_in = src.length(); - strm.next_out = reinterpret_cast(tmpBuf); + strm.next_in = reinterpret_cast(data.constData()); + strm.avail_in = uInt(data.size()); + strm.next_out = reinterpret_cast(tmpBuf); strm.avail_out = BUFSIZE; // windowBits = 15 + 16 to enable gzip // From the zlib manual: windowBits can also be greater than 15 for optional gzip encoding. Add 16 to windowBits // to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper. - ret = deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); + int result = deflateInit2(&strm, level, Z_DEFLATED, (15 + 16), 9, Z_DEFAULT_STRATEGY); + if (result != Z_OK) + return {}; - if (ret != Z_OK) - return false; + QByteArray output; + output.reserve(deflateBound(&strm, data.size())); - while (strm.avail_in != 0) { - ret = deflate(&strm, Z_NO_FLUSH); - if (ret != Z_OK) - return false; + // feed to deflate + while (strm.avail_in > 0) { + result = deflate(&strm, Z_NO_FLUSH); - if (strm.avail_out == 0) { - dest.append(tmpBuf, BUFSIZE); - strm.next_out = reinterpret_cast(tmpBuf); - strm.avail_out = BUFSIZE; + if (result != Z_OK) { + deflateEnd(&strm); + return {}; } + + output.append(tmpBuf, (BUFSIZE - strm.avail_out)); + strm.next_out = reinterpret_cast(tmpBuf); + strm.avail_out = BUFSIZE; } - int deflateRes = Z_OK; - while (deflateRes == Z_OK) { - if (strm.avail_out == 0) { - dest.append(tmpBuf, BUFSIZE); - strm.next_out = reinterpret_cast(tmpBuf); - strm.avail_out = BUFSIZE; - } + // flush the rest from deflate + while (result != Z_STREAM_END) { + result = deflate(&strm, Z_FINISH); - deflateRes = deflate(&strm, Z_FINISH); + output.append(tmpBuf, (BUFSIZE - strm.avail_out)); + strm.next_out = reinterpret_cast(tmpBuf); + strm.avail_out = BUFSIZE; } - if (deflateRes != Z_STREAM_END) - return false; - - dest.append(tmpBuf, BUFSIZE - strm.avail_out); deflateEnd(&strm); - return true; + if (ok) *ok = true; + return output; } -bool Utils::Gzip::uncompress(QByteArray src, QByteArray &dest) +QByteArray Utils::Gzip::decompress(const QByteArray &data, bool *ok) { - dest.clear(); + if (ok) *ok = false; - if (src.size() <= 4) { - qWarning("uncompress: Input data is truncated"); - return false; - } + if (data.isEmpty()) + return {}; - z_stream strm; - static const int CHUNK_SIZE = 1024; - char out[CHUNK_SIZE]; + const int BUFSIZE = 1024 * 1024; + char tmpBuf[BUFSIZE] = {0}; - /* allocate inflate state */ + z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; - strm.avail_in = static_cast(src.size()); - strm.next_in = reinterpret_cast(src.data()); + strm.next_in = reinterpret_cast(data.constData()); + strm.avail_in = uInt(data.size()); + strm.next_out = reinterpret_cast(tmpBuf); + strm.avail_out = BUFSIZE; - const int windowBits = 15; - const int ENABLE_ZLIB_GZIP = 32; + // windowBits must be greater than or equal to the windowBits value provided to deflateInit2() while compressing + // Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection + int result = inflateInit2(&strm, (15 + 32)); + if (result != Z_OK) + return {}; - int ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP); // gzip decoding - if (ret != Z_OK) - return false; + QByteArray output; + // from lzbench, level 9 average compression ratio is: 31.92%, which decompression ratio is: 1 / 0.3192 = 3.13 + output.reserve(data.size() * 3); - // run inflate() - do { - strm.avail_out = CHUNK_SIZE; - strm.next_out = reinterpret_cast(out); + // run inflate + while (true) { + result = inflate(&strm, Z_NO_FLUSH); - ret = inflate(&strm, Z_NO_FLUSH); - Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered + if (result == Z_STREAM_END) { + output.append(tmpBuf, (BUFSIZE - strm.avail_out)); + break; + } - switch (ret) { - case Z_NEED_DICT: - case Z_DATA_ERROR: - case Z_MEM_ERROR: + if (result != Z_OK) { inflateEnd(&strm); - return false; + return {}; } - dest.append(out, CHUNK_SIZE - strm.avail_out); + output.append(tmpBuf, (BUFSIZE - strm.avail_out)); + strm.next_out = reinterpret_cast(tmpBuf); + strm.avail_out = BUFSIZE; } - while (!strm.avail_out); - // clean up and return inflateEnd(&strm); - return true; + + if (ok) *ok = true; + return output; } diff --git a/src/base/utils/gzip.h b/src/base/utils/gzip.h index 29dee59c6..4954a4023 100644 --- a/src/base/utils/gzip.h +++ b/src/base/utils/gzip.h @@ -36,8 +36,8 @@ namespace Utils { namespace Gzip { - bool compress(QByteArray src, QByteArray &dest); - bool uncompress(QByteArray src, QByteArray &dest); + QByteArray compress(const QByteArray &data, int level = 6, bool *ok = nullptr); + QByteArray decompress(const QByteArray &data, bool *ok = nullptr); } }