QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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  delete mReply;
35 }
36 
37 void QgsNetworkContentFetcher::fetchContent( const QUrl &url, const QString &authcfg )
38 {
39  QNetworkRequest req( url );
40  QgsSetRequestInitiatorClass( req, QStringLiteral( "QgsNetworkContentFetcher" ) );
41 
42  fetchContent( req, authcfg );
43 }
44 
45 void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
46 {
47  QNetworkRequest request( r );
48 
49  mAuthCfg = authcfg;
50  if ( !mAuthCfg.isEmpty() )
51  {
52  QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg );
53  }
54 
55  mContentLoaded = false;
56  mIsCanceled = false;
57 
58  if ( mReply )
59  {
60  //cancel any in progress requests
61  mReply->abort();
62  mReply->deleteLater();
63  mReply = nullptr;
64  }
65 
66  mReply = QgsNetworkAccessManager::instance()->get( request );
67  if ( !mAuthCfg.isEmpty() )
68  {
69  QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg );
70  }
71  mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
72  connect( mReply, &QNetworkReply::finished, this, [ = ] { contentLoaded(); } );
73  connect( mReply, &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
74 
75  auto onError = [ = ]( QNetworkReply::NetworkError code )
76  {
77  // could have been canceled in the meantime
78  if ( mReply )
79  emit errorOccurred( code, mReply->errorString() );
80  };
81 
82 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
83  connect( mReply, qOverload<QNetworkReply::NetworkError>( &QNetworkReply::error ), this, onError );
84 #else
85  connect( mReply, &QNetworkReply::errorOccurred, this, onError );
86 #endif
87 }
88 
90 {
91  if ( !mContentLoaded )
92  {
93  return nullptr;
94  }
95 
96  return mReply;
97 }
98 
100 {
101  if ( !mContentLoaded || !mReply )
102  {
103  return QString();
104  }
105 
106  QByteArray array = mReply->readAll();
107 
108  //correctly encode reply as unicode
109  QTextCodec *codec = codecForHtml( array );
110  return codec->toUnicode( array );
111 }
112 
114 {
115  mIsCanceled = true;
116 
117  if ( mReply )
118  {
119  //cancel any in progress requests
120  mReply->abort();
121  mReply->deleteLater();
122  mReply = nullptr;
123  }
124 }
125 
127 {
128  return mIsCanceled;
129 }
130 
131 QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
132 {
133  //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
134  //see https://bugreports.qt.io/browse/QTBUG-41011
135  //so test for that ourselves
136 
137  //basic check
138  QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
139  if ( codec )
140  {
141  return codec;
142  }
143 
144  //check for meta charset tag
145  const QByteArray header = array.left( 1024 ).toLower();
146  int pos = header.indexOf( "meta charset=" );
147  if ( pos != -1 )
148  {
149  pos += int( strlen( "meta charset=" ) ) + 1;
150  const int pos2 = header.indexOf( '\"', pos );
151  const QByteArray cs = header.mid( pos, pos2 - pos );
152  codec = QTextCodec::codecForName( cs );
153  if ( codec )
154  {
155  return codec;
156  }
157  }
158 
159  //fallback to QTextCodec::codecForHtml
160  codec = QTextCodec::codecForHtml( array, codec );
161  if ( codec )
162  {
163  return codec;
164  }
165 
166  //no luck, default to utf-8
167  return QTextCodec::codecForName( "UTF-8" );
168 }
169 
170 void QgsNetworkContentFetcher::contentLoaded( bool ok )
171 {
172  Q_UNUSED( ok )
173 
174  if ( mIsCanceled )
175  {
176  emit finished();
177  return;
178  }
179 
180  if ( mReply->error() != QNetworkReply::NoError )
181  {
182  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
183  mContentLoaded = true;
184  emit finished();
185  return;
186  }
187 
188  const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
189  if ( redirect.isNull() )
190  {
191  //no error or redirect, got target
192  const QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
193  if ( !status.isNull() && status.toInt() >= 400 )
194  {
195  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
196  }
197  mContentLoaded = true;
198  emit finished();
199  return;
200  }
201 
202  //redirect, so fetch redirect target
203  mReply->deleteLater();
204  fetchContent( redirect.toUrl(), mAuthCfg );
205 }
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
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,...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
void finished()
Emitted when content has loaded.
bool wasCanceled() const
Returns true if the fetching was canceled.
void errorOccurred(QNetworkReply::NetworkError code, const QString &errorMsg)
Emitted when an error with code error occurred while processing the request errorMsg is a textual des...
void cancel()
Cancels any ongoing request.
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Emitted when data is received.
QNetworkReply * reply()
Returns a reference to the network reply.
QString contentAsString() const
Returns the fetched content as a string.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
#define QgsSetRequestInitiatorClass(request, _class)