• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15899162844

26 Jun 2025 10:14AM UTC coverage: 71.088% (+0.004%) from 71.084%
15899162844

Pull #12623

github

web-flow
Merge c704a8392 into f5cb024d4
Pull Request #12623: gdal raster overview add: add a --overview-src option

209 of 244 new or added lines in 5 files covered. (85.66%)

96 existing lines in 44 files now uncovered.

574014 of 807474 relevant lines covered (71.09%)

250815.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.79
/port/cpl_vsil_curl_streaming.cpp
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for HTTP/FTP files in streaming mode
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2012-2015, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "cpl_port.h"
14
#include "cpl_vsi.h"
15
#include "cpl_vsi_virtual.h"
16
#include "cpl_vsil_curl_class.h"
17

18
#include <algorithm>
19
#include <map>
20

21
#include "cpl_aws.h"
22
#include "cpl_google_cloud.h"
23
#include "cpl_azure.h"
24
#include "cpl_alibaba_oss.h"
25
#include "cpl_swift.h"
26
#include "cpl_hash_set.h"
27
#include "cpl_http.h"
28
#include "cpl_multiproc.h"
29
#include "cpl_string.h"
30
#include "cpl_time.h"
31

32
#if !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB)
33

34
void VSIInstallCurlStreamingFileHandler(void)
35
{
36
    // Not supported.
37
}
38

39
void VSIInstallS3StreamingFileHandler(void)
40
{
41
    // Not supported.
42
}
43

44
void VSIInstallGSStreamingFileHandler(void)
45
{
46
    // Not supported.
47
}
48

49
void VSIInstallAzureStreamingFileHandler(void)
50
{
51
    // Not supported
52
}
53

54
void VSIInstallOSSStreamingFileHandler(void)
55
{
56
    // Not supported
57
}
58

59
void VSIInstallSwiftStreamingFileHandler(void)
60
{
61
    // Not supported
62
}
63

64
#ifdef HAVE_CURL
65
void VSICurlStreamingClearCache(void)
66
{
67
    // Not supported
68
}
69
#endif
70

71
#else
72

73
//! @cond Doxygen_Suppress
74

75
#include <curl/curl.h>
76

77
#define ENABLE_DEBUG 0
78

79
#define N_MAX_REGIONS 10
80

81
#define BKGND_BUFFER_SIZE (1024 * 1024)
82

83
#define unchecked_curl_easy_setopt(handle, opt, param)                         \
84
    CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
85

86
/************************************************************************/
87
/*                               RingBuffer                             */
88
/************************************************************************/
89

90
class RingBuffer
91
{
92
    CPL_DISALLOW_COPY_ASSIGN(RingBuffer)
93

94
    GByte *pabyBuffer = nullptr;
95
    size_t nCapacity = 0;
96
    size_t nOffset = 0;
97
    size_t nLength = 0;
98

99
  public:
100
    explicit RingBuffer(size_t nCapacity = BKGND_BUFFER_SIZE);
101
    ~RingBuffer();
102

103
    size_t GetCapacity() const
201✔
104
    {
105
        return nCapacity;
201✔
106
    }
107

108
    size_t GetSize() const
700✔
109
    {
110
        return nLength;
700✔
111
    }
112

113
    void Reset();
114
    void Write(void *pBuffer, size_t nSize);
115
    void Read(void *pBuffer, size_t nSize);
116
};
117

118
RingBuffer::RingBuffer(size_t nCapacityIn)
65✔
119
    : pabyBuffer(static_cast<GByte *>(CPLMalloc(nCapacityIn))),
65✔
120
      nCapacity(nCapacityIn)
65✔
121
{
122
}
65✔
123

124
RingBuffer::~RingBuffer()
130✔
125
{
126
    CPLFree(pabyBuffer);
65✔
127
}
65✔
128

129
void RingBuffer::Reset()
153✔
130
{
131
    nOffset = 0;
153✔
132
    nLength = 0;
153✔
133
}
153✔
134

135
void RingBuffer::Write(void *pBuffer, size_t nSize)
199✔
136
{
137
    CPLAssert(nLength + nSize <= nCapacity);
199✔
138

139
    const size_t nEndOffset = (nOffset + nLength) % nCapacity;
199✔
140
    const size_t nSz = std::min(nSize, nCapacity - nEndOffset);
199✔
141
    memcpy(pabyBuffer + nEndOffset, pBuffer, nSz);
199✔
142
    if (nSz < nSize)
199✔
143
        memcpy(pabyBuffer, static_cast<GByte *>(pBuffer) + nSz, nSize - nSz);
×
144

145
    nLength += nSize;
199✔
146
}
199✔
147

148
void RingBuffer::Read(void *pBuffer, size_t nSize)
209✔
149
{
150
    CPLAssert(nSize <= nLength);
209✔
151

152
    if (pBuffer)
209✔
153
    {
154
        const size_t nSz = std::min(nSize, nCapacity - nOffset);
209✔
155
        memcpy(pBuffer, pabyBuffer + nOffset, nSz);
209✔
156
        if (nSz < nSize)
209✔
157
            memcpy(static_cast<GByte *>(pBuffer) + nSz, pabyBuffer,
×
158
                   nSize - nSz);
159
    }
160

161
    nOffset = (nOffset + nSize) % nCapacity;
209✔
162
    nLength -= nSize;
209✔
163
}
209✔
164

165
/************************************************************************/
166

167
namespace
168
{
169

170
typedef struct
171
{
172
    char *pBuffer;
173
    size_t nSize;
174
    int bIsHTTP;
175
    int bIsInHeader;
176
    int nHTTPCode;
177
    int bDownloadHeaderOnly;
178
} WriteFuncStructStreaming;
179

180
}  // namespace
181

182
namespace cpl
183
{
184

185
/************************************************************************/
186
/*                       VSICurlStreamingFSHandler                      */
187
/************************************************************************/
188

189
class VSICurlStreamingHandle;
190

191
class VSICurlStreamingFSHandler : public VSIFilesystemHandler
192
{
193
    CPL_DISALLOW_COPY_ASSIGN(VSICurlStreamingFSHandler)
194

195
    // LRU cache that just keeps in memory if this file system handler is
196
    // spposed to know the file properties of a file. The actual cache is a
197
    // shared one among all network file systems.
198
    // The aim of that design is that invalidating /vsis3/foo results in
199
    // /vsis3_streaming/foo to be invalidated as well.
200
    lru11::Cache<std::string, bool> oCacheFileProp;
201

202
  protected:
203
    CPLMutex *hMutex = nullptr;
204

205
    virtual VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
206
                                                     const char *pszURL);
207

208
    virtual std::string GetNonStreamingPrefix() const
10✔
209
    {
210
        return "/vsicurl/";
10✔
211
    }
212

213
  public:
214
    VSICurlStreamingFSHandler();
215
    virtual ~VSICurlStreamingFSHandler();
216

217
    virtual VSIVirtualHandle *Open(const char *pszFilename,
218
                                   const char *pszAccess, bool bSetError,
219
                                   CSLConstList /* papszOptions */) override;
220
    virtual int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
221
                     int nFlags) override;
222

223
    virtual CPLString GetFSPrefix() const
64✔
224
    {
225
        return "/vsicurl_streaming/";
64✔
226
    }
227

228
    std::string
229
    GetNonStreamingFilename(const std::string &osFilename) const override;
230

231
    const char *GetActualURL(const char *pszFilename) override;
232

233
    const char *GetOptions() override
1✔
234
    {
235
        return VSIGetFileSystemOptions("/vsicurl/");
1✔
236
    }
237

238
    void AcquireMutex();
239
    void ReleaseMutex();
240

241
    bool GetCachedFileProp(const char *pszURL, FileProp &oFileProp);
242
    void SetCachedFileProp(const char *pszURL, FileProp &oFileProp);
243

244
    virtual void ClearCache();
245
};
246

247
/************************************************************************/
248
/*                        VSICurlStreamingHandle                        */
249
/************************************************************************/
250

251
class VSICurlStreamingHandle : public VSIVirtualHandle
252
{
253
    CPL_DISALLOW_COPY_ASSIGN(VSICurlStreamingHandle)
254

255
  protected:
256
    VSICurlStreamingFSHandler *m_poFS = nullptr;
257
    CPLStringList m_aosHTTPOptions{};
258
    const CPLHTTPRetryParameters m_oRetryParameters;
259

260
  private:
261
    char *m_pszURL = nullptr;
262

263
#ifdef notdef
264
    unsigned int nRecomputedChecksumOfFirst1024Bytes = 0;
265
#endif
266
    vsi_l_offset curOffset = 0;
267
    vsi_l_offset fileSize = 0;
268
    bool bHasComputedFileSize = false;
269
    ExistStatus eExists = EXIST_UNKNOWN;
270
    bool bIsDirectory = false;
271

272
    bool bCanTrustCandidateFileSize = true;
273
    bool bHasCandidateFileSize = false;
274
    vsi_l_offset nCandidateFileSize = 0;
275

276
    bool bEOF = false;
277
    bool m_bError = false;
278

279
    size_t nCachedSize = 0;
280
    GByte *pCachedData = nullptr;
281

282
    volatile int bDownloadInProgress = FALSE;
283
    volatile int bDownloadStopped = FALSE;
284
    volatile int bAskDownloadEnd = FALSE;
285
    vsi_l_offset nRingBufferFileOffset = 0;
286
    CPLJoinableThread *hThread = nullptr;
287
    CPLMutex *hRingBufferMutex = nullptr;
288
    CPLCond *hCondProducer = nullptr;
289
    CPLCond *hCondConsumer = nullptr;
290
    RingBuffer oRingBuffer{};
291
    void StartDownload();
292
    void StopDownload();
293
    void PutRingBufferInCache();
294

295
    GByte *pabyHeaderData = nullptr;
296
    size_t nHeaderSize = 0;
297
    vsi_l_offset nBodySize = 0;
298
    int nHTTPCode = 0;
299
    char m_szCurlErrBuf[CURL_ERROR_SIZE + 1];
300
    bool m_bErrorOccurredInThread = false;
301

302
    void AcquireMutex();
303
    void ReleaseMutex();
304

305
    void AddRegion(vsi_l_offset nFileOffsetStart, size_t nSize, GByte *pData);
306

307
  protected:
308
    virtual struct curl_slist *
309
    GetCurlHeaders(const CPLString &,
14✔
310
                   const struct curl_slist * /* psExistingHeaders */)
311
    {
312
        return nullptr;
14✔
313
    }
314

315
    virtual bool StopReceivingBytesOnError()
9✔
316
    {
317
        return true;
9✔
318
    }
319

320
    virtual bool CanRestartOnError(const char * /*pszErrorMsg*/,
×
321
                                   const char * /*pszHeaders*/,
322
                                   bool /*bSetError*/)
323
    {
324
        return false;
×
325
    }
326

327
    virtual bool InterpretRedirect()
125✔
328
    {
329
        return true;
125✔
330
    }
331

332
    void SetURL(const char *pszURL);
333

334
  public:
335
    VSICurlStreamingHandle(VSICurlStreamingFSHandler *poFS,
336
                           const char *pszFilename, const char *pszURL);
337
    ~VSICurlStreamingHandle() override;
338

339
    int Seek(vsi_l_offset nOffset, int nWhence) override;
340
    vsi_l_offset Tell() override;
341
    size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
342
    size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
343
    void ClearErr() override;
344
    int Error() override;
345
    int Eof() override;
346
    int Flush() override;
347
    int Close() override;
348

349
    void DownloadInThread();
350
    size_t ReceivedBytes(GByte *buffer, size_t count, size_t nmemb);
351
    size_t ReceivedBytesHeader(GByte *buffer, size_t count, size_t nmemb);
352

353
    bool IsKnownFileSize() const
10✔
354
    {
355
        return bHasComputedFileSize;
10✔
356
    }
357

358
    vsi_l_offset GetFileSize();
359
    bool Exists(const char *pszFilename, CSLConstList papszOptions);
360

361
    bool IsDirectory() const
19✔
362
    {
363
        return bIsDirectory;
19✔
364
    }
365

366
    const char *GetURL() const
3✔
367
    {
368
        return m_pszURL;
3✔
369
    }
370
};
371

