QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsnetworkcontentfetcher.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsnetworkcontentfetcher.cpp
3  -------------------
4  begin : July, 2014
5  copyright : (C) 2014 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7 
8  ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
21 #include "qgsmessagelog.h"
22 #include "qgsapplication.h"
23 #include "qgsauthmanager.h"
24 #include <QNetworkReply>
25 #include <QTextCodec>
26 
28 {
29  if ( mReply && mReply->isRunning() )
30  {
31  //cancel running request
32  mReply->abort();
33  }
34  if ( mReply )
35  {
36  mReply->deleteLater();
37  }
38 }
39 
40 void QgsNetworkContentFetcher::fetchContent( const QUrl &url, const QString &authcfg )
41 {
42  QNetworkRequest req( url );
43  QgsSetRequestInitiatorClass( req, QStringLiteral( "QgsNetworkContentFetcher" ) );
44 
45  fetchContent( req, authcfg );
46 }
47 
48 void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
49 {
50  QNetworkRequest request( r );
51 
52  mAuthCfg = authcfg;
53  if ( !mAuthCfg.isEmpty() )
54  {
55  QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg );
56  }
57 
58  mContentLoaded = false;
59  mIsCanceled = false;
60 
61  if ( mReply )
62  {
63  //cancel any in progress requests
64  mReply->abort();
65  mReply->deleteLater();
66  mReply = nullptr;
67  }
68 
69  mReply = QgsNetworkAccessManager::instance()->get( request );
70  if ( !mAuthCfg.isEmpty() )
71  {
72  QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg );
73  }
74  mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
75  connect( mReply, &QNetworkReply::finished, this, [ = ] { contentLoaded(); } );
76  connect( mReply, &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
77 }
78 
80 {
81  if ( !mContentLoaded )
82  {
83  return nullptr;
84  }
85 
86  return mReply;
87 }
88 
90 {
91  if ( !mContentLoaded || !mReply )
92  {
93  return QString();
94  }
95 
96  QByteArray array = mReply->readAll();
97 
98  //correctly encode reply as unicode
99  QTextCodec *codec = codecForHtml( array );
100  return codec->toUnicode( array );
101 }
102 
104 {
105  mIsCanceled = true;
106 
107  if ( mReply )
108  {
109  //cancel any in progress requests
110  mReply->abort();
111  mReply->deleteLater();
112  mReply = nullptr;
113  }
114 }
115 
117 {
118  return mIsCanceled;
119 }
120 
121 QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
122 {
123  //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
124  //see https://bugreports.qt.io/browse/QTBUG-41011
125  //so test for that ourselves
126 
127  //basic check
128  QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
129  if ( codec )
130  {
131  return codec;
132  }
133 
134  //check for meta charset tag
135  QByteArray header = array.left( 1024 ).toLower();
136  int pos = header.indexOf( "meta charset=" );
137  if ( pos != -1 )
138  {
139  pos += int( strlen( "meta charset=" ) ) + 1;
140  int pos2 = header.indexOf( '\"', pos );
141  QByteArray cs = header.mid( pos, pos2 - pos );
142  codec = QTextCodec::codecForName( cs );
143  if ( codec )
144  {
145  return codec;
146  }
147  }
148 
149  //fallback to QTextCodec::codecForHtml
150  codec = QTextCodec::codecForHtml( array, codec );
151  if ( codec )
152  {
153  return codec;
154  }
155 
156  //no luck, default to utf-8
157  return QTextCodec::codecForName( "UTF-8" );
158 }
159 
160 void QgsNetworkContentFetcher::contentLoaded( bool ok )
161 {
162  Q_UNUSED( ok )
163 
164  if ( mIsCanceled )
165  {
166  emit finished();
167  return;
168  }
169 
170  if ( mReply->error() != QNetworkReply::NoError )
171  {
172  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
173  mContentLoaded = true;
174  emit finished();
175  return;
176  }
177 
178  QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
179  if ( redirect.isNull() )
180  {
181  //no error or redirect, got target
182  QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
183  if ( !status.isNull() && status.toInt() >= 400 )
184  {
185  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
186  }
187  mContentLoaded = true;
188  emit finished();
189  return;
190  }
191 
192  //redirect, so fetch redirect target
193  mReply->deleteLater();
194  fetchContent( redirect.toUrl(), mAuthCfg );
195 }
196 
197 
198 
199 
#define QgsSetRequestInitiatorClass(request, _class)
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Emitted when data is received.
bool wasCanceled() const
Returns true if the fetching was canceled.
QNetworkReply * reply()
Returns a reference to the network reply.
QString contentAsString() const
Returns the fetched content as a string.
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 finished()
Emitted when content has loaded.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
void cancel()
Cancels any ongoing request.
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.
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkReply with an authentication config (used to skip known SSL errors...