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