372
/************************************************************************/
373
/*                       VSICurlStreamingHandle()                       */
374
/************************************************************************/
375

376
VSICurlStreamingHandle::VSICurlStreamingHandle(VSICurlStreamingFSHandler *poFS,
65✔
377
                                               const char *pszFilename,
378
                                               const char *pszURL)
65✔
379
    : m_poFS(poFS), m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
380
      m_oRetryParameters(m_aosHTTPOptions), m_pszURL(CPLStrdup(pszURL))
65✔
381
{
382
    FileProp cachedFileProp;
65✔
383
    poFS->GetCachedFileProp(pszURL, cachedFileProp);
65✔
384
    eExists = cachedFileProp.eExists;
65✔
385
    fileSize = cachedFileProp.fileSize;
65✔
386
    bHasComputedFileSize = cachedFileProp.bHasComputedFileSize;
65✔
387
    bIsDirectory = cachedFileProp.bIsDirectory;
65✔
388
    poFS->SetCachedFileProp(pszURL, cachedFileProp);
65✔
389

390
    hRingBufferMutex = CPLCreateMutex();
65✔
391
    ReleaseMutex();
65✔
392
    hCondProducer = CPLCreateCond();
65✔
393
    hCondConsumer = CPLCreateCond();
65✔
394

395
    memset(m_szCurlErrBuf, 0, sizeof(m_szCurlErrBuf));
65✔
396
}
65✔
397

398
/************************************************************************/
399
/*                       ~VSICurlStreamingHandle()                      */
400
/************************************************************************/
401

402
VSICurlStreamingHandle::~VSICurlStreamingHandle()
75✔
403
{
404
    StopDownload();
65✔
405

406
    CPLFree(m_pszURL);
65✔
407

408
    CPLFree(pCachedData);
65✔
409

410
    CPLFree(pabyHeaderData);
65✔
411

412
    CPLDestroyMutex(hRingBufferMutex);
65✔
413
    CPLDestroyCond(hCondProducer);
65✔
414
    CPLDestroyCond(hCondConsumer);
65✔
415
}
75✔
416

417
/************************************************************************/
418
/*                            SetURL()                                  */
419
/************************************************************************/
420

421
void VSICurlStreamingHandle::SetURL(const char *pszURLIn)
4✔
422
{
423
    CPLFree(m_pszURL);
4✔
424
    m_pszURL = CPLStrdup(pszURLIn);
4✔
425
}
4✔
426

427
/************************************************************************/
428
/*                         AcquireMutex()                               */
429
/************************************************************************/
430

431
void VSICurlStreamingHandle::AcquireMutex()
1,203✔
432
{
433
    CPLAcquireMutex(hRingBufferMutex, 1000.0);
1,203✔
434
}
1,203✔
435

436
/************************************************************************/
437
/*                          ReleaseMutex()                              */
438
/************************************************************************/
439

440
void VSICurlStreamingHandle::ReleaseMutex()
1,268✔
441
{
442
    CPLReleaseMutex(hRingBufferMutex);
1,268✔
443
}
1,268✔
444

445
/************************************************************************/
446
/*                                Seek()                                */
447
/************************************************************************/
448

449
int VSICurlStreamingHandle::Seek(vsi_l_offset nOffset, int nWhence)
66✔
450
{
451
    if (curOffset >= BKGND_BUFFER_SIZE)
66✔
452
    {
453
        if (ENABLE_DEBUG)
454
            CPLDebug("VSICURL",
455
                     "Invalidating cache and file size due to Seek() "
456
                     "beyond caching zone");
457
        CPLFree(pCachedData);
×
458
        pCachedData = nullptr;
×
459
        nCachedSize = 0;
×
460
        AcquireMutex();
×
461
        bHasComputedFileSize = false;
×
462
        fileSize = 0;
×
463
        ReleaseMutex();
×
464
    }
465

466
    if (nWhence == SEEK_SET)
66✔
467
    {
468
        curOffset = nOffset;
65✔
469
    }
470
    else if (nWhence == SEEK_CUR)
1✔
471
    {
472
        curOffset = curOffset + nOffset;
×
473
    }
474
    else
475
    {
476
        curOffset = GetFileSize() + nOffset;
1✔
477
    }
478
    bEOF = false;
66✔
479
    return 0;
66✔
480
}
481

482
/************************************************************************/
483
/*                  VSICURLStreamingInitWriteFuncStructStreaming() */
484
/************************************************************************/
485

486
static void
487
VSICURLStreamingInitWriteFuncStructStreaming(WriteFuncStructStreaming *psStruct)
18✔
488
{
489
    psStruct->pBuffer = nullptr;
18✔
490
    psStruct->nSize = 0;
18✔
491
    psStruct->bIsHTTP = FALSE;
18✔
492
    psStruct->bIsInHeader = TRUE;
18✔
493
    psStruct->nHTTPCode = 0;
18✔
494
    psStruct->bDownloadHeaderOnly = FALSE;
18✔
495
}
18✔
496

497
/************************************************************************/
498
/*                 VSICurlStreamingHandleWriteFuncForHeader()           */
499
/************************************************************************/
500

501
static size_t VSICurlStreamingHandleWriteFuncForHeader(void *buffer,
50✔
502
                                                       size_t count,
503
                                                       size_t nmemb, void *req)
504
{
505
    WriteFuncStructStreaming *psStruct =
50✔
506
        static_cast<WriteFuncStructStreaming *>(req);
507
    const size_t nSize = count * nmemb;
50✔
508

509
    char *pNewBuffer = static_cast<char *>(
510
        VSIRealloc(psStruct->pBuffer, psStruct->nSize + nSize + 1));
50✔
511
    if (pNewBuffer)
50✔
512
    {
513
        psStruct->pBuffer = pNewBuffer;
50✔
514
        memcpy(psStruct->pBuffer + psStruct->nSize, buffer, nSize);
50✔
515
        psStruct->pBuffer[psStruct->nSize + nSize] = '\0';
50✔
516
        if (psStruct->bIsHTTP && psStruct->bIsInHeader)
50✔
517
        {
518
            char *pszLine = psStruct->pBuffer + psStruct->nSize;
×
519
            if (STARTS_WITH_CI(pszLine, "HTTP/"))
×
520
            {
521
                const char *pszSpace =
522
                    strchr(const_cast<const char *>(pszLine), ' ');
×
523
                if (pszSpace)
×
524
                    psStruct->nHTTPCode = atoi(pszSpace + 1);
×
525
            }
526

527
            if (pszLine[0] == '\r' || pszLine[0] == '\n')
×
528
            {
529
                if (psStruct->bDownloadHeaderOnly)
×
530
                {
531
                    // If moved permanently/temporarily, go on.
532
                    // Otherwise stop now.
533
                    if (!(psStruct->nHTTPCode == 301 ||
×
534
                          psStruct->nHTTPCode == 302 ||
×
535
                          psStruct->nHTTPCode == 303))
×
536
                        return 0;
×
537
                }
538
                else
539
                {
540
                    psStruct->bIsInHeader = FALSE;
×
541
                }
542
            }
543
        }
544
        psStruct->nSize += nSize;
50✔
545
        return nmemb;
50✔
546
    }
547
    else
548
    {
549
        return 0;
×
550
    }
551
}
552

553
/************************************************************************/
554
/*                           GetFileSize()                              */
555
/************************************************************************/
556

557
vsi_l_offset VSICurlStreamingHandle::GetFileSize()
11✔
558
{
559
    WriteFuncStructStreaming sWriteFuncData;
560
    WriteFuncStructStreaming sWriteFuncHeaderData;
561

562
    AcquireMutex();
11✔
563
    if (bHasComputedFileSize)
11✔
564
    {
565
        const vsi_l_offset nRet = fileSize;
2✔
566
        ReleaseMutex();
2✔
567
        return nRet;
2✔
568
    }
569
    ReleaseMutex();
9✔
570

571
    CURL *hLocalHandle = curl_easy_init();
9✔
572

573
    struct curl_slist *headers =
574
        VSICurlSetOptions(hLocalHandle, m_pszURL, m_aosHTTPOptions.List());
9✔
575

576
    VSICURLStreamingInitWriteFuncStructStreaming(&sWriteFuncHeaderData);
9✔
577

578
    // HACK for mbtiles driver: Proper fix would be to auto-detect servers that
579
    // don't accept HEAD http://a.tiles.mapbox.com/v3/ doesn't accept HEAD, so
580
    // let's start a GET and interrupt is as soon as the header is found.
581
    CPLString osVerb;
18✔
582
    if (strstr(m_pszURL, ".tiles.mapbox.com/") != nullptr)
9✔
583
    {
584
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADERDATA,
×
585
                                   &sWriteFuncHeaderData);
586
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADERFUNCTION,
×
587
                                   VSICurlStreamingHandleWriteFuncForHeader);
588

589
        sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
×
590
        sWriteFuncHeaderData.bDownloadHeaderOnly = TRUE;
×
591
        osVerb = "GET";
×
592
    }
593
    else
594
    {
595
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_NOBODY, 1);
9✔
596
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HTTPGET, 0);
9✔
597
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADER, 1);
9✔
598
        osVerb = "HEAD";
9✔
599
    }
600

601
    headers = VSICurlMergeHeaders(headers, GetCurlHeaders(osVerb, headers));
9✔
602
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HTTPHEADER, headers);
9✔
603

604
    // We need that otherwise OSGEO4W's libcurl issue a dummy range request
605
    // when doing a HEAD when recycling connections.
606
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_RANGE, nullptr);
9✔
607

608
    // Bug with older curl versions (<=7.16.4) and FTP.
609
    // See http://curl.haxx.se/mail/lib-2007-08/0312.html
610
    VSICURLStreamingInitWriteFuncStructStreaming(&sWriteFuncData);
9✔
611
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_WRITEDATA,
9✔
612
                               &sWriteFuncData);
613
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_WRITEFUNCTION,
9✔
614
                               VSICurlStreamingHandleWriteFuncForHeader);
