QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgscplhttpfetchoverrider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscplhttpfetchoverrider.cpp
3 ----------------------------
4 begin : September 2020
5 copyright : (C) 2020 by Even Rouault
6 email : even.rouault at spatialys.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17#include "qgslogger.h"
19
20#include "cpl_http.h"
21#include "gdal.h"
22
24 : mAuthCfg( authCfg )
25 , mFeedback( feedback )
26 , mThread( QThread::currentThread() )
27{
28 CPLHTTPPushFetchCallback( QgsCPLHTTPFetchOverrider::callback, this );
29}
30
32{
33 CPLHTTPPopFetchCallback();
34}
35
36
37CPLHTTPResult *QgsCPLHTTPFetchOverrider::callback( const char *pszURL,
38 CSLConstList papszOptions,
39 GDALProgressFunc /* pfnProgress */,
40 void * /*pProgressArg */,
41 CPLHTTPFetchWriteFunc pfnWrite,
42 void *pWriteArg,
43 void *pUserData )
44{
45 QgsCPLHTTPFetchOverrider *pThis = static_cast<QgsCPLHTTPFetchOverrider *>( pUserData );
46
47 auto psResult = static_cast<CPLHTTPResult *>( CPLCalloc( sizeof( CPLHTTPResult ), 1 ) );
48 if ( CSLFetchNameValue( papszOptions, "CLOSE_PERSISTENT" ) )
49 {
50 // CLOSE_PERSISTENT is a CPL trick to maintain a curl handle open over
51 // a series of CPLHTTPFetch() call to the same server.
52 // Just return a dummy result to acknowledge we 'processed' it
53 return psResult;
54 }
55
56 // Look for important options we don't handle yet
57 for ( const char *pszOption : { "FORM_FILE_PATH", "FORM_ITEM_COUNT" } )
58 {
59 if ( CSLFetchNameValue( papszOptions, pszOption ) )
60 {
61 QgsDebugError( QStringLiteral( "Option %1 not handled" ).arg( pszOption ) );
62 return nullptr;
63 }
64 }
65
66 if ( pThis->mFeedback && pThis->mFeedback->isCanceled() )
67 {
68 psResult->nStatus = 1;
69 psResult->pszErrBuf = CPLStrdup( "download interrupted by user" );
70 return psResult;
71 }
72
73 QgsBlockingNetworkRequest blockingRequest;
74 blockingRequest.setAuthCfg( pThis->mAuthCfg );
75
76 QNetworkRequest request( QString::fromUtf8( pszURL ) );
77 for ( const auto &keyValue : pThis->mAttributes )
78 {
79 request.setAttribute( keyValue.first, keyValue.second );
80 }
81
82 // Store request headers
83 const char *pszHeaders = CSLFetchNameValue( papszOptions, "HEADERS" );
84 if ( pszHeaders )
85 {
86 char **papszTokensHeaders = CSLTokenizeString2( pszHeaders, "\r\n", 0 );
87 for ( int i = 0; papszTokensHeaders[i] != nullptr; ++i )
88 {
89 char *pszKey = nullptr;
90 const char *pszValue = CPLParseNameValue( papszTokensHeaders[i], &pszKey );
91 if ( pszKey && pszValue )
92 {
93 request.setRawHeader(
94 QByteArray::fromStdString( pszKey ),
95 QByteArray::fromStdString( pszValue ) );
96 }
97 CPLFree( pszKey );
98 }
99 CSLDestroy( papszTokensHeaders );
100 }
101
102 constexpr bool forceRefresh = true;
103 const char *pszCustomRequest = CSLFetchNameValue( papszOptions, "CUSTOMREQUEST" );
104 const char *pszPostFields = CSLFetchNameValue( papszOptions, "POSTFIELDS" );
106 if ( pszPostFields )
107 {
108 if ( pszCustomRequest == nullptr || EQUAL( pszCustomRequest, "POST" ) )
109 {
110 errCode = blockingRequest.post( request,
111 QByteArray::fromStdString( pszPostFields ),
112 forceRefresh,
113 pThis->mFeedback );
114 }
115 else if ( EQUAL( pszCustomRequest, "PUT" ) )
116 {
117 errCode = blockingRequest.put( request,
118 QByteArray::fromStdString( pszPostFields ),
119 pThis->mFeedback );
120 }
121 else
122 {
123 QgsDebugError( QStringLiteral( "Invalid CUSTOMREQUEST = %1 when POSTFIELDS is defined" ).arg( pszCustomRequest ) );
124 return nullptr;
125 }
126 }
127 else
128 {
129 if ( pszCustomRequest == nullptr || EQUAL( pszCustomRequest, "GET" ) )
130 {
131 errCode = blockingRequest.get( request, forceRefresh, pThis->mFeedback );
132 }
133 else if ( EQUAL( pszCustomRequest, "HEAD" ) )
134 {
135 errCode = blockingRequest.head( request, forceRefresh, pThis->mFeedback );
136 }
137 else if ( EQUAL( pszCustomRequest, "DELETE" ) )
138 {
139 errCode = blockingRequest.deleteResource( request, pThis->mFeedback );
140 }
141 else
142 {
143 QgsDebugError( QStringLiteral( "Invalid CUSTOMREQUEST = %1 when POSTFIELDS is not defined" ).arg( pszCustomRequest ) );
144 return nullptr;
145 }
146 }
147 if ( errCode != QgsBlockingNetworkRequest::NoError )
148 {
149 psResult->nStatus = 1;
150 psResult->pszErrBuf = CPLStrdup( blockingRequest.errorMessage().toUtf8() );
151 return psResult;
152 }
153
154 const QgsNetworkReplyContent reply( blockingRequest.reply() );
155
156 // Store response headers
157 for ( const auto &pair : reply.rawHeaderPairs() )
158 {
159 if ( EQUAL( pair.first.toStdString().c_str(), "Content-Type" ) )
160 {
161 CPLFree( psResult->pszContentType );
162 psResult->pszContentType = CPLStrdup( pair.second.toStdString().c_str() );
163 }
164 psResult->papszHeaders = CSLAddNameValue(
165 psResult->papszHeaders,
166 pair.first.toStdString().c_str(),
167 pair.second.toStdString().c_str() );
168 }
169
170 // Process content
171 QByteArray content( reply.content() );
172
173 // Poor-man implementation of the pfnWrite mechanism which is supposed to be
174 // called on the fly as bytes are received
175 if ( pfnWrite )
176 {
177 if ( static_cast<int>( pfnWrite( content.data(), 1, content.size(), pWriteArg ) ) != content.size() )
178 {
179 psResult->nStatus = 1;
180 psResult->pszErrBuf = CPLStrdup( "download interrupted by user" );
181 return psResult;
182 }
183 }
184 else
185 {
186 psResult->nDataLen = static_cast<int>( content.size() );
187 psResult->pabyData = static_cast<GByte *>( CPLMalloc( psResult->nDataLen + 1 ) );
188 memcpy( psResult->pabyData, content.constData(), psResult->nDataLen );
189 psResult->pabyData[psResult->nDataLen] = 0;
190 }
191
192 return psResult;
193}
194
195void QgsCPLHTTPFetchOverrider::setAttribute( QNetworkRequest::Attribute code, const QVariant &value )
196{
197 mAttributes[code] = value;
198}
199
201{
202 mFeedback = feedback;
203}
204
206{
207 return mThread;
208}
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
ErrorCode put(QNetworkRequest &request, QIODevice *data, QgsFeedback *feedback=nullptr)
Performs a "put" operation on the specified request, using the given data.
ErrorCode head(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "head" operation on the specified request.
ErrorCode post(QNetworkRequest &request, QIODevice *data, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "post" operation on the specified request, using the given data.
ErrorCode deleteResource(QNetworkRequest &request, QgsFeedback *feedback=nullptr)
Performs a "delete" operation on the specified request.
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Utility class to redirect GDAL's CPL HTTP calls through QgsBlockingNetworkRequest.
QgsCPLHTTPFetchOverrider(const QString &authCfg=QString(), QgsFeedback *feedback=nullptr)
Installs the redirection for the current thread.
void setAttribute(QNetworkRequest::Attribute code, const QVariant &value)
Define attribute that must be forwarded to the actual QNetworkRequest.
QThread * thread() const
Returns the thread associated with the overrider.
void setFeedback(QgsFeedback *feedback)
Sets the feedback cancellation object for the redirection.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
#define QgsDebugError(str)
Definition: qgslogger.h:38