QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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
18#include <cpl_http.h>
19#include <gdal.h>
20
22#include "qgslogger.h"
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
29 : mAuthCfg( authCfg )
30 , mFeedback( feedback )
31 , mThread( QThread::currentThread() )
32{
33 CPLHTTPPushFetchCallback( QgsCPLHTTPFetchOverrider::callback, this );
34}
35
37{
38 CPLHTTPPopFetchCallback();
39}
40
41
42CPLHTTPResult *QgsCPLHTTPFetchOverrider::callback( const char *pszURL,
43 CSLConstList papszOptions,
44 GDALProgressFunc /* pfnProgress */,
45 void * /*pProgressArg */,
46 CPLHTTPFetchWriteFunc pfnWrite,
47 void *pWriteArg,
48 void *pUserData )
49{
50 QgsCPLHTTPFetchOverrider *pThis = static_cast<QgsCPLHTTPFetchOverrider *>( pUserData );
51
52 auto psResult = static_cast<CPLHTTPResult *>( CPLCalloc( sizeof( CPLHTTPResult ), 1 ) );
53 if ( CSLFetchNameValue( papszOptions, "CLOSE_PERSISTENT" ) )
54 {
55 // CLOSE_PERSISTENT is a CPL trick to maintain a curl handle open over
56 // a series of CPLHTTPFetch() call to the same server.
57 // Just return a dummy result to acknowledge we 'processed' it
58 return psResult;
59 }
60
61 // Look for important options we don't handle yet
62 for ( const char *pszOption : { "FORM_FILE_PATH", "FORM_ITEM_COUNT" } )
63 {
64 if ( CSLFetchNameValue( papszOptions, pszOption ) )
65 {
66 QgsDebugError( u"Option %1 not handled"_s.arg( pszOption ) );
67 return nullptr;
68 }
69 }
70
71 if ( pThis->mFeedback && pThis->mFeedback->isCanceled() )
72 {
73 psResult->nStatus = 1;
74 psResult->pszErrBuf = CPLStrdup( "download interrupted by user" );
75 return psResult;
76 }
77
78 QgsBlockingNetworkRequest blockingRequest;
79 blockingRequest.setAuthCfg( pThis->mAuthCfg );
80
81 QNetworkRequest request( QString::fromUtf8( pszURL ) );
82 for ( const auto &keyValue : pThis->mAttributes )
83 {
84 request.setAttribute( keyValue.first, keyValue.second );
85 }
86
87 // Store request headers
88 const char *pszHeaders = CSLFetchNameValue( papszOptions, "HEADERS" );
89 if ( pszHeaders )
90 {
91 char **papszTokensHeaders = CSLTokenizeString2( pszHeaders, "\r\n", 0 );
92 for ( int i = 0; papszTokensHeaders[i] != nullptr; ++i )
93 {
94 char *pszKey = nullptr;
95 const char *pszValue = CPLParseNameValue( papszTokensHeaders[i], &pszKey );
96 if ( pszKey && pszValue )
97 {
98 request.setRawHeader(
99 QByteArray::fromStdString( pszKey ),
100 QByteArray::fromStdString( pszValue ) );
101 }
102 CPLFree( pszKey );
103 }
104 CSLDestroy( papszTokensHeaders );
105 }
106
107 constexpr bool forceRefresh = true;
108 const char *pszCustomRequest = CSLFetchNameValue( papszOptions, "CUSTOMREQUEST" );
109 const char *pszPostFields = CSLFetchNameValue( papszOptions, "POSTFIELDS" );
111 if ( pszPostFields )
112 {
113 if ( !pszCustomRequest || EQUAL( pszCustomRequest, "POST" ) )
114 {
115 errCode = blockingRequest.post( request,
116 QByteArray::fromStdString( pszPostFields ),
117 forceRefresh,
118 pThis->mFeedback );
119 }
120 else if ( EQUAL( pszCustomRequest, "PUT" ) )
121 {
122 errCode = blockingRequest.put( request,
123 QByteArray::fromStdString( pszPostFields ),
124 pThis->mFeedback );
125 }
126 else
127 {
128 QgsDebugError( u"Invalid CUSTOMREQUEST = %1 when POSTFIELDS is defined"_s.arg( pszCustomRequest ) );
129 return nullptr;
130 }
131 }
132 else
133 {
134 if ( !pszCustomRequest || EQUAL( pszCustomRequest, "GET" ) )
135 {
136 errCode = blockingRequest.get( request, forceRefresh, pThis->mFeedback, QgsBlockingNetworkRequest::RequestFlag::EmptyResponseIsValid );
137 }
138 else if ( EQUAL( pszCustomRequest, "HEAD" ) )
139 {
140 errCode = blockingRequest.head( request, forceRefresh, pThis->mFeedback );
141 }
142 else if ( EQUAL( pszCustomRequest, "DELETE" ) )
143 {
144 errCode = blockingRequest.deleteResource( request, pThis->mFeedback );
145 }
146 else
147 {
148 QgsDebugError( u"Invalid CUSTOMREQUEST = %1 when POSTFIELDS is not defined"_s.arg( pszCustomRequest ) );
149 return nullptr;
150 }
151 }
152 if ( errCode != QgsBlockingNetworkRequest::NoError )
153 {
154 psResult->nStatus = 1;
155 psResult->pszErrBuf = CPLStrdup( blockingRequest.errorMessage().toUtf8() );
156 return psResult;
157 }
158
159 const QgsNetworkReplyContent reply( blockingRequest.reply() );
160
161 // Store response headers
162 for ( const auto &pair : reply.rawHeaderPairs() )
163 {
164 if ( EQUAL( pair.first.toStdString().c_str(), "Content-Type" ) )
165 {
166 CPLFree( psResult->pszContentType );
167 psResult->pszContentType = CPLStrdup( pair.second.toStdString().c_str() );
168 }
169 psResult->papszHeaders = CSLAddNameValue(
170 psResult->papszHeaders,
171 pair.first.toStdString().c_str(),
172 pair.second.toStdString().c_str() );
173 }
174
175 // Process content
176 QByteArray content( reply.content() );
177
178 // Poor-man implementation of the pfnWrite mechanism which is supposed to be
179 // called on the fly as bytes are received
180 if ( pfnWrite )
181 {
182 if ( static_cast<int>( pfnWrite( content.data(), 1, content.size(), pWriteArg ) ) != content.size() )
183 {
184 psResult->nStatus = 1;
185 psResult->pszErrBuf = CPLStrdup( "download interrupted by user" );
186 return psResult;
187 }
188 }
189 else
190 {
191 psResult->nDataLen = static_cast<int>( content.size() );
192 psResult->pabyData = static_cast<GByte *>( CPLMalloc( psResult->nDataLen + 1 ) );
193 memcpy( psResult->pabyData, content.constData(), psResult->nDataLen );
194 psResult->pabyData[psResult->nDataLen] = 0;
195 }
196
197 return psResult;
198}
199
200void QgsCPLHTTPFetchOverrider::setAttribute( QNetworkRequest::Attribute code, const QVariant &value )
201{
202 mAttributes[code] = value;
203}
204
206{
207 mFeedback = feedback;
208}
209
211{
212 return mThread;
213}
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.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ EmptyResponseIsValid
Do not generate an error if getting an empty response (e.g. HTTP 204).
@ 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:55
#define QgsDebugError(str)
Definition qgslogger.h:59