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