615

616
    char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
9✔
617
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
9✔
618

619
    void *old_handler = CPLHTTPIgnoreSigPipe();
9✔
620
    curl_easy_perform(hLocalHandle);
9✔
621
    CPLHTTPRestoreSigPipeHandler(old_handler);
9✔
622
    if (headers != nullptr)
9✔
623
        curl_slist_free_all(headers);
9✔
624

625
    AcquireMutex();
9✔
626

627
    eExists = EXIST_UNKNOWN;
9✔
628
    bHasComputedFileSize = true;
9✔
629

630
    if (STARTS_WITH(m_pszURL, "ftp"))
9✔
631
    {
632
        if (sWriteFuncData.pBuffer != nullptr &&
×
633
            STARTS_WITH_CI(sWriteFuncData.pBuffer, "Content-Length: "))
×
634
        {
635
            const char *pszBuffer =
×
636
                sWriteFuncData.pBuffer + strlen("Content-Length: ");
×
637
            eExists = EXIST_YES;
×
638
            fileSize = CPLScanUIntBig(
×
639
                pszBuffer, static_cast<int>(sWriteFuncData.nSize -
×
640
                                            strlen("Content-Length: ")));
641
            if (ENABLE_DEBUG)
642
                CPLDebug("VSICURL", "GetFileSize(%s)=" CPL_FRMT_GUIB, m_pszURL,
643
                         fileSize);
644
        }
645
    }
646

647
    double dfSize = 0;
9✔
648
    if (eExists != EXIST_YES)
9✔
649
    {
650
        curl_off_t nSizeTmp = 0;
9✔
651
        const CURLcode code = curl_easy_getinfo(
9✔
652
            hLocalHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nSizeTmp);
653
        CPL_IGNORE_RET_VAL(dfSize);
9✔
654
        dfSize = static_cast<double>(nSizeTmp);
9✔
655
        if (code == 0)
9✔
656
        {
657
            eExists = EXIST_YES;
9✔
658
            if (dfSize < 0)
9✔
659
                fileSize = 0;
×
660
            else
661
                fileSize = static_cast<GUIntBig>(dfSize);
9✔
662
        }
663
        else
664
        {
665
            eExists = EXIST_NO;
×
666
            fileSize = 0;
×
667
            CPLError(CE_Failure, CPLE_AppDefined,
×
668
                     "VSICurlStreamingHandle::GetFileSize failed");
669
        }
670

671
        long response_code = 0;
9✔
672
        curl_easy_getinfo(hLocalHandle, CURLINFO_HTTP_CODE, &response_code);
9✔
673
        if (response_code != 200)
9✔
674
        {
675
            eExists = EXIST_NO;
×
676
            fileSize = 0;
×
677
        }
678

679
        // Try to guess if this is a directory. Generally if this is a
680
        // directory, curl will retry with an URL with slash added.
681
        char *pszEffectiveURL = nullptr;
9✔
682
        curl_easy_getinfo(hLocalHandle, CURLINFO_EFFECTIVE_URL,
9✔
683
                          &pszEffectiveURL);
684
        if (pszEffectiveURL != nullptr &&
9✔
685
            strncmp(m_pszURL, pszEffectiveURL, strlen(m_pszURL)) == 0 &&
9✔
686
            pszEffectiveURL[strlen(m_pszURL)] == '/')
9✔
687
        {
688
            eExists = EXIST_YES;
×
689
            fileSize = 0;
×
690
            bIsDirectory = true;
×
691
        }
692

693
        if (ENABLE_DEBUG)
694
            CPLDebug("VSICURL",
695
                     "GetFileSize(%s)=" CPL_FRMT_GUIB " response_code=%d",
696
                     m_pszURL, fileSize, static_cast<int>(response_code));
697
    }
698

699
    CPLFree(sWriteFuncData.pBuffer);
9✔
700
    CPLFree(sWriteFuncHeaderData.pBuffer);
9✔
701

702
    FileProp cachedFileProp;
9✔
703
    m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
9✔
704
    cachedFileProp.bHasComputedFileSize = true;
9✔
705
    cachedFileProp.fileSize = fileSize;
9✔
706
    cachedFileProp.eExists = eExists;
9✔
707
    cachedFileProp.bIsDirectory = bIsDirectory;
9✔
708
    if (cachedFileProp.nMode == 0)
9✔
709
        cachedFileProp.nMode = bIsDirectory ? S_IFDIR : S_IFREG;
9✔
710
    m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
9✔
711

712
    const vsi_l_offset nRet = fileSize;
9✔
713
    ReleaseMutex();
9✔
714

715
    curl_easy_cleanup(hLocalHandle);
9✔
716

717
    return nRet;
9✔
718
}
719

720
/************************************************************************/
721
/*                                 Exists()                             */
722
/************************************************************************/
723

724
bool VSICurlStreamingHandle::Exists(const char *pszFilename,
62✔
725
                                    CSLConstList papszOptions)
726
{
727
    if (eExists == EXIST_UNKNOWN)
62✔
728
    {
729
        if (!papszOptions ||
49✔
730
            !CPLTestBool(CSLFetchNameValueDef(
×
731
                papszOptions, "IGNORE_FILENAME_RESTRICTIONS", "NO")))
732
        {
733
            if (!VSICurlFilesystemHandlerBase::IsAllowedFilename(pszFilename))
49✔
734
            {
735
                eExists = EXIST_NO;
×
736
                fileSize = 0;
×
737

738
                FileProp cachedFileProp;
×
739
                m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
×
740
                cachedFileProp.bHasComputedFileSize = true;
×
741
                cachedFileProp.fileSize = fileSize;
×
742
                cachedFileProp.eExists = eExists;
×
743
                cachedFileProp.bIsDirectory = false;
×
744
                cachedFileProp.nMode = S_IFREG;
×
745
                m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
×
746

747
                return false;
×
748
            }
749
        }
750

751
        char chFirstByte = '\0';
49✔
752
        int bExists = (Read(&chFirstByte, 1, 1) == 1);
49✔
753

754
        FileProp cachedFileProp;
98✔
755
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
49✔
756
        cachedFileProp.eExists = eExists = bExists ? EXIST_YES : EXIST_NO;
49✔
757
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
49✔
758

759
        Seek(0, SEEK_SET);
49✔
760
    }
761

762
    return eExists == EXIST_YES;
62✔
763
}
764

765
/************************************************************************/
766
/*                                  Tell()                              */
767
/************************************************************************/
768

769
vsi_l_offset VSICurlStreamingHandle::Tell()
3✔
770
{
771
    return curOffset;
3✔
772
}
773

774
/************************************************************************/
775
/*                         ReceivedBytes()                              */
776
/************************************************************************/
777

778
size_t VSICurlStreamingHandle::ReceivedBytes(GByte *buffer, size_t count,
198✔
779
                                             size_t nmemb)
780
{
781
    size_t nSize = count * nmemb;
198✔
782
    nBodySize += nSize;
198✔
783

784
    if (ENABLE_DEBUG)
785
        CPLDebug("VSICURL", "Receiving %d bytes...", static_cast<int>(nSize));
786

787
    if (bHasCandidateFileSize && bCanTrustCandidateFileSize &&
198✔
788
        !bHasComputedFileSize)
184✔
789
    {
790
        FileProp cachedFileProp;
368✔
791
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
184✔
792
        cachedFileProp.fileSize = fileSize = nCandidateFileSize;
184✔
793
        bHasCandidateFileSize = TRUE;
184✔
794
        cachedFileProp.bHasComputedFileSize = bHasComputedFileSize;
184✔
795
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
184✔
796
        if (ENABLE_DEBUG)
797
            CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
798
    }
799

800
    AcquireMutex();
198✔
801
    if (eExists == EXIST_UNKNOWN)
198✔
802
    {
803
        FileProp cachedFileProp;
×
804
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
×
805
        cachedFileProp.eExists = eExists = EXIST_YES;
×
806
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
×
807
    }
808
    else if (eExists == EXIST_NO && StopReceivingBytesOnError())
198✔
809
    {
810
        ReleaseMutex();
×
811
        return 0;
×
812
    }
813

814
    while (true)
815
    {
816
        const size_t nFree = oRingBuffer.GetCapacity() - oRingBuffer.GetSize();
199✔
817
        if (nSize <= nFree)
199✔
818
        {
819
            oRingBuffer.Write(buffer, nSize);
198✔
820

821
            // Signal to the consumer that we have added bytes to the buffer.
822
            CPLCondSignal(hCondProducer);
198✔
823

824
            if (bAskDownloadEnd)
198✔
825
            {
826
                if (ENABLE_DEBUG)
827
                    CPLDebug("VSICURL", "Download interruption asked");
828

UNCOV
829
                ReleaseMutex();
×
UNCOV
830
                return 0;
×
831
            }
832
            break;
198✔
833
        }
834
        else
835
        {
836
            oRingBuffer.Write(buffer, nFree);
1✔
837
            buffer += nFree;
1✔
838
            nSize -= nFree;
1✔
839

840
            // Signal to the consumer that we have added bytes to the buffer.
841
            CPLCondSignal(hCondProducer);
1✔
842

843
            if (ENABLE_DEBUG)
844
                CPLDebug("VSICURL",
845
                         "Waiting for reader to consume some bytes...");
846

847
            while (oRingBuffer.GetSize() == oRingBuffer.GetCapacity() &&
3✔
848
                   !bAskDownloadEnd)
1✔
849
            {
850
                CPLCondWait(hCondConsumer, hRingBufferMutex);
1✔
851
            }
852

853
            if (bAskDownloadEnd)
1✔
854
            {
855
                if (ENABLE_DEBUG)
856
                    CPLDebug("VSICURL", "Download interruption asked");
857

858
                ReleaseMutex();
×
859
                return 0;
×
860
            }
861
        }
862
    }
1✔
863

864
    ReleaseMutex();
198✔
865

866
    return nmemb;
198✔
867
}
868

869
/************************************************************************/
870
/*                 VSICurlStreamingHandleReceivedBytes()                */
871
/************************************************************************/
872

873
static size_t VSICurlStreamingHandleReceivedBytes(void *buffer, size_t count,
198✔
874
                                                  size_t nmemb, void *req)
875
{
876
    return static_cast<VSICurlStreamingHandle *>(req)->ReceivedBytes(
198✔
877
        static_cast<GByte *>(buffer), count, nmemb);
198✔
878
}
879

880
/************************************************************************/
881
/*              VSICurlStreamingHandleReceivedBytesHeader()             */
882
/************************************************************************/
883

884
#define HEADER_SIZE 32768
885

886
size_t VSICurlStreamingHandle::ReceivedBytesHeader(GByte *buffer, size_t count,
409✔
887
                                                   size_t nmemb)
