37 #include <QNetworkReply>
38 #include <QThreadStorage>
39 #include <QAuthenticator>
40 #include <QStandardPaths>
43 #include <QSslConfiguration>
52 class QgsNetworkProxyFactory :
public QNetworkProxyFactory
55 QgsNetworkProxyFactory() =
default;
57 QList<QNetworkProxy> queryProxy(
const QNetworkProxyQuery &query = QNetworkProxyQuery() )
override
63 for ( QNetworkProxyFactory *f : constProxyFactories )
65 QList<QNetworkProxy> systemproxies = f->systemProxyForQuery( query );
66 if ( !systemproxies.isEmpty() )
69 QList<QNetworkProxy> proxies = f->queryProxy( query );
70 if ( !proxies.isEmpty() )
75 if ( query.queryType() != QNetworkProxyQuery::UrlRequest )
78 QString url = query.url().toString();
81 for (
const QString &noProxy : constNoProxyList )
83 if ( !noProxy.trimmed().isEmpty() && url.startsWith( noProxy ) )
85 QgsDebugMsgLevel( QStringLiteral(
"don't using any proxy for %1 [exclude %2]" ).arg( url, noProxy ), 4 );
86 return QList<QNetworkProxy>() << QNetworkProxy( QNetworkProxy::NoProxy );
91 for (
const QString &exclude : constExcludeList )
93 if ( !exclude.trimmed().isEmpty() && url.startsWith( exclude ) )
95 QgsDebugMsgLevel( QStringLiteral(
"using default proxy for %1 [exclude %2]" ).arg( url, exclude ), 4 );
96 return QList<QNetworkProxy>() << QNetworkProxy( QNetworkProxy::DefaultProxy );
102 QgsDebugMsgLevel( QStringLiteral(
"requesting system proxy for query %1" ).arg( url ), 4 );
103 QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery( query );
104 if ( !proxies.isEmpty() )
107 .arg( proxies.first().hostName() ).arg( proxies.first().port() ), 4 );
112 QgsDebugMsgLevel( QStringLiteral(
"using fallback proxy for %1" ).arg( url ), 4 );
123 static QThreadStorage<QgsNetworkAccessManager> sInstances;
126 if ( nam->thread() == qApp->thread() )
129 if ( !nam->mInitialized )
136 : QNetworkAccessManager( parent )
138 setProxyFactory(
new QgsNetworkProxyFactory() );
143 Q_ASSERT( sMainNAM ==
this );
144 mSslErrorHandler = std::move( handler );
149 Q_ASSERT( sMainNAM ==
this );
150 mAuthHandler = std::move( handler );
155 mProxyFactories.insert( 0, factory );
160 mProxyFactories.removeAll( factory );
165 return mProxyFactories;
170 return mExcludedURLs;
180 return mFallbackProxy;
185 QgsDebugMsgLevel( QStringLiteral(
"proxy settings: (type:%1 host: %2:%3, user:%4, password:%5" )
186 .arg( proxy.type() == QNetworkProxy::DefaultProxy ? QStringLiteral(
"DefaultProxy" ) :
187 proxy.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral(
"Socks5Proxy" ) :
188 proxy.type() == QNetworkProxy::NoProxy ? QStringLiteral(
"NoProxy" ) :
189 proxy.type() == QNetworkProxy::HttpProxy ? QStringLiteral(
"HttpProxy" ) :
190 proxy.type() == QNetworkProxy::HttpCachingProxy ? QStringLiteral(
"HttpCachingProxy" ) :
191 proxy.type() == QNetworkProxy::FtpCachingProxy ? QStringLiteral(
"FtpCachingProxy" ) :
192 QStringLiteral(
"Undefined" ),
196 proxy.password().isEmpty() ? QStringLiteral(
"not set" ) : QStringLiteral(
"set" ) ), 4 );
198 mFallbackProxy = proxy;
199 mExcludedURLs = excludes;
201 mExcludedURLs.erase( std::remove_if( mExcludedURLs.begin(), mExcludedURLs.end(),
202 [](
const QString & url )
204 return url.trimmed().isEmpty();
205 } ), mExcludedURLs.end() );
207 mNoProxyURLs = noProxyURLs;
208 mNoProxyURLs.erase( std::remove_if( mNoProxyURLs.begin(), mNoProxyURLs.end(),
209 [](
const QString & url )
211 return url.trimmed().isEmpty();
212 } ), mNoProxyURLs.end() );
219 QNetworkRequest *pReq(
const_cast< QNetworkRequest *
>( &req ) );
221 QString userAgent = s.
value( QStringLiteral(
"/qgis/networkAndProxy/userAgent" ),
"Mozilla/5.0" ).toString();
222 if ( !userAgent.isEmpty() )
225 pReq->setRawHeader(
"User-Agent", userAgent.toLatin1() );
228 bool ishttps = pReq->url().scheme().compare( QLatin1String(
"https" ), Qt::CaseInsensitive ) == 0;
231 QgsDebugMsgLevel( QStringLiteral(
"Adding trusted CA certs to request" ), 3 );
232 QSslConfiguration sslconfig( pReq->sslConfiguration() );
236 QString hostport( QStringLiteral(
"%1:%2" )
237 .arg( pReq->url().host().trimmed() )
238 .arg( pReq->url().port() != -1 ? pReq->url().port() : 443 ) );
240 if ( !servconfig.
isNull() )
242 QgsDebugMsg( QStringLiteral(
"Adding SSL custom config to request for %1" ).arg( hostport ) );
248 pReq->setSslConfiguration( sslconfig );
252 static QAtomicInt sRequestId = 0;
253 const int requestId = ++sRequestId;
255 if ( QBuffer *buffer = qobject_cast<QBuffer *>( outgoingData ) )
257 content = buffer->buffer();
264 QNetworkReply *reply = QNetworkAccessManager::createRequest( op, req, outgoingData );
265 reply->setProperty(
"requestId", requestId );
271 connect( reply, &QNetworkReply::downloadProgress,
this, &QgsNetworkAccessManager::onReplyDownloadProgress );
273 connect( reply, &QNetworkReply::sslErrors,
this, &QgsNetworkAccessManager::onReplySslErrors );
279 QTimer *timer =
new QTimer( reply );
280 timer->setObjectName( QStringLiteral(
"timeoutTimer" ) );
281 connect( timer, &QTimer::timeout,
this, &QgsNetworkAccessManager::abortRequest );
282 timer->setSingleShot(
true );
285 connect( reply, &QNetworkReply::downloadProgress, timer, [timer] { timer->start(); } );
286 connect( reply, &QNetworkReply::uploadProgress, timer, [timer] { timer->start(); } );
287 connect( reply, &QNetworkReply::finished, timer, &QTimer::stop );
288 QgsDebugMsgLevel( QStringLiteral(
"Created [reply:%1]" ).arg(
reinterpret_cast< qint64
>( reply ), 0, 16 ), 3 );
294 void QgsNetworkAccessManager::unlockAfterSslErrorHandled()
296 Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
297 mSslErrorWaitCondition.wakeOne();
301 void QgsNetworkAccessManager::abortRequest()
303 QTimer *timer = qobject_cast<QTimer *>( sender() );
306 QNetworkReply *reply = qobject_cast<QNetworkReply *>( timer->parent() );
310 QgsDebugMsgLevel( QStringLiteral(
"Abort [reply:%1] %2" ).arg(
reinterpret_cast< qint64
>( reply ), 0, 16 ).arg( reply->url().toString() ), 3 );
317 void QgsNetworkAccessManager::onReplyFinished( QNetworkReply *reply )
322 void QgsNetworkAccessManager::onReplyDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
324 if ( QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() ) )
331 void QgsNetworkAccessManager::onReplySslErrors(
const QList<QSslError> &errors )
333 QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() );
335 Q_ASSERT( reply->manager() ==
this );
337 QgsDebugMsg( QStringLiteral(
"Stopping network reply timeout whilst SSL error is handled" ) );
338 pauseTimeout( reply );
344 emit sslErrorsOccurred( reply, errors );
345 if (
this != sMainNAM )
349 mSslErrorHandlerMutex.lock();
350 mSslErrorWaitCondition.wait( &mSslErrorHandlerMutex );
351 mSslErrorHandlerMutex.unlock();
352 afterSslErrorHandled( reply );
356 void QgsNetworkAccessManager::afterSslErrorHandled( QNetworkReply *reply )
358 if ( reply->manager() ==
this )
360 restartTimeout( reply );
361 emit sslErrorsHandled( reply );
363 else if (
this == sMainNAM )
366 qobject_cast< QgsNetworkAccessManager *>( reply->manager() )->unlockAfterSslErrorHandled();
370 void QgsNetworkAccessManager::unlockAfterAuthRequestHandled()
372 Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
373 mAuthRequestWaitCondition.wakeOne();
376 void QgsNetworkAccessManager::afterAuthRequestHandled( QNetworkReply *reply )
378 if ( reply->manager() ==
this )
380 restartTimeout( reply );
381 emit authRequestHandled( reply );
383 else if (
this == sMainNAM )
386 qobject_cast< QgsNetworkAccessManager *>( reply->manager() )->unlockAfterAuthRequestHandled();
390 void QgsNetworkAccessManager::pauseTimeout( QNetworkReply *reply )
392 Q_ASSERT( reply->manager() ==
this );
394 QTimer *timer = reply->findChild<QTimer *>( QStringLiteral(
"timeoutTimer" ) );
395 if ( timer && timer->isActive() )
401 void QgsNetworkAccessManager::restartTimeout( QNetworkReply *reply )
403 Q_ASSERT( reply->manager() ==
this );
405 QTimer *timer = reply->findChild<QTimer *>( QStringLiteral(
"timeoutTimer" ) );
408 Q_ASSERT( !timer->isActive() );
409 QgsDebugMsg( QStringLiteral(
"Restarting network reply timeout" ) );
410 timer->setSingleShot(
true );
415 int QgsNetworkAccessManager::getRequestId( QNetworkReply *reply )
417 return reply->property(
"requestId" ).toInt();
420 void QgsNetworkAccessManager::handleSslErrors( QNetworkReply *reply,
const QList<QSslError> &errors )
422 mSslErrorHandler->handleSslErrors( reply, errors );
423 afterSslErrorHandled( reply );
428 void QgsNetworkAccessManager::onAuthRequired( QNetworkReply *reply, QAuthenticator *auth )
431 Q_ASSERT( reply->manager() ==
this );
433 QgsDebugMsg( QStringLiteral(
"Stopping network reply timeout whilst auth request is handled" ) );
434 pauseTimeout( reply );
440 emit authRequestOccurred( reply, auth );
442 if (
this != sMainNAM )
446 mAuthRequestHandlerMutex.lock();
447 mAuthRequestWaitCondition.wait( &mAuthRequestHandlerMutex );
448 mAuthRequestHandlerMutex.unlock();
449 afterAuthRequestHandled( reply );
453 void QgsNetworkAccessManager::handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth )
455 mAuthHandler->handleAuthRequest( reply, auth );
459 afterAuthRequestHandled( reply );
466 case QNetworkRequest::AlwaysNetwork:
467 return QStringLiteral(
"AlwaysNetwork" );
468 case QNetworkRequest::PreferNetwork:
469 return QStringLiteral(
"PreferNetwork" );
470 case QNetworkRequest::PreferCache:
471 return QStringLiteral(
"PreferCache" );
472 case QNetworkRequest::AlwaysCache:
473 return QStringLiteral(
"AlwaysCache" );
475 return QStringLiteral(
"PreferNetwork" );
480 if ( name == QLatin1String(
"AlwaysNetwork" ) )
482 return QNetworkRequest::AlwaysNetwork;
484 else if ( name == QLatin1String(
"PreferNetwork" ) )
486 return QNetworkRequest::PreferNetwork;
488 else if ( name == QLatin1String(
"PreferCache" ) )
490 return QNetworkRequest::PreferCache;
492 else if ( name == QLatin1String(
"AlwaysCache" ) )
494 return QNetworkRequest::AlwaysCache;
496 return QNetworkRequest::PreferNetwork;
502 mUseSystemProxy =
false;
504 Q_ASSERT( sMainNAM );
506 if ( sMainNAM !=
this )
508 connect(
this, &QNetworkAccessManager::proxyAuthenticationRequired,
509 sMainNAM, &QNetworkAccessManager::proxyAuthenticationRequired,
527 connect(
this, &QNetworkAccessManager::sslErrors,
528 sMainNAM, &QNetworkAccessManager::sslErrors,
541 setAuthHandler( qgis::make_unique< QgsNetworkAuthenticationHandler>() );
544 connect(
this, &QgsNetworkAccessManager::sslErrorsOccurred, sMainNAM, &QgsNetworkAccessManager::handleSslErrors );
546 connect(
this, &QNetworkAccessManager::authenticationRequired,
this, &QgsNetworkAccessManager::onAuthRequired );
547 connect(
this, &QgsNetworkAccessManager::authRequestOccurred, sMainNAM, &QgsNetworkAccessManager::handleAuthRequest );
549 connect(
this, &QNetworkAccessManager::finished,
this, &QgsNetworkAccessManager::onReplyFinished );
554 QStringList excludes;
555 QStringList noProxyURLs;
557 bool proxyEnabled = settings.
value( QStringLiteral(
"proxy/proxyEnabled" ),
false ).toBool();
562 excludes = settings.
value( QStringLiteral(
"proxy/proxyExcludedUrls" ), QStringList() ).toStringList();
564 noProxyURLs = settings.
value( QStringLiteral(
"proxy/noProxyUrls" ), QStringList() ).toStringList();
567 QString proxyHost = settings.
value( QStringLiteral(
"proxy/proxyHost" ),
"" ).toString();
568 int proxyPort = settings.
value( QStringLiteral(
"proxy/proxyPort" ),
"" ).toString().toInt();
570 QString proxyUser = settings.
value( QStringLiteral(
"proxy/proxyUser" ),
"" ).toString();
571 QString proxyPassword = settings.
value( QStringLiteral(
"proxy/proxyPassword" ),
"" ).toString();
573 QString proxyTypeString = settings.
value( QStringLiteral(
"proxy/proxyType" ),
"" ).toString();
575 if ( proxyTypeString == QLatin1String(
"DefaultProxy" ) )
577 mUseSystemProxy =
true;
578 QNetworkProxyFactory::setUseSystemConfiguration(
true );
579 QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery();
580 if ( !proxies.isEmpty() )
582 proxy = proxies.first();
588 QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
589 if ( proxyTypeString == QLatin1String(
"Socks5Proxy" ) )
591 proxyType = QNetworkProxy::Socks5Proxy;
593 else if ( proxyTypeString == QLatin1String(
"HttpProxy" ) )
595 proxyType = QNetworkProxy::HttpProxy;
597 else if ( proxyTypeString == QLatin1String(
"HttpCachingProxy" ) )
599 proxyType = QNetworkProxy::HttpCachingProxy;
601 else if ( proxyTypeString == QLatin1String(
"FtpCachingProxy" ) )
603 proxyType = QNetworkProxy::FtpCachingProxy;
605 QgsDebugMsg( QStringLiteral(
"setting proxy %1 %2:%3 %4/%5" )
607 .arg( proxyHost ).arg( proxyPort )
608 .arg( proxyUser, proxyPassword )
610 proxy = QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword );
613 QString authcfg = settings.
value( QStringLiteral(
"proxy/authcfg" ),
"" ).toString();
614 if ( !authcfg.isEmpty( ) )
616 QgsDebugMsg( QStringLiteral(
"setting proxy from stored authentication configuration %1" ).arg( authcfg ) );
629 QString cacheDirectory = settings.
value( QStringLiteral(
"cache/directory" ) ).toString();
630 if ( cacheDirectory.isEmpty() )
631 cacheDirectory = QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
632 qint64 cacheSize = settings.
value( QStringLiteral(
"cache/size" ), 50 * 1024 * 1024 ).toLongLong();
638 if ( cache() != newcache )
639 setCache( newcache );
644 return QgsSettings().
value( QStringLiteral(
"/qgis/networkAndProxy/networkTimeout" ), 60000 ).toInt();
656 br.
get( request, forceRefresh, feedback );
664 br.
post( request, data, forceRefresh, feedback );
674 : mOperation( operation )
675 , mRequest( request )
676 , mOriginatingThreadId( QStringLiteral(
"0x%2" ).arg( reinterpret_cast<quintptr>( QThread::currentThread() ), 2 * QT_POINTER_SIZE, 16, QLatin1Char(
'0' ) ) )
677 , mRequestId( requestId )
678 , mContent( content )
679 , mInitiatorClass( request.attribute( static_cast< QNetworkRequest::Attribute >(
QgsNetworkRequestParameters::AttributeInitiatorClass ) ).toString() )
680 , mInitiatorRequestId( request.attribute( static_cast< QNetworkRequest::Attribute >(
QgsNetworkRequestParameters::AttributeInitiatorRequestId ) ) )
692 QgsDebugMsg( QStringLiteral(
"SSL errors occurred accessing URL:\n%1" ).arg( reply->request().url().toString() ) );
702 QgsDebugMsg( QStringLiteral(
"Network reply required authentication, but no handler was in place to provide this authentication request while accessing the URL:\n%1" ).arg( reply->request().url().toString() ) );