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