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