Skip to content
Permalink
Browse files
Synchronize cookie jar between all running QgsNetworkAccessManager in…
…stances
  • Loading branch information
manisandro authored and nyalldawson committed Jun 19, 2021
1 parent ad618e7 commit d17071070f853177f5761033b23a19345f972b06
@@ -502,6 +502,14 @@ Emitted when external browser logins are to be aborted.
.. versionadded:: 3.20
%End


void cookiesChanged( const QList<QNetworkCookie> &cookies );
%Docstring
Emitted when the cookies changed.

.. versionadded:: 3.22
%End

protected:
virtual QNetworkReply *createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0 );

@@ -35,6 +35,11 @@
#include <QTimer>
#include <QBuffer>
#include <QNetworkReply>
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
#include <QMutex>
#else
#include <QRecursiveMutex>
#endif
#include <QThreadStorage>
#include <QAuthenticator>
#include <QStandardPaths>
@@ -115,6 +120,78 @@ class QgsNetworkProxyFactory : public QNetworkProxyFactory
};
///@endcond

/// @cond PRIVATE
class QgsNetworkCookieJar : public QNetworkCookieJar
{
Q_OBJECT

public:
QgsNetworkCookieJar( QgsNetworkAccessManager *parent )
: QNetworkCookieJar( parent )
, mNam( parent )
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
, mMutex( QMutex::Recursive )
#endif
{}

bool deleteCookie( const QNetworkCookie &cookie ) override
{
QMutexLocker locker( &mMutex );
if ( QNetworkCookieJar::deleteCookie( cookie ) )
{
emit mNam->cookiesChanged( allCookies() );
return true;
}
return false;
}
bool insertCookie( const QNetworkCookie &cookie ) override
{
QMutexLocker locker( &mMutex );
if ( QNetworkCookieJar::insertCookie( cookie ) )
{
emit mNam->cookiesChanged( allCookies() );
return true;
}
return false;
}
bool setCookiesFromUrl( const QList<QNetworkCookie> &cookieList, const QUrl &url ) override
{
QMutexLocker locker( &mMutex );
return QNetworkCookieJar::setCookiesFromUrl( cookieList, url );
}
bool updateCookie( const QNetworkCookie &cookie ) override
{
QMutexLocker locker( &mMutex );
if ( QNetworkCookieJar::updateCookie( cookie ) )
{
emit mNam->cookiesChanged( allCookies() );
return true;
}
return false;
}

// Override these to make them public
QList<QNetworkCookie> allCookies() const
{
QMutexLocker locker( &mMutex );
return QNetworkCookieJar::allCookies();
}
void setAllCookies( const QList<QNetworkCookie> &cookieList )
{
QMutexLocker locker( &mMutex );
QNetworkCookieJar::setAllCookies( cookieList );
}

QgsNetworkAccessManager *mNam = nullptr;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
mutable QMutex mMutex;
#else
mutable QRecursiveMutex mMutex;
#endif
};
///@endcond


//
// Static calls to enforce singleton behavior
//
@@ -139,6 +216,7 @@ QgsNetworkAccessManager::QgsNetworkAccessManager( QObject *parent )
: QNetworkAccessManager( parent )
{
setProxyFactory( new QgsNetworkProxyFactory() );
setCookieJar( new QgsNetworkCookieJar( this ) );
}

void QgsNetworkAccessManager::setSslErrorHandler( std::unique_ptr<QgsSslErrorHandler> handler )
@@ -576,6 +654,8 @@ void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType conn
#endif

connect( this, &QgsNetworkAccessManager::requestRequiresAuth, sMainNAM, &QgsNetworkAccessManager::requestRequiresAuth );
connect( sMainNAM, &QgsNetworkAccessManager::cookiesChanged, this, &QgsNetworkAccessManager::syncCookies );
connect( this, &QgsNetworkAccessManager::cookiesChanged, sMainNAM, &QgsNetworkAccessManager::syncCookies );
}
else
{
@@ -681,6 +761,23 @@ void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType conn

if ( cache() != newcache )
setCache( newcache );

if ( this != sMainNAM )
{
static_cast<QgsNetworkCookieJar *>( cookieJar() )->setAllCookies( static_cast<QgsNetworkCookieJar *>( sMainNAM->cookieJar() )->allCookies() );
}
}

