QGIS API Documentation  3.23.0-Master (dd0cd13a00)
qgsnetworkaccessmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsnetworkaccessmanager.cpp
3  This class implements a QNetworkManager with the ability to chain in
4  own proxy factories.
5 
6  -------------------
7  begin : 2010-05-08
8  copyright : (C) 2010 by Juergen E. Fischer
9  email : jef at norbit dot de
10 
11 ***************************************************************************/
12 
13 /***************************************************************************
14  * *
15  * This program is free software; you can redistribute it and/or modify *
16  * it under the terms of the GNU General Public License as published by *
17  * the Free Software Foundation; either version 2 of the License, or *
18  * (at your option) any later version. *
19  * *
20  ***************************************************************************/
21 
23 
24 #include "qgsapplication.h"
25 #include "qgsmessagelog.h"
26 #include "qgslogger.h"
27 #include "qgis.h"
28 #include "qgssettings.h"
29 #include "qgsnetworkdiskcache.h"
30 #include "qgsauthmanager.h"
31 #include "qgsnetworkreply.h"
33 
34 #include <QUrl>
35 #include <QTimer>
36 #include <QBuffer>
37 #include <QNetworkReply>
38 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
39 #include <QMutex>
40 #else
41 #include <QRecursiveMutex>
42 #endif
43 #include <QThreadStorage>
44 #include <QAuthenticator>
45 #include <QStandardPaths>
46 #include <QUuid>
47 
48 #ifndef QT_NO_SSL
49 #include <QSslConfiguration>
50 #endif
51 
52 #include "qgsnetworkdiskcache.h"
53 #include "qgsauthmanager.h"
54 
55 QgsNetworkAccessManager *QgsNetworkAccessManager::sMainNAM = nullptr;
56 
57 static std::vector< std::pair< QString, std::function< void( QNetworkRequest * ) > > > sCustomPreprocessors;
58 
60 class QgsNetworkProxyFactory : public QNetworkProxyFactory
61 {
62  public:
63  QgsNetworkProxyFactory() = default;
64 
65  QList<QNetworkProxy> queryProxy( const QNetworkProxyQuery &query = QNetworkProxyQuery() ) override
66  {
68 
69  // iterate proxies factories and take first non empty list
70  const auto constProxyFactories = nam->proxyFactories();
71  for ( QNetworkProxyFactory *f : constProxyFactories )
72  {
73  QList<QNetworkProxy> systemproxies = f->systemProxyForQuery( query );
74  if ( !systemproxies.isEmpty() )
75  return systemproxies;
76 
77  QList<QNetworkProxy> proxies = f->queryProxy( query );
78  if ( !proxies.isEmpty() )
79  return proxies;
80  }
81 
82  // no proxies from the proxy factory list check for excludes
83  if ( query.queryType() != QNetworkProxyQuery::UrlRequest )
84  return QList<QNetworkProxy>() << nam->fallbackProxy();
85 
86  const QString url = query.url().toString();
87 
88  const auto constNoProxyList = nam->noProxyList();
89  for ( const QString &noProxy : constNoProxyList )
90  {
91  if ( !noProxy.trimmed().isEmpty() && url.startsWith( noProxy ) )
92  {
93  QgsDebugMsgLevel( QStringLiteral( "don't using any proxy for %1 [exclude %2]" ).arg( url, noProxy ), 4 );
94  return QList<QNetworkProxy>() << QNetworkProxy( QNetworkProxy::NoProxy );
95  }
96  }
97 
98  const auto constExcludeList = nam->excludeList();
99  for ( const QString &exclude : constExcludeList )
100  {
101  if ( !exclude.trimmed().isEmpty() && url.startsWith( exclude ) )
102  {
103  QgsDebugMsgLevel( QStringLiteral( "using default proxy for %1 [exclude %2]" ).arg( url, exclude ), 4 );
104  return QList<QNetworkProxy>() << QNetworkProxy( QNetworkProxy::DefaultProxy );
105  }
106  }
107 
108  if ( nam->useSystemProxy() )
109  {
110  QgsDebugMsgLevel( QStringLiteral( "requesting system proxy for query %1" ).arg( url ), 4 );
111  QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery( query );
112  if ( !proxies.isEmpty() )
113  {
114  QgsDebugMsgLevel( QStringLiteral( "using system proxy %1:%2 for query" )
115  .arg( proxies.first().hostName() ).arg( proxies.first().port() ), 4 );
116  return proxies;
117  }
118  }
119 
120  QgsDebugMsgLevel( QStringLiteral( "using fallback proxy for %1" ).arg( url ), 4 );
121  return QList<QNetworkProxy>() << nam->fallbackProxy();
122  }
123 };
125 
127 class QgsNetworkCookieJar : public QNetworkCookieJar
128 {
129  Q_OBJECT
130 
131  public:
132  QgsNetworkCookieJar( QgsNetworkAccessManager *parent )
133  : QNetworkCookieJar( parent )
134  , mNam( parent )
135 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
136  , mMutex( QMutex::Recursive )
137 #endif
138  {}
139 
140  bool deleteCookie( const QNetworkCookie &cookie ) override
141  {
142  const QMutexLocker locker( &mMutex );
143  if ( QNetworkCookieJar::deleteCookie( cookie ) )
144  {
145  emit mNam->cookiesChanged( allCookies() );
146  return true;
147  }
148  return false;
149  }
150  bool insertCookie( const QNetworkCookie &cookie ) override
151  {
152  const QMutexLocker locker( &mMutex );
153  if ( QNetworkCookieJar::insertCookie( cookie ) )
154  {
155  emit mNam->cookiesChanged( allCookies() );
156  return true;
157  }
158  return false;
159  }
160  bool setCookiesFromUrl( const QList<QNetworkCookie> &cookieList, const QUrl &url ) override
161  {
162  const QMutexLocker locker( &mMutex );
163  return QNetworkCookieJar::setCookiesFromUrl( cookieList, url );
164  }
165  bool updateCookie( const QNetworkCookie &cookie ) override
166  {
167  const QMutexLocker locker( &mMutex );
168  if ( QNetworkCookieJar::updateCookie( cookie ) )
169  {
170  emit mNam->cookiesChanged( allCookies() );
171  return true;
172  }
173  return false;
174  }
175 
176  // Override these to make them public
177  QList<QNetworkCookie> allCookies() const
178  {
179  const QMutexLocker locker( &mMutex );
180  return QNetworkCookieJar::allCookies();
181  }
182  void setAllCookies( const QList<QNetworkCookie> &cookieList )
183  {
184  const QMutexLocker locker( &mMutex );
185  QNetworkCookieJar::setAllCookies( cookieList );
186  }
187 
188  QgsNetworkAccessManager *mNam = nullptr;
189 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
190  mutable QMutex mMutex;
191 #else
192  mutable QRecursiveMutex mMutex;
193 #endif
194 };
196 
197 
198 //
199 // Static calls to enforce singleton behavior
200 //
201 QgsNetworkAccessManager *QgsNetworkAccessManager::instance( Qt::ConnectionType connectionType )
202 {
203  static QThreadStorage<QgsNetworkAccessManager> sInstances;
204  QgsNetworkAccessManager *nam = &sInstances.localData();
205 
206  if ( nam->thread() == qApp->thread() )
207  sMainNAM = nam;
208 
209  if ( !nam->mInitialized )
210  {
211  nam->setupDefaultProxyAndCache( connectionType );
212  nam->setCacheDisabled( sMainNAM->cacheDisabled() );
213  }
214 
215  return nam;
216 }
217 
219  : QNetworkAccessManager( parent )
220  , mAuthRequestHandlerSemaphore( 1 )
221 {
222  setProxyFactory( new QgsNetworkProxyFactory() );
223  setCookieJar( new QgsNetworkCookieJar( this ) );
224 }
225 
226 void QgsNetworkAccessManager::setSslErrorHandler( std::unique_ptr<QgsSslErrorHandler> handler )
227 {
228  Q_ASSERT( sMainNAM == this );
229  mSslErrorHandler = std::move( handler );
230 }
231 
232 void QgsNetworkAccessManager::setAuthHandler( std::unique_ptr<QgsNetworkAuthenticationHandler> handler )
233 {
234  Q_ASSERT( sMainNAM == this );
235  mAuthHandler = std::move( handler );
236 }
237 
238 void QgsNetworkAccessManager::insertProxyFactory( QNetworkProxyFactory *factory )
239 {
240  mProxyFactories.insert( 0, factory );
241 }
242 
243 void QgsNetworkAccessManager::removeProxyFactory( QNetworkProxyFactory *factory )
244 {
245  mProxyFactories.removeAll( factory );
246 }
247 
248 const QList<QNetworkProxyFactory *> QgsNetworkAccessManager::proxyFactories() const
249 {
250  return mProxyFactories;
251 }
252 
254 {
255  return mExcludedURLs;
256 }
257 
259 {
260  return mNoProxyURLs;
261 }
262 
263 const QNetworkProxy &QgsNetworkAccessManager::fallbackProxy() const
264 {
265  return mFallbackProxy;
266 }
267 
268 void QgsNetworkAccessManager::setFallbackProxyAndExcludes( const QNetworkProxy &proxy, const QStringList &excludes, const QStringList &noProxyURLs )
269 {
270  QgsDebugMsgLevel( QStringLiteral( "proxy settings: (type:%1 host: %2:%3, user:%4, password:%5" )
271  .arg( proxy.type() == QNetworkProxy::DefaultProxy ? QStringLiteral( "DefaultProxy" ) :
272  proxy.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral( "Socks5Proxy" ) :
273  proxy.type() == QNetworkProxy::NoProxy ? QStringLiteral( "NoProxy" ) :
274  proxy.type() == QNetworkProxy::HttpProxy ? QStringLiteral( "HttpProxy" ) :
275  proxy.type() == QNetworkProxy::HttpCachingProxy ? QStringLiteral( "HttpCachingProxy" ) :
276  proxy.type() == QNetworkProxy::FtpCachingProxy ? QStringLiteral( "FtpCachingProxy" ) :
277  QStringLiteral( "Undefined" ),
278  proxy.hostName() )
279  .arg( proxy.port() )
280  .arg( proxy.user(),
281  proxy.password().isEmpty() ? QStringLiteral( "not set" ) : QStringLiteral( "set" ) ), 4 );
282 
283  mFallbackProxy = proxy;
284  mExcludedURLs = excludes;
285  // remove empty records from excludes list -- these would otherwise match ANY url, so the proxy would always be skipped!
286  mExcludedURLs.erase( std::remove_if( mExcludedURLs.begin(), mExcludedURLs.end(), // clazy:exclude=detaching-member
287  []( const QString & url )
288  {
289  return url.trimmed().isEmpty();
290  } ), mExcludedURLs.end() ); // clazy:exclude=detaching-member
291 
292  mNoProxyURLs = noProxyURLs;
293  mNoProxyURLs.erase( std::remove_if( mNoProxyURLs.begin(), mNoProxyURLs.end(), // clazy:exclude=detaching-member
294  []( const QString & url )
295  {
296  return url.trimmed().isEmpty();
297  } ), mNoProxyURLs.end() ); // clazy:exclude=detaching-member
298 }
299 
300 QNetworkReply *QgsNetworkAccessManager::createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData )
301 {
302  const QgsSettings s;
303 
304  QNetworkRequest *pReq( const_cast< QNetworkRequest * >( &req ) ); // hack user agent
305 
306  QString userAgent = s.value( QStringLiteral( "/qgis/networkAndProxy/userAgent" ), "Mozilla/5.0" ).toString();
307  if ( !userAgent.isEmpty() )
308  userAgent += ' ';
309  userAgent += QStringLiteral( "QGIS/%1/%2" ).arg( Qgis::versionInt() ).arg( QSysInfo::prettyProductName() );
310  pReq->setRawHeader( "User-Agent", userAgent.toLatin1() );
311 
312 #ifndef QT_NO_SSL
313  const bool ishttps = pReq->url().scheme().compare( QLatin1String( "https" ), Qt::CaseInsensitive ) == 0;
314  if ( ishttps && !QgsApplication::authManager()->isDisabled() )
315  {
316  QgsDebugMsgLevel( QStringLiteral( "Adding trusted CA certs to request" ), 3 );
317  QSslConfiguration sslconfig( pReq->sslConfiguration() );
318  // Merge trusted CAs with any additional CAs added by the authentication methods
319  sslconfig.setCaCertificates( QgsAuthCertUtils::casMerge( QgsApplication::authManager()->trustedCaCertsCache(), sslconfig.caCertificates( ) ) );
320  // check for SSL cert custom config
321  const QString hostport( QStringLiteral( "%1:%2" )
322  .arg( pReq->url().host().trimmed() )
323  .arg( pReq->url().port() != -1 ? pReq->url().port() : 443 ) );
324  const QgsAuthConfigSslServer servconfig = QgsApplication::authManager()->sslCertCustomConfigByHost( hostport.trimmed() );
325  if ( !servconfig.isNull() )
326  {
327  QgsDebugMsg( QStringLiteral( "Adding SSL custom config to request for %1" ).arg( hostport ) );
328  sslconfig.setProtocol( servconfig.sslProtocol() );
329  sslconfig.setPeerVerifyMode( servconfig.sslPeerVerifyMode() );
330  sslconfig.setPeerVerifyDepth( servconfig.sslPeerVerifyDepth() );
331  }
332 
333  pReq->setSslConfiguration( sslconfig );
334  }
335 #endif
336 
337  if ( sMainNAM->mCacheDisabled )
338  {
339  // if caching is disabled then we override whatever the request actually has set!
340  pReq->setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
341  pReq->setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
342  }
343 
344  for ( const auto &preprocessor : sCustomPreprocessors )
345  {
346  preprocessor.second( pReq );
347  }
348 
349  static QAtomicInt sRequestId = 0;
350  const int requestId = ++sRequestId;
351  QByteArray content;
352  if ( QBuffer *buffer = qobject_cast<QBuffer *>( outgoingData ) )
353  {
354  content = buffer->buffer();
355  }
356 
357  emit requestAboutToBeCreated( QgsNetworkRequestParameters( op, req, requestId, content ) );
359  emit requestAboutToBeCreated( op, req, outgoingData );
361  QNetworkReply *reply = QNetworkAccessManager::createRequest( op, req, outgoingData );
362  reply->setProperty( "requestId", requestId );
363 
365  emit requestCreated( reply );
367 
368  connect( reply, &QNetworkReply::downloadProgress, this, &QgsNetworkAccessManager::onReplyDownloadProgress );
369 #ifndef QT_NO_SSL
370  connect( reply, &QNetworkReply::sslErrors, this, &QgsNetworkAccessManager::onReplySslErrors );
371 #endif
372 
373  // The timer will call abortRequest slot to abort the connection if needed.
374  // The timer is stopped by the finished signal and is restarted on downloadProgress and
375  // uploadProgress.
376  if ( timeout() )
377  {
378  QTimer *timer = new QTimer( reply );
379  timer->setObjectName( QStringLiteral( "timeoutTimer" ) );
380  connect( timer, &QTimer::timeout, this, &QgsNetworkAccessManager::abortRequest );
381  timer->setSingleShot( true );
382  timer->start( timeout() );
383 
384  connect( reply, &QNetworkReply::downloadProgress, timer, [timer] { timer->start(); } );
385  connect( reply, &QNetworkReply::uploadProgress, timer, [timer] { timer->start(); } );
386  connect( reply, &QNetworkReply::finished, timer, &QTimer::stop );
387  }
388  QgsDebugMsgLevel( QStringLiteral( "Created [reply:%1]" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ), 3 );
389 
390  return reply;
391 }
392 
393 #ifndef QT_NO_SSL
394 void QgsNetworkAccessManager::unlockAfterSslErrorHandled()
395 {
396  Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
397  mSslErrorWaitCondition.wakeOne();
398 }
399 #endif
400 
401 void QgsNetworkAccessManager::abortRequest()
402 {
403  QTimer *timer = qobject_cast<QTimer *>( sender() );
404  Q_ASSERT( timer );
405 
406  QNetworkReply *reply = qobject_cast<QNetworkReply *>( timer->parent() );
407  Q_ASSERT( reply );
408 
409  reply->abort();
410  QgsDebugMsgLevel( QStringLiteral( "Abort [reply:%1] %2" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ).arg( reply->url().toString() ), 3 );
411  QgsMessageLog::logMessage( tr( "Network request %1 timed out" ).arg( reply->url().toString() ), tr( "Network" ) );
412  // Notify the application
413  emit requestTimedOut( QgsNetworkRequestParameters( reply->operation(), reply->request(), getRequestId( reply ) ) );
414  emit requestTimedOut( reply );
415 }
416 
417 void QgsNetworkAccessManager::onReplyFinished( QNetworkReply *reply )
418 {
419  emit finished( QgsNetworkReplyContent( reply ) );
420 }
421 
422 void QgsNetworkAccessManager::onReplyDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
423 {
424  if ( QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() ) )
425  {
426  emit downloadProgress( getRequestId( reply ), bytesReceived, bytesTotal );
427  }
428 }
429 
430 #ifndef QT_NO_SSL
431 void QgsNetworkAccessManager::onReplySslErrors( const QList<QSslError> &errors )
432 {
433  QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() );
434  Q_ASSERT( reply );
435  Q_ASSERT( reply->manager() == this );
436 
437  QgsDebugMsg( QStringLiteral( "Stopping network reply timeout whilst SSL error is handled" ) );
438  pauseTimeout( reply );
439 
440  emit requestEncounteredSslErrors( getRequestId( reply ), errors );
441 
442  // in main thread this will trigger SSL error handler immediately and return once the errors are handled,
443  // while in worker thread the signal will be queued (and return immediately) -- hence the need to lock the thread in the next block
444  emit sslErrorsOccurred( reply, errors );
445  if ( this != sMainNAM )
446  {
447  // lock thread and wait till error is handled. If we return from this slot now, then the reply will resume
448  // without actually giving the main thread the chance to act on the ssl error and possibly ignore it.
449  mSslErrorHandlerMutex.lock();
450  mSslErrorWaitCondition.wait( &mSslErrorHandlerMutex );
451  mSslErrorHandlerMutex.unlock();
452  afterSslErrorHandled( reply );
453  }
454 }
455 
456 void QgsNetworkAccessManager::afterSslErrorHandled( QNetworkReply *reply )
457 {
458  if ( reply->manager() == this )
459  {
460  restartTimeout( reply );
461  emit sslErrorsHandled( reply );
462  }
463  else if ( this == sMainNAM )
464  {
465  // notify other threads to allow them to handle the reply
466  qobject_cast< QgsNetworkAccessManager *>( reply->manager() )->unlockAfterSslErrorHandled(); // safe to call directly - the other thread will be stuck waiting for us
467  }
468 }
469 
470 void QgsNetworkAccessManager::afterAuthRequestHandled( QNetworkReply *reply )
471 {
472  if ( reply->manager() == this )
473  {
474  restartTimeout( reply );
475  emit authRequestHandled( reply );
476  }
477 }
478 
479 void QgsNetworkAccessManager::pauseTimeout( QNetworkReply *reply )
480 {
481  Q_ASSERT( reply->manager() == this );
482 
483  QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
484  if ( timer && timer->isActive() )
485  {
486  timer->stop();
487  }
488 }
489 
490 void QgsNetworkAccessManager::restartTimeout( QNetworkReply *reply )
491 {
492  Q_ASSERT( reply->manager() == this );
493  // restart reply timeout
494  QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
495  if ( timer )
496  {
497  Q_ASSERT( !timer->isActive() );
498  QgsDebugMsg( QStringLiteral( "Restarting network reply timeout" ) );
499  timer->setSingleShot( true );
500  timer->start( timeout() );
501  }
502 }
503 
504 int QgsNetworkAccessManager::getRequestId( QNetworkReply *reply )
505 {
506  return reply->property( "requestId" ).toInt();
507 }
508 
509 void QgsNetworkAccessManager::handleSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
510 {
511  mSslErrorHandler->handleSslErrors( reply, errors );
512  afterSslErrorHandled( reply );
513 }
514 
515 #endif
516 
517 void QgsNetworkAccessManager::onAuthRequired( QNetworkReply *reply, QAuthenticator *auth )
518 {
519  Q_ASSERT( reply );
520  Q_ASSERT( reply->manager() == this );
521 
522  QgsDebugMsg( QStringLiteral( "Stopping network reply timeout whilst auth request is handled" ) );
523  pauseTimeout( reply );
524 
525  emit requestRequiresAuth( getRequestId( reply ), auth->realm() );
526 
527  mAuthRequestHandlerSemaphore.acquire();
528  // in main thread this will trigger auth handler immediately and return once the request is satisfied,
529  // while in worker thread the signal will be queued (and return immediately) -- hence the need to lock the thread in the next block
530  emit authRequestOccurred( reply, auth );
531 
532  if ( this != sMainNAM )
533  {
534  // lock thread and wait till error is handled. If we return from this slot now, then the reply will resume
535  // without actually giving the main thread the chance to act on the ssl error and possibly ignore it.
536  mAuthRequestHandlerSemaphore.acquire();
537  mAuthRequestHandlerSemaphore.release();
538  afterAuthRequestHandled( reply );
539  }
540 }
541 
543 {
544  if ( this != sMainNAM )
545  {
546  sMainNAM->requestAuthOpenBrowser( url );
548  return;
549  }
550  mAuthHandler->handleAuthRequestOpenBrowser( url );
551 }
552 
554 {
555  if ( this != sMainNAM )
556  {
557  sMainNAM->requestAuthCloseBrowser();
559  return;
560  }
561  mAuthHandler->handleAuthRequestCloseBrowser();
562 }
563 
565 {
566  if ( this != sMainNAM )
567  {
569  }
570  emit authBrowserAborted();
571 }
572 
573 void QgsNetworkAccessManager::handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth )
574 {
575  mAuthHandler->handleAuthRequest( reply, auth );
576 
577  emit requestAuthDetailsAdded( getRequestId( reply ), auth->realm(), auth->user(), auth->password() );
578 
579  afterAuthRequestHandled( reply );
580  qobject_cast<QgsNetworkAccessManager *>( reply->manager() )->mAuthRequestHandlerSemaphore.release();
581 }
582 
583 QString QgsNetworkAccessManager::cacheLoadControlName( QNetworkRequest::CacheLoadControl control )
584 {
585  switch ( control )
586  {
587  case QNetworkRequest::AlwaysNetwork:
588  return QStringLiteral( "AlwaysNetwork" );
589  case QNetworkRequest::PreferNetwork:
590  return QStringLiteral( "PreferNetwork" );
591  case QNetworkRequest::PreferCache:
592  return QStringLiteral( "PreferCache" );
593  case QNetworkRequest::AlwaysCache:
594  return QStringLiteral( "AlwaysCache" );
595  }
596  return QStringLiteral( "PreferNetwork" );
597 }
598 
599 QNetworkRequest::CacheLoadControl QgsNetworkAccessManager::cacheLoadControlFromName( const QString &name )
600 {
601  if ( name == QLatin1String( "AlwaysNetwork" ) )
602  {
603  return QNetworkRequest::AlwaysNetwork;
604  }
605  else if ( name == QLatin1String( "PreferNetwork" ) )
606  {
607  return QNetworkRequest::PreferNetwork;
608  }
609  else if ( name == QLatin1String( "PreferCache" ) )
610  {
611  return QNetworkRequest::PreferCache;
612  }
613  else if ( name == QLatin1String( "AlwaysCache" ) )
614  {
615  return QNetworkRequest::AlwaysCache;
616  }
617  return QNetworkRequest::PreferNetwork;
618 }
619 
620 void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType connectionType )
621 {
622  mInitialized = true;
623  mUseSystemProxy = false;
624 
625  Q_ASSERT( sMainNAM );
626 
627  if ( sMainNAM != this )
628  {
629  connect( this, &QNetworkAccessManager::proxyAuthenticationRequired,
630  sMainNAM, &QNetworkAccessManager::proxyAuthenticationRequired,
631  connectionType );
632 
633  connect( this, qOverload< QNetworkReply *>( &QgsNetworkAccessManager::requestTimedOut ),
634  sMainNAM, qOverload< QNetworkReply *>( &QgsNetworkAccessManager::requestTimedOut ) );
635 
636  connect( this, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestTimedOut ),
637  sMainNAM, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestTimedOut ) );
638 
639  connect( this, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestAboutToBeCreated ),
640  sMainNAM, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestAboutToBeCreated ) );
641 
642  connect( this, qOverload< QgsNetworkReplyContent >( &QgsNetworkAccessManager::finished ),
643  sMainNAM, qOverload< QgsNetworkReplyContent >( &QgsNetworkAccessManager::finished ) );
644 
646 
647 #ifndef QT_NO_SSL
648  connect( this, &QNetworkAccessManager::sslErrors,
649  sMainNAM, &QNetworkAccessManager::sslErrors,
650  connectionType );
651 
653 #endif
654 
656  connect( sMainNAM, &QgsNetworkAccessManager::cookiesChanged, this, &QgsNetworkAccessManager::syncCookies );
657  connect( this, &QgsNetworkAccessManager::cookiesChanged, sMainNAM, &QgsNetworkAccessManager::syncCookies );
658  }
659  else
660  {
661 #ifndef QT_NO_SSL
662  setSslErrorHandler( std::make_unique< QgsSslErrorHandler >() );
663 #endif
664  setAuthHandler( std::make_unique< QgsNetworkAuthenticationHandler>() );
665  }
666 #ifndef QT_NO_SSL
667  connect( this, &QgsNetworkAccessManager::sslErrorsOccurred, sMainNAM, &QgsNetworkAccessManager::handleSslErrors );
668 #endif
669  connect( this, &QNetworkAccessManager::authenticationRequired, this, &QgsNetworkAccessManager::onAuthRequired );
670  connect( this, &QgsNetworkAccessManager::authRequestOccurred, sMainNAM, &QgsNetworkAccessManager::handleAuthRequest );
671 
672  connect( this, &QNetworkAccessManager::finished, this, &QgsNetworkAccessManager::onReplyFinished );
673 
674  // check if proxy is enabled
675  const QgsSettings settings;
676  QNetworkProxy proxy;
677  QStringList excludes;
678  QStringList noProxyURLs;
679 
680  const bool proxyEnabled = settings.value( QStringLiteral( "proxy/proxyEnabled" ), false ).toBool();
681  if ( proxyEnabled )
682  {
683  // This settings is keep for retrocompatibility, the returned proxy for these URL is the default one,
684  // meaning the system one
685  excludes = settings.value( QStringLiteral( "proxy/proxyExcludedUrls" ), QStringList() ).toStringList();
686 
687  noProxyURLs = settings.value( QStringLiteral( "proxy/noProxyUrls" ), QStringList() ).toStringList();
688 
689  //read type, host, port, user, passw from settings
690  const QString proxyHost = settings.value( QStringLiteral( "proxy/proxyHost" ), "" ).toString();
691  const int proxyPort = settings.value( QStringLiteral( "proxy/proxyPort" ), "" ).toString().toInt();
692 
693  const QString proxyUser = settings.value( QStringLiteral( "proxy/proxyUser" ), "" ).toString();
694  const QString proxyPassword = settings.value( QStringLiteral( "proxy/proxyPassword" ), "" ).toString();
695 
696  const QString proxyTypeString = settings.value( QStringLiteral( "proxy/proxyType" ), "" ).toString();
697 
698  if ( proxyTypeString == QLatin1String( "DefaultProxy" ) )
699  {
700  mUseSystemProxy = true;
701  QNetworkProxyFactory::setUseSystemConfiguration( true );
702  QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery();
703  if ( !proxies.isEmpty() )
704  {
705  proxy = proxies.first();
706  }
707  QgsDebugMsgLevel( QStringLiteral( "setting default proxy" ), 4 );
708  }
709  else
710  {
711  QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
712  if ( proxyTypeString == QLatin1String( "Socks5Proxy" ) )
713  {
714  proxyType = QNetworkProxy::Socks5Proxy;
715  }
716  else if ( proxyTypeString == QLatin1String( "HttpProxy" ) )
717  {
718  proxyType = QNetworkProxy::HttpProxy;
719  }
720  else if ( proxyTypeString == QLatin1String( "HttpCachingProxy" ) )
721  {
722  proxyType = QNetworkProxy::HttpCachingProxy;
723  }
724  else if ( proxyTypeString == QLatin1String( "FtpCachingProxy" ) )
725  {
726  proxyType = QNetworkProxy::FtpCachingProxy;
727  }
728  QgsDebugMsg( QStringLiteral( "setting proxy %1 %2:%3 %4/%5" )
729  .arg( proxyType )
730  .arg( proxyHost ).arg( proxyPort )
731  .arg( proxyUser, proxyPassword )
732  );
733  proxy = QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword );
734  }
735  // Setup network proxy authentication configuration
736  const QString authcfg = settings.value( QStringLiteral( "proxy/authcfg" ), "" ).toString();
737  if ( !authcfg.isEmpty( ) )
738  {
739  QgsDebugMsg( QStringLiteral( "setting proxy from stored authentication configuration %1" ).arg( authcfg ) );
740  // Never crash! Never.
742  QgsApplication::authManager()->updateNetworkProxy( proxy, authcfg );
743  }
744  }
745 
746  setFallbackProxyAndExcludes( proxy, excludes, noProxyURLs );
747 
748  QgsNetworkDiskCache *newcache = qobject_cast<QgsNetworkDiskCache *>( cache() );
749  if ( !newcache )
750  newcache = new QgsNetworkDiskCache( this );
751 
752  QString cacheDirectory = settings.value( QStringLiteral( "cache/directory" ) ).toString();
753  if ( cacheDirectory.isEmpty() )
754  cacheDirectory = QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
755  const qint64 cacheSize = settings.value( QStringLiteral( "cache/size" ), 50 * 1024 * 1024 ).toLongLong();
756  newcache->setCacheDirectory( cacheDirectory );
757  newcache->setMaximumCacheSize( cacheSize );
758  QgsDebugMsgLevel( QStringLiteral( "cacheDirectory: %1" ).arg( newcache->cacheDirectory() ), 4 );
759  QgsDebugMsgLevel( QStringLiteral( "maximumCacheSize: %1" ).arg( newcache->maximumCacheSize() ), 4 );
760 
761  if ( cache() != newcache )
762  setCache( newcache );
763 
764  if ( this != sMainNAM )
765  {
766  static_cast<QgsNetworkCookieJar *>( cookieJar() )->setAllCookies( static_cast<QgsNetworkCookieJar *>( sMainNAM->cookieJar() )->allCookies() );
767  }
768 }
769 
770 void QgsNetworkAccessManager::syncCookies( const QList<QNetworkCookie> &cookies )
771 {
772  if ( sender() != this )
773  {
774  static_cast<QgsNetworkCookieJar *>( cookieJar() )->setAllCookies( cookies );
775  if ( this == sMainNAM )
776  {
777  emit cookiesChanged( cookies );
778  }
779  }
780 }
781 
783 {
784  return settingsNetworkTimeout.value();
785 }
786 
788 {
789  settingsNetworkTimeout.setValue( time );
790 }
791 
792 QgsNetworkReplyContent QgsNetworkAccessManager::blockingGet( QNetworkRequest &request, const QString &authCfg, bool forceRefresh, QgsFeedback *feedback )
793 {
795  br.setAuthCfg( authCfg );
796  br.get( request, forceRefresh, feedback );
797  return br.reply();
798 }
799 
800 QgsNetworkReplyContent QgsNetworkAccessManager::blockingPost( QNetworkRequest &request, const QByteArray &data, const QString &authCfg, bool forceRefresh, QgsFeedback *feedback )
801 {
803  br.setAuthCfg( authCfg );
804  br.post( request, data, forceRefresh, feedback );
805  return br.reply();
806 }
807 
808 QString QgsNetworkAccessManager::setRequestPreprocessor( const std::function<void ( QNetworkRequest * )> &processor )
809 {
810  QString id = QUuid::createUuid().toString();
811  sCustomPreprocessors.emplace_back( std::make_pair( id, processor ) );
812  return id;
813 }
814 
816 {
817  const size_t prevCount = sCustomPreprocessors.size();
818  sCustomPreprocessors.erase( std::remove_if( sCustomPreprocessors.begin(), sCustomPreprocessors.end(), [id]( std::pair< QString, std::function< void( QNetworkRequest * ) > > &a )
819  {
820  return a.first == id;
821  } ), sCustomPreprocessors.end() );
822  return prevCount != sCustomPreprocessors.size();
823 }
824 
825 void QgsNetworkAccessManager::preprocessRequest( QNetworkRequest *req ) const
826 {
827  for ( const auto &preprocessor : sCustomPreprocessors )
828  {
829  preprocessor.second( req );
830  }
831 }
832 
833 
834 //
835 // QgsNetworkRequestParameters
836 //
837 
838 QgsNetworkRequestParameters::QgsNetworkRequestParameters( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, int requestId, const QByteArray &content )
839  : mOperation( operation )
840  , mRequest( request )
841  , mOriginatingThreadId( QStringLiteral( "0x%2" ).arg( reinterpret_cast<quintptr>( QThread::currentThread() ), 2 * QT_POINTER_SIZE, 16, QLatin1Char( '0' ) ) )
842  , mRequestId( requestId )
843  , mContent( content )
844  , mInitiatorClass( request.attribute( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeInitiatorClass ) ).toString() )
845  , mInitiatorRequestId( request.attribute( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeInitiatorRequestId ) ) )
846 {
847 }
848 
849 
850 //
851 // QgsSslErrorHandler
852 //
853 
854 void QgsSslErrorHandler::handleSslErrors( QNetworkReply *reply, const QList<QSslError> & )
855 {
856  Q_UNUSED( reply )
857  QgsDebugMsg( QStringLiteral( "SSL errors occurred accessing URL:\n%1" ).arg( reply->request().url().toString() ) );
858 }
859 
860 //
861 // QgsNetworkAuthenticationHandler
862 //
863 
864 void QgsNetworkAuthenticationHandler::handleAuthRequest( QNetworkReply *reply, QAuthenticator * )
865 {
866  Q_UNUSED( reply )
867  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() ) );
868 }
869 
871 {
872  Q_UNUSED( url )
873  QgsDebugMsg( QStringLiteral( "Network authentication required external browser to open URL %1, but no handler was in place" ).arg( url.toString() ) );
874 }
875 
877 {
878  QgsDebugMsg( QStringLiteral( "Network authentication required external browser closed, but no handler was in place" ) );
879 }
880 
881 // For QgsNetworkCookieJar
882 #include "qgsnetworkaccessmanager.moc"
static int versionInt()
Version number used for comparing versions using the "Check QGIS Version" function.
Definition: qgis.cpp:290
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
static QList< QSslCertificate > casMerge(const QList< QSslCertificate > &bundle1, const QList< QSslCertificate > &bundle2)
casMerge merges two certificate bundles in a single one removing duplicates, the certificates from th...
Configuration container for SSL server connection exceptions or overrides.
QSsl::SslProtocol sslProtocol() const
SSL server protocol to use in connections.
int sslPeerVerifyDepth() const
Number or SSL client's peer to verify in connections.
bool isNull() const
Whether configuration is null (missing components)
QSslSocket::PeerVerifyMode sslPeerVerifyMode() const
SSL client's peer verify mode to use in connections.
bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkProxy with an authentication config.
const QgsAuthConfigSslServer sslCertCustomConfigByHost(const QString &hostport)
sslCertCustomConfigByHost get an SSL certificate custom config by hostport (host:port)
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
ErrorCode post(QNetworkRequest &request, QIODevice *data, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "post" operation on the specified request, using the given data.
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get() or post() request has been made.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:45
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
network access manager for QGIS
QStringList noProxyList() const
Returns the no proxy list.
void finished(QgsNetworkReplyContent reply)
Emitted whenever a pending network reply is finished.
void cookiesChanged(const QList< QNetworkCookie > &cookies)
Emitted when the cookies changed.
static QgsNetworkReplyContent blockingGet(QNetworkRequest &request, const QString &authCfg=QString(), bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Posts a GET request to obtain the contents of the target request and returns a new QgsNetworkReplyCon...
void insertProxyFactory(QNetworkProxyFactory *factory)
Inserts a factory into the proxy factories list.
void setSslErrorHandler(std::unique_ptr< QgsSslErrorHandler > handler)
Sets the application SSL error handler, which is used to respond to SSL errors encountered during net...
void abortAuthBrowser()
Abort any outstanding external browser login request.
void setCacheDisabled(bool disabled)
Sets whether all network caching should be disabled.
const QList< QNetworkProxyFactory * > proxyFactories() const
Returns a list of proxy factories used by the manager.
void downloadProgress(int requestId, qint64 bytesReceived, qint64 bytesTotal)
Emitted when a network reply receives a progress report.
void requestAuthOpenBrowser(const QUrl &url) const
Forwards an external browser login url opening request to the authentication handler.
void requestAuthCloseBrowser() const
Forwards an external browser login closure request to the authentication handler.
void requestEncounteredSslErrors(int requestId, const QList< QSslError > &errors)
Emitted when a network request encounters SSL errors.
static QString cacheLoadControlName(QNetworkRequest::CacheLoadControl control)
Returns the name for QNetworkRequest::CacheLoadControl.
static const QgsSettingsEntryInteger settingsNetworkTimeout
Settings entry network timeout.
static bool removeRequestPreprocessor(const QString &id)
Removes the custom pre-processor function with matching id.
void requestAuthDetailsAdded(int requestId, const QString &realm, const QString &user, const QString &password)
Emitted when network authentication details have been added to a request.
static QNetworkRequest::CacheLoadControl cacheLoadControlFromName(const QString &name)
Returns QNetworkRequest::CacheLoadControl from a name.
bool cacheDisabled() const
Returns true if all network caching is disabled.
QgsNetworkAccessManager(QObject *parent=nullptr)
void requestRequiresAuth(int requestId, const QString &realm)
Emitted when a network request prompts an authentication request.
void preprocessRequest(QNetworkRequest *req) const
Preprocesses request.
void setAuthHandler(std::unique_ptr< QgsNetworkAuthenticationHandler > handler)
Sets the application network authentication handler, which is used to respond to network authenticati...
static void setTimeout(int time)
Sets the maximum timeout time for network requests, in milliseconds.
static QgsNetworkReplyContent blockingPost(QNetworkRequest &request, const QByteArray &data, const QString &authCfg=QString(), bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Posts a POST request to obtain the contents of the target request, using the given data,...
const QNetworkProxy & fallbackProxy() const
Returns the fallback proxy used by the manager.
static int timeout()
Returns the network timeout length, in milliseconds.
void setupDefaultProxyAndCache(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Setup the QgsNetworkAccessManager (NAM) according to the user's settings.
static QString setRequestPreprocessor(const std::function< void(QNetworkRequest *request)> &processor)
Sets a request pre-processor function, which allows manipulation of a network request before it is pr...
void setFallbackProxyAndExcludes(const QNetworkProxy &proxy, const QStringList &excludes, const QStringList &noProxyURLs)
Sets the fallback proxy and URLs which shouldn't use it.
Q_DECL_DEPRECATED void requestCreated(QNetworkReply *)
Q_DECL_DEPRECATED void requestAboutToBeCreated(QNetworkAccessManager::Operation, const QNetworkRequest &, QIODevice *)
QStringList excludeList() const
Returns the proxy exclude list.
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
void removeProxyFactory(QNetworkProxyFactory *factory)
Removes a factory from the proxy factories list.
void authBrowserAborted()
Emitted when external browser logins are to be aborted.
void requestTimedOut(QgsNetworkRequestParameters request)
Emitted when a network request has timed out.
bool useSystemProxy() const
Returns whether the system proxy should be used.
QNetworkReply * createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData=nullptr) override
virtual void handleAuthRequest(QNetworkReply *reply, QAuthenticator *auth)
Called whenever network authentication requests are encountered during a network reply.
virtual void handleAuthRequestCloseBrowser()
Called to terminate a network authentication through external browser.
virtual void handleAuthRequestOpenBrowser(const QUrl &url)
Called to initiate a network authentication through external browser url.
Wrapper implementation of QNetworkDiskCache with all methods guarded by a mutex soly for internal use...
void setCacheDirectory(const QString &cacheDir)
qint64 maximumCacheSize() const
void setMaximumCacheSize(qint64 size)
QString cacheDirectory() const
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
Encapsulates parameters and properties of a network request.
QgsNetworkRequestParameters()=default
Default constructor.
virtual void handleSslErrors(QNetworkReply *reply, const QList< QSslError > &errors)
Called whenever SSL errors are encountered during a network reply.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2000
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1999
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38