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