888
{
889
    const size_t nSize = count * nmemb;
409✔
890
    if (ENABLE_DEBUG)
891
        CPLDebug("VSICURL", "Receiving %d bytes for header...",
892
                 static_cast<int>(nSize));
893

894
    // Reset buffer if we have followed link after a redirect.
895
    if (nSize >= 9 && InterpretRedirect() &&
351✔
896
        (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303) &&
760✔
897
        STARTS_WITH_CI(reinterpret_cast<char *>(buffer), "HTTP/"))
×
898
    {
899
        nHeaderSize = 0;
×
900
        nHTTPCode = 0;
×
901
    }
902

903
    if (nHeaderSize < HEADER_SIZE)
409✔
904
    {
905
        const size_t nSz = std::min(nSize, HEADER_SIZE - nHeaderSize);
409✔
906
        memcpy(pabyHeaderData + nHeaderSize, buffer, nSz);
409✔
907
        pabyHeaderData[nHeaderSize + nSz] = '\0';
409✔
908
        nHeaderSize += nSz;
409✔
909

910
#if DEBUG_VERBOSE
911
        CPLDebug("VSICURL", "Header : %s", pabyHeaderData);
912
#endif
913

914
        AcquireMutex();
409✔
915

916
        if (nHTTPCode == 0 &&
409✔
917
            strchr(reinterpret_cast<char *>(pabyHeaderData), '\n') != nullptr &&
58✔
918
            STARTS_WITH_CI(reinterpret_cast<char *>(pabyHeaderData), "HTTP/"))
58✔
919
        {
920
            const char *pszSpace =
921
                strchr(const_cast<const char *>(
58✔
922
                           reinterpret_cast<char *>(pabyHeaderData)),
58✔
923
                       ' ');
924
            if (pszSpace)
58✔
925
                nHTTPCode = atoi(pszSpace + 1);
58✔
926
            if (ENABLE_DEBUG)
927
                CPLDebug("VSICURL", "HTTP code = %d", nHTTPCode);
928

929
            // If moved permanently/temporarily, go on.
930
            if (eExists == EXIST_UNKNOWN &&
109✔
931
                !(InterpretRedirect() &&
51✔
932
                  (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303)))
8✔
933
            {
934
                eExists = nHTTPCode == 200 ? EXIST_YES : EXIST_NO;
51✔
935
                FileProp cachedFileProp;
102✔
936
                m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
51✔
937
                cachedFileProp.eExists = eExists;
51✔
938
                m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
51✔
939
            }
940
        }
941

942
        if (!(InterpretRedirect() &&
409✔
943
              (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303)) &&
818✔
944
            !bHasComputedFileSize)
409✔
945
        {
946
            // Caution: When gzip compression is enabled, the content-length is
947
            // the compressed size, which we are not interested in, so we must
948
            // not take it into account.
949

950
            const char *pszContentLength = strstr(
380✔
951
                reinterpret_cast<char *>(pabyHeaderData), "Content-Length: ");
380✔
952
            const char *pszEndOfLine =
380✔
953
                pszContentLength ? strchr(pszContentLength, '\n') : nullptr;
380✔
954
            if (bCanTrustCandidateFileSize && pszEndOfLine != nullptr)
380✔
955
            {
956
                const char *pszVal =
101✔
957
                    pszContentLength + strlen("Content-Length: ");
958
                bHasCandidateFileSize = true;
101✔
959
                nCandidateFileSize = CPLScanUIntBig(
101✔
960
                    pszVal, static_cast<int>(pszEndOfLine - pszVal));
101✔
961
                if (ENABLE_DEBUG)
962
                    CPLDebug("VSICURL",
963
                             "Has found candidate file size = " CPL_FRMT_GUIB,
964
                             nCandidateFileSize);
965
            }
966

967
            const char *pszContentEncoding = strstr(
380✔
968
                reinterpret_cast<char *>(pabyHeaderData), "Content-Encoding: ");
380✔
969
            pszEndOfLine =
380✔
970
                pszContentEncoding ? strchr(pszContentEncoding, '\n') : nullptr;
380✔
971
            if (bHasCandidateFileSize && pszEndOfLine != nullptr)
380✔
972
            {
973
                const char *pszVal =
×
974
                    pszContentEncoding + strlen("Content-Encoding: ");
975
                if (STARTS_WITH(pszVal, "gzip"))
×
976
                {
977
                    if (ENABLE_DEBUG)
978
                        CPLDebug("VSICURL", "GZip compression enabled --> "
979
                                            "cannot trust candidate file size");
980
                    bCanTrustCandidateFileSize = false;
×
981
                }
982
            }
983
        }
984

985
        ReleaseMutex();
409✔
986
    }
987

988
    return nmemb;
409✔
989
}
990

991
/************************************************************************/
992
/*                 VSICurlStreamingHandleReceivedBytesHeader()          */
993
/************************************************************************/
994

995
static size_t VSICurlStreamingHandleReceivedBytesHeader(void *buffer,
409✔
996
                                                        size_t count,
997
                                                        size_t nmemb, void *req)
998
{
999
    return static_cast<VSICurlStreamingHandle *>(req)->ReceivedBytesHeader(
409✔
1000
        static_cast<GByte *>(buffer), count, nmemb);
409✔
1001
}
1002

1003
/************************************************************************/
1004
/*                       DownloadInThread()                             */
1005
/************************************************************************/
1006

1007
void VSICurlStreamingHandle::DownloadInThread()
60✔
1008
{
1009
    CURL *hCurlHandle = curl_easy_init();
60✔
1010

1011
    struct curl_slist *headers =
1012
        VSICurlSetOptions(hCurlHandle, m_pszURL, m_aosHTTPOptions.List());
60✔
1013
    headers = VSICurlMergeHeaders(headers, GetCurlHeaders("GET", headers));
60✔
1014
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
60✔
1015

1016
    static bool bHasCheckVersion = false;
1017
    static bool bSupportGZip = false;
1018
    if (!bHasCheckVersion)
60✔
1019
    {
1020
        bSupportGZip = strstr(curl_version(), "zlib/") != nullptr;
3✔
1021
        bHasCheckVersion = true;
3✔
1022
    }
1023
    if (bSupportGZip && CPLTestBool(CPLGetConfigOption("CPL_CURL_GZIP", "YES")))
60✔
1024
    {
1025
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ENCODING, "gzip");
60✔
1026
    }
1027

1028
    if (pabyHeaderData == nullptr)
60✔
1029
        pabyHeaderData = static_cast<GByte *>(CPLMalloc(HEADER_SIZE + 1));
52✔
1030
    nHeaderSize = 0;
60✔
1031
    nBodySize = 0;
60✔
1032
    nHTTPCode = 0;
60✔
1033

1034
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, this);
60✔
1035
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
60✔
1036
                               VSICurlStreamingHandleReceivedBytesHeader);
1037

1038
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, this);
60✔
1039
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
60✔
1040
                               VSICurlStreamingHandleReceivedBytes);
1041

1042
    m_szCurlErrBuf[0] = '\0';
60✔
1043
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
60✔
1044
                               m_szCurlErrBuf);
1045

1046
    void *old_handler = CPLHTTPIgnoreSigPipe();
60✔
1047
    CURLcode eRet = curl_easy_perform(hCurlHandle);
60✔
1048
    CPLHTTPRestoreSigPipeHandler(old_handler);
60✔
1049
    if (headers != nullptr)
60✔
1050
        curl_slist_free_all(headers);
47✔
1051

1052
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
60✔
1053
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
60✔
1054
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
60✔
1055
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
60✔
1056

1057
    AcquireMutex();
60✔
1058
    m_bErrorOccurredInThread = eRet != CURLE_OK;
60✔
1059
    if (m_bErrorOccurredInThread)
60✔
1060
    {
1061
        // For autotest purposes only !
1062
        const char *pszSimulatedCurlError = CPLGetConfigOption(
7✔
1063
            "CPL_VSIL_CURL_STREMAING_SIMULATED_CURL_ERROR", nullptr);
1064
        if (pszSimulatedCurlError)
7✔
1065
            snprintf(m_szCurlErrBuf, sizeof(m_szCurlErrBuf), "%s",
5✔
1066
                     pszSimulatedCurlError);
1067
    }
1068

1069
    if (!bAskDownloadEnd && eRet == CURLE_OK && !bHasComputedFileSize)
60✔
1070
    {
1071
        FileProp cachedFileProp;
98✔
1072
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
49✔
1073
        fileSize = nBodySize;
49✔
1074
        cachedFileProp.fileSize = fileSize;
49✔
1075
        bHasComputedFileSize = true;
49✔
1076
        cachedFileProp.bHasComputedFileSize = bHasComputedFileSize;
49✔
1077
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
49✔
1078
        if (ENABLE_DEBUG)
1079
            CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
1080
    }
1081

1082
    bDownloadInProgress = FALSE;
60✔
1083
    bDownloadStopped = TRUE;
60✔
1084

1085
    // Signal to the consumer that the download has ended.
1086
    CPLCondSignal(hCondProducer);
60✔
1087
    ReleaseMutex();
60✔
1088

1089
    curl_easy_cleanup(hCurlHandle);
60✔
1090
}
60✔
1091

1092
static void VSICurlDownloadInThread(void *pArg)
60✔
1093
{
1094
    static_cast<VSICurlStreamingHandle *>(pArg)->DownloadInThread();
60✔
1095
}
60✔
1096

1097
/************************************************************************/
1098
/*                            StartDownload()                            */
1099
/************************************************************************/
1100

1101
void VSICurlStreamingHandle::StartDownload()
93✔
1102
{
1103
    if (bDownloadInProgress || bDownloadStopped)
93✔
1104
        return;
33✔
1105

1106
    CPLDebug("VSICURL", "Start download for %s", m_pszURL);
60✔
1107

1108
    oRingBuffer.Reset();
60✔
1109
    bDownloadInProgress = TRUE;
60✔
1110
    nRingBufferFileOffset = 0;
60✔
1111
    m_bErrorOccurredInThread = false;
60✔
1112
    hThread = CPLCreateJoinableThread(VSICurlDownloadInThread, this);
60✔
1113
}
1114

1115
/************************************************************************/
1116
/*                            StopDownload()                            */
1117
/************************************************************************/
1118

1119
void VSICurlStreamingHandle::StopDownload()
93✔
1120
{
1121
    if (hThread)
93✔
1122
    {
1123
        CPLDebug("VSICURL", "Stop download for %s", m_pszURL);
60✔
1124

1125
        AcquireMutex();
60✔
1126
        // Signal to the producer that we ask for download interruption.
1127
        bAskDownloadEnd = TRUE;
60✔
1128
        CPLCondSignal(hCondConsumer);
60✔
1129

1130
        // Wait for the producer to have finished.
1131
        while (bDownloadInProgress)
61✔
1132
            CPLCondWait(hCondProducer, hRingBufferMutex);
1✔
1133

1134
        bAskDownloadEnd = FALSE;
60✔
1135

1136
        ReleaseMutex();
60✔
1137

1138
        CPLJoinThread(hThread);
60✔
1139
        hThread = nullptr;
60✔
1140
    }
1141

1142
    oRingBuffer.Reset();
93✔
1143
    bDownloadStopped = FALSE;
93✔
1144
    m_bErrorOccurredInThread = false;
93✔
1145
    nRingBufferFileOffset = 0;
93✔
1146
    bEOF = false;
93✔
1147
}
93✔
1148

