QGIS API Documentation  3.27.0-Master (597e8eebd4)
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 
37 void 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 
45 void QgsNetworkContentFetcher::fetchContent( const QNetworkRequest &r, const QString &authcfg )
46 {
47  QNetworkRequest request( r );
48 
49  mAuthCfg = authcfg;
50  if ( !mAuthCfg.isEmpty() )
51  {
52  QgsApplication::authManager()->updateNetworkRequest( request, mAuthCfg );
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  {
69  QgsApplication::authManager()->updateNetworkReply( mReply, mAuthCfg );
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 {
101  return mReply ? QgsNetworkReplyContent::extractFilenameFromContentDispositionHeader( mReply ) : QString();
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 
136 QTextCodec *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 
175 void 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)