QGIS API Documentation  2.17.0-Master (06698cd)
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_OPENSSL
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 ) );
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 
130 {
131  mProxyFactories.insert( 0, factory );
132 }
133 
135 {
136  mProxyFactories.removeAll( factory );
137 }
138 
140 {
141  return mProxyFactories;
142 }
143 
145 {
146  return mExcludedURLs;
147 }
148 
150 {
151  return mFallbackProxy;
152 }
153 
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( "/qgis/networkAndProxy/userAgent", "Mozilla/5.0" ).toString();
180  if ( !userAgent.isEmpty() )
181  userAgent += ' ';
182  userAgent += QString( "QGIS/%1" ).arg( QGis::QGIS_VERSION );
183  pReq->setRawHeader( "User-Agent", userAgent.toUtf8() );
184 
185 #ifndef QT_NO_OPENSSL
186  bool ishttps = pReq->url().scheme().toLower() == "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( QString( "%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( "timeoutTimer" );
220  connect( timer, SIGNAL( timeout() ), this, SLOT( abortRequest() ) );
221  timer->setSingleShot( true );
222  timer->start( s.value( "/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 "AlwaysNetwork";
255  case QNetworkRequest::PreferNetwork:
256  return "PreferNetwork";
257  case QNetworkRequest::PreferCache:
258  return "PreferCache";
259  case QNetworkRequest::AlwaysCache:
260  return "AlwaysCache";
261  default:
262  break;
263  }
264  return "PreferNetwork";
265 }
266 
267 QNetworkRequest::CacheLoadControl QgsNetworkAccessManager::cacheLoadControlFromName( const QString &theName )
268 {
269  if ( theName == "AlwaysNetwork" )
270  {
271  return QNetworkRequest::AlwaysNetwork;
272  }
273  else if ( theName == "PreferNetwork" )
274  {
275  return QNetworkRequest::PreferNetwork;
276  }
277  else if ( theName == "PreferCache" )
278  {
279  return QNetworkRequest::PreferCache;
280  }
281  else if ( theName == "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  {
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_OPENSSL
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;
318  QStringList excludes;
319 
320  bool proxyEnabled = settings.value( "proxy/proxyEnabled", false ).toBool();
321  if ( proxyEnabled )
322  {
323  excludes = settings.value( "proxy/proxyExcludedUrls", "" ).toString().split( '|', QString::SkipEmptyParts );
324 
325  //read type, host, port, user, passw from settings
326  QString proxyHost = settings.value( "proxy/proxyHost", "" ).toString();
327  int proxyPort = settings.value( "proxy/proxyPort", "" ).toString().toInt();
328  QString proxyUser = settings.value( "proxy/proxyUser", "" ).toString();
329  QString proxyPassword = settings.value( "proxy/proxyPassword", "" ).toString();
330 
331  QString proxyTypeString = settings.value( "proxy/proxyType", "" ).toString();
332 
333  if ( proxyTypeString == "DefaultProxy" )
334  {
335  mUseSystemProxy = true;
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 == "Socks5Proxy" )
348  {
349  proxyType = QNetworkProxy::Socks5Proxy;
350  }
351  else if ( proxyTypeString == "HttpProxy" )
352  {
353  proxyType = QNetworkProxy::HttpProxy;
354  }
355  else if ( proxyTypeString == "HttpCachingProxy" )
356  {
357  proxyType = QNetworkProxy::HttpCachingProxy;
358  }
359  else if ( proxyTypeString == "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( "cache/directory" ).toString();
379  if ( cacheDirectory.isEmpty() )
380  cacheDirectory = QgsApplication::qgisSettingsDirPath() + "cache";
381  qint64 cacheSize = settings.value( "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 
392 {
393  QNetworkReply * reply = get( request );
394  emit requestSent( reply, QObject::sender() );
395 }
396 
398 {
399  if ( !reply )
400  {
401  return;
402  }
403  reply->abort();
404  reply->deleteLater();
405 }
const QStringList & excludeList() const
retrieve exclude list (urls shouldn&#39;t use the fallback proxy)
void requestCreated(QNetworkReply *)
virtual QNetworkReply * createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData)
QString password() const
static QString cacheLoadControlName(QNetworkRequest::CacheLoadControl theControl)
Get name for QNetworkRequest::CacheLoadControl.
Q_DECL_DEPRECATED void sendGet(const QNetworkRequest &request)
Send GET request, calls get().
void sslErrors(QNetworkReply *reply, const QList< QSslError > &errors)
static QgsAuthManager * instance()
Enforce singleton pattern.
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user&#39;s home dir.
QString user() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
quint16 port() const
QObject * sender() const
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QSslConfiguration sslConfiguration() const
static QNetworkRequest::CacheLoadControl cacheLoadControlFromName(const QString &theName)
Get QNetworkRequest::CacheLoadControl from name.
void setCacheDirectory(const QString &cacheDir)
QString host() const
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 authenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
void setupDefaultProxyAndCache()
Setup the NAM according to the user&#39;s settings.
int port() const
QThread * thread() const
QString cacheDirectory() const
qulonglong toULongLong(bool *ok) const
QString tr(const char *sourceText, const char *disambiguation, int n)
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)
QAbstractNetworkCache * cache() const
void setProxyFactory(QNetworkProxyFactory *factory)
QNetworkProxy proxy() const
QString hostName() const
void setCaCertificates(const QList< QSslCertificate > &certificates)
virtual QList< QNetworkProxy > queryProxy(const QNetworkProxyQuery &query)=0
Q_DECL_DEPRECATED void deleteReply(QNetworkReply *reply)
Abort and delete reply.
int toInt(bool *ok) const
QSslSocket::PeerVerifyMode sslPeerVerifyMode() const
SSL client&#39;s peer verify mode to use in connections.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
QNetworkProxy::ProxyType type() const
void requestTimedOut(QNetworkReply *)
QSsl::SslProtocol sslProtocol() const
SSL server protocol to use in connections.
int toInt(bool *ok, int base) const
bool isEmpty() const
bool useSystemProxy() const
return whether the system proxy should be used
void setObjectName(const QString &name)
bool isEmpty() const
int removeAll(const T &value)
QString trimmed() const
void requestSent(QNetworkReply *reply, QObject *sender)
Emitted when request was sent by request()
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
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 deleteLater()
T & first()
QString scheme() const
static QString QGIS_VERSION
Definition: qgis.h:46
QString toLower() const
virtual void abort()=0
void insertProxyFactory(QNetworkProxyFactory *factory)
insert a factory into the proxy factories list
QUrl url() const
QVariant value(const QString &key, const QVariant &defaultValue) const
int sslPeerVerifyDepth() const
Number or SSL client&#39;s peer to verify in connections.
static QgsNetworkAccessManager * instance()
returns a pointer to the single instance
void insert(int i, const T &value)
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
QList< QNetworkProxy > systemProxyForQuery(const QNetworkProxyQuery &query)
const QgsAuthConfigSslServer getSslCertCustomConfigByHost(const QString &hostport)
Get an SSL certificate custom config by host:port.
void finished(QNetworkReply *reply)
bool toBool() const
void start(int msec)
void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)
void setUseSystemConfiguration(bool enable)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const
void requestAboutToBeCreated(QNetworkAccessManager::Operation, const QNetworkRequest &, QIODevice *)
qint64 maximumCacheSize() const
void setCache(QAbstractNetworkCache *cache)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() 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)
void setSslConfiguration(const QSslConfiguration &config)
Wrapper implementation of QNetworkDiskCache with all methods guarded by a mutex soly for internal use...
void setSingleShot(bool singleShot)
QByteArray toUtf8() const