1149
/************************************************************************/
1150
/*                        PutRingBufferInCache()                        */
1151
/************************************************************************/
1152

1153
void VSICurlStreamingHandle::PutRingBufferInCache()
42✔
1154
{
1155
    if (nRingBufferFileOffset >= BKGND_BUFFER_SIZE)
42✔
1156
        return;
×
1157

1158
    AcquireMutex();
42✔
1159

1160
    // Cache any remaining bytes available in the ring buffer.
1161
    size_t nBufSize = oRingBuffer.GetSize();
42✔
1162
    if (nBufSize > 0)
42✔
1163
    {
1164
        if (nRingBufferFileOffset + nBufSize > BKGND_BUFFER_SIZE)
28✔
1165
            nBufSize =
1✔
1166
                static_cast<size_t>(BKGND_BUFFER_SIZE - nRingBufferFileOffset);
1✔
1167
        GByte *pabyTmp = static_cast<GByte *>(CPLMalloc(nBufSize));
28✔
1168
        oRingBuffer.Read(pabyTmp, nBufSize);
28✔
1169

1170
        // Signal to the producer that we have ingested some bytes.
1171
        CPLCondSignal(hCondConsumer);
28✔
1172

1173
        AddRegion(nRingBufferFileOffset, nBufSize, pabyTmp);
28✔
1174
        nRingBufferFileOffset += nBufSize;
28✔
1175
        CPLFree(pabyTmp);
28✔
1176
    }
1177

1178
    ReleaseMutex();
42✔
1179
}
1180

1181
/************************************************************************/
1182
/*                                Read()                                */
1183
/************************************************************************/
1184

1185
size_t VSICurlStreamingHandle::Read(void *const pBuffer, size_t const nSize,
120✔
1186
                                    size_t const nMemb)
1187
{
1188
    const size_t nBufferRequestSize = nSize * nMemb;
120✔
1189
    const vsi_l_offset curOffsetOri = curOffset;
120✔
1190
    const vsi_l_offset nRingBufferFileOffsetOri = nRingBufferFileOffset;
120✔
1191
    if (nBufferRequestSize == 0)
120✔
1192
        return 0;
×
1193

1194
    CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
240✔
1195

1196
retry:
128✔
1197
    GByte *pabyBuffer = static_cast<GByte *>(pBuffer);
128✔
1198
    size_t nRemaining = nBufferRequestSize;
128✔
1199

1200
    AcquireMutex();
128✔
1201
    // fileSize might be set wrongly to 0, such as
1202
    // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
1203
    const bool bHasComputedFileSizeLocal = bHasComputedFileSize && fileSize > 0;
128✔
1204
    const vsi_l_offset fileSizeLocal = fileSize;
128✔
1205
    ReleaseMutex();
128✔
1206

1207
    if (bHasComputedFileSizeLocal && curOffset >= fileSizeLocal)
128✔
1208
    {
1209
        CPLDebug("VSICURL", "Read attempt beyond end of file");
×
1210
        bEOF = true;
×
1211
    }
1212
    if (bEOF)
128✔
1213
        return 0;
×
1214

1215
    if (curOffset < nRingBufferFileOffset)
128✔
1216
        PutRingBufferInCache();
42✔
1217

1218
    if (ENABLE_DEBUG)
1219
        CPLDebug("VSICURL", "Read [" CPL_FRMT_GUIB ", " CPL_FRMT_GUIB "[ in %s",
1220
                 curOffset, curOffset + nBufferRequestSize, m_pszURL);
1221

1222
    // Can we use the cache?
1223
    if (pCachedData != nullptr && curOffset < nCachedSize)
128✔
1224
    {
1225
        const size_t nSz =
1226
            std::min(nRemaining, static_cast<size_t>(nCachedSize - curOffset));
46✔
1227
        if (ENABLE_DEBUG)
1228
            CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1229
                     static_cast<int>(curOffset),
1230
                     static_cast<int>(curOffset + nSz), m_pszURL);
1231
        memcpy(pabyBuffer, pCachedData + curOffset, nSz);
46✔
1232
        pabyBuffer += nSz;
46✔
1233
        curOffset += nSz;
46✔
1234
        nRemaining -= nSz;
46✔
1235
    }
1236

1237
    // Is the request partially covered by the cache and going beyond file size?
1238
    if (pCachedData != nullptr && bHasComputedFileSizeLocal &&
128✔
1239
        curOffset <= nCachedSize && curOffset + nRemaining > fileSizeLocal &&
43✔
1240
        fileSize == nCachedSize)
38✔
1241
    {
1242
        size_t nSz = static_cast<size_t>(nCachedSize - curOffset);
26✔
1243
        if (ENABLE_DEBUG && nSz != 0)
1244
            CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1245
                     static_cast<int>(curOffset),
1246
                     static_cast<int>(curOffset + nSz), m_pszURL);
1247
        memcpy(pabyBuffer, pCachedData + curOffset, nSz);
26✔
1248
        pabyBuffer += nSz;
26✔
1249
        curOffset += nSz;
26✔
1250
        nRemaining -= nSz;
26✔
1251
        bEOF = true;
26✔
1252
    }
1253

1254
    bool bErrorOccurred = false;
128✔
1255

1256
    // Has a Seek() being done since the last Read()?
1257
    if (!bEOF && nRemaining > 0 && curOffset != nRingBufferFileOffset)
128✔
1258
    {
1259
        // Backward seek: Need to restart the download from the beginning.
1260
        if (curOffset < nRingBufferFileOffset)
4✔
1261
            StopDownload();
×
1262

1263
        StartDownload();
4✔
1264

1265
        const vsi_l_offset SKIP_BUFFER_SIZE = 32768;
4✔
1266
        GByte *pabyTmp = static_cast<GByte *>(CPLMalloc(SKIP_BUFFER_SIZE));
4✔
1267

1268
        CPLAssert(curOffset >= nRingBufferFileOffset);
4✔
1269
        vsi_l_offset nBytesToSkip = curOffset - nRingBufferFileOffset;
4✔
1270
        while (nBytesToSkip > 0)
12✔
1271
        {
1272
            vsi_l_offset nBytesToRead = nBytesToSkip;
10✔
1273

1274
            AcquireMutex();
10✔
1275
            if (nBytesToRead > oRingBuffer.GetSize())
10✔
1276
                nBytesToRead = oRingBuffer.GetSize();
8✔
1277
            if (nBytesToRead > SKIP_BUFFER_SIZE)
10✔
1278
                nBytesToRead = SKIP_BUFFER_SIZE;
×
1279
            oRingBuffer.Read(pabyTmp, static_cast<size_t>(nBytesToRead));
10✔
1280

1281
            // Signal to the producer that we have ingested some bytes.
1282
            CPLCondSignal(hCondConsumer);
10✔
1283
            ReleaseMutex();
10✔
1284

1285
            if (nBytesToRead)
10✔
1286
                AddRegion(nRingBufferFileOffset,
4✔
1287
                          static_cast<size_t>(nBytesToRead), pabyTmp);
1288

1289
            nBytesToSkip -= nBytesToRead;
10✔
1290
            nRingBufferFileOffset += nBytesToRead;
10✔
1291

1292
            if (nBytesToRead == 0 && nBytesToSkip != 0)
10✔
1293
            {
1294
                if (ENABLE_DEBUG)
1295
                    CPLDebug("VSICURL",
1296
                             "Waiting for writer to produce some bytes...");
1297

1298
                AcquireMutex();
6✔
1299
                while (oRingBuffer.GetSize() == 0 && bDownloadInProgress)
12✔
1300
                    CPLCondWait(hCondProducer, hRingBufferMutex);
6✔
1301
                const int bBufferEmpty = (oRingBuffer.GetSize() == 0);
6✔
1302
                bErrorOccurred = m_bErrorOccurredInThread;
6✔
1303
                ReleaseMutex();
6✔
1304

1305
                if (bBufferEmpty && !bDownloadInProgress)
6✔
1306
                    break;
2✔
1307
            }
1308
        }
1309

1310
        CPLFree(pabyTmp);
4✔
1311

1312
        if (nBytesToSkip != 0 && !bErrorOccurred)
4✔
1313
        {
1314
            bEOF = true;
×
1315
            return 0;
×
1316
        }
1317
    }
1318

1319
    if (!bEOF && nRemaining > 0 && !bErrorOccurred)
128✔
1320
    {
1321
        StartDownload();
89✔
1322
        CPLAssert(curOffset == nRingBufferFileOffset);
89✔
1323
    }
1324

1325
    // Fill the destination buffer from the ring buffer.
1326
    while (!bEOF && nRemaining > 0 && !bErrorOccurred)
266✔
1327
    {
1328
        AcquireMutex();
171✔
1329
        size_t nToRead = oRingBuffer.GetSize();
171✔
1330
        if (nToRead > nRemaining)
171✔
1331
            nToRead = nRemaining;
52✔
1332
        oRingBuffer.Read(pabyBuffer, nToRead);
171✔
1333

1334
        // Signal to the producer that we have ingested some bytes.
1335
        CPLCondSignal(hCondConsumer);
171✔
1336
        ReleaseMutex();
171✔
1337

1338
        if (nToRead)
171✔
1339
            AddRegion(curOffset, nToRead, pabyBuffer);
83✔
1340

1341
        nRemaining -= nToRead;
171✔
1342
        pabyBuffer += nToRead;
171✔
1343
        curOffset += nToRead;
171✔
1344
        nRingBufferFileOffset += nToRead;
171✔
1345

1346
        if (nToRead == 0 && nRemaining != 0)
171✔
1347
        {
1348
            if (ENABLE_DEBUG)
1349
                CPLDebug("VSICURL",
1350
                         "Waiting for writer to produce some bytes...");
1351

1352
            AcquireMutex();
88✔
1353
            while (oRingBuffer.GetSize() == 0 && bDownloadInProgress)
162✔
1354
                CPLCondWait(hCondProducer, hRingBufferMutex);
74✔
1355
            const bool bBufferEmpty = oRingBuffer.GetSize() == 0;
88✔
1356
            bErrorOccurred = m_bErrorOccurredInThread;
88✔
1357
            ReleaseMutex();
88✔
1358

1359
            if (bBufferEmpty && !bDownloadInProgress)
88✔
1360
                break;
33✔
1361
        }
1362
    }
1363

1364
    if (ENABLE_DEBUG)
1365
        CPLDebug("VSICURL", "Read(%d) = %d",
1366
                 static_cast<int>(nBufferRequestSize),
1367
                 static_cast<int>(nBufferRequestSize - nRemaining));
1368
    size_t nRet = (nBufferRequestSize - nRemaining) / nSize;
128✔
1369
    if (nRet < nMemb)
128✔
1370
        bEOF = true;
61✔
1371

1372
    // Give a chance to specialized filesystem to deal with errors to redirect
1373
    // elsewhere.
1374
    if (curOffsetOri == 0 && nRingBufferFileOffsetOri == 0 &&
92✔
1375
        !StopReceivingBytesOnError() && eExists == EXIST_NO &&
244✔
1376
        nRemaining < nBufferRequestSize)
