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