QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
77 {
78  if ( !mContentLoaded )
79  {
80  return nullptr;
81  }
82 
83  return mReply;
84 }
85 
87 {
88  if ( !mContentLoaded || !mReply )
89  {
90  return QString();
91  }
92 
93  QByteArray array = mReply->readAll();
94 
95  //correctly encode reply as unicode
96  QTextCodec *codec = codecForHtml( array );
97  return codec->toUnicode( array );
98 }
99 
101 {
102  mIsCanceled = true;
103 
104  if ( mReply )
105  {
106  //cancel any in progress requests
107  mReply->abort();
108  mReply->deleteLater();
109  mReply = nullptr;
110  }
111 }
112 
114 {
115  return mIsCanceled;
116 }
117 
118 QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
119 {
120  //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
121  //see https://bugreports.qt.io/browse/QTBUG-41011
122  //so test for that ourselves
123 
124  //basic check
125  QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
126  if ( codec )
127  {
128  return codec;
129  }
130 
131  //check for meta charset tag
132  QByteArray header = array.left( 1024 ).toLower();
133  int pos = header.indexOf( "meta charset=" );
134  if ( pos != -1 )
135  {
136  pos += int( strlen( "meta charset=" ) ) + 1;
137  int pos2 = header.indexOf( '\"', pos );
138  QByteArray cs = header.mid( pos, pos2 - pos );
139  codec = QTextCodec::codecForName( cs );
140  if ( codec )
141  {
142  return codec;
143  }
144  }
145 
146  //fallback to QTextCodec::codecForHtml
147  codec = QTextCodec::codecForHtml( array, codec );
148  if ( codec )
149  {
150  return codec;
151  }
152 
153  //no luck, default to utf-8
154  return QTextCodec::codecForName( "UTF-8" );
155 }
156 
157 void QgsNetworkContentFetcher::contentLoaded( bool ok )
158 {
159  Q_UNUSED( ok )
160 
161  if ( mIsCanceled )
162  {
163  emit finished();
164  return;
165  }
166 
167  if ( mReply->error() != QNetworkReply::NoError )
168  {
169  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
170  mContentLoaded = true;
171  emit finished();
172  return;
173  }
174 
175  QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
176  if ( redirect.isNull() )
177  {
178  //no error or redirect, got target
179  QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
180  if ( !status.isNull() && status.toInt() >= 400 )
181  {
182  QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
183  }
184  mContentLoaded = true;
185  emit finished();
186  return;
187  }
188 
189  //redirect, so fetch redirect target
190  mReply->deleteLater();
191  fetchContent( redirect.toUrl(), mAuthCfg );
192 }
193 
194 
195 
196 
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 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)