1 /******************************************************************************
2  *
3  * Project:  CPL - Common Portability Library
4  * Purpose:  Implement VSI large file api for WebHDFS REST API
5  * Author:   Even Rouault, even.rouault at spatialys.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2018, Even Rouault <even.rouault at spatialys.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "cpl_port.h"
30 #include "cpl_http.h"
31 #include "cpl_json.h"
32 #include "cpl_vsil_curl_priv.h"
33 #include "cpl_vsil_curl_class.h"
34 
35 #include <errno.h>
36 
37 #include <algorithm>
38 #include <set>
39 #include <map>
40 #include <memory>
41 
42 #include "cpl_alibaba_oss.h"
43 
44 CPL_CVSID("$Id: cpl_vsil_webhdfs.cpp 818288a58e026803402172eaa5a53bcc31ebf7aa 2021-03-14 17:20:00 +0300 drons $")
45 
46 #ifndef HAVE_CURL
47 
VSIInstallWebHdfsHandler(void)48 void VSIInstallWebHdfsHandler( void )
49 {
50     // Not supported
51 }
52 
53 
54 #else
55 
56 #if !CURL_AT_LEAST_VERSION(7,18,2)
57 // Needed for CURLINFO_REDIRECT_URL
58 #error Need libcurl version 7.18.2 or newer
59 #endif
60 
61 //! @cond Doxygen_Suppress
62 #ifndef DOXYGEN_SKIP
63 
64 #define ENABLE_DEBUG 0
65 
66 namespace cpl {
67 
68 
69 
70 /************************************************************************/
71 /*                         VSIWebHDFSFSHandler                          */
72 /************************************************************************/
73 
74 class VSIWebHDFSFSHandler final : public VSICurlFilesystemHandler
75 {
76     CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSFSHandler)
77 
78 protected:
79         VSICurlHandle* CreateFileHandle( const char* pszFilename ) override;
80 
81         int HasOptimizedReadMultiRange( const char* /* pszPath */ )
82             override { return false; }
83 
84         char** GetFileList(const char *pszFilename,
85                                int nMaxFiles,
86                                bool* pbGotFileList) override;
87 
88         CPLString GetURLFromFilename( const CPLString& osFilename ) override;
89 
90 public:
91         VSIWebHDFSFSHandler() = default;
92         ~VSIWebHDFSFSHandler() override = default;
93 
94         VSIVirtualHandle *Open( const char *pszFilename,
95                                 const char *pszAccess,
96                                 bool bSetError,
97                                 CSLConstList papszOptions ) override;
98         int Unlink( const char *pszFilename ) override;
99         int Rmdir( const char *pszFilename ) override;
100         int Mkdir( const char *pszDirname, long nMode ) override;
101 
102         CPLString GetFSPrefix() const override { return "/vsiwebhdfs/"; }
103 
104         const char* GetOptions() override;
105 };
106 
107 /************************************************************************/
108 /*                            VSIWebHDFSHandle                          */
109 /************************************************************************/
110 
111 class VSIWebHDFSHandle final : public VSICurlHandle
112 {
113     CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSHandle)
114 
115     CPLString       m_osDataNodeHost{};
116     CPLString       m_osUsernameParam{};
117     CPLString       m_osDelegationParam{};
118 
119    std::string      DownloadRegion(vsi_l_offset startOffset, int nBlocks) override;
120 
121   public:
122     VSIWebHDFSHandle( VSIWebHDFSFSHandler* poFS,
123                       const char* pszFilename, const char* pszURL );
124     ~VSIWebHDFSHandle() override = default;
125 
126     int ReadMultiRange( int nRanges, void ** ppData,
127                         const vsi_l_offset* panOffsets,
128                         const size_t* panSizes ) override {
129         return VSIVirtualHandle::ReadMultiRange( nRanges,  ppData,
130                                                  panOffsets, panSizes );
131     }
132 
133     vsi_l_offset GetFileSize( bool bSetError ) override;
134 };
135 
136 /************************************************************************/
137 /*                           PatchWebHDFSUrl()                          */
138 /************************************************************************/
139 
140 static CPLString PatchWebHDFSUrl(const CPLString& osURLIn,
141                                  const CPLString& osNewHost)
142 {
143     CPLString osURL(osURLIn);
144     size_t nStart = 0;
145     if( osURL.find("http://") == 0 )
146         nStart = strlen("http://");
147     else if( osURL.find("https://") == 0 )
148         nStart = strlen("https://");
149     if( nStart )
150     {
151         size_t nHostEnd = osURL.find(':', nStart);
152         if( nHostEnd != std::string::npos )
153         {
154             osURL = osURL.substr(0, nStart) + osNewHost +
155                     osURL.substr(nHostEnd);
156         }
157     }
158     return osURL;
159 }
160 
161 /************************************************************************/
162 /*                       GetWebHDFSDataNodeHost()                       */
163 /************************************************************************/
164 
165 static CPLString GetWebHDFSDataNodeHost()
166 {
167     CPLString osDataNodeHost = CPLGetConfigOption("WEBHDFS_DATANODE_HOST", "");
168     return osDataNodeHost;
169 }
170 
171 /************************************************************************/
172 /*                         VSIWebHDFSWriteHandle                        */
173 /************************************************************************/
174 
175 class VSIWebHDFSWriteHandle final : public VSIAppendWriteHandle
176 {
177     CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSWriteHandle)
178 
179     CPLString           m_osURL{};
180     CPLString           m_osDataNodeHost{};
181     CPLString           m_osUsernameParam{};
182     CPLString           m_osDelegationParam{};
183 
184     bool                Send(bool bIsLastBlock) override;
185     bool                CreateFile();
186     bool                Append();
187 
188     void                InvalidateParentDirectory();
189 
190     public:
191         VSIWebHDFSWriteHandle( VSIWebHDFSFSHandler* poFS,
192                                const char* pszFilename );
193         virtual ~VSIWebHDFSWriteHandle();
194 };
195 
196 /************************************************************************/
197 /*                        GetWebHDFSBufferSize()                        */
198 /************************************************************************/
199 
200 static int GetWebHDFSBufferSize()
201 {
202     int nBufferSize;
203     int nChunkSizeMB = atoi(CPLGetConfigOption("VSIWEBHDFS_SIZE", "4"));
204     if( nChunkSizeMB <= 0 || nChunkSizeMB > 1000 )
205         nBufferSize = 4 * 1024 * 1024;
206     else
207         nBufferSize = nChunkSizeMB * 1024 * 1024;
208 
209     // For testing only !
210     const char* pszChunkSizeBytes =
211         CPLGetConfigOption("VSIWEBHDFS_SIZE_BYTES", nullptr);
212     if( pszChunkSizeBytes )
213         nBufferSize = atoi(pszChunkSizeBytes);
214     if( nBufferSize <= 0 || nBufferSize > 1000 * 1024 * 1024 )
215         nBufferSize = 4 * 1024 * 1024;
216     return nBufferSize;
217 }
218 
219 /************************************************************************/
220 /*                      VSIWebHDFSWriteHandle()                         */
221 /************************************************************************/
222 
223 VSIWebHDFSWriteHandle::VSIWebHDFSWriteHandle( VSIWebHDFSFSHandler* poFS,
224                                               const char* pszFilename) :
225         VSIAppendWriteHandle(poFS, poFS->GetFSPrefix(), pszFilename,
226                              GetWebHDFSBufferSize()),
227         m_osURL( pszFilename + poFS->GetFSPrefix().size() ),
228         m_osDataNodeHost( GetWebHDFSDataNodeHost() )
229 {
230     // cppcheck-suppress useInitializationList
231     m_osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
232     if( !m_osUsernameParam.empty() )
233         m_osUsernameParam = "&user.name=" + m_osUsernameParam;
234     m_osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
235     if( !m_osDelegationParam.empty() )
236         m_osDelegationParam = "&delegation=" + m_osDelegationParam;
237 
238     if( m_pabyBuffer != nullptr && !CreateFile() )
239     {
240         CPLFree(m_pabyBuffer);
241         m_pabyBuffer = nullptr;
242     }
243 }
244 
245 /************************************************************************/
246 /*                     ~VSIWebHDFSWriteHandle()                         */
247 /************************************************************************/
248 
249 VSIWebHDFSWriteHandle::~VSIWebHDFSWriteHandle()
250 {
251     Close();
252 }
253 
254 /************************************************************************/
255 /*                    InvalidateParentDirectory()                       */
256 /************************************************************************/
257 
258 void VSIWebHDFSWriteHandle::InvalidateParentDirectory()
259 {
260     m_poFS->InvalidateCachedData(m_osURL);
261 
262     CPLString osFilenameWithoutSlash(m_osFilename);
263     if( !osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/' )
264         osFilenameWithoutSlash.resize( osFilenameWithoutSlash.size() - 1 );
265     m_poFS->InvalidateDirContent( CPLGetDirname(osFilenameWithoutSlash) );
266 }
267 
268 /************************************************************************/
269 /*                             Send()                                   */
270 /************************************************************************/
271 
272 bool VSIWebHDFSWriteHandle::Send(bool /* bIsLastBlock */)
273 {
274     if( m_nCurOffset > 0 )
275         return Append();
276     return true;
277 }
278 
279 /************************************************************************/
280 /*                           CreateFile()                               */
281 /************************************************************************/
282 
283 bool VSIWebHDFSWriteHandle::CreateFile()
284 {
285     if( m_osUsernameParam.empty() && m_osDelegationParam.empty() )
286     {
287         CPLError(CE_Failure, CPLE_AppDefined,
288                  "Configuration option WEBHDFS_USERNAME or WEBHDFS_DELEGATION "
289                  "should be defined");
290         return false;
291     }
292 
293     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix());
294     NetworkStatisticsFile oContextFile(m_osFilename);
295     NetworkStatisticsAction oContextAction("Write");
296 
297     CPLString osURL = m_osURL + "?op=CREATE&overwrite=true" +
298         m_osUsernameParam + m_osDelegationParam;
299 
300     CPLString osPermission = CPLGetConfigOption("WEBHDFS_PERMISSION", "");
301     if( !osPermission.empty() )
302         osURL += "&permission=" + osPermission;
303 
304     CPLString osReplication = CPLGetConfigOption("WEBHDFS_REPLICATION", "");
305     if( !osReplication.empty() )
306         osURL += "&replication=" + osReplication;
307 
308     bool bInRedirect = false;
309 
310 retry:
311     CURL* hCurlHandle = curl_easy_init();
312 
313     struct curl_slist* headers = static_cast<struct curl_slist*>(
314         CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
315 
316     curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
317     curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
318 
319     if( !m_osDataNodeHost.empty() )
320     {
321         curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
322     }
323 
324     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
325 
326     WriteFuncStruct sWriteFuncData;
327     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
328     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
329     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
330                         VSICurlHandleWriteFunc);
331 
332     MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
333 
334     curl_slist_free_all(headers);
335 
336     NetworkStatisticsLogger::LogPUT(0);
337 
338     long response_code = 0;
339     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
340 
341     if( !bInRedirect )
342     {
343         char *pszRedirectURL = nullptr;
344         curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
345         if( pszRedirectURL &&
346             strstr(pszRedirectURL, osURL.c_str()) == nullptr )
347         {
348             CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
349 
350             bInRedirect = true;
351             osURL = pszRedirectURL;
352             if( !m_osDataNodeHost.empty() )
353             {
354                 osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
355             }
356 
357             curl_easy_cleanup(hCurlHandle);
358             CPLFree(sWriteFuncData.pBuffer);
359 
360             goto retry;
361         }
362     }
363 
364     curl_easy_cleanup(hCurlHandle);
365 
366     if( response_code == 201 )
367     {
368         InvalidateParentDirectory();
369     }
370     else
371     {
372         CPLDebug("WEBHDFS",
373                     "%s",
374                     sWriteFuncData.pBuffer
375                     ? sWriteFuncData.pBuffer
376                     : "(null)");
377         CPLError(CE_Failure, CPLE_AppDefined,
378                     "PUT of %s failed",
379                     m_osURL.c_str());
380     }
381     CPLFree(sWriteFuncData.pBuffer);
382 
383     return response_code == 201;
384 }
385 
386 /************************************************************************/
387 /*                             Append()                                 */
388 /************************************************************************/
389 
390 bool VSIWebHDFSWriteHandle::Append()
391 {
392     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix());
393     NetworkStatisticsFile oContextFile(m_osFilename);
394     NetworkStatisticsAction oContextAction("Write");
395 
396     CPLString osURL = m_osURL + "?op=APPEND" +
397                       m_osUsernameParam + m_osDelegationParam;
398 
399     CURL* hCurlHandle = curl_easy_init();
400 
401     struct curl_slist* headers = static_cast<struct curl_slist*>(
402         CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
403 
404     curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
405     curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
406     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
407 
408     WriteFuncStruct sWriteFuncData;
409     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
410     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
411     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
412                         VSICurlHandleWriteFunc);
413 
414     MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
415 
416     curl_slist_free_all(headers);
417 
418     NetworkStatisticsLogger::LogPOST(0, 0);
419 
420     long response_code = 0;
421     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
422 
423     if( response_code != 307 )
424     {
425         CPLDebug("WEBHDFS",
426                     "%s",
427                     sWriteFuncData.pBuffer
428                     ? sWriteFuncData.pBuffer
429                     : "(null)");
430         CPLError(CE_Failure, CPLE_AppDefined,
431                     "POST of %s failed",
432                     m_osURL.c_str());
433         curl_easy_cleanup(hCurlHandle);
434         CPLFree(sWriteFuncData.pBuffer);
435         return false;
436     }
437 
438     char *pszRedirectURL = nullptr;
439     curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
440     if( pszRedirectURL == nullptr )
441     {
442         curl_easy_cleanup(hCurlHandle);
443         CPLFree(sWriteFuncData.pBuffer);
444         return false;
445     }
446     CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
447 
448     osURL = pszRedirectURL;
449     if( !m_osDataNodeHost.empty() )
450     {
451         osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
452     }
453 
454     curl_easy_cleanup(hCurlHandle);
455     CPLFree(sWriteFuncData.pBuffer);
456 
457 
458     // After redirection
459 
460     hCurlHandle = curl_easy_init();
461 
462     headers = static_cast<struct curl_slist*>(
463         CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
464     headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
465 
466     curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, m_pabyBuffer);
467     curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDSIZE , m_nBufferOff);
468     curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
469     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
470 
471     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
472     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
473     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
474                         VSICurlHandleWriteFunc);
475 
476     MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
477 
478     curl_slist_free_all(headers);
479 
480     NetworkStatisticsLogger::LogPOST(m_nBufferOff, 0);
481 
482     response_code = 0;
483     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
484 
485     curl_easy_cleanup(hCurlHandle);
486 
487     if( response_code != 200 )
488     {
489         CPLDebug("WEBHDFS",
490                     "%s",
491                     sWriteFuncData.pBuffer
492                     ? sWriteFuncData.pBuffer
493                     : "(null)");
494         CPLError(CE_Failure, CPLE_AppDefined,
495                     "POST of %s failed",
496                     m_osURL.c_str());
497     }
498     CPLFree(sWriteFuncData.pBuffer);
499 
500     return response_code == 200;
501 }
502 
503 /************************************************************************/
504 /*                                Open()                                */
505 /************************************************************************/
506 
507 VSIVirtualHandle* VSIWebHDFSFSHandler::Open( const char *pszFilename,
508                                         const char *pszAccess,
509                                         bool bSetError,
510                                         CSLConstList papszOptions )
511 {
512     if( !STARTS_WITH_CI(pszFilename, GetFSPrefix()) )
513         return nullptr;
514 
515     if( strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, 'a') != nullptr )
516     {
517         if( strchr(pszAccess, '+') != nullptr &&
518             !CPLTestBool(CPLGetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) )
519         {
520             CPLError(CE_Failure, CPLE_AppDefined,
521                         "w+ not supported for /vsiwebhdfs, unless "
522                         "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE is set to YES");
523             errno = EACCES;
524             return nullptr;
525         }
526 
527         VSIWebHDFSWriteHandle* poHandle =
528             new VSIWebHDFSWriteHandle(this, pszFilename);
529         if( !poHandle->IsOK() )
530         {
531             delete poHandle;
532             return nullptr;
533         }
534         if( strchr(pszAccess, '+') != nullptr)
535         {
536             return VSICreateUploadOnCloseFile(poHandle);
537         }
538         return poHandle;
539     }
540 
541     return
542         VSICurlFilesystemHandler::Open(pszFilename, pszAccess, bSetError, papszOptions);
543 }
544 
545 /************************************************************************/
546 /*                           GetOptions()                               */
547 /************************************************************************/
548 
549 const char* VSIWebHDFSFSHandler::GetOptions()
550 {
551     static CPLString osOptions(
552         CPLString("<Options>") +
553     "  <Option name='WEBHDFS_USERNAME' type='string' "
554         "description='username (when security is off)'/>"
555     "  <Option name='WEBHDFS_DELEGATION' type='string' "
556         "description='Hadoop delegation token (when security is on)'/>"
557     "  <Option name='WEBHDFS_DATANODE_HOST' type='string' "
558         "description='For APIs using redirect, substitute the redirection "
559         "hostname with the one provided by this option (normally resolvable "
560         "hostname should be rewritten by a proxy)'/>"
561     "  <Option name='WEBHDFS_REPLICATION' type='integer' "
562         "description='Replication value used when creating a file'/>"
563     "  <Option name='WEBHDFS_PERMISSION' type='integer' "
564         "description='Permission mask (to provide as decimal number) when "
565         "creating a file or directory'/>" +
566         VSICurlFilesystemHandler::GetOptionsStatic() +
567         "</Options>");
568     return osOptions.c_str();
569 }
570 
571 /************************************************************************/
572 /*                          CreateFileHandle()                          */
573 /************************************************************************/
574 
575 VSICurlHandle* VSIWebHDFSFSHandler::CreateFileHandle(const char* pszFilename)
576 {
577     return new VSIWebHDFSHandle(this, pszFilename,
578                                 pszFilename + GetFSPrefix().size());
579 }
580 
581 /************************************************************************/
582 /*                          GetURLFromFilename()                         */
583 /************************************************************************/
584 
585 CPLString VSIWebHDFSFSHandler::GetURLFromFilename( const CPLString& osFilename )
586 {
587     return osFilename.substr(GetFSPrefix().size());
588 }
589 /************************************************************************/
590 /*                           GetFileList()                              */
591 /************************************************************************/
592 
593 char** VSIWebHDFSFSHandler::GetFileList( const char *pszDirname,
594                                          int /*nMaxFiles*/,
595                                          bool* pbGotFileList )
596 {
597     if( ENABLE_DEBUG )
598         CPLDebug("WEBHDFS", "GetFileList(%s)" , pszDirname);
599     *pbGotFileList = false;
600 
601     NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
602     NetworkStatisticsAction oContextAction("ListBucket");
603 
604     CPLAssert( strlen(pszDirname) >= GetFSPrefix().size() );
605     CPLString osDirnameWithoutPrefix = pszDirname + GetFSPrefix().size();
606 
607     CPLString osBaseURL = osDirnameWithoutPrefix;
608     if( !osBaseURL.empty() && osBaseURL.back() != '/' )
609         osBaseURL += '/';
610 
611     CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
612 
613     CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
614     if( !osUsernameParam.empty() )
615         osUsernameParam = "&user.name=" + osUsernameParam;
616     CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
617     if( !osDelegationParam.empty() )
618         osDelegationParam = "&delegation=" + osDelegationParam;
619     CPLString osURL = osBaseURL + "?op=LISTSTATUS" + osUsernameParam + osDelegationParam;
620 
621     CURL* hCurlHandle = curl_easy_init();
622 
623     struct curl_slist* headers =
624             VSICurlSetOptions(hCurlHandle, osURL, nullptr);
625 
626     WriteFuncStruct sWriteFuncData;
627     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
628     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
629     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
630                      VSICurlHandleWriteFunc);
631 
632     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
633 
634     MultiPerform(hCurlMultiHandle, hCurlHandle);
635 
636     VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
637 
638     curl_slist_free_all(headers);
639 
640     NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
641 
642     long response_code = 0;
643     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
644 
645     CPLStringList aosList;
646     bool bOK = false;
647     if( response_code == 200 && sWriteFuncData.pBuffer )
648     {
649         CPLJSONDocument oDoc;
650         if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
651         {
652             CPLJSONArray oFileStatus = oDoc.GetRoot().GetArray("FileStatuses/FileStatus");
653             bOK = oFileStatus.IsValid();
654             for( int i = 0; i < oFileStatus.Size(); i++ )
655             {
656                 CPLJSONObject oItem = oFileStatus[i];
657                 vsi_l_offset fileSize = oItem.GetLong("length");
658                 size_t mTime = static_cast<size_t>(
659                     oItem.GetLong("modificationTime") / 1000);
660                 bool bIsDirectory = oItem.GetString("type") == "DIRECTORY";
661                 CPLString osName = oItem.GetString("pathSuffix");
662                 // can be empty if we for example ask to list a file: in that
663                 // case the file entry is reported but with an empty pathSuffix
664                 if( !osName.empty() )
665                 {
666                     aosList.AddString(osName);
667 
668                     FileProp prop;
669                     prop.eExists = EXIST_YES;
670                     prop.bIsDirectory = bIsDirectory;
671                     prop.bHasComputedFileSize = true;
672                     prop.fileSize = fileSize;
673                     prop.mTime = mTime;
674                     CPLString osCachedFilename(osBaseURL + osName);
675 #if DEBUG_VERBOSE
676                     CPLDebug("WEBHDFS", "Cache %s", osCachedFilename.c_str());
677 #endif
678                     SetCachedFileProp(osCachedFilename, prop);
679                 }
680             }
681         }
682     }
683 
684     *pbGotFileList = bOK;
685 
686     CPLFree(sWriteFuncData.pBuffer);
687     curl_easy_cleanup(hCurlHandle);
688 
689     if( bOK )
690         return aosList.StealList();
691     else
692         return nullptr;
693 }
694 
695 /************************************************************************/
696 /*                               Unlink()                               */
697 /************************************************************************/
698 
699 int VSIWebHDFSFSHandler::Unlink( const char *pszFilename )
700 {
701     if( !STARTS_WITH_CI(pszFilename, GetFSPrefix()) )
702         return -1;
703 
704     NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
705     NetworkStatisticsAction oContextAction("Unlink");
706 
707     CPLString osBaseURL = GetURLFromFilename(pszFilename);
708 
709     CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
710 
711     CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
712     if( !osUsernameParam.empty() )
713         osUsernameParam = "&user.name=" + osUsernameParam;
714     CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
715     if( !osDelegationParam.empty() )
716         osDelegationParam = "&delegation=" + osDelegationParam;
717     CPLString osURL = osBaseURL + "?op=DELETE" + osUsernameParam + osDelegationParam;
718 
719     CURL* hCurlHandle = curl_easy_init();
720 
721     curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "DELETE");
722 
723     struct curl_slist* headers =
724             VSICurlSetOptions(hCurlHandle, osURL, nullptr);
725 
726     WriteFuncStruct sWriteFuncData;
727     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
728     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
729     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
730                      VSICurlHandleWriteFunc);
731 
732     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
733 
734     MultiPerform(hCurlMultiHandle, hCurlHandle);
735 
736     VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
737 
738     curl_slist_free_all(headers);
739 
740     NetworkStatisticsLogger::LogDELETE();
741 
742     long response_code = 0;
743     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
744 
745     CPLStringList aosList;
746     bool bOK = false;
747     if( response_code == 200 && sWriteFuncData.pBuffer )
748     {
749         CPLJSONDocument oDoc;
750         if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
751         {
752             bOK = oDoc.GetRoot().GetBool("boolean");
753         }
754     }
755     if( bOK )
756     {
757         InvalidateCachedData(osBaseURL);
758 
759         CPLString osFilenameWithoutSlash(pszFilename);
760         if( !osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/' )
761             osFilenameWithoutSlash.resize( osFilenameWithoutSlash.size() - 1 );
762 
763         InvalidateDirContent( CPLGetDirname(osFilenameWithoutSlash) );
764     }
765     else
766     {
767         CPLDebug("WEBHDFS",
768                     "%s",
769                     sWriteFuncData.pBuffer
770                     ? sWriteFuncData.pBuffer
771                     : "(null)");
772     }
773 
774     CPLFree(sWriteFuncData.pBuffer);
775     curl_easy_cleanup(hCurlHandle);
776 
777     return bOK ? 0 : -1;
778 }
779 
780 /************************************************************************/
781 /*                               Rmdir()                                */
782 /************************************************************************/
783 
784 int VSIWebHDFSFSHandler::Rmdir( const char *pszFilename )
785 {
786     NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
787     NetworkStatisticsAction oContextAction("Rmdir");
788 
789     return Unlink(pszFilename);
790 }
791 
792 /************************************************************************/
793 /*                               Mkdir()                                */
794 /************************************************************************/
795 
796 int VSIWebHDFSFSHandler::Mkdir( const char *pszDirname, long nMode )
797 {
798     if( !STARTS_WITH_CI(pszDirname, GetFSPrefix()) )
799         return -1;
800 
801     CPLString osDirnameWithoutEndSlash(pszDirname);
802     if( !osDirnameWithoutEndSlash.empty() &&
803         osDirnameWithoutEndSlash.back() == '/' )
804     {
805         osDirnameWithoutEndSlash.resize( osDirnameWithoutEndSlash.size() - 1 );
806     }
807 
808     if( osDirnameWithoutEndSlash.find("/webhdfs/v1") ==
809         osDirnameWithoutEndSlash.size() - strlen("/webhdfs/v1") &&
810         std::count(osDirnameWithoutEndSlash.begin(),
811                    osDirnameWithoutEndSlash.end(),'/') == 6 )
812     {
813         // The server does weird things (creating a webhdfs/v1 subfolder)
814         // if we provide the root directory like
815         // /vsiwebhdfs/http://localhost:50070/webhdfs/v1
816         return -1;
817     }
818 
819     NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
820     NetworkStatisticsAction oContextAction("Mkdir");
821 
822     CPLString osBaseURL = GetURLFromFilename(osDirnameWithoutEndSlash);
823 
824     CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
825 
826     CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
827     if( !osUsernameParam.empty() )
828         osUsernameParam = "&user.name=" + osUsernameParam;
829     CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
830     if( !osDelegationParam.empty() )
831         osDelegationParam = "&delegation=" + osDelegationParam;
832     CPLString osURL = osBaseURL + "?op=MKDIRS" + osUsernameParam + osDelegationParam;
833     if( nMode )
834     {
835         osURL += "&permission=";
836         osURL += CPLSPrintf("%o", static_cast<int>(nMode));
837     }
838 
839     CURL* hCurlHandle = curl_easy_init();
840 
841     curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
842 
843     struct curl_slist* headers =
844             VSICurlSetOptions(hCurlHandle, osURL, nullptr);
845 
846     WriteFuncStruct sWriteFuncData;
847     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
848     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
849     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
850                      VSICurlHandleWriteFunc);
851 
852     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
853 
854     MultiPerform(hCurlMultiHandle, hCurlHandle);
855 
856     VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
857 
858     curl_slist_free_all(headers);
859 
860     NetworkStatisticsLogger::LogPUT(0);
861 
862     long response_code = 0;
863     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
864 
865     CPLStringList aosList;
866     bool bOK = false;
867     if( response_code == 200 && sWriteFuncData.pBuffer )
868     {
869         CPLJSONDocument oDoc;
870         if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
871         {
872             bOK = oDoc.GetRoot().GetBool("boolean");
873         }
874     }
875     if( bOK )
876     {
877         InvalidateDirContent( CPLGetDirname(osDirnameWithoutEndSlash) );
878 
879         FileProp cachedFileProp;
880         cachedFileProp.eExists = EXIST_YES;
881         cachedFileProp.bIsDirectory = true;
882         cachedFileProp.bHasComputedFileSize = true;
883         SetCachedFileProp(GetURLFromFilename(osDirnameWithoutEndSlash), cachedFileProp);
884 
885         RegisterEmptyDir(osDirnameWithoutEndSlash);
886     }
887     else
888     {
889         CPLDebug("WEBHDFS",
890                     "%s",
891                     sWriteFuncData.pBuffer
892                     ? sWriteFuncData.pBuffer
893                     : "(null)");
894     }
895 
896     CPLFree(sWriteFuncData.pBuffer);
897     curl_easy_cleanup(hCurlHandle);
898 
899     return bOK ? 0 : -1;
900 }
901 
902 /************************************************************************/
903 /*                            VSIWebHDFSHandle()                        */
904 /************************************************************************/
905 
906 VSIWebHDFSHandle::VSIWebHDFSHandle( VSIWebHDFSFSHandler* poFSIn,
907                           const char* pszFilename, const char* pszURL ) :
908         VSICurlHandle(poFSIn, pszFilename, pszURL),
909         m_osDataNodeHost(GetWebHDFSDataNodeHost())
910 {
911     // cppcheck-suppress useInitializationList
912     m_osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
913     if( !m_osUsernameParam.empty() )
914         m_osUsernameParam = "&user.name=" + m_osUsernameParam;
915     m_osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
916     if( !m_osDelegationParam.empty() )
917         m_osDelegationParam = "&delegation=" + m_osDelegationParam;
918 
919 }
920 
921 /************************************************************************/
922 /*                           GetFileSize()                              */
923 /************************************************************************/
924 
925 vsi_l_offset VSIWebHDFSHandle::GetFileSize( bool bSetError )
926 {
927     if( oFileProp.bHasComputedFileSize )
928         return oFileProp.fileSize;
929 
930     NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix());
931     NetworkStatisticsFile oContextFile(m_osFilename);
932     NetworkStatisticsAction oContextAction("GetFileSize");
933 
934     oFileProp.bHasComputedFileSize = true;
935 
936     CURLM* hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
937 
938     CPLString osURL(m_pszURL);
939 
940     if( osURL.size() > strlen("/webhdfs/v1") &&
941         osURL.find("/webhdfs/v1") == osURL.size() - strlen("/webhdfs/v1") &&
942         std::count(osURL.begin(),
943                    osURL.end(),'/') == 4 )
944     {
945         // If this is the root directory, add a trailing slash
946         osURL += "/";
947     }
948 
949     osURL += "?op=GETFILESTATUS" + m_osUsernameParam + m_osDelegationParam;
950 
951     CURL* hCurlHandle = curl_easy_init();
952 
953     struct curl_slist* headers =
954             VSICurlSetOptions(hCurlHandle, osURL, m_papszHTTPOptions);
955 
956     WriteFuncStruct sWriteFuncData;
957     VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
958     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
959     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
960                      VSICurlHandleWriteFunc);
961 
962     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
963 
964     char szCurlErrBuf[CURL_ERROR_SIZE+1] = {};
965     szCurlErrBuf[0] = '\0';
966     curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
967 
968     MultiPerform(hCurlMultiHandle, hCurlHandle);
969 
970     VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
971 
972     curl_slist_free_all(headers);
973 
974     NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
975 
976     long response_code = 0;
977     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
978 
979     oFileProp.eExists = EXIST_NO;
980     if( response_code == 200 && sWriteFuncData.pBuffer )
981     {
982         CPLJSONDocument oDoc;
983         if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
984         {
985             CPLJSONObject oFileStatus = oDoc.GetRoot().GetObj("FileStatus");
986             oFileProp.fileSize = oFileStatus.GetLong("length");
987             oFileProp.mTime = static_cast<size_t>(
988                 oFileStatus.GetLong("modificationTime") / 1000);
989             oFileProp.bIsDirectory = oFileStatus.GetString("type") == "DIRECTORY";
990             oFileProp.eExists = EXIST_YES;
991         }
992     }
993 
994     // If there was no VSI error thrown in the process,
995     // fail by reporting the HTTP response code.
996     if( response_code != 200 && bSetError && VSIGetLastErrorNo() == 0 )
997     {
998         if( strlen(szCurlErrBuf) > 0 )
999         {
1000             if( response_code == 0 )
1001             {
1002                 VSIError(VSIE_HttpError,
1003                             "CURL error: %s", szCurlErrBuf);
1004             }
1005             else
1006             {
1007                 VSIError(VSIE_HttpError,
1008                             "HTTP response code: %d - %s",
1009                             static_cast<int>(response_code), szCurlErrBuf);
1010             }
1011         }
1012         else
1013         {
1014             VSIError(VSIE_HttpError, "HTTP response code: %d",
1015                         static_cast<int>(response_code));
1016         }
1017     }
1018 
1019     if( ENABLE_DEBUG )
1020         CPLDebug("WEBHDFS", "GetFileSize(%s)=" CPL_FRMT_GUIB
1021                     "  response_code=%d",
1022                     osURL.c_str(), oFileProp.fileSize,
1023                     static_cast<int>(response_code));
1024 
1025     CPLFree(sWriteFuncData.pBuffer);
1026     curl_easy_cleanup(hCurlHandle);
1027 
1028     oFileProp.bHasComputedFileSize = true;
1029     poFS->SetCachedFileProp(m_pszURL, oFileProp);
1030 
1031     return oFileProp.fileSize;
1032 }
1033 
1034 /************************************************************************/
1035 /*                          DownloadRegion()                            */
1036 /************************************************************************/
1037 
1038 std::string VSIWebHDFSHandle::DownloadRegion( const vsi_l_offset startOffset,
1039                                               const int nBlocks )
1040 {
1041     if( bInterrupted && bStopOnInterruptUntilUninstall )
1042         return std::string();
1043 
1044     poFS->GetCachedFileProp(m_pszURL, oFileProp);
1045     if( oFileProp.eExists == EXIST_NO )
1046         return std::string();
1047 
1048     NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix());
1049     NetworkStatisticsFile oContextFile(m_osFilename);
1050     NetworkStatisticsAction oContextAction("Read");
1051 
1052     CURLM* hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
1053 
1054     CPLString osURL(m_pszURL);
1055 
1056     WriteFuncStruct sWriteFuncData;
1057     int nRetryCount = 0;
1058     double dfRetryDelay = m_dfRetryDelay;
1059     bool bInRedirect = false;
1060     const vsi_l_offset nEndOffset =
1061         startOffset + nBlocks * VSICURLGetDownloadChunkSize() - 1;
1062 
1063 retry:
1064     CURL* hCurlHandle = curl_easy_init();
1065 
1066     VSICURLInitWriteFuncStruct(&sWriteFuncData,
1067                                reinterpret_cast<VSILFILE *>(this),
1068                                pfnReadCbk, pReadCbkUserData);
1069     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
1070     curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1071                      VSICurlHandleWriteFunc);
1072 
1073     if( !bInRedirect )
1074     {
1075         osURL += "?op=OPEN&offset=";
1076         osURL += CPLSPrintf(CPL_FRMT_GUIB, startOffset);
1077         osURL += "&length=";
1078         osURL += CPLSPrintf(CPL_FRMT_GUIB, nEndOffset - startOffset + 1);
1079         osURL += m_osUsernameParam + m_osDelegationParam;
1080     }
1081 
1082     struct curl_slist* headers =
1083         VSICurlSetOptions(hCurlHandle, osURL, m_papszHTTPOptions);
1084 
1085     if( !m_osDataNodeHost.empty() )
1086     {
1087         curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
1088     }
1089 
1090     if( ENABLE_DEBUG )
1091         CPLDebug("WEBHDFS", "Downloading %s...", osURL.c_str());
1092 
1093     char szCurlErrBuf[CURL_ERROR_SIZE+1] = {};
1094     szCurlErrBuf[0] = '\0';
1095     curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
1096 
1097     curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1098 
1099     MultiPerform(hCurlMultiHandle, hCurlHandle);
1100 
1101     VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
1102 
1103     curl_slist_free_all(headers);
1104 
1105     NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
1106 
1107     if( sWriteFuncData.bInterrupted )
1108     {
1109         bInterrupted = true;
1110 
1111         CPLFree(sWriteFuncData.pBuffer);
1112         curl_easy_cleanup(hCurlHandle);
1113 
1114         return std::string();
1115     }
1116 
1117     long response_code = 0;
1118     curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
1119 
1120     if( ENABLE_DEBUG )
1121         CPLDebug("WEBHDFS", "Got response_code=%ld", response_code);
1122 
1123     if( !bInRedirect )
1124     {
1125         char *pszRedirectURL = nullptr;
1126         curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
1127         if( pszRedirectURL &&
1128             strstr(pszRedirectURL, m_pszURL) == nullptr )
1129         {
1130             CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
1131 
1132             bInRedirect = true;
1133             osURL = pszRedirectURL;
1134             if( !m_osDataNodeHost.empty() )
1135             {
1136                 osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
1137             }
1138 
1139             CPLFree(sWriteFuncData.pBuffer);
1140             curl_easy_cleanup(hCurlHandle);
1141 
1142             goto retry;
1143         }
1144     }
1145 
1146     if( response_code != 200 )
1147     {
1148         // If HTTP 429, 500, 502, 503, 504 error retry after a
1149         // pause.
1150         const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1151             static_cast<int>(response_code), dfRetryDelay,
1152             nullptr, szCurlErrBuf);
1153         if( dfNewRetryDelay > 0 &&
1154             nRetryCount < m_nMaxRetry )
1155         {
1156             CPLError(CE_Warning, CPLE_AppDefined,
1157                         "HTTP error code: %d - %s. "
1158                         "Retrying again in %.1f secs",
1159                         static_cast<int>(response_code), m_pszURL,
1160                         dfRetryDelay);
1161             CPLSleep(dfRetryDelay);
1162             dfRetryDelay = dfNewRetryDelay;
1163             nRetryCount++;
1164             CPLFree(sWriteFuncData.pBuffer);
1165             curl_easy_cleanup(hCurlHandle);
1166             goto retry;
1167         }
1168 
1169         if( response_code >= 400 && szCurlErrBuf[0] != '\0' )
1170         {
1171             CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
1172                         static_cast<int>(response_code), szCurlErrBuf);
1173         }
1174         if( !oFileProp.bHasComputedFileSize && startOffset == 0 )
1175         {
1176             oFileProp.bHasComputedFileSize = true;
1177             oFileProp.fileSize = 0;
1178             oFileProp.eExists = EXIST_NO;
1179             poFS->SetCachedFileProp(m_pszURL, oFileProp);
1180         }
1181         CPLFree(sWriteFuncData.pBuffer);
1182         curl_easy_cleanup(hCurlHandle);
1183         return std::string();
1184     }
1185 
1186     oFileProp.eExists = EXIST_YES;
1187     poFS->SetCachedFileProp(m_pszURL, oFileProp);
1188 
1189     DownloadRegionPostProcess(startOffset, nBlocks,
1190                               sWriteFuncData.pBuffer,
1191                               sWriteFuncData.nSize);
1192 
1193     std::string osRet;
1194     osRet.assign(sWriteFuncData.pBuffer, sWriteFuncData.nSize);
1195 
1196     CPLFree(sWriteFuncData.pBuffer);
1197     curl_easy_cleanup(hCurlHandle);
1198 
1199     return osRet;
1200 }
1201 
1202 
1203 } /* end of namespace cpl */
1204 
1205 
1206 #endif // DOXYGEN_SKIP
1207 //! @endcond
1208 
1209 /************************************************************************/
1210 /*                      VSIInstallWebHdfsHandler()                      */
1211 /************************************************************************/
1212 
1213 /**
1214  * \brief Install /vsiwebhdfs/ WebHDFS (Hadoop File System) REST API file
1215  * system handler (requires libcurl)
1216  *
1217  * @see <a href="gdal_virtual_file_systems.html#gdal_virtual_file_systems_vsiwebhdfs">/vsiwebhdfs/ documentation</a>
1218  *
1219  * @since GDAL 2.4
1220  */
1221 void VSIInstallWebHdfsHandler( void )
1222 {
1223     VSIFileManager::InstallHandler( "/vsiwebhdfs/", new cpl::VSIWebHDFSFSHandler );
1224 }
1225 
1226 #endif /* HAVE_CURL */
1227