QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
45void QgsNetworkContentFetcher::fetchContent( const QUrl &url, const QString &authcfg, const QgsHttpHeaders &headers )
46{
47 QNetworkRequest req( url );
48 QgsSetRequestInitiatorClass( req, u"QgsNetworkContentFetcher"_s );
49
50 // Apply custom headers
51 headers.updateNetworkRequest( req );
52
53 fetchContent( req, authcfg );
54}
55
56void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
57{
58 QNetworkRequest request( r );
59 request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy );
60
61 mAuthCfg = authcfg;
62 if ( !mAuthCfg.isEmpty() )
63 {
65 }
66
67 mContentLoaded = false;
68 mIsCanceled = false;
69
70 if ( mReply )
71 {
72 //cancel any in progress requests
73 mReply->abort();
74 mReply.release()->deleteLater();
75 mReply = nullptr;
76 }
77
78 mReply.reset( QgsNetworkAccessManager::instance()->get( request ) );
79 if ( !mAuthCfg.isEmpty() )
80 {
81 QgsApplication::authManager()->updateNetworkReply( mReply.get(), mAuthCfg );
82 }
83 mReply->setParent( nullptr ); // we don't want thread locale QgsNetworkAccessManagers to delete the reply - we want ownership of it to belong to this object
84 connect( mReply.get(), &QNetworkReply::finished, this, [this] { contentLoaded(); } );
85 connect( mReply.get(), &QNetworkReply::downloadProgress, this, &QgsNetworkContentFetcher::downloadProgress );
86
87 auto onError = [this]( QNetworkReply::NetworkError code ) {
88 // could have been canceled in the meantime
89 if ( mReply )
90 emit errorOccurred( code, mReply->errorString() );
91 };
92
93 connect( mReply.get(), &QNetworkReply::errorOccurred, this, onError );
94}
95
97{
98 if ( !mContentLoaded )
99 {
100 return nullptr;
101 }
102
103 return mReply.get();
104}
105
110
112{
113 if ( !mContentLoaded || !mReply )
114 {
115 return QString();
116 }
117
118 QByteArray array = mReply->readAll();
119
120 //correctly encode reply as unicode
121 QTextCodec *codec = codecForHtml( array );
122 return codec->toUnicode( array );
123}
124
126{
127 mIsCanceled = true;
128
129 if ( mReply )
130 {
131 //cancel any in progress requests
132 mReply->abort();
133 mReply.release()->deleteLater();
134 mReply = nullptr;
135 }
136}
137
139{
140 return mIsCanceled;
141}
142
143QTextCodec *QgsNetworkContentFetcher::codecForHtml( QByteArray &array ) const
144{
145 //QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
146 //see https://bugreports.qt.io/browse/QTBUG-41011
147 //so test for that ourselves
148
149 //basic check
150 QTextCodec *codec = QTextCodec::codecForUtfText( array, nullptr );
151 if ( codec )
152 {
153 return codec;
154 }
155
156 //check for meta charset tag
157 const QByteArray header = array.left( 1024 ).toLower();
158 int pos = header.indexOf( "meta charset=" );
159 if ( pos != -1 )
160 {
161 pos += int( strlen( "meta charset=" ) ) + 1;
162 const int pos2 = header.indexOf( '\"', pos );
163 const QByteArray cs = header.mid( pos, pos2 - pos );
164 codec = QTextCodec::codecForName( cs );
165 if ( codec )
166 {
167 return codec;
168 }
169 }
170
171 //fallback to QTextCodec::codecForHtml
172 codec = QTextCodec::codecForHtml( array, codec );
173 if ( codec )
174 {
175 return codec;
176 }
177
178 //no luck, default to utf-8
179 return QTextCodec::codecForName( "UTF-8" );
180}
181
182void QgsNetworkContentFetcher::contentLoaded( bool ok )
183{
184 Q_UNUSED( ok )
185
186 if ( mIsCanceled )
187 {
188 emit finished();
189 return;
190 }
191
192 if ( mReply->error() != QNetworkReply::NoError )
193 {
194 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), mReply->errorString() ) );
195 mContentLoaded = true;
196 emit finished();
197 return;
198 }
199
200 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
201 if ( QgsVariantUtils::isNull( redirect ) )
202 {
203 //no error or redirect, got target
204 const QVariant status = mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
205 if ( !QgsVariantUtils::isNull( status ) && status.toInt() >= 400 )
206 {
207 QgsMessageLog::logMessage( tr( "HTTP fetch %1 failed with error %2" ).arg( mReply->url().toString(), status.toString() ) );
208 }
209 mContentLoaded = true;
210 emit finished();
211 return;
212 }
213
214 //redirect, so fetch redirect target
215 mReply.release()->deleteLater();
216 fetchContent( redirect.toUrl(), mAuthCfg );
217}
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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)