void QgsNetworkAccessManager::syncCookies( const QList<QNetworkCookie> &cookies )
{
if ( sender() != this )
{
static_cast<QgsNetworkCookieJar *>( cookieJar() )->setAllCookies( cookies );
if ( this == sMainNAM )
{
emit cookiesChanged( cookies );
}
}
}

int QgsNetworkAccessManager::timeout()
@@ -756,3 +853,6 @@ void QgsNetworkAuthenticationHandler::handleAuthRequestCloseBrowser()
{
QgsDebugMsg( QStringLiteral( "Network authentication required external browser closed, but no handler was in place" ) );
}

// For QgsNetworkCookieJar
#include "qgsnetworkaccessmanager.moc"
@@ -23,6 +23,8 @@
#include "qgis_sip.h"
#include <QStringList>
#include <QNetworkAccessManager>
#include <QNetworkCookie>
#include <QNetworkCookieJar>
#include <QNetworkProxy>
#include <QNetworkRequest>
#include <QMutex>
@@ -694,6 +696,13 @@ class CORE_EXPORT QgsNetworkAccessManager : public QNetworkAccessManager
*/
void authBrowserAborted();

/**
* Emitted when the cookies changed.
* \since QGIS 3.22
*/

void cookiesChanged( const QList<QNetworkCookie> &cookies );

private slots:
void abortRequest();

@@ -709,6 +718,8 @@ class CORE_EXPORT QgsNetworkAccessManager : public QNetworkAccessManager
void onAuthRequired( QNetworkReply *reply, QAuthenticator *auth );
void handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth );

void syncCookies( const QList<QNetworkCookie> &cookies );

protected:
QNetworkReply *createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData = nullptr ) override;

@@ -22,6 +22,7 @@
#include <QObject>
#include "qgstest.h"
#include "qgssettings.h"
#include <QNetworkCookieJar>
#include <QNetworkReply>
#include <QAuthenticator>
#include <QThread>
@@ -158,6 +159,7 @@ class TestQgsNetworkAccessManager : public QObject
void testSslErrorHandler();
void testAuthRequestHandler();
void fetchTimeout();
void testCookieManagement();

private:

@@ -1070,5 +1072,63 @@ void TestQgsNetworkAccessManager::fetchTimeout()
blockingThread->deleteLater();
}

class FunctionThread : public QThread
{
public:
FunctionThread( const std::function<bool()> &f ) : m_f( f ), m_result( false ) {}
bool getResult() const
{
return m_result;
}
private:
std::function<bool()> m_f;
bool m_result;

void run()
{
m_result = m_f();
}
};

void TestQgsNetworkAccessManager::testCookieManagement()
{
QUrl url( "http://example.com" );
// Set cookie in a thread and verify that it also set in main thread
QEventLoop evLoop;
FunctionThread thread1( [ = ]
{
QgsNetworkAccessManager::instance()->cookieJar()->setCookiesFromUrl(
QList<QNetworkCookie>() << QNetworkCookie( "foo=bar" ), url );
return true;
} );
QObject::connect( &thread1, &QThread::finished, &evLoop, &QEventLoop::quit );
thread1.start();
evLoop.exec();

QList<QNetworkCookie> cookies = QgsNetworkAccessManager::instance()->cookieJar()->cookiesForUrl( url );

QCOMPARE( cookies.size(), 1 );
QCOMPARE( cookies[0].toRawForm(), "foo=bar=; domain=example.com; path=/" );

QVERIFY( QgsNetworkAccessManager::instance()->cookieJar()->deleteCookie( cookies[0] ) );

// Set cookie in main thread and verify that it is also set in other thread
QCOMPARE( QgsNetworkAccessManager::instance()->cookieJar()->cookiesForUrl( url ).size(), 0 );
QgsNetworkAccessManager::instance()->cookieJar()->setCookiesFromUrl(
QList<QNetworkCookie>() << QNetworkCookie( "baz=yadda" ), url
);

FunctionThread thread2( [ = ]
{
QList<QNetworkCookie> cookies = QgsNetworkAccessManager::instance()->cookieJar()->cookiesForUrl( url );
return cookies.size() == 1 && cookies[0].toRawForm() == "baz=yadda=; domain=example.com; path=/";
} );
QObject::connect( &thread2, &QThread::finished, &evLoop, &QEventLoop::quit );
thread2.start();
evLoop.exec();
QVERIFY( thread2.getResult() );

}

QGSTEST_MAIN( TestQgsNetworkAccessManager )
#include "testqgsnetworkaccessmanager.moc"

0 comments on commit d170710

Please sign in to comment.