24✔
1377
    {
1378
        const size_t nErrorBufferMaxSize = 4096;
24✔
1379
        std::unique_ptr<GByte, VSIFreeReleaser> pabyErrorBuffer(
1380
            static_cast<GByte *>(CPLMalloc(nErrorBufferMaxSize + 1)));
24✔
1381
        size_t nRead = nBufferRequestSize - nRemaining;
24✔
1382
        size_t nErrorBufferSize = std::min(nErrorBufferMaxSize, nRead);
24✔
1383
        memcpy(pabyErrorBuffer.get(), pBuffer, nErrorBufferSize);
24✔
1384
        if (nRead < nErrorBufferMaxSize)
24✔
1385
            nErrorBufferSize += Read(pabyErrorBuffer.get() + nRead, 1,
24✔
1386
                                     nErrorBufferMaxSize - nRead);
24✔
1387
        (pabyErrorBuffer.get())[nErrorBufferSize] = 0;
24✔
1388
        StopDownload();
24✔
1389
        if (CanRestartOnError(reinterpret_cast<char *>(pabyErrorBuffer.get()),
24✔
1390
                              reinterpret_cast<char *>(pabyHeaderData), true))
24✔
1391
        {
1392
            curOffset = 0;
4✔
1393

1394
            AcquireMutex();
4✔
1395
            eExists = EXIST_UNKNOWN;
4✔
1396
            bHasComputedFileSize = false;
4✔
1397
            fileSize = 0;
4✔
1398
            ReleaseMutex();
4✔
1399
            nCachedSize = 0;
4✔
1400

1401
            FileProp cachedFileProp;
4✔
1402
            m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
4✔
1403
            cachedFileProp.bHasComputedFileSize = false;
4✔
1404
            cachedFileProp.fileSize = 0;
4✔
1405
            cachedFileProp.eExists = EXIST_UNKNOWN;
4✔
1406
            m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
4✔
1407

1408
            goto retry;
4✔
1409
        }
1410
        else
1411
        {
1412
            CPLDebug("VSICURL", "Error buffer: %s",
20✔
1413
                     reinterpret_cast<char *>(pabyErrorBuffer.get()));
20✔
1414
            nRet = 0;
20✔
1415
        }
1416
    }
1417

1418
    if (bErrorOccurred)
124✔
1419
    {
1420
        // Look if we should attempt a retry
1421
        AcquireMutex();
7✔
1422
        const bool bRetry = oRetryContext.CanRetry(static_cast<int>(nHTTPCode),
14✔
1423
                                                   nullptr, m_szCurlErrBuf);
7✔
1424
        ReleaseMutex();
7✔
1425
        if (bRetry)
7✔
1426
        {
1427
            StopDownload();
4✔
1428

1429
            CPLError(CE_Warning, CPLE_AppDefined,
4✔
1430
                     "HTTP error code: %d - %s. "
1431
                     "Retrying again in %.1f secs",
1432
                     static_cast<int>(nHTTPCode), m_pszURL,
4✔
1433
                     oRetryContext.GetCurrentDelay());
1434
            CPLSleep(oRetryContext.GetCurrentDelay());
4✔
1435
            curOffset = curOffsetOri;
4✔
1436
            goto retry;
4✔
1437
        }
1438
    }
1439

1440
    if (bErrorOccurred)
120✔
1441
        m_bError = true;
3✔
1442

1443
    return nRet;
120✔
1444
}
1445

1446
/************************************************************************/
1447
/*                          AddRegion()                                 */
1448
/************************************************************************/
1449

1450
void VSICurlStreamingHandle::AddRegion(vsi_l_offset nFileOffsetStart,
115✔
1451
                                       size_t nSize, GByte *pData)
1452
{
1453
    if (nFileOffsetStart >= BKGND_BUFFER_SIZE)
115✔
1454
        return;
1✔
1455

1456
    if (pCachedData == nullptr)
114✔
1457
        pCachedData = static_cast<GByte *>(CPLMalloc(BKGND_BUFFER_SIZE));
50✔
1458

1459
    if (nFileOffsetStart <= nCachedSize &&
114✔
1460
        nFileOffsetStart + nSize > nCachedSize)
114✔
1461
    {
1462
        const size_t nSz = std::min(
1463
            nSize, static_cast<size_t>(BKGND_BUFFER_SIZE - nFileOffsetStart));
110✔
1464
        if (ENABLE_DEBUG)
1465
            CPLDebug("VSICURL", "Writing [%d, %d[ in cache for %s",
1466
                     static_cast<int>(nFileOffsetStart),
1467
                     static_cast<int>(nFileOffsetStart + nSz), m_pszURL);
1468
        memcpy(pCachedData + nFileOffsetStart, pData, nSz);
110✔
1469
        nCachedSize = static_cast<size_t>(nFileOffsetStart + nSz);
110✔
1470
    }
1471
}
1472

1473
/************************************************************************/
1474
/*                               Write()                                */
1475
/************************************************************************/
1476

1477
size_t VSICurlStreamingHandle::Write(const void * /* pBuffer */,
×
1478
                                     size_t /* nSize */, size_t /* nMemb */)
1479
{
1480
    return 0;
×
1481
}
1482

1483
/************************************************************************/
1484
/*                                 Eof()                                */
1485
/************************************************************************/
1486

1487
int VSICurlStreamingHandle::Eof()
4✔
1488
{
1489
    return bEOF;
4✔
1490
}
1491

1492
/************************************************************************/
1493
/*                               Error()                                */
1494
/************************************************************************/
1495

1496
int VSICurlStreamingHandle::Error()
11✔
1497

1498
{
1499
    return m_bError;
11✔
1500
}
1501

1502
/************************************************************************/
1503
/*                             ClearErr()                               */
1504
/************************************************************************/
1505

1506
void VSICurlStreamingHandle::ClearErr()
×
1507
{
1508
    bEOF = false;
×
1509
    m_bError = false;
×
1510
}
×
1511

1512
/************************************************************************/
1513
/*                                 Flush()                              */
1514
/************************************************************************/
1515

1516
int VSICurlStreamingHandle::Flush()
×
1517
{
1518
    return 0;
×
1519
}
1520

1521
/************************************************************************/
1522
/*                                  Close()                             */
1523
/************************************************************************/
1524

1525
int VSICurlStreamingHandle::Close()
30✔
1526
{
1527
    return 0;
30✔
1528
}
1529

1530
/************************************************************************/
1531
/*                      VSICurlStreamingFSHandler()                     */
1532
/************************************************************************/
1533

1534
VSICurlStreamingFSHandler::VSICurlStreamingFSHandler()
9,852✔
1535
    : oCacheFileProp{100 * 1024}
9,852✔
1536
{
1537
    hMutex = CPLCreateMutex();
9,852✔
1538
    CPLReleaseMutex(hMutex);
9,852✔
1539
}
9,852✔
1540

1541
/************************************************************************/
1542
/*                      ~VSICurlStreamingFSHandler()                    */
1543
/************************************************************************/
1544

1545
VSICurlStreamingFSHandler::~VSICurlStreamingFSHandler()
7,791✔
1546
{
1547
    VSICurlStreamingFSHandler::ClearCache();
6,678✔
1548

1549
    CPLDestroyMutex(hMutex);
6,678✔
1550
    hMutex = nullptr;
6,678✔
1551
}
7,791✔
1552

1553
/************************************************************************/
1554
/*                            ClearCache()                              */
1555
/************************************************************************/
1556

1557
void VSICurlStreamingFSHandler::ClearCache()
8,640✔
1558
{
1559
    CPLMutexHolder oHolder(&hMutex);
17,280✔
1560

1561
    {
1562
        const auto lambda = [](const lru11::KeyValuePair<std::string, bool> &kv)
60✔
1563
        { VSICURLInvalidateCachedFileProp(kv.key.c_str()); };
60✔
1564
        oCacheFileProp.cwalk(lambda);
8,640✔
1565
        oCacheFileProp.clear();
8,640✔
1566
    }
1567
}
8,640✔
1568

1569
/************************************************************************/
1570
/*                         AcquireMutex()                               */
1571
/************************************************************************/
1572

1573
void VSICurlStreamingFSHandler::AcquireMutex()
×
1574
{
1575
    CPLAcquireMutex(hMutex, 1000.0);
×
1576
}
×
1577

1578
/************************************************************************/
1579
/*                         ReleaseMutex()                               */
1580
/************************************************************************/
1581

1582
void VSICurlStreamingFSHandler::ReleaseMutex()
×
1583
{
1584
    CPLReleaseMutex(hMutex);
×
1585
}
×
1586

1587
/************************************************************************/
1588
/*                          CreateFileHandle()                          */
1589
/************************************************************************/
1590

1591
VSICurlStreamingHandle *
1592
VSICurlStreamingFSHandler::CreateFileHandle(const char *pszFilename,
10✔
1593
                                            const char *pszURL)
1594
{
1595
    return new VSICurlStreamingHandle(this, pszFilename, pszURL);
10✔
1596
}
1597

1598
/************************************************************************/
1599
/*                                Open()                                */
1600
/************************************************************************/
1601

1602
VSIVirtualHandle *VSICurlStreamingFSHandler::Open(const char *pszFilename,
59✔
1603
                                                  const char *pszAccess,
1604
                                                  bool /* bSetError */,
1605
                                                  CSLConstList papszOptions)
1606
{
1607
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
59✔
1608
        return nullptr;
2✔
1609

1610
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
57✔
1611
    {
1612
        CPLError(CE_Failure, CPLE_AppDefined,
×
1613
                 "Only read-only mode is supported for %s",
1614
                 GetFSPrefix().c_str());
×
1615
        return nullptr;
×
1616
    }
1617

1618
    VSICurlStreamingHandle *poHandle =
1619
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size());
57✔
1620
    // If we didn't get a filelist, check that the file really exists.
1621
    if (poHandle == nullptr || !poHandle->Exists(pszFilename, papszOptions))
57✔
1622
    {
1623
        delete poHandle;
27✔
1624
        return nullptr;
27✔
1625
    }
1626

1627
    if (CPLTestBool(CPLGetConfigOption("VSI_CACHE", "FALSE")))
30✔
1628
        return VSICreateCachedFile(poHandle);
×
1629

1630
    return poHandle;
30✔
1631
}
1632

1633
/************************************************************************/
1634
/*                         GetCachedFileProp()                          */
1635
/************************************************************************/
1636

1637
bool VSICurlStreamingFSHandler::GetCachedFileProp(const char *pszURL,
411✔
1638
                                                  FileProp &oFileProp)
1639
{
1640
    CPLMutexHolder oHolder(&hMutex);
822✔
1641
    bool inCache;
1642
    if (oCacheFileProp.tryGet(std::string(pszURL), inCache))
411✔
1643
    {
1644
        if (VSICURLGetCachedFileProp(pszURL, oFileProp))
349✔
1645
        {
1646
            return true;
349✔
1647
        }
1648
        oCacheFileProp.remove(std::string(pszURL));
×
1649
    }
1650
    return false;
62✔
1651
}
1652

