QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
32 #include <QUrl>
33 #include <QTimer>
34 #include <QNetworkReply>
35 #include <QThreadStorage>
36 #include <QAuthenticator>
37 #include <QStandardPaths>
38 
39 #ifndef QT_NO_SSL
40 #include <QSslConfiguration>
41 #endif
42 
43 #include "qgsnetworkdiskcache.h"
44 #include "qgsauthmanager.h"
45 
46 QgsNetworkAccessManager *QgsNetworkAccessManager::sMainNAM = nullptr;
47 
49 class QgsNetworkProxyFactory : public QNetworkProxyFactory
50 {
51  public:
52  QgsNetworkProxyFactory() = default;
53 
54  QList<QNetworkProxy> queryProxy( const QNetworkProxyQuery &query = QNetworkProxyQuery() ) override
55  {
57 
58  // iterate proxies factories and take first non empty list
59  Q_FOREACH ( QNetworkProxyFactory *f, nam->proxyFactories() )
60  {
61  QList<QNetworkProxy> systemproxies = f->systemProxyForQuery( query );
62  if ( !systemproxies.isEmpty() )
63  return systemproxies;
64 
65  QList<QNetworkProxy> proxies = f->queryProxy( query );
66  if ( !proxies.isEmpty() )
67  return proxies;
68  }
69 
70  // no proxies from the proxy factory list check for excludes
71  if ( query.queryType() != QNetworkProxyQuery::UrlRequest )
72  return QList<QNetworkProxy>() << nam->fallbackProxy();
73 
74  QString url = query.url().toString();
75 
76  Q_FOREACH ( const QString &exclude, nam->excludeList() )
77  {
78  if ( !exclude.trimmed().isEmpty() && url.startsWith( exclude ) )
79  {
80  QgsDebugMsgLevel( QStringLiteral( "using default proxy for %1 [exclude %2]" ).arg( url, exclude ), 4 );
81  return QList<QNetworkProxy>() << QNetworkProxy();
82  }
83  }
84 
85  if ( nam->useSystemProxy() )
86  {
87  QgsDebugMsgLevel( QStringLiteral( "requesting system proxy for query %1" ).arg( url ), 4 );
88  QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery( query );
89  if ( !proxies.isEmpty() )
90  {
91  QgsDebugMsgLevel( QStringLiteral( "using system proxy %1:%2 for query" )
92  .arg( proxies.first().hostName() ).arg( proxies.first().port() ), 4 );
93  return proxies;
94  }
95  }
96 
97  QgsDebugMsgLevel( QStringLiteral( "using fallback proxy for %1" ).arg( url ), 4 );
98  return QList<QNetworkProxy>() << nam->fallbackProxy();
99  }
100 };
102 
103 //
104 // Static calls to enforce singleton behavior
105 //
106 QgsNetworkAccessManager *QgsNetworkAccessManager::instance( Qt::ConnectionType connectionType )
107 {
108  static QThreadStorage<QgsNetworkAccessManager> sInstances;
109  QgsNetworkAccessManager *nam = &sInstances.localData();
110 
111  if ( nam->thread() == qApp->thread() )
112  sMainNAM = nam;
113 
114  if ( !nam->mInitialized )
115  nam->setupDefaultProxyAndCache( connectionType );
116 
117  return nam;
118 }
119 
121  : QNetworkAccessManager( parent )
122 {
123  setProxyFactory( new QgsNetworkProxyFactory() );
124 }
125 
126 void QgsNetworkAccessManager::insertProxyFactory( QNetworkProxyFactory *factory )
127 {
128  mProxyFactories.insert( 0, factory );
129 }
130 
131 void QgsNetworkAccessManager::removeProxyFactory( QNetworkProxyFactory *factory )
132 {
133  mProxyFactories.removeAll( factory );
134 }
135 
136 const QList<QNetworkProxyFactory *> QgsNetworkAccessManager::proxyFactories() const
137 {
138  return mProxyFactories;
139 }
140 
142 {
143  return mExcludedURLs;
144 }
145 
146 const QNetworkProxy &QgsNetworkAccessManager::fallbackProxy() const
147 {
148  return mFallbackProxy;
149 }
150 
151 void QgsNetworkAccessManager::setFallbackProxyAndExcludes( const QNetworkProxy &proxy, const QStringList &excludes )
152 {
153  QgsDebugMsgLevel( QStringLiteral( "proxy settings: (type:%1 host: %2:%3, user:%4, password:%5" )
154  .arg( proxy.type() == QNetworkProxy::DefaultProxy ? QStringLiteral( "DefaultProxy" ) :
155  proxy.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral( "Socks5Proxy" ) :
156  proxy.type() == QNetworkProxy::NoProxy ? QStringLiteral( "NoProxy" ) :
157  proxy.type() == QNetworkProxy::HttpProxy ? QStringLiteral( "HttpProxy" ) :
158  proxy.type() == QNetworkProxy::HttpCachingProxy ? QStringLiteral( "HttpCachingProxy" ) :
159  proxy.type() == QNetworkProxy::FtpCachingProxy ? QStringLiteral( "FtpCachingProxy" ) :
160  QStringLiteral( "Undefined" ),
161  proxy.hostName() )
162  .arg( proxy.port() )
163  .arg( proxy.user(),
164  proxy.password().isEmpty() ? QStringLiteral( "not set" ) : QStringLiteral( "set" ) ), 4 );
165 
166  mFallbackProxy = proxy;
167  mExcludedURLs = excludes;
168  // remove empty records from excludes list -- these would otherwise match ANY url, so the proxy would always be skipped!
169  mExcludedURLs.erase( std::remove_if( mExcludedURLs.begin(), mExcludedURLs.end(), // clazy:exclude=detaching-member
170  []( const QString & url )
171  {
172  return url.trimmed().isEmpty();
173  } ), mExcludedURLs.end() ); // clazy:exclude=detaching-member
174 
175 }
176 
177 QNetworkReply *QgsNetworkAccessManager::createRequest( QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData )
178 {
179  QgsSettings s;
180 
181  QNetworkRequest *pReq( const_cast< QNetworkRequest * >( &req ) ); // hack user agent
182 
183  QString userAgent = s.value( QStringLiteral( "/qgis/networkAndProxy/userAgent" ), "Mozilla/5.0" ).toString();
184  if ( !userAgent.isEmpty() )
185  userAgent += ' ';
186  userAgent += QStringLiteral( "QGIS/%1" ).arg( Qgis::QGIS_VERSION );
187  pReq->setRawHeader( "User-Agent", userAgent.toUtf8() );
188 
189 #ifndef QT_NO_SSL
190  bool ishttps = pReq->url().scheme().compare( QLatin1String( "https" ), Qt::CaseInsensitive ) == 0;
191  if ( ishttps && !QgsApplication::authManager()->isDisabled() )
192  {
193  QgsDebugMsgLevel( QStringLiteral( "Adding trusted CA certs to request" ), 3 );
194  QSslConfiguration sslconfig( pReq->sslConfiguration() );
195  // Merge trusted CAs with any additional CAs added by the authentication methods
196  sslconfig.setCaCertificates( QgsAuthCertUtils::casMerge( QgsApplication::authManager()->trustedCaCertsCache(), sslconfig.caCertificates( ) ) );
197  // check for SSL cert custom config
198  QString hostport( QStringLiteral( "%1:%2" )
199  .arg( pReq->url().host().trimmed() )
200  .arg( pReq->url().port() != -1 ? pReq->url().port() : 443 ) );
202  if ( !servconfig.isNull() )
203  {
204  QgsDebugMsg( QStringLiteral( "Adding SSL custom config to request for %1" ).arg( hostport ) );
205  sslconfig.setProtocol( servconfig.sslProtocol() );
206  sslconfig.setPeerVerifyMode( servconfig.sslPeerVerifyMode() );
207  sslconfig.setPeerVerifyDepth( servconfig.sslPeerVerifyDepth() );
208  }
209 
210  pReq->setSslConfiguration( sslconfig );
211  }
212 #endif
213 
214  emit requestAboutToBeCreated( op, req, outgoingData );
215  QNetworkReply *reply = QNetworkAccessManager::createRequest( op, req, outgoingData );
216 
217  emit requestCreated( reply );
218 
219  // The timer will call abortRequest slot to abort the connection if needed.
220  // The timer is stopped by the finished signal and is restarted on downloadProgress and
221  // uploadProgress.
222  QTimer *timer = new QTimer( reply );
223  timer->setObjectName( QStringLiteral( "timeoutTimer" ) );
224  connect( timer, &QTimer::timeout, this, &QgsNetworkAccessManager::abortRequest );
225  timer->setSingleShot( true );
226  timer->start( s.value( QStringLiteral( "/qgis/networkAndProxy/networkTimeout" ), "60000" ).toInt() );
227 
228  connect( reply, &QNetworkReply::downloadProgress, timer, [timer] { timer->start(); } );
229  connect( reply, &QNetworkReply::uploadProgress, timer, [timer] { timer->start(); } );
230  connect( reply, &QNetworkReply::finished, timer, &QTimer::stop );
231  QgsDebugMsgLevel( QStringLiteral( "Created [reply:%1]" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ), 3 );
232 
233  return reply;
234 }
235 
236 void QgsNetworkAccessManager::abortRequest()
237 {
238  QTimer *timer = qobject_cast<QTimer *>( sender() );
239  Q_ASSERT( timer );
240 
241  QNetworkReply *reply = qobject_cast<QNetworkReply *>( timer->parent() );
242  Q_ASSERT( reply );
243 
244  reply->abort();
245  QgsDebugMsgLevel( QStringLiteral( "Abort [reply:%1] %2" ).arg( reinterpret_cast< qint64 >( reply ), 0, 16 ).arg( reply->url().toString() ), 3 );
246  QgsMessageLog::logMessage( tr( "Network request %1 timed out" ).arg( reply->url().toString() ), tr( "Network" ) );
247  // Notify the application
248  emit requestTimedOut( reply );
249 
250 }
251 
252 
253 QString QgsNetworkAccessManager::cacheLoadControlName( QNetworkRequest::CacheLoadControl control )
254 {
255  switch ( control )
256  {
257  case QNetworkRequest::AlwaysNetwork:
258  return QStringLiteral( "AlwaysNetwork" );
259  case QNetworkRequest::PreferNetwork:
260  return QStringLiteral( "PreferNetwork" );
261  case QNetworkRequest::PreferCache:
262  return QStringLiteral( "PreferCache" );
263  case QNetworkRequest::AlwaysCache:
264  return QStringLiteral( "AlwaysCache" );
265  default:
266  break;
267  }
268  return QStringLiteral( "PreferNetwork" );
269 }
270 
271 QNetworkRequest::CacheLoadControl QgsNetworkAccessManager::cacheLoadControlFromName( const QString &name )
272 {
273  if ( name == QLatin1String( "AlwaysNetwork" ) )
274  {
275  return QNetworkRequest::AlwaysNetwork;
276  }
277  else if ( name == QLatin1String( "PreferNetwork" ) )
278  {
279  return QNetworkRequest::PreferNetwork;
280  }
281  else if ( name == QLatin1String( "PreferCache" ) )
282  {
283  return QNetworkRequest::PreferCache;
284  }
285  else if ( name == QLatin1String( "AlwaysCache" ) )
286  {
287  return QNetworkRequest::AlwaysCache;
288  }
289  return QNetworkRequest::PreferNetwork;
290 }
291 
292 void QgsNetworkAccessManager::setupDefaultProxyAndCache( Qt::ConnectionType connectionType )
293 {
294  mInitialized = true;
295  mUseSystemProxy = false;
296 
297  Q_ASSERT( sMainNAM );
298 
299  if ( sMainNAM != this )
300  {
301  connect( this, &QNetworkAccessManager::authenticationRequired,
302  sMainNAM, &QNetworkAccessManager::authenticationRequired,
303  connectionType );
304 
305  connect( this, &QNetworkAccessManager::proxyAuthenticationRequired,
306  sMainNAM, &QNetworkAccessManager::proxyAuthenticationRequired,
307  connectionType );
308 
311 
312 #ifndef QT_NO_SSL
313  connect( this, &QNetworkAccessManager::sslErrors,
314  sMainNAM, &QNetworkAccessManager::sslErrors,
315  connectionType );
316 #endif
317  }
318 
319  // check if proxy is enabled
320  QgsSettings settings;
321  QNetworkProxy proxy;
322  QStringList excludes;
323 
324  bool proxyEnabled = settings.value( QStringLiteral( "proxy/proxyEnabled" ), false ).toBool();
325  if ( proxyEnabled )
326  {
327  excludes = settings.value( QStringLiteral( "proxy/proxyExcludedUrls" ), QStringList() ).toStringList();
328 
329  //read type, host, port, user, passw from settings
330  QString proxyHost = settings.value( QStringLiteral( "proxy/proxyHost" ), "" ).toString();
331  int proxyPort = settings.value( QStringLiteral( "proxy/proxyPort" ), "" ).toString().toInt();
332 
333  QString proxyUser = settings.value( QStringLiteral( "proxy/proxyUser" ), "" ).toString();
334  QString proxyPassword = settings.value( QStringLiteral( "proxy/proxyPassword" ), "" ).toString();
335 
336  QString proxyTypeString = settings.value( QStringLiteral( "proxy/proxyType" ), "" ).toString();
337 
338  if ( proxyTypeString == QLatin1String( "DefaultProxy" ) )
339  {
340  mUseSystemProxy = true;
341  QNetworkProxyFactory::setUseSystemConfiguration( true );
342  QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery();
343  if ( !proxies.isEmpty() )
344  {
345  proxy = proxies.first();
346  }
347  QgsDebugMsgLevel( QStringLiteral( "setting default proxy" ), 4 );
348  }
349  else
350  {
351  QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
352  if ( proxyTypeString == QLatin1String( "Socks5Proxy" ) )
353  {
354  proxyType = QNetworkProxy::Socks5Proxy;
355  }
356  else if ( proxyTypeString == QLatin1String( "HttpProxy" ) )
357  {
358  proxyType = QNetworkProxy::HttpProxy;
359  }
360  else if ( proxyTypeString == QLatin1String( "HttpCachingProxy" ) )
361  {
362  proxyType = QNetworkProxy::HttpCachingProxy;
363  }
364  else if ( proxyTypeString == QLatin1String( "FtpCachingProxy" ) )
365  {
366  proxyType = QNetworkProxy::FtpCachingProxy;
367  }
368  QgsDebugMsg( QStringLiteral( "setting proxy %1 %2:%3 %4/%5" )
369  .arg( proxyType )
370  .arg( proxyHost ).arg( proxyPort )
371  .arg( proxyUser, proxyPassword )
372  );
373  proxy = QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword );
374  }
375  // Setup network proxy authentication configuration
376  QString authcfg = settings.value( QStringLiteral( "proxy/authcfg" ), "" ).toString();
377  if ( !authcfg.isEmpty( ) )
378  {
379  QgsDebugMsg( QStringLiteral( "setting proxy from stored authentication configuration %1" ).arg( authcfg ) );
380  // Never crash! Never.
382  QgsApplication::authManager()->updateNetworkProxy( proxy, authcfg );
383  }
384  }
385 
386  setFallbackProxyAndExcludes( proxy, excludes );
387 
388  QgsNetworkDiskCache *newcache = qobject_cast<QgsNetworkDiskCache *>( cache() );
389  if ( !newcache )
390  newcache = new QgsNetworkDiskCache( this );
391 
392  QString cacheDirectory = settings.value( QStringLiteral( "cache/directory" ) ).toString();
393  if ( cacheDirectory.isEmpty() )
394  cacheDirectory = QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
395  qint64 cacheSize = settings.value( QStringLiteral( "cache/size" ), 50 * 1024 * 1024 ).toLongLong();
396  newcache->setCacheDirectory( cacheDirectory );
397  newcache->setMaximumCacheSize( cacheSize );
398  QgsDebugMsgLevel( QStringLiteral( "cacheDirectory: %1" ).arg( newcache->cacheDirectory() ), 4 );
399  QgsDebugMsgLevel( QStringLiteral( "maximumCacheSize: %1" ).arg( newcache->maximumCacheSize() ), 4 );
400 
401  if ( cache() != newcache )
402  setCache( newcache );
403 }
404 
void requestCreated(QNetworkReply *)
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...
static const QString QGIS_VERSION
Version string.
Definition: qgis.h:64
static QString cacheLoadControlName(QNetworkRequest::CacheLoadControl control)
Gets name for QNetworkRequest::CacheLoadControl.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool updateNetworkProxy(QNetworkProxy &proxy, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkProxy with an authentication config.
void setCacheDirectory(const QString &cacheDir)
bool isNull() const
Whether configuration is null (missing components)
const QNetworkProxy & fallbackProxy() const
retrieve fall back proxy (for urls that no factory returned proxies for)
Configuration container for SSL server connection exceptions or overrides.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QString cacheDirectory() const
QNetworkReply * createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *outgoingData=nullptr) override
const QList< QNetworkProxyFactory * > proxyFactories() const
retrieve proxy factory list
static QNetworkRequest::CacheLoadControl cacheLoadControlFromName(const QString &name)
Gets QNetworkRequest::CacheLoadControl from name.
QgsNetworkAccessManager(QObject *parent=nullptr)
QSslSocket::PeerVerifyMode sslPeerVerifyMode() const
SSL client&#39;s peer verify mode to use in connections.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void requestTimedOut(QNetworkReply *)
QSsl::SslProtocol sslProtocol() const
SSL server protocol to use in connections.
bool useSystemProxy() const
Returns whether the system proxy should be used.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void removeProxyFactory(QNetworkProxyFactory *factory)
remove a factory from the proxy factories list
const QgsAuthConfigSslServer sslCertCustomConfigByHost(const QString &hostport)
sslCertCustomConfigByHost get an SSL certificate custom config by hostport (host:port) ...
void insertProxyFactory(QNetworkProxyFactory *factory)
insert a factory into the proxy factories list
static QgsAuthManager * authManager()
Returns the application&#39;s authentication manager instance.
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
void setupDefaultProxyAndCache(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Setup the QgsNetworkAccessManager (NAM) according to the user&#39;s settings.
int sslPeerVerifyDepth() const
Number or SSL client&#39;s peer to verify in connections.
void requestAboutToBeCreated(QNetworkAccessManager::Operation, const QNetworkRequest &, QIODevice *)
qint64 maximumCacheSize() const
network access manager for QGISThis class implements the QGIS network access manager.
void setFallbackProxyAndExcludes(const QNetworkProxy &proxy, const QStringList &excludes)
Sets fallback proxy and URL that shouldn&#39;t use it.
void setMaximumCacheSize(qint64 size)
QStringList excludeList() const
retrieve exclude list (urls shouldn&#39;t use the fallback proxy)
Wrapper implementation of QNetworkDiskCache with all methods guarded by a mutex soly for internal use...