QGIS API Documentation 3.99.0-Master (e9821da5c6b)
Loading...
Searching...
No Matches
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
20
21#include "qgsapplication.h"
22#include "qgsauthmanager.h"
23#include "qgsmessagelog.h"
26#include "qgsvariantutils.h"
27
28#include <QNetworkReply>
29#include <QString>
30#include <QTextCodec>
31
32#include "moc_qgsnetworkcontentfetcher.cpp"
33
34using namespace Qt::StringLiterals;
35
37{
38 if ( mReply && mReply->isRunning() )
39 {
40 //cancel running request
41 mReply->abort();
42 }
43
44}
45
46void QgsNetworkContentFetcher::fetchContent( const QUrl &url, const QString &authcfg, const QgsHttpHeaders &headers )
47{
48 QNetworkRequest req( url );
49 QgsSetRequestInitiatorClass( req, u"QgsNetworkContentFetcher"_s );
50
51 // Apply custom headers
52 headers.updateNetworkRequest( req );
53
54 fetchContent( req, authcfg );
55}
56
57void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
58{
59 QNetworkRequest request( r );
60 request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
61
62 mAuthCfg = authcfg;
63 if ( !mAuthCfg.isEmpty() )
64 {
66 }
67
68 mContentLoaded = false;
69 mIsCanceled = false;
70
71 if ( mReply )
72 {
73 //cancel any in progress requests
74 mReply->abort();
75 mReply.release()->deleteLater();
76 mReply = nullptr;
77 }
78
79 mReply.reset( QgsNetworkAccessManager::instance()->get( request ) );
80 if ( !mAuthCfg.isEmpty() )
81 {
82 QgsApplication::authManager()->updateNetworkReply( mReply.get(), mAuthCfg );
83 }
84 mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
85 connect( mReply.get(), &QNetworkReply::finished, this, [this] { contentLoaded(); } );
86 connect( mReply.get(), &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
87
88 auto onError = [this]( QNetworkReply::NetworkError code )
89 {
90 // could have been canceled in the meantime
91 if ( mReply )
92 emit errorOccurred( code, mReply->errorString() );
93 };
94
95 connect( mReply.get(), &QNetworkReply::errorOccurred, this, onError );
96}
97
99{
100 if ( !mContentLoaded )
101 {
102 return nullptr;
103 }
104
105 return mReply.get();
106}
107
112
114{
115 if ( !mContentLoaded || !mReply )
116 {
117 return QString();
118 }
119
120 QByteArray array = mReply->readAll();
121
122 //correctly encode reply as unicode
123 QTextCodec *codec = codecForHtml( array );
124 return codec->toUnicode( array );
125}
126
128{
129 mIsCanceled = true;
130
131 if ( mReply )
132 {
133 //cancel any in progress requests
134 mReply->abort();
135 mReply.release()->deleteLater();
136 mReply = nullptr;
137 }
138}
139
141{
142 return mIsCanceled;
143}
144
145QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
146{
147 //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
148 //see https://bugreports.qt.io/browse/QTBUG-41011
149 //so test for that ourselves
150
151 //basic check
152 QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
153 if ( codec )
154 {
155 return codec;
156 }
157
158 //check for meta charset tag
159 const QByteArray header = array.left( 1024 ).toLower();
160 int pos = header.indexOf( "meta charset=" );
161 if ( pos != -1 )
162 {
163 pos += int( strlen( "meta charset=" ) ) + 1;
164 const int pos2 = header.indexOf( '\"', pos );
165 const QByteArray cs = header.mid( pos, pos2 - pos );
166 codec = QTextCodec::codecForName( cs );
167 if ( codec )
168 {
169 return codec;
170 }
171 }
172
173 //fallback to QTextCodec::codecForHtml
174 codec = QTextCodec::codecForHtml( array, codec );
175 if ( codec )
176 {
177 return codec;
178 }
179
180 //no luck, default to utf-8
181 return QTextCodec::codecForName( "UTF-8" );
182}
183
184void QgsNetworkContentFetcher::contentLoaded( bool ok )
185{
186 Q_UNUSED( ok )
187
188 if ( mIsCanceled )
189 {
190 emit finished();
191 return;
192 }
193
194 if ( mReply->error() != QNetworkReply::NoError )
195 {
196 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
197 mContentLoaded = true;
198 emit finished();
199 return;
200 }
201
202 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
203 if ( QgsVariantUtils::isNull( redirect ) )
204 {
205 //no error or redirect, got target
206 const QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
207 if ( !QgsVariantUtils::isNull( status ) && status.toInt() >= 400 )
208 {
209 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
210 }
211 mContentLoaded = true;
212 emit finished();
213 return;
214 }
215
216 //redirect, so fetch redirect target
217 mReply.release()->deleteLater();
218 fetchContent( redirect.toUrl(), mAuthCfg );
219}
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,...
Implements simple HTTP header management.
bool updateNetworkRequest(QNetworkRequest &request) const
Updates a request by adding all the HTTP headers.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
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 fetchContent(const QUrl &url, const QString &authcfg=QString(), const QgsHttpHeaders &headers=QgsHttpHeaders())
Fetches content from a remote URL and handles redirects.
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.
static QString extractFilenameFromContentDispositionHeader(QNetworkReply *reply)
Extracts the filename component of the content disposition header from a network reply.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define QgsSetRequestInitiatorClass(request, _class)