1653
/************************************************************************/
1654
/*                         SetCachedFileProp()                          */
1655
/************************************************************************/
1656

1657
void VSICurlStreamingFSHandler::SetCachedFileProp(const char *pszURL,
411✔
1658
                                                  FileProp &oFileProp)
1659
{
1660
    CPLMutexHolder oHolder(&hMutex);
822✔
1661
    oCacheFileProp.insert(std::string(pszURL), true);
411✔
1662
    VSICURLSetCachedFileProp(pszURL, oFileProp);
411✔
1663
}
411✔
1664

1665
/************************************************************************/
1666
/*                                Stat()                                */
1667
/************************************************************************/
1668

1669
int VSICurlStreamingFSHandler::Stat(const char *pszFilename,
12✔
1670
                                    VSIStatBufL *pStatBuf, int nFlags)
1671
{
1672
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
12✔
1673
        return -1;
2✔
1674

1675
    if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
10✔
1676
    {
1677
        const std::string osVSICURLFilename =
1678
            std::string("/vsicurl/") + (pszFilename + GetFSPrefix().size());
×
1679
        return VSIStatExL(osVSICURLFilename.c_str(), pStatBuf, nFlags);
×
1680
    }
1681

1682
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
10✔
1683

1684
    VSICurlStreamingHandle *poHandle =
1685
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size());
10✔
1686
    if (poHandle == nullptr)
10✔
1687
    {
1688
        return -1;
×
1689
    }
1690
    if (poHandle->IsKnownFileSize() ||
19✔
1691
        ((nFlags & VSI_STAT_SIZE_FLAG) && !poHandle->IsDirectory() &&
9✔
1692
         CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_SLOW_GET_SIZE", "YES"))))
9✔
1693
    {
1694
        pStatBuf->st_size = poHandle->GetFileSize();
10✔
1695
    }
1696

1697
    int nRet = (poHandle->Exists(pszFilename, nullptr)) ? 0 : -1;
10✔
1698
    pStatBuf->st_mode = poHandle->IsDirectory() ? S_IFDIR : S_IFREG;
10✔
1699

1700
    delete poHandle;
10✔
1701
    return nRet;
10✔
1702
}
1703

1704
/************************************************************************/
1705
/*                          GetActualURL()                              */
1706
/************************************************************************/
1707

1708
const char *VSICurlStreamingFSHandler::GetActualURL(const char *pszFilename)
3✔
1709
{
1710
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
3✔
1711
        return pszFilename;
×
1712
    auto poHandle = std::unique_ptr<VSICurlStreamingHandle>(
1713
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size()));
6✔
1714
    if (poHandle == nullptr)
3✔
1715
        return pszFilename;
×
1716
    return CPLSPrintf("%s", poHandle->GetURL());
3✔
1717
}
1718

1719
/************************************************************************/
1720
/*                       GetNonStreamingFilename()                      */
1721
/************************************************************************/
1722

1723
std::string VSICurlStreamingFSHandler::GetNonStreamingFilename(
65✔
1724
    const std::string &osFilename) const
1725
{
1726
    if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
65✔
1727
        return GetNonStreamingPrefix() +
130✔
1728
               osFilename.substr(GetFSPrefix().size());
195✔
1729
    return osFilename;
×
1730
}
1731

1732
/************************************************************************/
1733
/*                      IVSIS3LikeStreamingFSHandler                    */
1734
/************************************************************************/
1735

1736
class IVSIS3LikeStreamingFSHandler : public VSICurlStreamingFSHandler
1737
{
1738
    CPL_DISALLOW_COPY_ASSIGN(IVSIS3LikeStreamingFSHandler)
1739

1740
  public:
1741
    IVSIS3LikeStreamingFSHandler() = default;
8,210✔
1742

1743
    char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
1744

1745
    const char *GetOptions() override
5✔
1746
    {
1747
        return VSIGetFileSystemOptions(GetNonStreamingPrefix().c_str());
5✔
1748
    }
1749
};
1750

1751
char **IVSIS3LikeStreamingFSHandler::ReadDirEx(const char *pszDirname,
1✔
1752
                                               int nMaxFiles)
1753
{
1754
    if (STARTS_WITH(pszDirname, GetFSPrefix()))
1✔
1755
    {
1756
        return VSIReadDirEx(
1✔
1757
            (GetNonStreamingPrefix() + (pszDirname + GetFSPrefix().size()))
2✔
1758
                .c_str(),
1759
            nMaxFiles);
1✔
1760
    }
1761
    return nullptr;
×
1762
}
1763

1764
/************************************************************************/
1765
/*                       VSIS3StreamingFSHandler                        */
1766
/************************************************************************/
1767

1768
class VSIS3StreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1769
{
1770
    CPL_DISALLOW_COPY_ASSIGN(VSIS3StreamingFSHandler)
1771

1772
  protected:
1773
    CPLString GetFSPrefix() const override
228✔
1774
    {
1775
        return "/vsis3_streaming/";
228✔
1776
    }
1777

1778
    std::string GetNonStreamingPrefix() const override
33✔
1779
    {
1780
        return "/vsis3/";
33✔
1781
    }
1782

1783
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1784
                                             const char *pszURL) override;
1785

1786
  public:
1787
    VSIS3StreamingFSHandler() = default;
1,642✔
1788
    ~VSIS3StreamingFSHandler() override = default;
2,226✔
1789

1790
    void ClearCache() override
327✔
1791
    {
1792
        IVSIS3LikeStreamingFSHandler::ClearCache();
327✔
1793
        VSIS3UpdateParams::ClearCache();
327✔
1794
    }
327✔
1795
};
1796

1797
/************************************************************************/
1798
/*                          VSIS3LikeStreamingHandle                    */
1799
/************************************************************************/
1800

1801
class VSIS3LikeStreamingHandle final : public VSICurlStreamingHandle
1802
{
1803
    CPL_DISALLOW_COPY_ASSIGN(VSIS3LikeStreamingHandle)
1804

1805
    IVSIS3LikeHandleHelper *m_poS3HandleHelper = nullptr;
1806

1807
  protected:
1808
    struct curl_slist *
1809
    GetCurlHeaders(const CPLString &osVerb,
1810
                   const struct curl_slist *psExistingHeaders) override;
1811

1812
    bool StopReceivingBytesOnError() override
71✔
1813
    {
1814
        return false;
71✔
1815
    }
1816

1817
    bool CanRestartOnError(const char *pszErrorMsg, const char *pszHeaders,
1818
                           bool bSetError) override;
1819

1820
    bool InterpretRedirect() override
686✔
1821
    {
1822
        return false;
686✔
1823
    }
1824

1825
  public:
1826
    VSIS3LikeStreamingHandle(IVSIS3LikeStreamingFSHandler *poFS,
1827
                             const char *pszFilename,
1828
                             IVSIS3LikeHandleHelper *poS3HandleHelper);
1829
    ~VSIS3LikeStreamingHandle() override;
1830
};
1831

1832
/************************************************************************/
1833
/*                          CreateFileHandle()                          */
1834
/************************************************************************/
1835

1836
VSICurlStreamingHandle *
1837
VSIS3StreamingFSHandler::CreateFileHandle(const char *pszFilename,
32✔
1838
                                          const char *pszURL)
1839
{
1840
    VSIS3HandleHelper *poS3HandleHelper =
1841
        VSIS3HandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str(), false);
32✔
1842
    if (poS3HandleHelper)
32✔
1843
    {
1844
        return new VSIS3LikeStreamingHandle(this, pszFilename,
1845
                                            poS3HandleHelper);
31✔
1846
    }
1847
    return nullptr;
1✔
1848
}
1849

1850
/************************************************************************/
1851
/*                     VSIS3LikeStreamingHandle()                       */
1852
/************************************************************************/
1853

1854
VSIS3LikeStreamingHandle::VSIS3LikeStreamingHandle(
55✔
1855
    IVSIS3LikeStreamingFSHandler *poFS, const char *pszFilename,
1856
    IVSIS3LikeHandleHelper *poS3HandleHelper)
55✔
1857
    : VSICurlStreamingHandle(poFS, pszFilename,
1858
                             poS3HandleHelper->GetURL().c_str()),
110✔
1859
      m_poS3HandleHelper(poS3HandleHelper)
55✔
1860
{
1861
}
55✔
1862

1863
/************************************************************************/
1864
/*                     ~VSIS3LikeStreamingHandle()                      */
1865
/************************************************************************/
1866

1867
VSIS3LikeStreamingHandle::~VSIS3LikeStreamingHandle()
110✔
1868
{
1869
    delete m_poS3HandleHelper;
55✔
1870
}
110✔
1871

1872
/************************************************************************/
1873
/*                           GetCurlHeaders()                           */
1874
/************************************************************************/
1875

1876
struct curl_slist *VSIS3LikeStreamingHandle::GetCurlHeaders(
55✔
1877
    const CPLString &osVerb, const struct curl_slist *psExistingHeaders)
1878
{
1879
    return m_poS3HandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
55✔
1880
}
1881

1882
/************************************************************************/
1883
/*                          CanRestartOnError()                         */
1884
/************************************************************************/
1885

1886
bool VSIS3LikeStreamingHandle::CanRestartOnError(const char *pszErrorMsg,
24✔
1887
                                                 const char *pszHeaders,
1888
                                                 bool bSetError)
1889
{
1890
    if (m_poS3HandleHelper->CanRestartOnError(pszErrorMsg, pszHeaders,
24✔
1891
                                              bSetError))
24✔
1892
    {
1893
        SetURL(m_poS3HandleHelper->GetURL().c_str());
4✔
1894
        return true;
4✔
1895
    }
1896
    return false;
20✔
1897
}
1898

1899
/************************************************************************/
1900
/*                       VSIGSStreamingFSHandler                        */
1901
/************************************************************************/
1902

1903
class VSIGSStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1904
{
1905
  protected:
1906
    CPLString GetFSPrefix() const override
67✔
1907
    {
1908
        return "/vsigs_streaming/";
67✔
1909
    }
1910

1911
    std::string GetNonStreamingPrefix() const override
10✔
1912
    {
1913
        return "/vsigs/";
10✔
1914
    }
1915

1916
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1917
                                             const char *pszURL) override;
1918

1919
  public:
1920
    VSIGSStreamingFSHandler()
1,642✔
1921
    {
1,642✔
1922
    }
1,642✔
1923

1924
    ~VSIGSStreamingFSHandler() override
2,226✔
1925
    {
1,113✔
1926
    }
2,226✔
1927
};
1928

1929
/************************************************************************/
1930
/*                          CreateFileHandle()                          */
1931
/************************************************************************/
1932

1933
VSICurlStreamingHandle *
1934
VSIGSStreamingFSHandler::CreateFileHandle(const char *pszFilename,
10✔
1935
                                          const char *pszURL)
1936
{
1937
    VSIGSHandleHelper *poGCHandleHelper =
1938
        VSIGSHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
10✔
1939
    if (poGCHandleHelper)
10✔
1940
    {
1941
        return new VSIS3LikeStreamingHandle(this, pszFilename,
1942
                                            poGCHandleHelper);
9✔
1943
    }
1944
    return nullptr;
1✔
1945
}
1946

