mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-20 21:33:27 -07:00
commit
0a5bb6685f
15 changed files with 349 additions and 223 deletions
96
.travis.yml
96
.travis.yml
|
@ -33,16 +33,15 @@ notifications:
|
||||||
on_success: change
|
on_success: change
|
||||||
on_failure: change
|
on_failure: change
|
||||||
|
|
||||||
# container-based builds
|
|
||||||
#sudo: false
|
|
||||||
cache:
|
cache:
|
||||||
ccache: true
|
ccache: true
|
||||||
directories:
|
directories:
|
||||||
- $HOME/hombebrew_cache
|
- $HOME/hombebrew_cache
|
||||||
|
|
||||||
# opt-in Ubuntu Trusty
|
# opt-in Ubuntu Trusty
|
||||||
sudo: required
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
|
# container-based builds
|
||||||
|
sudo: false
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
coverity_scan:
|
coverity_scan:
|
||||||
|
@ -74,6 +73,7 @@ addons:
|
||||||
# Qt 5.5.1
|
# Qt 5.5.1
|
||||||
- qt55base
|
- qt55base
|
||||||
- qt55tools
|
- qt55tools
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
# only allow specific build for coverity scan, others will stop
|
# 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
|
- 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 $build_system
|
||||||
- echo $ltconf
|
- echo $ltconf
|
||||||
- echo $qbtconf
|
- echo $qbtconf
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- |
|
#- |
|
||||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
#if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||||
# build libtorrent from source
|
# build libtorrent from source
|
||||||
#if [ "$lt_branch" != "dist" ]; then
|
#if [ "$lt_branch" != "dist" ]; then
|
||||||
#cd "$HOME" && pwd && git clone --depth 1 https://github.com/arvidn/libtorrent.git --branch $lt_branch ;
|
#cd "$HOME" && pwd && git clone --depth 1 https://github.com/arvidn/libtorrent.git --branch $lt_branch
|
||||||
#cd libtorrent && ./autotool.sh && ./configure $ltconf && make install ;
|
#cd libtorrent && ./autotool.sh && ./configure $ltconf && make install
|
||||||
#fi ;
|
#fi
|
||||||
|
#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
|
|
||||||
- |
|
- |
|
||||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
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
|
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
|
# First delete old files
|
||||||
rm -r "$HOME/hombebrew_cache" ;
|
rm -r "$HOME/hombebrew_cache"
|
||||||
mkdir "$HOME/hombebrew_cache";
|
mkdir "$HOME/hombebrew_cache"
|
||||||
cp "version" $HOME/hombebrew_cache ;
|
cp "version" $HOME/hombebrew_cache
|
||||||
cd "$HOME/hombebrew_cache" ;
|
cd "$HOME/hombebrew_cache"
|
||||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb ;
|
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/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.rb
|
||||||
wget https://builds.shiki.hu/homebrew/qt5-5.7.1_1.el_capitan.bottle.tar.gz ;
|
wget https://builds.shiki.hu/homebrew/qt5-5.7.1_1.el_capitan.bottle.tar.gz
|
||||||
fi
|
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
|
# 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
|
# Also install our custom libtorrent formula by passing the local path to it
|
||||||
# These 2 files are restored from Travis' cache.
|
# These 2 files are restored from Travis' cache.
|
||||||
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz" "$(brew --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" ;
|
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||||
|
|
||||||
if [ "$build_system" = "cmake" ]; then
|
if [ "$build_system" = "cmake" ]; then
|
||||||
brew install qt5 ;
|
brew unlink cmake
|
||||||
brew link --force qt5 ;
|
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/mkspecs /usr/local/mkspecs
|
||||||
ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
||||||
else
|
else
|
||||||
|
@ -157,18 +154,17 @@ install:
|
||||||
# Copy custom qt5 bottle to homebrew's cache so it can find and install it
|
# 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
|
# Also install our custom qt5 formula by passing the local path to it
|
||||||
# These 2 files are restored from Travis' cache.
|
# These 2 files are restored from Travis' cache.
|
||||||
cp "$HOME/hombebrew_cache/qt5-5.7.1_1.el_capitan.bottle.tar.gz" "$(brew --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 install "$HOME/hombebrew_cache/qt5.rb"
|
||||||
brew link --force qt5 ;
|
brew link --force qt5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
|
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
|
||||||
|
fi
|
||||||
# ccache
|
- |
|
||||||
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
|
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
|
||||||
export PATH="/usr/local/opt/ccache/libexec:$PATH" && export use_ccache=true ;
|
export use_ccache=true
|
||||||
ccache -V && ccache --show-stats && ccache --zero-stats ;
|
ccache -V && ccache --show-stats && ccache --zero-stats
|
||||||
fi ;
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
@ -184,9 +180,9 @@ script:
|
||||||
if [ "$build_system" = "qmake" ]; then
|
if [ "$build_system" = "qmake" ]; then
|
||||||
./bootstrap.sh && ./configure $qbtconf
|
./bootstrap.sh && ./configure $qbtconf
|
||||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
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/^\(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/^\(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/^\(CXXFLAGS.*\)$/\1 -Wno-unused-local-typedefs -Wno-inconsistent-missing-override/' src/Makefile
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
- make && make install
|
- make && make install
|
||||||
|
@ -196,8 +192,8 @@ after_success:
|
||||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cd "$qbt_path/bin" ; fi
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cd "$qbt_path/bin" ; fi
|
||||||
- |
|
- |
|
||||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||||
macdeployqt "$TRAVIS_BUILD_DIR/src/$qbt_exe.app" ;
|
macdeployqt "$TRAVIS_BUILD_DIR/src/$qbt_exe.app"
|
||||||
cd "$TRAVIS_BUILD_DIR/src/$qbt_exe.app/Contents/MacOS" ;
|
cd "$TRAVIS_BUILD_DIR/src/$qbt_exe.app/Contents/MacOS"
|
||||||
fi
|
fi
|
||||||
- ./$qbt_exe --version
|
- ./$qbt_exe --version
|
||||||
|
|
||||||
|
|
18
configure
vendored
18
configure
vendored
|
@ -5188,12 +5188,12 @@ if test -n "$zlib_CFLAGS"; then
|
||||||
pkg_cv_zlib_CFLAGS="$zlib_CFLAGS"
|
pkg_cv_zlib_CFLAGS="$zlib_CFLAGS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib\""; } >&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") 2>&5
|
($PKG_CONFIG --exists --print-errors "zlib >= 1.2.5.2") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
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
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
|
@ -5205,12 +5205,12 @@ if test -n "$zlib_LIBS"; then
|
||||||
pkg_cv_zlib_LIBS="$zlib_LIBS"
|
pkg_cv_zlib_LIBS="$zlib_LIBS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"zlib\""; } >&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") 2>&5
|
($PKG_CONFIG --exists --print-errors "zlib >= 1.2.5.2") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
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
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
|
@ -5231,14 +5231,14 @@ else
|
||||||
_pkg_short_errors_supported=no
|
_pkg_short_errors_supported=no
|
||||||
fi
|
fi
|
||||||
if test $_pkg_short_errors_supported = yes; then
|
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
|
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
|
fi
|
||||||
# Put the nasty error message in config.log where it belongs
|
# Put the nasty error message in config.log where it belongs
|
||||||
echo "$zlib_PKG_ERRORS" >&5
|
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
|
$zlib_PKG_ERRORS
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ PKG_CHECK_MODULES(libtorrent,
|
||||||
LIBS="$libtorrent_LIBS $LIBS"])
|
LIBS="$libtorrent_LIBS $LIBS"])
|
||||||
|
|
||||||
PKG_CHECK_MODULES(zlib,
|
PKG_CHECK_MODULES(zlib,
|
||||||
[zlib],
|
[zlib >= 1.2.5.2],
|
||||||
[CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
|
[CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
|
||||||
LIBS="$zlib_LIBS $LIBS"])
|
LIBS="$zlib_LIBS $LIBS"])
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB 1.2.5.2 REQUIRED)
|
||||||
|
|
||||||
set(QBT_BASE_HEADERS
|
set(QBT_BASE_HEADERS
|
||||||
bittorrent/addtorrentparams.h
|
bittorrent/addtorrentparams.h
|
||||||
|
|
|
@ -29,14 +29,14 @@
|
||||||
* Contact : chris@qbittorrent.org
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QTcpSocket>
|
#include "connection.h"
|
||||||
#include <QDebug>
|
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include "types.h"
|
#include <QTcpSocket>
|
||||||
|
|
||||||
|
#include "irequesthandler.h"
|
||||||
#include "requestparser.h"
|
#include "requestparser.h"
|
||||||
#include "responsegenerator.h"
|
#include "responsegenerator.h"
|
||||||
#include "irequesthandler.h"
|
|
||||||
#include "connection.h"
|
|
||||||
|
|
||||||
using namespace Http;
|
using namespace Http;
|
||||||
|
|
||||||
|
@ -46,27 +46,33 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||||
, m_requestHandler(requestHandler)
|
, m_requestHandler(requestHandler)
|
||||||
{
|
{
|
||||||
m_socket->setParent(this);
|
m_socket->setParent(this);
|
||||||
|
m_idleTimer.start();
|
||||||
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
connect(m_socket, SIGNAL(readyRead()), SLOT(read()));
|
||||||
connect(m_socket, SIGNAL(disconnected()), SLOT(deleteLater()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection::~Connection()
|
Connection::~Connection()
|
||||||
{
|
{
|
||||||
|
m_socket->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::read()
|
void Connection::read()
|
||||||
{
|
{
|
||||||
m_receivedData.append(m_socket->readAll());
|
m_idleTimer.restart();
|
||||||
|
|
||||||
|
m_receivedData.append(m_socket->readAll());
|
||||||
Request request;
|
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) {
|
switch (err) {
|
||||||
case RequestParser::IncompleteRequest:
|
case RequestParser::IncompleteRequest:
|
||||||
// Partial request waiting for the rest
|
// Partial request waiting for the rest
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RequestParser::BadRequest:
|
case RequestParser::BadRequest:
|
||||||
sendResponse(Response(400, "Bad Request"));
|
sendResponse(Response(400, "Bad Request"));
|
||||||
|
m_receivedData.clear();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RequestParser::NoError:
|
case RequestParser::NoError:
|
||||||
Environment env;
|
Environment env;
|
||||||
env.clientAddress = m_socket->peerAddress();
|
env.clientAddress = m_socket->peerAddress();
|
||||||
|
@ -74,25 +80,65 @@ void Connection::read()
|
||||||
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
if (acceptsGzipEncoding(request.headers["accept-encoding"]))
|
||||||
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
response.headers[HEADER_CONTENT_ENCODING] = "gzip";
|
||||||
sendResponse(response);
|
sendResponse(response);
|
||||||
|
m_receivedData.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::sendResponse(const Response &response)
|
void Connection::sendResponse(const Response &response)
|
||||||
{
|
{
|
||||||
m_socket->write(ResponseGenerator::generate(response));
|
m_socket->write(toByteArray(response));
|
||||||
m_socket->disconnectFromHost();
|
m_socket->close(); // TODO: remove when HTTP pipelining is supported
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Connection::acceptsGzipEncoding(const QString &encoding)
|
bool Connection::hasExpired(const qint64 timeout) const
|
||||||
{
|
{
|
||||||
QRegExp rx("(gzip)(;q=([^,]+))?");
|
return m_idleTimer.hasExpired(timeout);
|
||||||
if (rx.indexIn(encoding) >= 0) {
|
}
|
||||||
if (rx.cap(2).size() > 0)
|
|
||||||
// check if quality factor > 0
|
bool Connection::isClosed() const
|
||||||
return (rx.cap(3).toDouble() > 0);
|
{
|
||||||
// if quality factor is not specified, then it's 1
|
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Connection::acceptsGzipEncoding(QString codings)
|
||||||
|
{
|
||||||
|
// [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 true;
|
||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,12 @@
|
||||||
#ifndef HTTP_CONNECTION_H
|
#ifndef HTTP_CONNECTION_H
|
||||||
#define HTTP_CONNECTION_H
|
#define HTTP_CONNECTION_H
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
QT_END_NAMESPACE
|
|
||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
|
@ -53,16 +53,20 @@ namespace Http
|
||||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = 0);
|
||||||
~Connection();
|
~Connection();
|
||||||
|
|
||||||
|
bool hasExpired(qint64 timeout) const;
|
||||||
|
bool isClosed() const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void read();
|
void read();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool acceptsGzipEncoding(const QString &encoding);
|
static bool acceptsGzipEncoding(QString codings);
|
||||||
void sendResponse(const Response &response);
|
void sendResponse(const Response &response);
|
||||||
|
|
||||||
QTcpSocket *m_socket;
|
QTcpSocket *m_socket;
|
||||||
IRequestHandler *m_requestHandler;
|
IRequestHandler *m_requestHandler;
|
||||||
QByteArray m_receivedData;
|
QByteArray m_receivedData;
|
||||||
|
QElapsedTimer m_idleTimer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,39 +29,79 @@
|
||||||
* Contact : chris@qbittorrent.org
|
* Contact : chris@qbittorrent.org
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "base/utils/gzip.h"
|
|
||||||
#include "responsegenerator.h"
|
#include "responsegenerator.h"
|
||||||
|
|
||||||
using namespace Http;
|
#include <QDateTime>
|
||||||
|
|
||||||
QByteArray ResponseGenerator::generate(Response response)
|
#include "base/utils/gzip.h"
|
||||||
|
|
||||||
|
QByteArray Http::toByteArray(Response response)
|
||||||
{
|
{
|
||||||
if (response.headers[HEADER_CONTENT_ENCODING] == "gzip") {
|
compressContent(response);
|
||||||
// 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());
|
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
|
||||||
|
response.headers[HEADER_DATE] = httpDate();
|
||||||
|
|
||||||
QString ret(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
|
QByteArray buf;
|
||||||
|
buf.reserve(10 * 1024);
|
||||||
|
|
||||||
QString header;
|
// Status Line
|
||||||
foreach (const QString& key, response.headers.keys())
|
buf += QString("HTTP/%1 %2 %3")
|
||||||
header += QString("%1: %2\r\n").arg(key).arg(response.headers[key]);
|
.arg("1.1", // TODO: depends on request
|
||||||
|
QString::number(response.status.code),
|
||||||
|
response.status.text)
|
||||||
|
.toLatin1()
|
||||||
|
.append(CRLF);
|
||||||
|
|
||||||
ret = ret.arg(response.status.code).arg(response.status.text).arg(header);
|
// 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);
|
||||||
|
|
||||||
// qDebug() << Q_FUNC_INFO;
|
// the first empty line
|
||||||
// qDebug() << "HTTP Response header:";
|
buf += CRLF;
|
||||||
// qDebug() << ret;
|
|
||||||
|
|
||||||
return ret.toUtf8() + response.content;
|
// 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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,11 +37,9 @@
|
||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
class ResponseGenerator
|
QByteArray toByteArray(Response response);
|
||||||
{
|
QString httpDate();
|
||||||
public:
|
void compressContent(Response &response);
|
||||||
static QByteArray generate(Response response);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HTTP_RESPONSEGENERATOR_H
|
#endif // HTTP_RESPONSEGENERATOR_H
|
||||||
|
|
|
@ -30,8 +30,10 @@
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
|
#include <QMutableListIterator>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
|
@ -41,6 +43,10 @@
|
||||||
|
|
||||||
#include "connection.h"
|
#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;
|
using namespace Http;
|
||||||
|
|
||||||
Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||||
|
@ -54,6 +60,10 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
QSslSocket::setDefaultCiphers(safeCipherList());
|
QSslSocket::setDefaultCiphers(safeCipherList());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
QTimer *dropConnectionTimer = new QTimer(this);
|
||||||
|
connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection);
|
||||||
|
dropConnectionTimer->start(CONNECTIONS_SCAN_INTERVAL * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Server::~Server()
|
Server::~Server()
|
||||||
|
@ -62,6 +72,8 @@ Server::~Server()
|
||||||
|
|
||||||
void Server::incomingConnection(qintptr socketDescriptor)
|
void Server::incomingConnection(qintptr socketDescriptor)
|
||||||
{
|
{
|
||||||
|
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
||||||
|
|
||||||
QTcpSocket *serverSocket;
|
QTcpSocket *serverSocket;
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
if (m_https)
|
if (m_https)
|
||||||
|
@ -70,7 +82,11 @@ void Server::incomingConnection(qintptr socketDescriptor)
|
||||||
#endif
|
#endif
|
||||||
serverSocket = new QTcpSocket(this);
|
serverSocket = new QTcpSocket(this);
|
||||||
|
|
||||||
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
|
if (!serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||||
|
delete serverSocket;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
if (m_https) {
|
if (m_https) {
|
||||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||||
|
@ -80,10 +96,20 @@ void Server::incomingConnection(qintptr socketDescriptor)
|
||||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
new Connection(serverSocket, m_requestHandler, this);
|
|
||||||
|
Connection *c = new Connection(serverSocket, m_requestHandler, this);
|
||||||
|
m_connections.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::dropTimedOutConnection()
|
||||||
|
{
|
||||||
|
QMutableListIterator<Connection *> i(m_connections);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
auto connection = i.next();
|
||||||
|
if (connection->isClosed() || connection->hasExpired(KEEP_ALIVE_DURATION)) {
|
||||||
|
delete connection;
|
||||||
|
i.remove();
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
serverSocket->deleteLater();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,10 +60,14 @@ namespace Http
|
||||||
void disableHttps();
|
void disableHttps();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void dropTimedOutConnection();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void incomingConnection(qintptr socketDescriptor);
|
void incomingConnection(qintptr socketDescriptor);
|
||||||
|
|
||||||
IRequestHandler *m_requestHandler;
|
IRequestHandler *m_requestHandler;
|
||||||
|
QList<Connection *> m_connections; // for tracking persistence connections
|
||||||
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
QList<QSslCipher> safeCipherList() const;
|
QList<QSslCipher> safeCipherList() const;
|
||||||
|
|
|
@ -29,32 +29,35 @@
|
||||||
#ifndef HTTP_TYPES_H
|
#ifndef HTTP_TYPES_H
|
||||||
#define HTTP_TYPES_H
|
#define HTTP_TYPES_H
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QMap>
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
#include "base/types.h"
|
#include "base/types.h"
|
||||||
|
|
||||||
namespace Http
|
namespace Http
|
||||||
{
|
{
|
||||||
const QString HEADER_SET_COOKIE = "Set-Cookie";
|
const char HEADER_CACHE_CONTROL[] = "Cache-Control";
|
||||||
const QString HEADER_CONTENT_TYPE = "Content-Type";
|
const char HEADER_CONTENT_ENCODING[] = "Content-Encoding";
|
||||||
const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
|
const char HEADER_CONTENT_LENGTH[] = "Content-Length";
|
||||||
const QString HEADER_CONTENT_LENGTH = "Content-Length";
|
const char HEADER_CONTENT_SECURITY_POLICY[] = "Content-Security-Policy";
|
||||||
const QString HEADER_CACHE_CONTROL = "Cache-Control";
|
const char HEADER_CONTENT_TYPE[] = "Content-Type";
|
||||||
const QString HEADER_X_FRAME_OPTIONS = "X-Frame-Options";
|
const char HEADER_DATE[] = "Date";
|
||||||
const QString HEADER_X_XSS_PROTECTION = "X-XSS-Protection";
|
const char HEADER_SET_COOKIE[] = "Set-Cookie";
|
||||||
const QString HEADER_X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
|
const char HEADER_X_CONTENT_TYPE_OPTIONS[] = "X-Content-Type-Options";
|
||||||
const QString HEADER_CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
const char HEADER_X_FRAME_OPTIONS[] = "X-Frame-Options";
|
||||||
|
const char HEADER_X_XSS_PROTECTION[] = "X-XSS-Protection";
|
||||||
|
|
||||||
const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8";
|
const char CONTENT_TYPE_CSS[] = "text/css; charset=UTF-8";
|
||||||
const QString CONTENT_TYPE_GIF = "image/gif";
|
const char CONTENT_TYPE_GIF[] = "image/gif";
|
||||||
const QString CONTENT_TYPE_HTML = "text/html; charset=UTF-8";
|
const char CONTENT_TYPE_HTML[] = "text/html; charset=UTF-8";
|
||||||
const QString CONTENT_TYPE_JS = "application/javascript; charset=UTF-8";
|
const char CONTENT_TYPE_JS[] = "application/javascript; charset=UTF-8";
|
||||||
const QString CONTENT_TYPE_JSON = "application/json";
|
const char CONTENT_TYPE_JSON[] = "application/json";
|
||||||
const QString CONTENT_TYPE_PNG = "image/png";
|
const char CONTENT_TYPE_PNG[] = "image/png";
|
||||||
const QString CONTENT_TYPE_TXT = "text/plain; charset=UTF-8";
|
const char 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
|
struct Environment
|
||||||
{
|
{
|
||||||
|
|
|
@ -92,8 +92,8 @@ void DownloadHandler::processFinishedDownload()
|
||||||
// Success
|
// Success
|
||||||
QByteArray replyData = m_reply->readAll();
|
QByteArray replyData = m_reply->readAll();
|
||||||
if (m_reply->rawHeader("Content-Encoding") == "gzip") {
|
if (m_reply->rawHeader("Content-Encoding") == "gzip") {
|
||||||
// uncompress gzip reply
|
// decompress gzip reply
|
||||||
Utils::Gzip::uncompress(replyData, replyData);
|
replyData = Utils::Gzip::decompress(replyData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_saveToFile) {
|
if (m_saveToFile) {
|
||||||
|
|
|
@ -416,8 +416,10 @@ void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
|
||||||
{
|
{
|
||||||
Q_UNUSED(url);
|
Q_UNUSED(url);
|
||||||
|
|
||||||
if (!Utils::Gzip::uncompress(data, data)) {
|
bool ok = false;
|
||||||
Logger::instance()->addMessage(tr("Could not uncompress GeoIP database file."), Log::WARNING);
|
data = Utils::Gzip::decompress(data, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
Logger::instance()->addMessage(tr("Could not decompress GeoIP database file."), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,116 +27,123 @@
|
||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
#include "gzip.h"
|
#include "gzip.h"
|
||||||
|
|
||||||
bool Utils::Gzip::compress(QByteArray src, QByteArray &dest)
|
#include <QByteArray>
|
||||||
{
|
|
||||||
static const int BUFSIZE = 128 * 1024;
|
|
||||||
char tmpBuf[BUFSIZE];
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
dest.clear();
|
#ifndef ZLIB_CONST
|
||||||
|
#define ZLIB_CONST // make z_stream.next_in const
|
||||||
|
#endif
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
QByteArray Utils::Gzip::compress(const QByteArray &data, const int level, bool *ok)
|
||||||
|
{
|
||||||
|
if (ok) *ok = false;
|
||||||
|
|
||||||
|
if (data.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const int BUFSIZE = 128 * 1024;
|
||||||
|
char tmpBuf[BUFSIZE] = {0};
|
||||||
|
|
||||||
z_stream strm;
|
z_stream strm;
|
||||||
strm.zalloc = Z_NULL;
|
strm.zalloc = Z_NULL;
|
||||||
strm.zfree = Z_NULL;
|
strm.zfree = Z_NULL;
|
||||||
strm.opaque = Z_NULL;
|
strm.opaque = Z_NULL;
|
||||||
strm.next_in = reinterpret_cast<uchar *>(src.data());
|
strm.next_in = reinterpret_cast<const Bytef *>(data.constData());
|
||||||
strm.avail_in = src.length();
|
strm.avail_in = uInt(data.size());
|
||||||
strm.next_out = reinterpret_cast<uchar *>(tmpBuf);
|
strm.next_out = reinterpret_cast<Bytef *>(tmpBuf);
|
||||||
strm.avail_out = BUFSIZE;
|
strm.avail_out = BUFSIZE;
|
||||||
|
|
||||||
// windowBits = 15 + 16 to enable gzip
|
// 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
|
// 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.
|
// 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)
|
QByteArray output;
|
||||||
return false;
|
output.reserve(deflateBound(&strm, data.size()));
|
||||||
|
|
||||||
while (strm.avail_in != 0) {
|
// feed to deflate
|
||||||
ret = deflate(&strm, Z_NO_FLUSH);
|
while (strm.avail_in > 0) {
|
||||||
if (ret != Z_OK)
|
result = deflate(&strm, Z_NO_FLUSH);
|
||||||
return false;
|
|
||||||
|
|
||||||
if (strm.avail_out == 0) {
|
if (result != Z_OK) {
|
||||||
dest.append(tmpBuf, BUFSIZE);
|
deflateEnd(&strm);
|
||||||
strm.next_out = reinterpret_cast<uchar *>(tmpBuf);
|
return {};
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int deflateRes = Z_OK;
|
output.append(tmpBuf, (BUFSIZE - strm.avail_out));
|
||||||
while (deflateRes == Z_OK) {
|
strm.next_out = reinterpret_cast<Bytef *>(tmpBuf);
|
||||||
if (strm.avail_out == 0) {
|
|
||||||
dest.append(tmpBuf, BUFSIZE);
|
|
||||||
strm.next_out = reinterpret_cast<uchar *>(tmpBuf);
|
|
||||||
strm.avail_out = BUFSIZE;
|
strm.avail_out = BUFSIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
deflateRes = deflate(&strm, Z_FINISH);
|
// flush the rest from deflate
|
||||||
|
while (result != Z_STREAM_END) {
|
||||||
|
result = deflate(&strm, Z_FINISH);
|
||||||
|
|
||||||
|
output.append(tmpBuf, (BUFSIZE - strm.avail_out));
|
||||||
|
strm.next_out = reinterpret_cast<Bytef *>(tmpBuf);
|
||||||
|
strm.avail_out = BUFSIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deflateRes != Z_STREAM_END)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
dest.append(tmpBuf, BUFSIZE - strm.avail_out);
|
|
||||||
deflateEnd(&strm);
|
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) {
|
if (data.isEmpty())
|
||||||
qWarning("uncompress: Input data is truncated");
|
return {};
|
||||||
return false;
|
|
||||||
}
|
const int BUFSIZE = 1024 * 1024;
|
||||||
|
char tmpBuf[BUFSIZE] = {0};
|
||||||
|
|
||||||
z_stream strm;
|
z_stream strm;
|
||||||
static const int CHUNK_SIZE = 1024;
|
|
||||||
char out[CHUNK_SIZE];
|
|
||||||
|
|
||||||
/* allocate inflate state */
|
|
||||||
strm.zalloc = Z_NULL;
|
strm.zalloc = Z_NULL;
|
||||||
strm.zfree = Z_NULL;
|
strm.zfree = Z_NULL;
|
||||||
strm.opaque = Z_NULL;
|
strm.opaque = Z_NULL;
|
||||||
strm.avail_in = static_cast<uint>(src.size());
|
strm.next_in = reinterpret_cast<const Bytef *>(data.constData());
|
||||||
strm.next_in = reinterpret_cast<uchar *>(src.data());
|
strm.avail_in = uInt(data.size());
|
||||||
|
strm.next_out = reinterpret_cast<Bytef *>(tmpBuf);
|
||||||
|
strm.avail_out = BUFSIZE;
|
||||||
|
|
||||||
const int windowBits = 15;
|
// windowBits must be greater than or equal to the windowBits value provided to deflateInit2() while compressing
|
||||||
const int ENABLE_ZLIB_GZIP = 32;
|
// 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
|
QByteArray output;
|
||||||
if (ret != Z_OK)
|
// from lzbench, level 9 average compression ratio is: 31.92%, which decompression ratio is: 1 / 0.3192 = 3.13
|
||||||
return false;
|
output.reserve(data.size() * 3);
|
||||||
|
|
||||||
// run inflate()
|
// run inflate
|
||||||
do {
|
while (true) {
|
||||||
strm.avail_out = CHUNK_SIZE;
|
result = inflate(&strm, Z_NO_FLUSH);
|
||||||
strm.next_out = reinterpret_cast<uchar *>(out);
|
|
||||||
|
|
||||||
ret = inflate(&strm, Z_NO_FLUSH);
|
if (result == Z_STREAM_END) {
|
||||||
Q_ASSERT(ret != Z_STREAM_ERROR); // state not clobbered
|
output.append(tmpBuf, (BUFSIZE - strm.avail_out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (ret) {
|
if (result != Z_OK) {
|
||||||
case Z_NEED_DICT:
|
|
||||||
case Z_DATA_ERROR:
|
|
||||||
case Z_MEM_ERROR:
|
|
||||||
inflateEnd(&strm);
|
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<Bytef *>(tmpBuf);
|
||||||
|
strm.avail_out = BUFSIZE;
|
||||||
}
|
}
|
||||||
while (!strm.avail_out);
|
|
||||||
|
|
||||||
// clean up and return
|
|
||||||
inflateEnd(&strm);
|
inflateEnd(&strm);
|
||||||
return true;
|
|
||||||
|
if (ok) *ok = true;
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ namespace Utils
|
||||||
{
|
{
|
||||||
namespace Gzip
|
namespace Gzip
|
||||||
{
|
{
|
||||||
bool compress(QByteArray src, QByteArray &dest);
|
QByteArray compress(const QByteArray &data, int level = 6, bool *ok = nullptr);
|
||||||
bool uncompress(QByteArray src, QByteArray &dest);
|
QByteArray decompress(const QByteArray &data, bool *ok = nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue