QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
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 "qgssettings.h"
27 #include "qgslogger.h"
28 #include "qgis.h"
29 #include "qgsnetworkdiskcache.h"
30 #include "qgsauthmanager.h"
31 #include "qgsnetworkreply.h"
33 #include "qgssettingsentryimpl.h"
34 #include "qgssettingstree.h"
35 
36 #include <QUrl>
37 #include <QTimer>
38 #include <QBuffer>
39 #include <QNetworkReply>
40 #include <QRecursiveMutex>
41 #include <QThreadStorage>
42 #include <QAuthenticator>
43 #include <QStandardPaths>
44 #include <QUuid>
45 
46 const QgsSettingsEntryInteger *QgsNetworkAccessManager::settingsNetworkTimeout = new QgsSettingsEntryInteger( QStringLiteral( "network-timeout" ), QgsSettingsTree::sTreeNetwork, 60000, QObject::tr( "Network timeout" ) );
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  {}
137 
138  bool deleteCookie( const QNetworkCookie &cookie ) override
139  {
140  const QMutexLocker locker( &mMutex );
141  if ( QNetworkCookieJar::deleteCookie( cookie ) )
142  {
143  emit mNam->cookiesChanged( allCookies() );
144  return true;
145  }
146  return false;
147  }
148  bool insertCookie( const QNetworkCookie &cookie ) override
149  {
150  const QMutexLocker locker( &mMutex );
151  if ( QNetworkCookieJar::insertCookie( cookie ) )
152  {
153  emit mNam->cookiesChanged( allCookies() );
154  return true;
155  }
156  return false;
157  }
158  bool setCookiesFromUrl( const QList<QNetworkCookie> &cookieList, const QUrl &url ) override
159  {
160  const QMutexLocker locker( &mMutex );
161  return QNetworkCookieJar::setCookiesFromUrl( cookieList, url );
162  }
163  bool updateCookie( const QNetworkCookie &cookie ) override
164  {
165  const QMutexLocker locker( &mMutex );
166  if ( QNetworkCookieJar::updateCookie( cookie ) )
167  {
168  emit mNam->cookiesChanged( allCookies() );
169  return true;
170  }
171  return false;
172  }
173 
174  // Override these to make them public
175  QList<QNetworkCookie> allCookies() const
176  {
177  const QMutexLocker locker( &mMutex );
178  return QNetworkCookieJar::allCookies();
179  }
180  void setAllCookies( const QList<QNetworkCookie> &cookieList )
181  {
182  const QMutexLocker locker( &mMutex );
183  QNetworkCookieJar::setAllCookies( cookieList );
184  }
185 
186  QgsNetworkAccessManager *mNam = nullptr;
187  mutable QRecursiveMutex mMutex;
188 };
190 
191 
192 //
193 // Static calls to enforce singleton behavior
194 //
195 QgsNetworkAccessManager *QgsNetworkAccessManager::instance( Qt::ConnectionType connectionType )
196 {
197  static QThreadStorage<QgsNetworkAccessManager> sInstances;
198  QgsNetworkAccessManager *nam = &sInstances.localData();
199 
200  if ( nam->thread() == qApp->thread() )
201  sMainNAM = nam;
202 
203  if ( !nam->mInitialized )
204  {
205  nam->setupDefaultProxyAndCache( connectionType );
206  nam->setCacheDisabled( sMainNAM->cacheDisabled() );
207  }
208 
209  return nam;
210 }
211 
213  : QNetworkAccessManager( parent )
214  , mSslErrorHandlerSemaphore( 1 )
215  , mAuthRequestHandlerSemaphore( 1 )
216 {
217  setProxyFactory( new QgsNetworkProxyFactory() );
218  setCookieJar( new QgsNetworkCookieJar( this ) );
219  enableStrictTransportSecurityStore( true );
220  setStrictTransportSecurityEnabled( true );
221 }
222 
223 void QgsNetworkAccessManager::setSslErrorHandler( std::unique_ptr<QgsSslErrorHandler> handler )
224 {
225  Q_ASSERT( sMainNAM == this );
226  mSslErrorHandler = std::move( handler );
227 }
228 
229 void QgsNetworkAccessManager::setAuthHandler( std::unique_ptr<QgsNetworkAuthenticationHandler> handler )
230 {
231  Q_ASSERT( sMainNAM == this );
232  mAuthHandler = std::move( handler );
233 }
234 
235 void QgsNetworkAccessManager::insertProxyFactory( QNetworkProxyFactory *factory )
236 {
237  mProxyFactories.insert( 0, factory );
238 }
239 
240 void QgsNetworkAccessManager::removeProxyFactory( QNetworkProxyFactory *factory )
241 {
242  mProxyFactories.removeAll( factory );
243 }
244 
245 const QList<QNetworkProxyFactory *> QgsNetworkAccessManager::proxyFactories() const
246 {
247  return mProxyFactories;
248 }
249 
251 {
252  return mExcludedURLs;
253 }
254 
256 {
257  return mNoProxyURLs;
258 }
259 
260 const QNetworkProxy &QgsNetworkAccessManager::fallbackProxy() const
261 {
262  return mFallbackProxy;
263 }
264 
265 void QgsNetworkAccessManager::setFallbackProxyAndExcludes( const QNetworkProxy &proxy, const QStringList &excludes, const QStringList &noProxyURLs )
266 {
267  QgsDebugMsgLevel( QStringLiteral( "proxy settings: (type:%1 host: %2:%3, user:%4, password:%5" )
268  .arg( proxy.type() == QNetworkProxy::DefaultProxy ? QStringLiteral( "DefaultProxy" ) :
269  proxy.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral( "Socks5Proxy" ) :
270  proxy.type() == QNetworkProxy::NoProxy ? QStringLiteral( "NoProxy" ) :
271  proxy.type() == QNetworkProxy::HttpProxy ? QStringLiteral( "HttpProxy" ) :
272  proxy.type() == QNetworkProxy::HttpCachingProxy ? QStringLiteral( "HttpCachingProxy" ) :
273  proxy.type() == QNetworkProxy::FtpCachingProxy ? QStringLiteral( "FtpCachingProxy" ) :
274  QStringLiteral( "Undefined" ),
275  proxy.hostName() )
276  .arg( proxy.port() )
277  .arg( proxy.user(),
278  proxy.password().isEmpty() ? QStringLiteral( "not set" ) : QStringLiteral( "set" ) ), 4 );
279 
280  mFallbackProxy = proxy;
281  mExcludedURLs = excludes;
282  // remove empty records from excludes list -- these would otherwise match ANY url, so the proxy would always be skipped!
283  mExcludedURLs.erase( std::remove_if( mExcludedURLs.begin(), mExcludedURLs.end(), // clazy:exclude=detaching-member
284  []( const QString & url )
285  {
286  return url.trimmed().isEmpty();
287  } ), mExcludedURLs.end() ); // clazy:exclude=detaching-member
288 
289  mNoProxyURLs = noProxyURLs;
290  mNoProxyURLs.erase( std::remove_if( mNoProxyURLs.begin(), mNoProxyURLs.end(), // clazy:exclude=detaching-member
291  []( const QString & url )
292  {
293  return url.trimmed().isEmpty();
294  } ), mNoProxyURLs.end() ); // clazy:exclude=detaching-member
295 }
296 
297 QNetworkReply *QgsNetworkAccessManager::createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData )
298 {
299  const QgsSettings s;
300 
301  QNetworkRequest *pReq( const_cast< QNetworkRequest * >( &req ) ); // hack user agent
302 
303  QString userAgent = s.value( QStringLiteral( "/qgis/networkAndProxy/userAgent" ), "Mozilla/5.0" ).toString();
304  if ( !userAgent.isEmpty() )
305  userAgent += ' ';
306  userAgent += QStringLiteral( "QGIS/%1/%2" ).arg( Qgis::versionInt() ).arg( QSysInfo::prettyProductName() );
307  pReq->setRawHeader( "User-Agent", userAgent.toLatin1() );
308 
309 #ifndef QT_NO_SSL
310  const bool ishttps = pReq->url().scheme().compare( QLatin1String( "https" ), Qt::CaseInsensitive ) == 0;
311  if ( ishttps && !QgsApplication::authManager()->isDisabled() )
312  {
313  QgsDebugMsgLevel( QStringLiteral( "Adding trusted CA certs to request" ), 3 );
314  QSslConfiguration sslconfig( pReq->sslConfiguration() );
315  // Merge trusted CAs with any additional CAs added by the authentication methods
316  sslconfig.setCaCertificates( QgsAuthCertUtils::casMerge( QgsApplication::authManager()->trustedCaCertsCache(), sslconfig.caCertificates( ) ) );
317  // check for SSL cert custom config
318  const QString hostport( QStringLiteral( "%1:%2" )
319  .arg( pReq->url().host().trimmed() )
320  .arg( pReq->url().port() != -1 ? pReq->url().port() : 443 ) );
321  const QgsAuthConfigSslServer servconfig = QgsApplication::authManager()->sslCertCustomConfigByHost( hostport.trimmed() );
322  if ( !servconfig.isNull() )
323  {
324  QgsDebugMsgLevel( QStringLiteral( "Adding SSL custom config to request for %1" ).arg( hostport ), 2 );
325  sslconfig.setProtocol( servconfig.sslProtocol() );
326  sslconfig.setPeerVerifyMode( servconfig.sslPeerVerifyMode() );
327  sslconfig.setPeerVerifyDepth( servconfig.sslPeerVerifyDepth() );
328  }
329 
330  pReq->setSslConfiguration( sslconfig );
331  }
332 #endif
333 
334  if ( sMainNAM->mCacheDisabled )
335  {
336  // if caching is disabled then we override whatever the request actually has set!
337  pReq->setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork );
338  pReq->setAttribute( QNetworkRequest::CacheSaveControlAttribute, false );
339  }
340 
341  for ( const auto &preprocessor : sCustomPreprocessors )
342  {
343  preprocessor.second( pReq );
344  }
345 
346  static QAtomicInt sRequestId = 0;
347  const int requestId = ++sRequestId;
348  QByteArray content;
349  if ( QBuffer *buffer = qobject_cast<QBuffer *>( outgoingData ) )
350  {
351  content = buffer->buffer();
352  }
353 
354  emit requestAboutToBeCreated( QgsNetworkRequestParameters( op, req, requestId, content ) );
356  emit requestAboutToBeCreated( op, req, outgoingData );
358  QNetworkReply *reply = QNetworkAccessManager::createRequest( op, req, outgoingData );
359  reply->setProperty( "requestId", requestId );
360 
361  emit requestCreated( QgsNetworkRequestParameters( op, reply->request(), requestId, content ) );
363  emit requestCreated( reply );
365 
366  connect( reply, &QNetworkReply::downloadProgress, this, &QgsNetworkAccessManager::onReplyDownloadProgress );
367 #ifndef QT_NO_SSL
368  connect( reply, &QNetworkReply::sslErrors, this, &QgsNetworkAccessManager::onReplySslErrors );
369 #endif
370 
371  for ( const auto &replyPreprocessor : sCustomReplyPreprocessors )
372  {
373  replyPreprocessor.second( req, reply );
374  }
375 
376  // The timer will call abortRequest slot to abort the connection if needed.
377  // The timer is stopped by the finished signal and is restarted on downloadProgress and
378  // uploadProgress.
379  if ( timeout() )
380  {
381  QTimer *timer = new QTimer( reply );
382  timer->setObjectName( QStringLiteral( "timeoutTimer" ) );
383  connect( timer, &QTimer::timeout, this, &QgsNetworkAccessManager::abortRequest );
384  timer->setSingleShot( true );
385  timer->start( timeout() );
386 
387  connect( reply, &QNetworkReply::downloadProgress, timer, [timer] { timer->start(); } );
388  connect( reply, &QNetworkReply::uploadProgress, timer, [timer] { timer->start(); } );
389  connect( reply, &QNetworkReply::finished, timer, &QTimer::stop );
390  }
391  QgsDebugMsgLevel( QStringLiteral( "Created [reply:%1]" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ), 3 );
392 
393  return reply;
394 }
395 
396 void QgsNetworkAccessManager::abortRequest()
397 {
398  QTimer *timer = qobject_cast<QTimer *>( sender() );
399  Q_ASSERT( timer );
400 
401  QNetworkReply *reply = qobject_cast<QNetworkReply *>( timer->parent() );
402  Q_ASSERT( reply );
403 
404  reply->abort();
405  QgsDebugMsgLevel( QStringLiteral( "Abort [reply:%1] %2" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ).arg( reply->url().toString() ), 3 );
406  QgsMessageLog::logMessage( tr( "Network request %1 timed out" ).arg( reply->url().toString() ), tr( "Network" ) );
407  // Notify the application
408  emit requestTimedOut( QgsNetworkRequestParameters( reply->operation(), reply->request(), getRequestId( reply ) ) );
409  emit requestTimedOut( reply );
410 }
411 
412 void QgsNetworkAccessManager::onReplyFinished( QNetworkReply *reply )
413 {
414  emit finished( QgsNetworkReplyContent( reply ) );
415 }
416 
417 void QgsNetworkAccessManager::onReplyDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
418 {
419  if ( QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() ) )
420  {
421  emit downloadProgress( getRequestId( reply ), bytesReceived, bytesTotal );
422  }
423 }
424 
425 #ifndef QT_NO_SSL
426 void QgsNetworkAccessManager::onReplySslErrors( const QList<QSslError> &errors )
427 {
428  QNetworkReply *reply = qobject_cast< QNetworkReply *>( sender() );
429  Q_ASSERT( reply );
430  Q_ASSERT( reply->manager() == this );
431 
432  QgsDebugMsgLevel( QStringLiteral( "Stopping network reply timeout whilst SSL error is handled" ), 2 );
433  pauseTimeout( reply );
434 
435  emit requestEncounteredSslErrors( getRequestId( reply ), errors );
436 
437  // acquire semaphore a first time, so we block next acquire until release is called
438  mSslErrorHandlerSemaphore.acquire();
439 
440  // in main thread this will trigger SSL error handler immediately and return once the errors are handled,
441  // while in worker thread the signal will be queued (and return immediately) -- hence the need to lock the thread in the next block
442  emit sslErrorsOccurred( reply, errors );
443  if ( this != sMainNAM )
444  {
445  // lock thread and wait till error is handled. If we return from this slot now, then the reply will resume
446  // without actually giving the main thread the chance to act on the ssl error and possibly ignore it.
447  mSslErrorHandlerSemaphore.acquire();
448  mSslErrorHandlerSemaphore.release();
449  afterSslErrorHandled( reply );
450  }
451 }
452 
453 void QgsNetworkAccessManager::afterSslErrorHandled( QNetworkReply *reply )
454 {
455  if ( reply->manager() == this )
456  {
457  restartTimeout( reply );
458  emit sslErrorsHandled( reply );
459  }
460 }
461 
462 void QgsNetworkAccessManager::afterAuthRequestHandled( QNetworkReply *reply )
463 {
464  if ( reply->manager() == this )
465  {
466  restartTimeout( reply );
467  emit authRequestHandled( reply );
468  }
469 }
470 
471 void QgsNetworkAccessManager::pauseTimeout( QNetworkReply *reply )
472 {
473  Q_ASSERT( reply->manager() == this );
474 
475  QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
476  if ( timer && timer->isActive() )
477  {
478  timer->stop();
479  }
480 }
481 
482 void QgsNetworkAccessManager::restartTimeout( QNetworkReply *reply )
483 {
484  Q_ASSERT( reply->manager() == this );
485  // restart reply timeout
486  QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
487  if ( timer )
488  {
489  Q_ASSERT( !timer->isActive() );
490  QgsDebugMsgLevel( QStringLiteral( "Restarting network reply timeout" ), 2 );
491  timer->setSingleShot( true );
492  timer->start( timeout() );
493  }
494 }
495 
496 int QgsNetworkAccessManager::getRequestId( QNetworkReply *reply )
497 {
498  return reply->property( "requestId" ).toInt();
499 }
500 
501 void QgsNetworkAccessManager::handleSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
502 {
503  mSslErrorHandler->handleSslErrors( reply, errors );
504  afterSslErrorHandled( reply );
505  qobject_cast<QgsNetworkAccessManager *>( reply->manager() )->mSslErrorHandlerSemaphore.release();
506 }
507 
508 #endif
509 
510 void QgsNetworkAccessManager::onAuthRequired( QNetworkReply *reply, QAuthenticator *auth )
511 {
512  Q_ASSERT( reply );
513  Q_ASSERT( reply->manager() == this );
514 
515  QgsDebugMsgLevel( QStringLiteral( "Stopping network reply timeout whilst auth request is handled" ), 2 );
516  pauseTimeout( reply );
517 
518  emit requestRequiresAuth( getRequestId( reply ), auth->realm() );
519 
520  // acquire semaphore a first time, so we block next acquire until release is called
521  mAuthRequestHandlerSemaphore.acquire();
522 
523  // in main thread this will trigger auth handler immediately and return once the request is satisfied,
524  // while in worker thread the signal will be queued (and return immediately) -- hence the need to lock the thread in the next block
525  emit authRequestOccurred( reply, auth );
526 
527  if ( this != sMainNAM )
528  {
529  // lock thread and wait till error is handled. If we return from this slot now, then the reply will resume
530  // without actually giving the main thread the chance to act on the ssl error and possibly ignore it.
531  mAuthRequestHandlerSemaphore.acquire();
532  mAuthRequestHandlerSemaphore.release();
533  afterAuthRequestHandled( reply );
534  }
535 }
536 
538 {
539  if ( this != sMainNAM )
540  {
541  sMainNAM->requestAuthOpenBrowser( url );
543  return;
544  }
545  mAuthHandler->handleAuthRequestOpenBrowser( url );
546 }
547 
549 {
550  if ( this != sMainNAM )
551  {
552  sMainNAM->requestAuthCloseBrowser();
554  return;
555  }
556  mAuthHandler->handleAuthRequestCloseBrowser();
557 }
558 
560 {
561  if ( this != sMainNAM )
562  {
564  }
565  emit authBrowserAborted();
566 }
567 
568 void QgsNetworkAccessManager::handleAuthRequest( QNetworkReply *reply, QAuthenticator *auth )
569 {
570  mAuthHandler->handleAuthRequest( reply, auth );
571 
572  emit requestAuthDetailsAdded( getRequestId( reply ), auth->realm(), auth->user(), auth->password() );
573 
574  afterAuthRequestHandled( reply );
575  qobject_cast<QgsNetworkAccessManager *>( reply->manager() )->mAuthRequestHandlerSemaphore.release();
576 }
577 
578 QString QgsNetworkAccessManager::cacheLoadControlName( QNetworkRequest::CacheLoadControl control )
579 {
580  switch ( control )
581  {
582  case QNetworkRequest::AlwaysNetwork:
583  return QStringLiteral( "AlwaysNetwork" );
584  case QNetworkRequest::PreferNetwork:
585  return QStringLiteral( "PreferNetwork" );
586  case QNetworkRequest::PreferCache:
587  return QStringLiteral( "PreferCache" );
588  case QNetworkRequest::AlwaysCache:
589  return QStringLiteral( "AlwaysCache" );
590  }
591  return QStringLiteral( "PreferNetwork" );
592 }
593 
594 QNetworkRequest::CacheLoadControl QgsNetworkAccessManager::cacheLoadControlFromName( const QString &name )
595 {
596  if ( name == QLatin1String( "AlwaysNetwork" ) )
597  {
598  return QNetworkRequest::AlwaysNetwork;
599  }
600  else if ( name == QLatin1String( "PreferNetwork" ) )
601  {
602  return QNetworkRequest::PreferNetwork;
603  }
604  else if ( name == QLatin1String( "PreferCache" ) )
605  {
606  return QNetworkRequest::PreferCache;
607  }
608  else if ( name == QLatin1String( "AlwaysCache" ) )
609  {
610  return QNetworkRequest::AlwaysCache;
611  }
612  return QNetworkRequest::PreferNetwork;
613 }
614 
615 void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType connectionType )
616 {
617  mInitialized = true;
618  mUseSystemProxy = false;
619 
620  Q_ASSERT( sMainNAM );
621 
622  if ( sMainNAM != this )
623  {
624  connect( this, &QNetworkAccessManager::proxyAuthenticationRequired,
625  sMainNAM, &QNetworkAccessManager::proxyAuthenticationRequired,
626  connectionType );
627 
628  connect( this, qOverload< QNetworkReply *>( &QgsNetworkAccessManager::requestTimedOut ),
629  sMainNAM, qOverload< QNetworkReply *>( &QgsNetworkAccessManager::requestTimedOut ) );
630 
631  connect( this, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestTimedOut ),
632  sMainNAM, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestTimedOut ) );
633 
634  connect( this, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestAboutToBeCreated ),
635  sMainNAM, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestAboutToBeCreated ) );
636 
637  connect( this, qOverload< const QgsNetworkRequestParameters & >( &QgsNetworkAccessManager::requestCreated ),
638  sMainNAM, qOverload< const QgsNetworkRequestParameters & >( &QgsNetworkAccessManager::requestCreated ) );
639 
640  connect( this, qOverload< QgsNetworkReplyContent >( &QgsNetworkAccessManager::finished ),
641  sMainNAM, qOverload< QgsNetworkReplyContent >( &QgsNetworkAccessManager::finished ) );
642 
644 
645 #ifndef QT_NO_SSL
646  connect( this, &QNetworkAccessManager::sslErrors,
647  sMainNAM, &QNetworkAccessManager::sslErrors,
648  connectionType );
649 
651 #endif
652 
654  connect( sMainNAM, &QgsNetworkAccessManager::cookiesChanged, this, &QgsNetworkAccessManager::syncCookies );
655  connect( this, &QgsNetworkAccessManager::cookiesChanged, sMainNAM, &QgsNetworkAccessManager::syncCookies );
656  }
657  else
658  {
659 #ifndef QT_NO_SSL
660  if ( !mSslErrorHandler )
661  setSslErrorHandler( std::make_unique< QgsSslErrorHandler >() );
662 #endif
663  if ( !mAuthHandler )
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  QgsDebugMsgLevel( QStringLiteral( "setting proxy %1 %2:%3 %4/%5" )
729  .arg( proxyType )
730  .arg( proxyHost ).arg( proxyPort )
731  .arg( proxyUser, proxyPassword ), 2
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  QgsDebugMsgLevel( QStringLiteral( "setting proxy from stored authentication configuration %1" ).arg( authcfg ), 2 );
740  // Never crash! Never.
741  if ( QgsAuthManager *authManager = QgsApplication::authManager() )
742  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" ), 256 * 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 {
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 QString QgsNetworkAccessManager::setReplyPreprocessor( const std::function<void ( const QNetworkRequest &, QNetworkReply * )> &processor )
826 {
827  QString id = QUuid::createUuid().toString();
828  sCustomReplyPreprocessors.emplace_back( std::make_pair( id, processor ) );
829  return id;
830 }
831 
833 {
834  const size_t prevCount = sCustomReplyPreprocessors.size();
835  sCustomReplyPreprocessors.erase( std::remove_if( sCustomReplyPreprocessors.begin(), sCustomReplyPreprocessors.end(), [id]( std::pair< QString, std::function< void( const QNetworkRequest &, QNetworkReply * ) > > &a )
836  {
837  return a.first == id;
838  } ), sCustomReplyPreprocessors.end() );
839  return prevCount != sCustomReplyPreprocessors.size();
840 }
841 
842 void QgsNetworkAccessManager::preprocessRequest( QNetworkRequest *req ) const
843 {
844  for ( const auto &preprocessor : sCustomPreprocessors )
845  {
846  preprocessor.second( req );
847  }
848 }
849 
850 
851 //
852 // QgsNetworkRequestParameters
853 //
854 
855 QgsNetworkRequestParameters::QgsNetworkRequestParameters( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, int requestId, const QByteArray &content )
856  : mOperation( operation )
857  , mRequest( request )
858  , mOriginatingThreadId( QStringLiteral( "0x%2" ).arg( reinterpret_cast<quintptr>( QThread::currentThread() ), 2 * QT_POINTER_SIZE, 16, QLatin1Char( '0' ) ) )
859  , mRequestId( requestId )
860  , mContent( content )
861  , mInitiatorClass( request.attribute( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeInitiatorClass ) ).toString() )
862  , mInitiatorRequestId( request.attribute( static_cast< QNetworkRequest::Attribute >( QgsNetworkRequestParameters::AttributeInitiatorRequestId ) ) )
863 {
864 }
865 
866 
867 //
868 // QgsSslErrorHandler
869 //
870 
871 void QgsSslErrorHandler::handleSslErrors( QNetworkReply *reply, const QList<QSslError> & )
872 {
873  Q_UNUSED( reply )
874  QgsDebugError( QStringLiteral( "SSL errors occurred accessing URL:\n%1" ).arg( reply->request().url().toString() ) );
875 }
876 
877 //
878 // QgsNetworkAuthenticationHandler
879 //
880 
881 void QgsNetworkAuthenticationHandler::handleAuthRequest( QNetworkReply *reply, QAuthenticator * )
882 {
883  Q_UNUSED( reply )
884  QgsDebugError( 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() ) );
885 }
886 
888 {
889  Q_UNUSED( url )
890  QgsDebugError( QStringLiteral( "Network authentication required external browser to open URL %1, but no handler was in place" ).arg( url.toString() ) );
891 }
892 
894 {
895  QgsDebugError( QStringLiteral( "Network authentication required external browser closed, but no handler was in place" ) );
896 }
897 
898 // For QgsNetworkCookieJar
899 #include "qgsnetworkaccessmanager.moc"
static int versionInt()
Version number used for comparing versions using the "Check QGIS Version" function.
Definition: qgis.cpp:263
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.
Singleton offering an interface to manage the authentication configuration database and to utilize co...
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(), post(), head() or put() request has been mad...
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
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.
static const QgsSettingsEntryInteger * settingsNetworkTimeout
Settings entry network timeout.
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.
void requestCreated(const QgsNetworkRequestParameters &request)
Emitted when a network request has been created.
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.
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.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
An integer settings entry.
static QgsSettingsTreeNode * sTreeNetwork
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
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:5741
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:5740
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38