1947
/************************************************************************/
1948
/*                      VSIAzureStreamingFSHandler                      */
1949
/************************************************************************/
1950

1951
class VSIAzureStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1952
{
1953
  protected:
1954
    CPLString GetFSPrefix() const override
39✔
1955
    {
1956
        return "/vsiaz_streaming/";
39✔
1957
    }
1958

1959
    std::string GetNonStreamingPrefix() const override
6✔
1960
    {
1961
        return "/vsiaz/";
6✔
1962
    }
1963

1964
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1965
                                             const char *pszURL) override;
1966

1967
  public:
1968
    VSIAzureStreamingFSHandler()
1,642✔
1969
    {
1,642✔
1970
    }
1,642✔
1971

1972
    ~VSIAzureStreamingFSHandler() override
2,226✔
1973
    {
1,113✔
1974
    }
2,226✔
1975
};
1976

1977
/************************************************************************/
1978
/*                          CreateFileHandle()                          */
1979
/************************************************************************/
1980

1981
VSICurlStreamingHandle *
1982
VSIAzureStreamingFSHandler::CreateFileHandle(const char *pszFilename,
6✔
1983
                                             const char *pszURL)
1984
{
1985
    VSIAzureBlobHandleHelper *poHandleHelper =
1986
        VSIAzureBlobHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
6✔
1987
    if (poHandleHelper)
6✔
1988
    {
1989
        return new VSIS3LikeStreamingHandle(this, pszFilename, poHandleHelper);
5✔
1990
    }
1991
    return nullptr;
1✔
1992
}
1993

1994
/************************************************************************/
1995
/*                       VSIOSSStreamingFSHandler                        */
1996
/************************************************************************/
1997

1998
class VSIOSSStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1999
{
2000
    CPL_DISALLOW_COPY_ASSIGN(VSIOSSStreamingFSHandler)
2001

2002
  protected:
2003
    CPLString GetFSPrefix() const override
60✔
2004
    {
2005
        return "/vsioss_streaming/";
60✔
2006
    }
2007

2008
    std::string GetNonStreamingPrefix() const override
9✔
2009
    {
2010
        return "/vsioss/";
9✔
2011
    }
2012

2013
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
2014
                                             const char *pszURL) override;
2015

2016
  public:
2017
    VSIOSSStreamingFSHandler() = default;
1,642✔
2018
    ~VSIOSSStreamingFSHandler() override = default;
2,226✔
2019

2020
    void ClearCache() override
327✔
2021
    {
2022
        IVSIS3LikeStreamingFSHandler::ClearCache();
327✔
2023
        VSIOSSUpdateParams::ClearCache();
327✔
2024
    }
327✔
2025
};
2026

2027
/************************************************************************/
2028
/*                          CreateFileHandle()                          */
2029
/************************************************************************/
2030

2031
VSICurlStreamingHandle *
2032
VSIOSSStreamingFSHandler::CreateFileHandle(const char *pszFilename,
9✔
2033
                                           const char *pszURL)
2034
{
2035
    VSIOSSHandleHelper *poOSSHandleHelper =
2036
        VSIOSSHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str(), false);
9✔
2037
    if (poOSSHandleHelper)
9✔
2038
    {
2039
        return new VSIS3LikeStreamingHandle(this, pszFilename,
2040
                                            poOSSHandleHelper);
8✔
2041
    }
2042
    return nullptr;
1✔
2043
}
2044

2045
/************************************************************************/
2046
/*                      VSISwiftStreamingFSHandler                      */
2047
/************************************************************************/
2048

2049
class VSISwiftStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
2050
{
2051
  protected:
2052
    CPLString GetFSPrefix() const override
18✔
2053
    {
2054
        return "/vsiswift_streaming/";
18✔
2055
    }
2056

2057
    std::string GetNonStreamingPrefix() const override
3✔
2058
    {
2059
        return "/vsiswift/";
3✔
2060
    }
2061

2062
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
2063
                                             const char *pszURL) override;
2064

2065
  public:
2066
    VSISwiftStreamingFSHandler()
1,642✔
2067
    {
1,642✔
2068
    }
1,642✔
2069

2070
    ~VSISwiftStreamingFSHandler() override
2,226✔
2071
    {
1,113✔
2072
    }
2,226✔
2073
};
2074

2075
/************************************************************************/
2076
/*                          CreateFileHandle()                          */
2077
/************************************************************************/
2078

2079
VSICurlStreamingHandle *
2080
VSISwiftStreamingFSHandler::CreateFileHandle(const char *pszFilename,
3✔
2081
                                             const char *pszURL)
2082
{
2083
    VSISwiftHandleHelper *poHandleHelper =
2084
        VSISwiftHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
3✔
2085
    if (poHandleHelper)
3✔
2086
    {
2087
        return new VSIS3LikeStreamingHandle(this, pszFilename, poHandleHelper);
2✔
2088
    }
2089
    return nullptr;
1✔
2090
}
2091

2092
//! @endcond
2093

2094
} /* namespace cpl */
2095

2096
/************************************************************************/
2097
/*                 VSIInstallCurlStreamingFileHandler()                 */
2098
/************************************************************************/
2099

2100
/*!
2101
 \brief Install /vsicurl_streaming/ HTTP/FTP file system handler (requires
2102
 libcurl).
2103

2104
  \verbatim embed:rst
2105
 See :ref:`/vsicurl_streaming/ documentation <vsicurl_streaming>`
2106
 \endverbatim
2107

2108
 @since GDAL 1.10
2109
 */
2110
void VSIInstallCurlStreamingFileHandler(void)
1,642✔
2111
{
2112
    VSIFileManager::InstallHandler("/vsicurl_streaming/",
1,642✔
2113
                                   new cpl::VSICurlStreamingFSHandler);
1,642✔
2114
}
1,642✔
2115

2116
/************************************************************************/
2117
/*                   VSIInstallS3StreamingFileHandler()                 */
2118
/************************************************************************/
2119

2120
/*!
2121
 \brief Install /vsis3_streaming/ Amazon S3 file system handler (requires
2122
 libcurl).
2123

2124
  \verbatim embed:rst
2125
 See :ref:`/vsis3_streaming/ documentation <vsis3_streaming>`
2126
 \endverbatim
2127

2128
 @since GDAL 2.1
2129
 */
2130
void VSIInstallS3StreamingFileHandler(void)
1,642✔
2131
{
2132
    VSIFileManager::InstallHandler("/vsis3_streaming/",
1,642✔
2133
                                   new cpl::VSIS3StreamingFSHandler);
1,642✔
2134
}
1,642✔
2135

2136
/************************************************************************/
2137
/*                      VSIInstallGSStreamingFileHandler()              */
2138
/************************************************************************/
2139

2140
/*!
2141
 \brief Install /vsigs_streaming/ Google Cloud Storage file system handler
2142
 (requires libcurl)
2143

2144
  \verbatim embed:rst
2145
 See :ref:`/vsigs_streaming/ documentation <vsigs_streaming>`
2146
 \endverbatim
2147

2148
 @since GDAL 2.2
2149
 */
2150

2151
void VSIInstallGSStreamingFileHandler(void)
1,642✔
2152
{
2153
    VSIFileManager::InstallHandler("/vsigs_streaming/",
1,642✔
2154
                                   new cpl::VSIGSStreamingFSHandler);
1,642✔
2155
}
1,642✔
2156

2157
/************************************************************************/
2158
/*                   VSIInstallAzureStreamingFileHandler()              */
2159
/************************************************************************/
2160

2161
/*!
2162
 \brief Install /vsiaz_streaming/ Microsoft Azure Blob file system handler
2163
 (requires libcurl)
2164

2165
  \verbatim embed:rst
2166
 See :ref:`/vsiaz_streaming/ documentation <vsiaz_streaming>`
2167
 \endverbatim
2168

2169
 @since GDAL 2.3
2170
 */
2171

2172
void VSIInstallAzureStreamingFileHandler(void)
1,642✔
2173
{
2174
    VSIFileManager::InstallHandler("/vsiaz_streaming/",
1,642✔
2175
                                   new cpl::VSIAzureStreamingFSHandler);
1,642✔
2176
}
1,642✔
2177

2178
/************************************************************************/
2179
/*                    VSIInstallOSSStreamingFileHandler()               */
2180
/************************************************************************/
2181

2182
/*!
2183
 \brief Install /vsiaz_streaming/ Alibaba Cloud Object Storage Service (OSS)
2184
 (requires libcurl)
2185

2186
  \verbatim embed:rst
2187
 See :ref:`/vsioss_streaming/ documentation <vsioss_streaming>`
2188
 \endverbatim
2189

2190
 @since GDAL 2.3
2191
 */
2192

2193
void VSIInstallOSSStreamingFileHandler(void)
1,642✔
2194
{
2195
    VSIFileManager::InstallHandler("/vsioss_streaming/",
1,642✔
2196
                                   new cpl::VSIOSSStreamingFSHandler);
1,642✔
2197
}
1,642✔
2198

2199
/************************************************************************/
2200
/*                  VSIInstallSwiftStreamingFileHandler()               */
2201
/************************************************************************/
2202

2203
/*!
2204
 \brief Install /vsiswift_streaming/ OpenStack Swif Object Storage (Swift) file
2205
 system handler (requires libcurl)
2206

2207
  \verbatim embed:rst
2208
 See :ref:`/vsiswift_streaming/ documentation <vsiswift_streaming>`
2209
 \endverbatim
2210

2211
 @since GDAL 2.3
2212
 */
2213

2214
void VSIInstallSwiftStreamingFileHandler(void)
1,642✔
2215
{
2216
    VSIFileManager::InstallHandler("/vsiswift_streaming/",
1,642✔
2217
                                   new cpl::VSISwiftStreamingFSHandler);
1,642✔
2218
}
1,642✔
2219

2220
//! @cond Doxygen_Suppress
2221

2222
/************************************************************************/
2223
/*                      VSICurlStreamingClearCache()                    */
2224
/************************************************************************/
2225

2226
void VSICurlStreamingClearCache(void)
327✔
2227
{
2228
    // FIXME ? Currently we have different filesystem instances for
2229
    // vsicurl/, /vsis3/, /vsigs/ . So each one has its own cache of regions.
2230
    // File properties cache are now shared
2231
    char **papszPrefix = VSIFileManager::GetPrefixes();
327✔
2232
    for (size_t i = 0; papszPrefix && papszPrefix[i]; ++i)
10,137✔
2233
    {
2234
        auto poFSHandler = dynamic_cast<cpl::VSICurlStreamingFSHandler *>(
×
2235
            VSIFileManager::GetHandler(papszPrefix[i]));
9,810✔
2236

2237
        if (poFSHandler)
9,810✔
2238
            poFSHandler->ClearCache();
1,962✔
2239
    }
2240
    CSLDestroy(papszPrefix);
327✔
2241
}
327✔
2242

2243
//! @endcond
2244

2245
#undef ENABLE_DEBUG
2246

2247
#endif  // !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc