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