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