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