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