QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "qgsvariantutils.h"
25#include <QNetworkReply>
26#include <QTextCodec>
27
29{
30 if ( mReply && mReply->isRunning() )
31 {
32 //cancel running request
33 mReply->abort();
34 }
35 delete mReply;
36}
37
38void QgsNetworkContentFetcher::fetchContent( const QUrl &url, const QString &authcfg )
39{
40 QNetworkRequest req( url );
41 QgsSetRequestInitiatorClass( req, QStringLiteral( "QgsNetworkContentFetcher" ) );
42
43 fetchContent( req, authcfg );
44}
45
46void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
47{
48 QNetworkRequest request( r );
49
50 mAuthCfg = authcfg;
51 if ( !mAuthCfg.isEmpty() )
52 {
54 }
55
56 mContentLoaded = false;
57 mIsCanceled = false;
58
59 if ( mReply )
60 {
61 //cancel any in progress requests
62 mReply->abort();
63 mReply->deleteLater();
64 mReply = nullptr;
65 }
66
67 mReply = QgsNetworkAccessManager::instance()->get( request );
68 if ( !mAuthCfg.isEmpty() )
69 {
71 }
72 mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
73 connect( mReply, &QNetworkReply::finished, this, [ = ] { contentLoaded(); } );
74 connect( mReply, &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
75
76 auto onError = [ = ]( QNetworkReply::NetworkError code )
77 {
78 // could have been canceled in the meantime
79 if ( mReply )
80 emit errorOccurred( code, mReply->errorString() );
81 };
82
83#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
84 connect( mReply, qOverload<QNetworkReply::NetworkError>( &QNetworkReply::error ), this, onError );
85#else
86 connect( mReply, &QNetworkReply::errorOccurred, this, onError );
87#endif
88}
89
91{
92 if ( !mContentLoaded )
93 {
94 return nullptr;
95 }
96
97 return mReply;
98}
99
101{
103}
104
106{
107 if ( !mContentLoaded || !mReply )
108 {
109 return QString();
110 }
111
112 QByteArray array = mReply->readAll();
113
114 //correctly encode reply as unicode
115 QTextCodec *codec = codecForHtml( array );
116 return codec->toUnicode( array );
117}
118
120{
121 mIsCanceled = true;
122
123 if ( mReply )
124 {
125 //cancel any in progress requests
126 mReply->abort();
127 mReply->deleteLater();
128 mReply = nullptr;
129 }
130}
131
133{
134 return mIsCanceled;
135}
136
137QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
138{
139 //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
140 //see https://bugreports.qt.io/browse/QTBUG-41011
141 //so test for that ourselves
142
143 //basic check
144 QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
145 if ( codec )
146 {
147 return codec;
148 }
149
150 //check for meta charset tag
151 const QByteArray header = array.left( 1024 ).toLower();
152 int pos = header.indexOf( "meta charset=" );
153 if ( pos != -1 )
154 {
155 pos += int( strlen( "meta charset=" ) ) + 1;
156 const int pos2 = header.indexOf( '\"', pos );
157 const QByteArray cs = header.mid( pos, pos2 - pos );
158 codec = QTextCodec::codecForName( cs );
159 if ( codec )
160 {
161 return codec;
162 }
163 }
164
165 //fallback to QTextCodec::codecForHtml
166 codec = QTextCodec::codecForHtml( array, codec );
167 if ( codec )
168 {
169 return codec;
170 }
171
172 //no luck, default to utf-8
173 return QTextCodec::codecForName( "UTF-8" );
174}
175
176void QgsNetworkContentFetcher::contentLoaded( bool ok )
177{
178 Q_UNUSED( ok )
179
180 if ( mIsCanceled )
181 {
182 emit finished();
183 return;
184 }
185
186 if ( mReply->error() != QNetworkReply::NoError )
187 {
188 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
189 mContentLoaded = true;
190 emit finished();
191 return;
192 }
193
194 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
195 if ( QgsVariantUtils::isNull( redirect ) )
196 {
197 //no error or redirect, got target
198 const QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
199 if ( !QgsVariantUtils::isNull( status ) && status.toInt() >= 400 )
200 {
201 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
202 }
203 mContentLoaded = true;
204 emit finished();
205 return;
206 }
207
208 //redirect, so fetch redirect target
209 mReply->deleteLater();
210 fetchContent( redirect.toUrl(), mAuthCfg );
211}
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.
QString contentDispositionFilename() const
Returns the associated filename from the reply's content disposition header, if present.
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.
static QString extractFilenameFromContentDispositionHeader(QNetworkReply *reply)
Extracts the filename component of the content disposition header from a network reply.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
#define QgsSetRequestInitiatorClass(request, _class)