Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_curl_streaming.cpp
Line
Count
Source
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 <cinttypes>
20
#include <map>
21
22
#include "cpl_aws.h"
23
#include "cpl_google_cloud.h"
24
#include "cpl_azure.h"
25
#include "cpl_alibaba_oss.h"
26
#include "cpl_swift.h"
27
#include "cpl_hash_set.h"
28
#include "cpl_http.h"
29
#include "cpl_multiproc.h"
30
#include "cpl_string.h"
31
#include "cpl_time.h"
32
33
#if !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB)
34
35
void VSIInstallCurlStreamingFileHandler(void)
36
{
37
    // Not supported.
38
}
39
40
void VSIInstallS3StreamingFileHandler(void)
41
{
42
    // Not supported.
43
}
44
45
void VSIInstallGSStreamingFileHandler(void)
46
{
47
    // Not supported.
48
}
49
50
void VSIInstallAzureStreamingFileHandler(void)
51
{
52
    // Not supported
53
}
54
55
void VSIInstallOSSStreamingFileHandler(void)
56
{
57
    // Not supported
58
}
59
60
void VSIInstallSwiftStreamingFileHandler(void)
61
{
62
    // Not supported
63
}
64
65
#ifdef HAVE_CURL
66
void VSICurlStreamingClearCache(void)
67
{
68
    // Not supported
69
}
70
#endif
71
72
#else
73
74
//! @cond Doxygen_Suppress
75
76
#include <curl/curl.h>
77
78
89.6k
#define ENABLE_DEBUG 0
79
80
#define N_MAX_REGIONS 10
81
82
10.8k
#define BKGND_BUFFER_SIZE (1024 * 1024)
83
84
#define unchecked_curl_easy_setopt(handle, opt, param)                         \
85
52.0k
    CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
86
87
/************************************************************************/
88
/*                              RingBuffer                              */
89
/************************************************************************/
90
91
class RingBuffer
92
{
93
    CPL_DISALLOW_COPY_ASSIGN(RingBuffer)
94
95
    GByte *pabyBuffer = nullptr;
96
    size_t nCapacity = 0;
97
    size_t nOffset = 0;
98
    size_t nLength = 0;
99
100
  public:
101
    explicit RingBuffer(size_t nCapacity = BKGND_BUFFER_SIZE);
102
    ~RingBuffer();
103
104
    size_t GetCapacity() const
105
61.8k
    {
106
61.8k
        return nCapacity;
107
61.8k
    }
108
109
    size_t GetSize() const
110
82.5k
    {
111
82.5k
        return nLength;
112
82.5k
    }
113
114
    void Reset();
115
    void Write(void *pBuffer, size_t nSize);
116
    void Read(void *pBuffer, size_t nSize);
117
};
118
119
RingBuffer::RingBuffer(size_t nCapacityIn)
120
11.7k
    : pabyBuffer(static_cast<GByte *>(CPLMalloc(nCapacityIn))),
121
11.7k
      nCapacity(nCapacityIn)
122
11.7k
{
123
11.7k
}
124
125
RingBuffer::~RingBuffer()
126
11.7k
{
127
11.7k
    CPLFree(pabyBuffer);
128
11.7k
}
129
130
void RingBuffer::Reset()
131
16.3k
{
132
16.3k
    nOffset = 0;
133
16.3k
    nLength = 0;
134
16.3k
}
135
136
void RingBuffer::Write(void *pBuffer, size_t nSize)
137
61.8k
{
138
61.8k
    CPLAssert(nLength + nSize <= nCapacity);
139
140
61.8k
    const size_t nEndOffset = (nOffset + nLength) % nCapacity;
141
61.8k
    const size_t nSz = std::min(nSize, nCapacity - nEndOffset);
142
61.8k
    memcpy(pabyBuffer + nEndOffset, pBuffer, nSz);
143
61.8k
    if (nSz < nSize)
144
0
        memcpy(pabyBuffer, static_cast<GByte *>(pBuffer) + nSz, nSize - nSz);
145
146
61.8k
    nLength += nSize;
147
61.8k
}
148
149
void RingBuffer::Read(void *pBuffer, size_t nSize)
150
6.57k
{
151
6.57k
    CPLAssert(nSize <= nLength);
152
153
6.57k
    if (pBuffer)
154
6.57k
    {
155
6.57k
        const size_t nSz = std::min(nSize, nCapacity - nOffset);
156
6.57k
        memcpy(pBuffer, pabyBuffer + nOffset, nSz);
157
6.57k
        if (nSz < nSize)
158
0
            memcpy(static_cast<GByte *>(pBuffer) + nSz, pabyBuffer,
159
0
                   nSize - nSz);
160
6.57k
    }
161
162
6.57k
    nOffset = (nOffset + nSize) % nCapacity;
163
6.57k
    nLength -= nSize;
164
6.57k
}
165
166
/************************************************************************/
167
168
namespace
169
{
170
171
typedef struct
172
{
173
    char *pBuffer;
174
    size_t nSize;
175
    int bIsHTTP;
176
    int bIsInHeader;
177
    int nHTTPCode;
178
    int bDownloadHeaderOnly;
179
} WriteFuncStructStreaming;
180
181
}  // namespace
182
183
namespace cpl
184
{
185
186
/************************************************************************/
187
/*                      VSICurlStreamingFSHandler                       */
188
/************************************************************************/
189
190
class VSICurlStreamingHandle;
191
192
class VSICurlStreamingFSHandler /* non final */ : public VSIFilesystemHandler
193
{
194
    CPL_DISALLOW_COPY_ASSIGN(VSICurlStreamingFSHandler)
195
196
    // LRU cache that just keeps in memory if this file system handler is
197
    // spposed to know the file properties of a file. The actual cache is a
198
    // shared one among all network file systems.
199
    // The aim of that design is that invalidating /vsis3/foo results in
200
    // /vsis3_streaming/foo to be invalidated as well.
201
    lru11::Cache<std::string, bool> oCacheFileProp;
202
203
  protected:
204
    CPLMutex *hMutex = nullptr;
205
206
    virtual VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
207
                                                     const char *pszURL);
208
209
    virtual std::string GetNonStreamingPrefix() const
210
10.4k
    {
211
10.4k
        return "/vsicurl/";
212
10.4k
    }
213
214
  public:
215
    VSICurlStreamingFSHandler();
216
    ~VSICurlStreamingFSHandler() override;
217
218
    VSIVirtualHandleUniquePtr Open(const char *pszFilename,
219
                                   const char *pszAccess, bool bSetError,
220
                                   CSLConstList /* papszOptions */) override;
221
222
    virtual int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
223
                     int nFlags) override;
224
225
    virtual CPLString GetFSPrefix() const
226
62.9k
    {
227
62.9k
        return "/vsicurl_streaming/";
228
62.9k
    }
229
230
    std::string
231
    GetNonStreamingFilename(const std::string &osFilename) const override;
232
233
    const char *GetActualURL(const char *pszFilename) override;
234
235
    const char *GetOptions() override
236
0
    {
237
0
        return VSIGetFileSystemOptions("/vsicurl/");
238
0
    }
239
240
    void AcquireMutex();
241
    void ReleaseMutex();
242
243
    bool GetCachedFileProp(const char *pszURL, FileProp &oFileProp);
244
    void SetCachedFileProp(const char *pszURL, FileProp &oFileProp);
245
246
    virtual void ClearCache();
247
};
248
249
/************************************************************************/
250
/*                        VSICurlStreamingHandle                        */
251
/************************************************************************/
252
253
class VSICurlStreamingHandle : public VSIVirtualHandle
254
{
255
    CPL_DISALLOW_COPY_ASSIGN(VSICurlStreamingHandle)
256
257
  protected:
258
    VSICurlStreamingFSHandler *m_poFS = nullptr;
259
    CPLStringList m_aosHTTPOptions{};
260
    const CPLHTTPRetryParameters m_oRetryParameters;
261
262
  private:
263
    char *m_pszURL = nullptr;
264
265
#ifdef notdef
266
    unsigned int nRecomputedChecksumOfFirst1024Bytes = 0;
267
#endif
268
    vsi_l_offset curOffset = 0;
269
    vsi_l_offset fileSize = 0;
270
    bool bHasComputedFileSize = false;
271
    ExistStatus eExists = EXIST_UNKNOWN;
272
    bool bIsDirectory = false;
273
274
    bool bCanTrustCandidateFileSize = true;
275
    bool bHasCandidateFileSize = false;
276
    vsi_l_offset nCandidateFileSize = 0;
277
278
    bool bEOF = false;
279
    bool m_bError = false;
280
281
    size_t nCachedSize = 0;
282
    GByte *pCachedData = nullptr;
283
284
    volatile int bDownloadInProgress = FALSE;
285
    volatile int bDownloadStopped = FALSE;
286
    volatile int bAskDownloadEnd = FALSE;
287
    vsi_l_offset nRingBufferFileOffset = 0;
288
    CPLJoinableThread *hThread = nullptr;
289
    CPLMutex *hRingBufferMutex = nullptr;
290
    CPLCond *hCondProducer = nullptr;
291
    CPLCond *hCondConsumer = nullptr;
292
    RingBuffer oRingBuffer{};
293
    void StartDownload();
294
    void StopDownload();
295
    void PutRingBufferInCache();
296
297
    GByte *pabyHeaderData = nullptr;
298
    size_t nHeaderSize = 0;
299
    vsi_l_offset nBodySize = 0;
300
    int nHTTPCode = 0;
301
    char m_szCurlErrBuf[CURL_ERROR_SIZE + 1];
302
    bool m_bErrorOccurredInThread = false;
303
304
    void AcquireMutex();
305
    void ReleaseMutex();
306
307
    void AddRegion(vsi_l_offset nFileOffsetStart, size_t nSize, GByte *pData);
308
309
  protected:
310
    virtual struct curl_slist *GetCurlHeaders(const CPLString &,
311
                                              struct curl_slist *psHeaders)
312
4.71k
    {
313
4.71k
        return psHeaders;
314
4.71k
    }
315
316
    virtual bool StopReceivingBytesOnError()
317
4.22k
    {
318
4.22k
        return true;
319
4.22k
    }
320
321
    virtual bool CanRestartOnError(const char * /*pszErrorMsg*/,
322
                                   const char * /*pszHeaders*/,
323
                                   bool /*bSetError*/)
324
0
    {
325
0
        return false;
326
0
    }
327
328
    virtual bool InterpretRedirect()
329
10.6k
    {
330
10.6k
        return true;
331
10.6k
    }
332
333
    void SetURL(const char *pszURL);
334
335
  public:
336
    VSICurlStreamingHandle(VSICurlStreamingFSHandler *poFS,
337
                           const char *pszFilename, const char *pszURL);
338
    ~VSICurlStreamingHandle() override;
339
340
    int Seek(vsi_l_offset nOffset, int nWhence) override;
341
    vsi_l_offset Tell() override;
342
    size_t Read(void *pBuffer, size_t nBytes) override;
343
    size_t Write(const void *pBuffer, size_t nBytes) override;
344
    void ClearErr() override;
345
    int Error() override;
346
    int Eof() override;
347
    int Flush() override;
348
    int Close() override;
349
350
    void DownloadInThread();
351
    size_t ReceivedBytes(GByte *buffer, size_t count, size_t nmemb);
352
    size_t ReceivedBytesHeader(GByte *buffer, size_t count, size_t nmemb);
353
354
    bool IsKnownFileSize() const
355
3.76k
    {
356
3.76k
        return bHasComputedFileSize;
357
3.76k
    }
358
359
    vsi_l_offset GetFileSize();
360
    bool Exists(const char *pszFilename, CSLConstList papszOptions);
361
362
    bool IsDirectory() const
363
4.18k
    {
364
4.18k
        return bIsDirectory;
365
4.18k
    }
366
367
    const char *GetURL() const
368
0
    {
369
0
        return m_pszURL;
370
0
    }
371
};
372
373
/************************************************************************/
374
/*                       VSICurlStreamingHandle()                       */
375
/************************************************************************/
376
377
VSICurlStreamingHandle::VSICurlStreamingHandle(VSICurlStreamingFSHandler *poFS,
378
                                               const char *pszFilename,
379
                                               const char *pszURL)
380
11.7k
    : m_poFS(poFS), m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
381
11.7k
      m_oRetryParameters(m_aosHTTPOptions), m_pszURL(CPLStrdup(pszURL))
382
11.7k
{
383
11.7k
    FileProp cachedFileProp;
384
11.7k
    poFS->GetCachedFileProp(pszURL, cachedFileProp);
385
11.7k
    eExists = cachedFileProp.eExists;
386
11.7k
    fileSize = cachedFileProp.fileSize;
387
11.7k
    bHasComputedFileSize = cachedFileProp.bHasComputedFileSize;
388
11.7k
    bIsDirectory = cachedFileProp.bIsDirectory;
389
11.7k
    poFS->SetCachedFileProp(pszURL, cachedFileProp);
390
391
11.7k
    hRingBufferMutex = CPLCreateMutex();
392
11.7k
    ReleaseMutex();
393
11.7k
    hCondProducer = CPLCreateCond();
394
11.7k
    hCondConsumer = CPLCreateCond();
395
396
11.7k
    memset(m_szCurlErrBuf, 0, sizeof(m_szCurlErrBuf));
397
11.7k
}
398
399
/************************************************************************/
400
/*                      ~VSICurlStreamingHandle()                       */
401
/************************************************************************/
402
403
VSICurlStreamingHandle::~VSICurlStreamingHandle()
404
11.7k
{
405
11.7k
    StopDownload();
406
407
11.7k
    CPLFree(m_pszURL);
408
409
11.7k
    CPLFree(pCachedData);
410
411
11.7k
    CPLFree(pabyHeaderData);
412
413
11.7k
    CPLDestroyMutex(hRingBufferMutex);
414
11.7k
    CPLDestroyCond(hCondProducer);
415
11.7k
    CPLDestroyCond(hCondConsumer);
416
11.7k
}
417
418
/************************************************************************/
419
/*                               SetURL()                               */
420
/************************************************************************/
421
422
void VSICurlStreamingHandle::SetURL(const char *pszURLIn)
423
0
{
424
0
    CPLFree(m_pszURL);
425
0
    m_pszURL = CPLStrdup(pszURLIn);
426
0
}
427
428
/************************************************************************/
429
/*                            AcquireMutex()                            */
430
/************************************************************************/
431
432
void VSICurlStreamingHandle::AcquireMutex()
433
100k
{
434
100k
    CPLAcquireMutex(hRingBufferMutex, 1000.0);
435
100k
}
436
437
/************************************************************************/
438
/*                            ReleaseMutex()                            */
439
/************************************************************************/
440
441
void VSICurlStreamingHandle::ReleaseMutex()
442
112k
{
443
112k
    CPLReleaseMutex(hRingBufferMutex);
444
112k
}
445
446
/************************************************************************/
447
/*                                Seek()                                */
448
/************************************************************************/
449
450
int VSICurlStreamingHandle::Seek(vsi_l_offset nOffset, int nWhence)
451
4.47k
{
452
4.47k
    if (curOffset >= BKGND_BUFFER_SIZE)
453
0
    {
454
0
        if (ENABLE_DEBUG)
455
0
            CPLDebug("VSICURL",
456
0
                     "Invalidating cache and file size due to Seek() "
457
0
                     "beyond caching zone");
458
0
        CPLFree(pCachedData);
459
0
        pCachedData = nullptr;
460
0
        nCachedSize = 0;
461
0
        AcquireMutex();
462
0
        bHasComputedFileSize = false;
463
0
        fileSize = 0;
464
0
        ReleaseMutex();
465
0
    }
466
467
4.47k
    if (nWhence == SEEK_SET)
468
4.23k
    {
469
4.23k
        curOffset = nOffset;
470
4.23k
    }
471
239
    else if (nWhence == SEEK_CUR)
472
0
    {
473
0
        curOffset = curOffset + nOffset;
474
0
    }
475
239
    else
476
239
    {
477
239
        curOffset = GetFileSize() + nOffset;
478
239
    }
479
4.47k
    bEOF = false;
480
4.47k
    return 0;
481
4.47k
}
482
483
/************************************************************************/
484
/*            VSICURLStreamingInitWriteFuncStructStreaming()            */
485
/************************************************************************/
486
487
static void
488
VSICURLStreamingInitWriteFuncStructStreaming(WriteFuncStructStreaming *psStruct)
489
846
{
490
846
    psStruct->pBuffer = nullptr;
491
846
    psStruct->nSize = 0;
492
846
    psStruct->bIsHTTP = FALSE;
493
846
    psStruct->bIsInHeader = TRUE;
494
846
    psStruct->nHTTPCode = 0;
495
846
    psStruct->bDownloadHeaderOnly = FALSE;
496
846
}
497
498
/************************************************************************/
499
/*              VSICurlStreamingHandleWriteFuncForHeader()              */
500
/************************************************************************/
501
502
static size_t VSICurlStreamingHandleWriteFuncForHeader(void *buffer,
503
                                                       size_t count,
504
                                                       size_t nmemb, void *req)
505
38
{
506
38
    WriteFuncStructStreaming *psStruct =
507
38
        static_cast<WriteFuncStructStreaming *>(req);
508
38
    const size_t nSize = count * nmemb;
509
510
38
    char *pNewBuffer = static_cast<char *>(
511
38
        VSIRealloc(psStruct->pBuffer, psStruct->nSize + nSize + 1));
512
38
    if (pNewBuffer)
513
38
    {
514
38
        psStruct->pBuffer = pNewBuffer;
515
38
        memcpy(psStruct->pBuffer + psStruct->nSize, buffer, nSize);
516
38
        psStruct->pBuffer[psStruct->nSize + nSize] = '\0';
517
38
        if (psStruct->bIsHTTP && psStruct->bIsInHeader)
518
0
        {
519
0
            char *pszLine = psStruct->pBuffer + psStruct->nSize;
520
0
            if (STARTS_WITH_CI(pszLine, "HTTP/"))
521
0
            {
522
0
                const char *pszSpace =
523
0
                    strchr(const_cast<const char *>(pszLine), ' ');
524
0
                if (pszSpace)
525
0
                    psStruct->nHTTPCode = atoi(pszSpace + 1);
526
0
            }
527
528
0
            if (pszLine[0] == '\r' || pszLine[0] == '\n')
529
0
            {
530
0
                if (psStruct->bDownloadHeaderOnly)
531
0
                {
532
                    // If moved permanently/temporarily, go on.
533
                    // Otherwise stop now.
534
0
                    if (!(psStruct->nHTTPCode == 301 ||
535
0
                          psStruct->nHTTPCode == 302 ||
536
0
                          psStruct->nHTTPCode == 303))
537
0
                        return 0;
538
0
                }
539
0
                else
540
0
                {
541
0
                    psStruct->bIsInHeader = FALSE;
542
0
                }
543
0
            }
544
0
        }
545
38
        psStruct->nSize += nSize;
546
38
        return nmemb;
547
38
    }
548
0
    else
549
0
    {
550
0
        return 0;
551
0
    }
552
38
}
553
554
/************************************************************************/
555
/*                            GetFileSize()                             */
556
/************************************************************************/
557
558
vsi_l_offset VSICurlStreamingHandle::GetFileSize()
559
1.97k
{
560
1.97k
    WriteFuncStructStreaming sWriteFuncData;
561
1.97k
    WriteFuncStructStreaming sWriteFuncHeaderData;
562
563
1.97k
    AcquireMutex();
564
1.97k
    if (bHasComputedFileSize)
565
1.54k
    {
566
1.54k
        const vsi_l_offset nRet = fileSize;
567
1.54k
        ReleaseMutex();
568
1.54k
        return nRet;
569
1.54k
    }
570
423
    ReleaseMutex();
571
572
423
    CURL *hLocalHandle = curl_easy_init();
573
574
423
    struct curl_slist *headers =
575
423
        VSICurlSetOptions(hLocalHandle, m_pszURL, m_aosHTTPOptions.List());
576
577
423
    VSICURLStreamingInitWriteFuncStructStreaming(&sWriteFuncHeaderData);
578
579
    // HACK for mbtiles driver: Proper fix would be to auto-detect servers that
580
    // don't accept HEAD http://a.tiles.mapbox.com/v3/ doesn't accept HEAD, so
581
    // let's start a GET and interrupt is as soon as the header is found.
582
423
    CPLString osVerb;
583
423
    if (strstr(m_pszURL, ".tiles.mapbox.com/") != nullptr)
584
110
    {
585
110
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADERDATA,
586
110
                                   &sWriteFuncHeaderData);
587
110
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADERFUNCTION,
588
110
                                   VSICurlStreamingHandleWriteFuncForHeader);
589
590
110
        sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
591
110
        sWriteFuncHeaderData.bDownloadHeaderOnly = TRUE;
592
110
        osVerb = "GET";
593
110
    }
594
313
    else
595
313
    {
596
313
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_NOBODY, 1);
597
313
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HTTPGET, 0);
598
313
        unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HEADER, 1);
599
313
        osVerb = "HEAD";
600
313
    }
601
602
423
    headers = GetCurlHeaders(osVerb, headers);
603
423
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_HTTPHEADER, headers);
604
605
    // We need that otherwise OSGEO4W's libcurl issue a dummy range request
606
    // when doing a HEAD when recycling connections.
607
423
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_RANGE, nullptr);
608
609
    // Bug with older curl versions (<=7.16.4) and FTP.
610
    // See http://curl.haxx.se/mail/lib-2007-08/0312.html
611
423
    VSICURLStreamingInitWriteFuncStructStreaming(&sWriteFuncData);
612
423
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_WRITEDATA,
613
423
                               &sWriteFuncData);
614
423
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_WRITEFUNCTION,
615
423
                               VSICurlStreamingHandleWriteFuncForHeader);
616
617
423
    char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
618
423
    unchecked_curl_easy_setopt(hLocalHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
619
620
423
    void *old_handler = CPLHTTPIgnoreSigPipe();
621
423
    curl_easy_perform(hLocalHandle);
622
423
    CPLHTTPRestoreSigPipeHandler(old_handler);
623
423
    if (headers != nullptr)
624
4
        curl_slist_free_all(headers);
625
626
423
    AcquireMutex();
627
628
423
    eExists = EXIST_UNKNOWN;
629
423
    bHasComputedFileSize = true;
630
631
423
    if (STARTS_WITH(m_pszURL, "ftp"))
632
0
    {
633
0
        if (sWriteFuncData.pBuffer != nullptr &&
634
0
            STARTS_WITH_CI(sWriteFuncData.pBuffer, "Content-Length: "))
635
0
        {
636
0
            const char *pszBuffer =
637
0
                sWriteFuncData.pBuffer + strlen("Content-Length: ");
638
0
            eExists = EXIST_YES;
639
0
            fileSize = CPLScanUIntBig(
640
0
                pszBuffer, static_cast<int>(sWriteFuncData.nSize -
641
0
                                            strlen("Content-Length: ")));
642
0
            if (ENABLE_DEBUG)
643
0
                CPLDebug("VSICURL", "GetFileSize(%s)=" CPL_FRMT_GUIB, m_pszURL,
644
0
                         fileSize);
645
0
        }
646
0
    }
647
648
423
    double dfSize = 0;
649
423
    if (eExists != EXIST_YES)
650
423
    {
651
423
        curl_off_t nSizeTmp = 0;
652
423
        const CURLcode code = curl_easy_getinfo(
653
423
            hLocalHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nSizeTmp);
654
423
        CPL_IGNORE_RET_VAL(dfSize);
655
423
        dfSize = static_cast<double>(nSizeTmp);
656
423
        if (code == 0)
657
423
        {
658
423
            eExists = EXIST_YES;
659
423
            if (dfSize < 0)
660
419
                fileSize = 0;
661
4
            else
662
4
                fileSize = static_cast<GUIntBig>(dfSize);
663
423
        }
664
0
        else
665
0
        {
666
0
            eExists = EXIST_NO;
667
0
            fileSize = 0;
668
0
            CPLError(CE_Failure, CPLE_AppDefined,
669
0
                     "VSICurlStreamingHandle::GetFileSize failed");
670
0
        }
671
672
423
        long response_code = 0;
673
423
        curl_easy_getinfo(hLocalHandle, CURLINFO_HTTP_CODE, &response_code);
674
423
        if (response_code != 200)
675
423
        {
676
423
            eExists = EXIST_NO;
677
423
            fileSize = 0;
678
423
        }
679
680
        // Try to guess if this is a directory. Generally if this is a
681
        // directory, curl will retry with an URL with slash added.
682
423
        char *pszEffectiveURL = nullptr;
683
423
        curl_easy_getinfo(hLocalHandle, CURLINFO_EFFECTIVE_URL,
684
423
                          &pszEffectiveURL);
685
423
        if (pszEffectiveURL != nullptr &&
686
423
            strncmp(m_pszURL, pszEffectiveURL, strlen(m_pszURL)) == 0 &&
687
348
            pszEffectiveURL[strlen(m_pszURL)] == '/')
688
0
        {
689
0
            eExists = EXIST_YES;
690
0
            fileSize = 0;
691
0
            bIsDirectory = true;
692
0
        }
693
694
423
        if (ENABLE_DEBUG)
695
0
            CPLDebug("VSICURL",
696
0
                     "GetFileSize(%s)=" CPL_FRMT_GUIB " response_code=%d",
697
0
                     m_pszURL, fileSize, static_cast<int>(response_code));
698
423
    }
699
700
423
    CPLFree(sWriteFuncData.pBuffer);
701
423
    CPLFree(sWriteFuncHeaderData.pBuffer);
702
703
423
    FileProp cachedFileProp;
704
423
    m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
705
423
    cachedFileProp.bHasComputedFileSize = true;
706
423
    cachedFileProp.fileSize = fileSize;
707
423
    cachedFileProp.eExists = eExists;
708
423
    cachedFileProp.bIsDirectory = bIsDirectory;
709
423
    if (cachedFileProp.nMode == 0)
710
423
        cachedFileProp.nMode = bIsDirectory ? S_IFDIR : S_IFREG;
711
423
    m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
712
713
423
    const vsi_l_offset nRet = fileSize;
714
423
    ReleaseMutex();
715
716
423
    curl_easy_cleanup(hLocalHandle);
717
718
423
    return nRet;
719
1.97k
}
720
721
/************************************************************************/
722
/*                               Exists()                               */
723
/************************************************************************/
724
725
bool VSICurlStreamingHandle::Exists(const char *pszFilename,
726
                                    CSLConstList papszOptions)
727
11.7k
{
728
11.7k
    if (eExists == EXIST_UNKNOWN)
729
3.22k
    {
730
3.22k
        if (!papszOptions ||
731
0
            !CPLTestBool(CSLFetchNameValueDef(
732
0
                papszOptions, "IGNORE_FILENAME_RESTRICTIONS", "NO")))
733
3.22k
        {
734
3.22k
            if (!VSICurlFilesystemHandlerBase::IsAllowedFilename(pszFilename))
735
0
            {
736
0
                eExists = EXIST_NO;
737
0
                fileSize = 0;
738
739
0
                FileProp cachedFileProp;
740
0
                m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
741
0
                cachedFileProp.bHasComputedFileSize = true;
742
0
                cachedFileProp.fileSize = fileSize;
743
0
                cachedFileProp.eExists = eExists;
744
0
                cachedFileProp.bIsDirectory = false;
745
0
                cachedFileProp.nMode = S_IFREG;
746
0
                m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
747
748
0
                return false;
749
0
            }
750
3.22k
        }
751
752
3.22k
        char chFirstByte = '\0';
753
3.22k
        int bExists = (Read(&chFirstByte, 1) == 1);
754
755
3.22k
        FileProp cachedFileProp;
756
3.22k
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
757
3.22k
        cachedFileProp.eExists = eExists = bExists ? EXIST_YES : EXIST_NO;
758
3.22k
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
759
760
3.22k
        Seek(0, SEEK_SET);
761
3.22k
    }
762
763
11.7k
    return eExists == EXIST_YES;
764
11.7k
}
765
766
/************************************************************************/
767
/*                                Tell()                                */
768
/************************************************************************/
769
770
vsi_l_offset VSICurlStreamingHandle::Tell()
771
2.80k
{
772
2.80k
    return curOffset;
773
2.80k
}
774
775
/************************************************************************/
776
/*                           ReceivedBytes()                            */
777
/************************************************************************/
778
779
size_t VSICurlStreamingHandle::ReceivedBytes(GByte *buffer, size_t count,
780
                                             size_t nmemb)
781
61.8k
{
782
61.8k
    size_t nSize = count * nmemb;
783
61.8k
    nBodySize += nSize;
784
785
61.8k
    if (ENABLE_DEBUG)
786
0
        CPLDebug("VSICURL", "Receiving %d bytes...", static_cast<int>(nSize));
787
788
61.8k
    if (bHasCandidateFileSize && bCanTrustCandidateFileSize &&
789
155
        !bHasComputedFileSize)
790
154
    {
791
154
        FileProp cachedFileProp;
792
154
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
793
154
        cachedFileProp.fileSize = fileSize = nCandidateFileSize;
794
154
        bHasComputedFileSize = TRUE;
795
154
        cachedFileProp.bHasComputedFileSize = bHasComputedFileSize;
796
154
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
797
154
        if (ENABLE_DEBUG)
798
0
            CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
799
154
    }
800
801
61.8k
    AcquireMutex();
802
61.8k
    if (eExists == EXIST_UNKNOWN)
803
98
    {
804
98
        FileProp cachedFileProp;
805
98
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
806
98
        cachedFileProp.eExists = eExists = EXIST_YES;
807
98
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
808
98
    }
809
61.7k
    else if (eExists == EXIST_NO && StopReceivingBytesOnError())
810
1
    {
811
1
        ReleaseMutex();
812
1
        return 0;
813
1
    }
814
815
61.8k
    while (true)
816
61.8k
    {
817
61.8k
        const size_t nFree = oRingBuffer.GetCapacity() - oRingBuffer.GetSize();
818
61.8k
        if (nSize <= nFree)
819
61.8k
        {
820
61.8k
            oRingBuffer.Write(buffer, nSize);
821
822
            // Signal to the consumer that we have added bytes to the buffer.
823
61.8k
            CPLCondSignal(hCondProducer);
824
825
61.8k
            if (bAskDownloadEnd)
826
11
            {
827
11
                if (ENABLE_DEBUG)
828
0
                    CPLDebug("VSICURL", "Download interruption asked");
829
830
11
                ReleaseMutex();
831
11
                return 0;
832
11
            }
833
61.8k
            break;
834
61.8k
        }
835
0
        else
836
0
        {
837
0
            oRingBuffer.Write(buffer, nFree);
838
0
            buffer += nFree;
839
0
            nSize -= nFree;
840
841
            // Signal to the consumer that we have added bytes to the buffer.
842
0
            CPLCondSignal(hCondProducer);
843
844
0
            if (ENABLE_DEBUG)
845
0
                CPLDebug("VSICURL",
846
0
                         "Waiting for reader to consume some bytes...");
847
848
0
            while (oRingBuffer.GetSize() == oRingBuffer.GetCapacity() &&
849
0
                   !bAskDownloadEnd)
850
0
            {
851
0
                CPLCondWait(hCondConsumer, hRingBufferMutex);
852
0
            }
853
854
0
            if (bAskDownloadEnd)
855
0
            {
856
0
                if (ENABLE_DEBUG)
857
0
                    CPLDebug("VSICURL", "Download interruption asked");
858
859
0
                ReleaseMutex();
860
0
                return 0;
861
0
            }
862
0
        }
863
61.8k
    }
864
865
61.8k
    ReleaseMutex();
866
867
61.8k
    return nmemb;
868
61.8k
}
869
870
/************************************************************************/
871
/*                VSICurlStreamingHandleReceivedBytes()                 */
872
/************************************************************************/
873
874
static size_t VSICurlStreamingHandleReceivedBytes(void *buffer, size_t count,
875
                                                  size_t nmemb, void *req)
876
61.8k
{
877
61.8k
    return static_cast<VSICurlStreamingHandle *>(req)->ReceivedBytes(
878
61.8k
        static_cast<GByte *>(buffer), count, nmemb);
879
61.8k
}
880
881
/************************************************************************/
882
/*             VSICurlStreamingHandleReceivedBytesHeader()              */
883
/************************************************************************/
884
885
18.9k
#define HEADER_SIZE 32768
886
887
size_t VSICurlStreamingHandle::ReceivedBytesHeader(GByte *buffer, size_t count,
888
                                                   size_t nmemb)
889
7.27k
{
890
7.27k
    const size_t nSize = count * nmemb;
891
7.27k
    if (ENABLE_DEBUG)
892
0
        CPLDebug("VSICURL", "Receiving %d bytes for header...",
893
0
                 static_cast<int>(nSize));
894
895
    // Reset buffer if we have followed link after a redirect.
896
7.27k
    if (nSize >= 9 && InterpretRedirect() &&
897
4.53k
        (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303) &&
898
712
        STARTS_WITH_CI(reinterpret_cast<char *>(buffer), "HTTP/"))
899
178
    {
900
178
        nHeaderSize = 0;
901
178
        nHTTPCode = 0;
902
178
    }
903
904
7.27k
    if (nHeaderSize < HEADER_SIZE)
905
7.27k
    {
906
7.27k
        const size_t nSz = std::min(nSize, HEADER_SIZE - nHeaderSize);
907
7.27k
        memcpy(pabyHeaderData + nHeaderSize, buffer, nSz);
908
7.27k
        pabyHeaderData[nHeaderSize + nSz] = '\0';
909
7.27k
        nHeaderSize += nSz;
910
911
#if DEBUG_VERBOSE
912
        CPLDebug("VSICURL", "Header : %s", pabyHeaderData);
913
#endif
914
915
7.27k
        AcquireMutex();
916
917
7.27k
        if (nHTTPCode == 0 &&
918
2.97k
            strchr(reinterpret_cast<char *>(pabyHeaderData), '\n') != nullptr &&
919
2.97k
            STARTS_WITH_CI(reinterpret_cast<char *>(pabyHeaderData), "HTTP/"))
920
493
        {
921
493
            const char *pszSpace =
922
493
                strchr(const_cast<const char *>(
923
493
                           reinterpret_cast<char *>(pabyHeaderData)),
924
493
                       ' ');
925
493
            if (pszSpace)
926
493
                nHTTPCode = atoi(pszSpace + 1);
927
493
            if (ENABLE_DEBUG)
928
0
                CPLDebug("VSICURL", "HTTP code = %d", nHTTPCode);
929
930
            // If moved permanently/temporarily, go on.
931
493
            if (eExists == EXIST_UNKNOWN &&
932
181
                !(InterpretRedirect() &&
933
44
                  (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303)))
934
159
            {
935
159
                eExists = nHTTPCode == 200 ? EXIST_YES : EXIST_NO;
936
159
                FileProp cachedFileProp;
937
159
                m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
938
159
                cachedFileProp.eExists = eExists;
939
159
                m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
940
159
            }
941
493
        }
942
943
7.27k
        if (!(InterpretRedirect() &&
944
6.04k
              (nHTTPCode == 301 || nHTTPCode == 302 || nHTTPCode == 303)) &&
945
6.38k
            !bHasComputedFileSize)
946
1.86k
        {
947
            // Caution: When gzip compression is enabled, the content-length is
948
            // the compressed size, which we are not interested in, so we must
949
            // not take it into account.
950
951
1.86k
            const char *pszContentLength = strstr(
952
1.86k
                reinterpret_cast<char *>(pabyHeaderData), "Content-Length: ");
953
1.86k
            const char *pszEndOfLine =
954
1.86k
                pszContentLength ? strchr(pszContentLength, '\n') : nullptr;
955
1.86k
            if (bCanTrustCandidateFileSize && pszEndOfLine != nullptr)
956
911
            {
957
911
                const char *pszVal =
958
911
                    pszContentLength + strlen("Content-Length: ");
959
911
                bHasCandidateFileSize = true;
960
911
                nCandidateFileSize = CPLScanUIntBig(
961
911
                    pszVal, static_cast<int>(pszEndOfLine - pszVal));
962
911
                if (ENABLE_DEBUG)
963
0
                    CPLDebug("VSICURL",
964
0
                             "Has found candidate file size = " CPL_FRMT_GUIB,
965
0
                             nCandidateFileSize);
966
911
            }
967
968
1.86k
            const char *pszContentEncoding = strstr(
969
1.86k
                reinterpret_cast<char *>(pabyHeaderData), "Content-Encoding: ");
970
1.86k
            pszEndOfLine =
971
1.86k
                pszContentEncoding ? strchr(pszContentEncoding, '\n') : nullptr;
972
1.86k
            if (bHasCandidateFileSize && pszEndOfLine != nullptr)
973
75
            {
974
75
                const char *pszVal =
975
75
                    pszContentEncoding + strlen("Content-Encoding: ");
976
75
                if (STARTS_WITH(pszVal, "gzip"))
977
75
                {
978
75
                    if (ENABLE_DEBUG)
979
0
                        CPLDebug("VSICURL", "GZip compression enabled --> "
980
0
                                            "cannot trust candidate file size");
981
75
                    bCanTrustCandidateFileSize = false;
982
75
                }
983
75
            }
984
1.86k
        }
985
986
7.27k
        ReleaseMutex();
987
7.27k
    }
988
989
7.27k
    return nmemb;
990
7.27k
}
991
992
/************************************************************************/
993
/*             VSICurlStreamingHandleReceivedBytesHeader()              */
994
/************************************************************************/
995
996
static size_t VSICurlStreamingHandleReceivedBytesHeader(void *buffer,
997
                                                        size_t count,
998
                                                        size_t nmemb, void *req)
999
7.27k
{
1000
7.27k
    return static_cast<VSICurlStreamingHandle *>(req)->ReceivedBytesHeader(
1001
7.27k
        static_cast<GByte *>(buffer), count, nmemb);
1002
7.27k
}
1003
1004
/************************************************************************/
1005
/*                          DownloadInThread()                          */
1006
/************************************************************************/
1007
1008
void VSICurlStreamingHandle::DownloadInThread()
1009
4.43k
{
1010
4.43k
    CURL *hCurlHandle = curl_easy_init();
1011
1012
4.43k
    struct curl_slist *headers =
1013
4.43k
        VSICurlSetOptions(hCurlHandle, m_pszURL, m_aosHTTPOptions.List());
1014
4.43k
    headers = GetCurlHeaders("GET", headers);
1015
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1016
1017
4.43k
    static bool bHasCheckVersion = false;
1018
4.43k
    static bool bSupportGZip = false;
1019
4.43k
    if (!bHasCheckVersion)
1020
11
    {
1021
11
        bSupportGZip = strstr(curl_version(), "zlib/") != nullptr;
1022
11
        bHasCheckVersion = true;
1023
11
    }
1024
4.43k
    if (bSupportGZip && CPLTestBool(CPLGetConfigOption("CPL_CURL_GZIP", "YES")))
1025
4.43k
    {
1026
4.43k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ENCODING, "gzip");
1027
4.43k
    }
1028
1029
4.43k
    if (pabyHeaderData == nullptr)
1030
4.43k
        pabyHeaderData = static_cast<GByte *>(CPLMalloc(HEADER_SIZE + 1));
1031
4.43k
    nHeaderSize = 0;
1032
4.43k
    nBodySize = 0;
1033
4.43k
    nHTTPCode = 0;
1034
1035
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, this);
1036
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
1037
4.43k
                               VSICurlStreamingHandleReceivedBytesHeader);
1038
1039
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, this);
1040
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1041
4.43k
                               VSICurlStreamingHandleReceivedBytes);
1042
1043
4.43k
    m_szCurlErrBuf[0] = '\0';
1044
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
1045
4.43k
                               m_szCurlErrBuf);
1046
1047
4.43k
    void *old_handler = CPLHTTPIgnoreSigPipe();
1048
4.43k
    CURLcode eRet = curl_easy_perform(hCurlHandle);
1049
4.43k
    CPLHTTPRestoreSigPipeHandler(old_handler);
1050
4.43k
    if (headers != nullptr)
1051
137
        curl_slist_free_all(headers);
1052
1053
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, nullptr);
1054
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, nullptr);
1055
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, nullptr);
1056
4.43k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, nullptr);
1057
1058
4.43k
    AcquireMutex();
1059
4.43k
    m_bErrorOccurredInThread = eRet != CURLE_OK;
1060
4.43k
    if (m_bErrorOccurredInThread)
1061
2.98k
    {
1062
        // For autotest purposes only !
1063
2.98k
        const char *pszSimulatedCurlError = CPLGetConfigOption(
1064
2.98k
            "CPL_VSIL_CURL_STREMAING_SIMULATED_CURL_ERROR", nullptr);
1065
2.98k
        if (pszSimulatedCurlError)
1066
0
            snprintf(m_szCurlErrBuf, sizeof(m_szCurlErrBuf), "%s",
1067
0
                     pszSimulatedCurlError);
1068
2.98k
    }
1069
1070
4.43k
    if (!bAskDownloadEnd && eRet == CURLE_OK && !bHasComputedFileSize)
1071
100
    {
1072
100
        FileProp cachedFileProp;
1073
100
        m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
1074
100
        fileSize = nBodySize;
1075
100
        cachedFileProp.fileSize = fileSize;
1076
100
        bHasComputedFileSize = true;
1077
100
        cachedFileProp.bHasComputedFileSize = bHasComputedFileSize;
1078
100
        m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
1079
100
        if (ENABLE_DEBUG)
1080
0
            CPLDebug("VSICURL", "File size = " CPL_FRMT_GUIB, fileSize);
1081
100
    }
1082
1083
4.43k
    bDownloadInProgress = FALSE;
1084
4.43k
    bDownloadStopped = TRUE;
1085
1086
    // Signal to the consumer that the download has ended.
1087
4.43k
    CPLCondSignal(hCondProducer);
1088
4.43k
    ReleaseMutex();
1089
1090
4.43k
    curl_easy_cleanup(hCurlHandle);
1091
4.43k
}
1092
1093
static void VSICurlDownloadInThread(void *pArg)
1094
4.43k
{
1095
4.43k
    static_cast<VSICurlStreamingHandle *>(pArg)->DownloadInThread();
1096
4.43k
}
1097
1098
/************************************************************************/
1099
/*                           StartDownload()                            */
1100
/************************************************************************/
1101
1102
void VSICurlStreamingHandle::StartDownload()
1103
4.73k
{
1104
4.73k
    if (bDownloadInProgress || bDownloadStopped)
1105
300
        return;
1106
1107
4.43k
    CPLDebug("VSICURL", "Start download for %s", m_pszURL);
1108
1109
4.43k
    oRingBuffer.Reset();
1110
4.43k
    bDownloadInProgress = TRUE;
1111
4.43k
    nRingBufferFileOffset = 0;
1112
4.43k
    m_bErrorOccurredInThread = false;
1113
4.43k
    hThread = CPLCreateJoinableThread(VSICurlDownloadInThread, this);
1114
4.43k
}
1115
1116
/************************************************************************/
1117
/*                            StopDownload()                            */
1118
/************************************************************************/
1119
1120
void VSICurlStreamingHandle::StopDownload()
1121
11.9k
{
1122
11.9k
    if (hThread)
1123
4.43k
    {
1124
4.43k
        CPLDebug("VSICURL", "Stop download for %s", m_pszURL);
1125
1126
4.43k
        AcquireMutex();
1127
        // Signal to the producer that we ask for download interruption.
1128
4.43k
        bAskDownloadEnd = TRUE;
1129
4.43k
        CPLCondSignal(hCondConsumer);
1130
1131
        // Wait for the producer to have finished.
1132
4.69k
        while (bDownloadInProgress)
1133
263
            CPLCondWait(hCondProducer, hRingBufferMutex);
1134
1135
4.43k
        bAskDownloadEnd = FALSE;
1136
1137
4.43k
        ReleaseMutex();
1138
1139
4.43k
        CPLJoinThread(hThread);
1140
4.43k
        hThread = nullptr;
1141
4.43k
    }
1142
1143
11.9k
    oRingBuffer.Reset();
1144
11.9k
    bDownloadStopped = FALSE;
1145
11.9k
    m_bErrorOccurredInThread = false;
1146
11.9k
    nRingBufferFileOffset = 0;
1147
11.9k
    bEOF = false;
1148
11.9k
}
1149
1150
/************************************************************************/
1151
/*                        PutRingBufferInCache()                        */
1152
/************************************************************************/
1153
1154
void VSICurlStreamingHandle::PutRingBufferInCache()
1155
716
{
1156
716
    if (nRingBufferFileOffset >= BKGND_BUFFER_SIZE)
1157
0
        return;
1158
1159
716
    AcquireMutex();
1160
1161
    // Cache any remaining bytes available in the ring buffer.
1162
716
    size_t nBufSize = oRingBuffer.GetSize();
1163
716
    if (nBufSize > 0)
1164
232
    {
1165
232
        if (nRingBufferFileOffset + nBufSize > BKGND_BUFFER_SIZE)
1166
0
            nBufSize =
1167
0
                static_cast<size_t>(BKGND_BUFFER_SIZE - nRingBufferFileOffset);
1168
232
        GByte *pabyTmp = static_cast<GByte *>(CPLMalloc(nBufSize));
1169
232
        oRingBuffer.Read(pabyTmp, nBufSize);
1170
1171
        // Signal to the producer that we have ingested some bytes.
1172
232
        CPLCondSignal(hCondConsumer);
1173
1174
232
        AddRegion(nRingBufferFileOffset, nBufSize, pabyTmp);
1175
232
        nRingBufferFileOffset += nBufSize;
1176
232
        CPLFree(pabyTmp);
1177
232
    }
1178
1179
716
    ReleaseMutex();
1180
716
}
1181
1182
/************************************************************************/
1183
/*                                Read()                                */
1184
/************************************************************************/
1185
1186
size_t VSICurlStreamingHandle::Read(void *const pBuffer, size_t const nBytes)
1187
5.38k
{
1188
5.38k
    const size_t nBufferRequestSize = nBytes;
1189
5.38k
    const vsi_l_offset curOffsetOri = curOffset;
1190
5.38k
    const vsi_l_offset nRingBufferFileOffsetOri = nRingBufferFileOffset;
1191
5.38k
    if (nBufferRequestSize == 0)
1192
0
        return 0;
1193
1194
5.38k
    CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
1195
1196
5.38k
retry:
1197
5.38k
    GByte *pabyBuffer = static_cast<GByte *>(pBuffer);
1198
5.38k
    size_t nRemaining = nBufferRequestSize;
1199
1200
5.38k
    AcquireMutex();
1201
    // fileSize might be set wrongly to 0, such as
1202
    // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
1203
5.38k
    const bool bHasComputedFileSizeLocal = bHasComputedFileSize && fileSize > 0;
1204
5.38k
    const vsi_l_offset fileSizeLocal = fileSize;
1205
5.38k
    ReleaseMutex();
1206
1207
5.38k
    if (bHasComputedFileSizeLocal && curOffset >= fileSizeLocal)
1208
0
    {
1209
0
        CPLDebug("VSICURL",
1210
0
                 "Read attempt beyond end of file (%" PRIu64 " >= %" PRIu64 ")",
1211
0
                 static_cast<uint64_t>(curOffset),
1212
0
                 static_cast<uint64_t>(fileSizeLocal));
1213
0
        bEOF = true;
1214
0
    }
1215
5.38k
    if (bEOF)
1216
0
        return 0;
1217
1218
5.38k
    if (curOffset < nRingBufferFileOffset)
1219
716
        PutRingBufferInCache();
1220
1221
5.38k
    if (ENABLE_DEBUG)
1222
0
        CPLDebug("VSICURL", "Read [" CPL_FRMT_GUIB ", " CPL_FRMT_GUIB "[ in %s",
1223
0
                 curOffset, curOffset + nBufferRequestSize, m_pszURL);
1224
1225
    // Can we use the cache?
1226
5.38k
    if (pCachedData != nullptr && curOffset < nCachedSize)
1227
716
    {
1228
716
        const size_t nSz =
1229
716
            std::min(nRemaining, static_cast<size_t>(nCachedSize - curOffset));
1230
716
        if (ENABLE_DEBUG)
1231
0
            CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1232
0
                     static_cast<int>(curOffset),
1233
0
                     static_cast<int>(curOffset + nSz), m_pszURL);
1234
716
        memcpy(pabyBuffer, pCachedData + curOffset, nSz);
1235
716
        pabyBuffer += nSz;
1236
716
        curOffset += nSz;
1237
716
        nRemaining -= nSz;
1238
716
    }
1239
1240
    // Is the request partially covered by the cache and going beyond file size?
1241
5.38k
    if (pCachedData != nullptr && bHasComputedFileSizeLocal &&
1242
928
        curOffset <= nCachedSize && curOffset + nRemaining > fileSizeLocal &&
1243
274
        fileSize == nCachedSize)
1244
136
    {
1245
136
        size_t nSz = static_cast<size_t>(nCachedSize - curOffset);
1246
136
        if (ENABLE_DEBUG && nSz != 0)
1247
0
            CPLDebug("VSICURL", "Using cache for [%d, %d[ in %s",
1248
0
                     static_cast<int>(curOffset),
1249
0
                     static_cast<int>(curOffset + nSz), m_pszURL);
1250
136
        memcpy(pabyBuffer, pCachedData + curOffset, nSz);
1251
136
        pabyBuffer += nSz;
1252
136
        curOffset += nSz;
1253
136
        nRemaining -= nSz;
1254
136
        bEOF = true;
1255
136
    }
1256
1257
5.38k
    bool bErrorOccurred = false;
1258
1259
    // Has a Seek() being done since the last Read()?
1260
5.38k
    if (!bEOF && nRemaining > 0 && curOffset != nRingBufferFileOffset)
1261
70
    {
1262
        // Backward seek: Need to restart the download from the beginning.
1263
70
        if (curOffset < nRingBufferFileOffset)
1264
0
            StopDownload();
1265
1266
70
        StartDownload();
1267
1268
70
        const vsi_l_offset SKIP_BUFFER_SIZE = 32768;
1269
70
        GByte *pabyTmp = static_cast<GByte *>(CPLMalloc(SKIP_BUFFER_SIZE));
1270
1271
70
        CPLAssert(curOffset >= nRingBufferFileOffset);
1272
70
        vsi_l_offset nBytesToSkip = curOffset - nRingBufferFileOffset;
1273
210
        while (nBytesToSkip > 0)
1274
140
        {
1275
140
            vsi_l_offset nBytesToRead = nBytesToSkip;
1276
1277
140
            AcquireMutex();
1278
140
            if (nBytesToRead > oRingBuffer.GetSize())
1279
70
                nBytesToRead = oRingBuffer.GetSize();
1280
140
            if (nBytesToRead > SKIP_BUFFER_SIZE)
1281
0
                nBytesToRead = SKIP_BUFFER_SIZE;
1282
140
            oRingBuffer.Read(pabyTmp, static_cast<size_t>(nBytesToRead));
1283
1284
            // Signal to the producer that we have ingested some bytes.
1285
140
            CPLCondSignal(hCondConsumer);
1286
140
            ReleaseMutex();
1287
1288
140
            if (nBytesToRead)
1289
70
                AddRegion(nRingBufferFileOffset,
1290
70
                          static_cast<size_t>(nBytesToRead), pabyTmp);
1291
1292
140
            nBytesToSkip -= nBytesToRead;
1293
140
            nRingBufferFileOffset += nBytesToRead;
1294
1295
140
            if (nBytesToRead == 0 && nBytesToSkip != 0)
1296
70
            {
1297
70
                if (ENABLE_DEBUG)
1298
0
                    CPLDebug("VSICURL",
1299
0
                             "Waiting for writer to produce some bytes...");
1300
1301
70
                AcquireMutex();
1302
140
                while (oRingBuffer.GetSize() == 0 && bDownloadInProgress)
1303
70
                    CPLCondWait(hCondProducer, hRingBufferMutex);
1304
70
                const int bBufferEmpty = (oRingBuffer.GetSize() == 0);
1305
70
                bErrorOccurred = m_bErrorOccurredInThread;
1306
70
                ReleaseMutex();
1307
1308
70
                if (bBufferEmpty && !bDownloadInProgress)
1309
0
                    break;
1310
70
            }
1311
140
        }
1312
1313
70
        CPLFree(pabyTmp);
1314
1315
70
        if (nBytesToSkip != 0 && !bErrorOccurred)
1316
0
        {
1317
0
            bEOF = true;
1318
0
            return 0;
1319
0
        }
1320
70
    }
1321
1322
5.38k
    if (!bEOF && nRemaining > 0 && !bErrorOccurred)
1323
4.66k
    {
1324
4.66k
        StartDownload();
1325
4.66k
        CPLAssert(curOffset == nRingBufferFileOffset);
1326
4.66k
    }
1327
1328
    // Fill the destination buffer from the ring buffer.
1329
8.47k
    while (!bEOF && nRemaining > 0 && !bErrorOccurred)
1330
6.20k
    {
1331
6.20k
        AcquireMutex();
1332
6.20k
        size_t nToRead = oRingBuffer.GetSize();
1333
6.20k
        if (nToRead > nRemaining)
1334
1.53k
            nToRead = nRemaining;
1335
6.20k
        oRingBuffer.Read(pabyBuffer, nToRead);
1336
1337
        // Signal to the producer that we have ingested some bytes.
1338
6.20k
        CPLCondSignal(hCondConsumer);
1339
6.20k
        ReleaseMutex();
1340
1341
6.20k
        if (nToRead)
1342
1.69k
            AddRegion(curOffset, nToRead, pabyBuffer);
1343
1344
6.20k
        nRemaining -= nToRead;
1345
6.20k
        pabyBuffer += nToRead;
1346
6.20k
        curOffset += nToRead;
1347
6.20k
        nRingBufferFileOffset += nToRead;
1348
1349
6.20k
        if (nToRead == 0 && nRemaining != 0)
1350
4.50k
        {
1351
4.50k
            if (ENABLE_DEBUG)
1352
0
                CPLDebug("VSICURL",
1353
0
                         "Waiting for writer to produce some bytes...");
1354
1355
4.50k
            AcquireMutex();
1356
8.87k
            while (oRingBuffer.GetSize() == 0 && bDownloadInProgress)
1357
4.36k
                CPLCondWait(hCondProducer, hRingBufferMutex);
1358
4.50k
            const bool bBufferEmpty = oRingBuffer.GetSize() == 0;
1359
4.50k
            bErrorOccurred = m_bErrorOccurredInThread;
1360
4.50k
            ReleaseMutex();
1361
1362
4.50k
            if (bBufferEmpty && !bDownloadInProgress)
1363
3.10k
                break;
1364
4.50k
        }
1365
6.20k
    }
1366
1367
5.38k
    if (ENABLE_DEBUG)
1368
0
        CPLDebug("VSICURL", "Read(%d) = %d",
1369
0
                 static_cast<int>(nBufferRequestSize),
1370
0
                 static_cast<int>(nBufferRequestSize - nRemaining));
1371
5.38k
    size_t nRet = nBufferRequestSize - nRemaining;
1372
5.38k
    if (nRet < nBytes)
1373
3.24k
        bEOF = true;
1374
1375
    // Give a chance to specialized filesystem to deal with errors to redirect
1376
    // elsewhere.
1377
5.38k
    if (curOffsetOri == 0 && nRingBufferFileOffsetOri == 0 &&
1378
4.36k
        !StopReceivingBytesOnError() && eExists == EXIST_NO &&
1379
137
        nRemaining < nBufferRequestSize)
1380
137
    {
1381
137
        const size_t nErrorBufferMaxSize = 4096;
1382
137
        std::unique_ptr<GByte, VSIFreeReleaser> pabyErrorBuffer(
1383
137
            static_cast<GByte *>(CPLMalloc(nErrorBufferMaxSize + 1)));
1384
137
        size_t nRead = nBufferRequestSize - nRemaining;
1385
137
        size_t nErrorBufferSize = std::min(nErrorBufferMaxSize, nRead);
1386
137
        memcpy(pabyErrorBuffer.get(), pBuffer, nErrorBufferSize);
1387
137
        if (nRead < nErrorBufferMaxSize)
1388
137
            nErrorBufferSize += Read(pabyErrorBuffer.get() + nRead,
1389
137
                                     nErrorBufferMaxSize - nRead);
1390
137
        (pabyErrorBuffer.get())[nErrorBufferSize] = 0;
1391
137
        StopDownload();
1392
137
        if (CanRestartOnError(reinterpret_cast<char *>(pabyErrorBuffer.get()),
1393
137
                              reinterpret_cast<char *>(pabyHeaderData), true))
1394
0
        {
1395
0
            curOffset = 0;
1396
1397
0
            AcquireMutex();
1398
0
            eExists = EXIST_UNKNOWN;
1399
0
            bHasComputedFileSize = false;
1400
0
            fileSize = 0;
1401
0
            ReleaseMutex();
1402
0
            nCachedSize = 0;
1403
1404
0
            FileProp cachedFileProp;
1405
0
            m_poFS->GetCachedFileProp(m_pszURL, cachedFileProp);
1406
0
            cachedFileProp.bHasComputedFileSize = false;
1407
0
            cachedFileProp.fileSize = 0;
1408
0
            cachedFileProp.eExists = EXIST_UNKNOWN;
1409
0
            m_poFS->SetCachedFileProp(m_pszURL, cachedFileProp);
1410
1411
0
            goto retry;
1412
0
        }
1413
137
        else
1414
137
        {
1415
137
            CPLDebug("VSICURL", "Error buffer: %s",
1416
137
                     reinterpret_cast<char *>(pabyErrorBuffer.get()));
1417
137
            nRet = 0;
1418
137
        }
1419
137
    }
1420
1421
5.38k
    if (bErrorOccurred)
1422
2.96k
    {
1423
        // Look if we should attempt a retry
1424
2.96k
        AcquireMutex();
1425
2.96k
        const bool bRetry = oRetryContext.CanRetry(static_cast<int>(nHTTPCode),
1426
2.96k
                                                   nullptr, m_szCurlErrBuf);
1427
2.96k
        ReleaseMutex();
1428
2.96k
        if (bRetry)
1429
0
        {
1430
0
            StopDownload();
1431
1432
0
            CPLError(CE_Warning, CPLE_AppDefined,
1433
0
                     "HTTP error code: %d - %s. "
1434
0
                     "Retrying again in %.1f secs",
1435
0
                     static_cast<int>(nHTTPCode), m_pszURL,
1436
0
                     oRetryContext.GetCurrentDelay());
1437
0
            CPLSleep(oRetryContext.GetCurrentDelay());
1438
0
            curOffset = curOffsetOri;
1439
0
            goto retry;
1440
0
        }
1441
2.96k
    }
1442
1443
5.38k
    if (bErrorOccurred)
1444
2.96k
        m_bError = true;
1445
1446
5.38k
    return nRet;
1447
5.38k
}
1448
1449
/************************************************************************/
1450
/*                             AddRegion()                              */
1451
/************************************************************************/
1452
1453
void VSICurlStreamingHandle::AddRegion(vsi_l_offset nFileOffsetStart,
1454
                                       size_t nSize, GByte *pData)
1455
1.99k
{
1456
1.99k
    if (nFileOffsetStart >= BKGND_BUFFER_SIZE)
1457
0
        return;
1458
1459
1.99k
    if (pCachedData == nullptr)
1460
1.46k
        pCachedData = static_cast<GByte *>(CPLMalloc(BKGND_BUFFER_SIZE));
1461
1462
1.99k
    if (nFileOffsetStart <= nCachedSize &&
1463
1.99k
        nFileOffsetStart + nSize > nCachedSize)
1464
1.99k
    {
1465
1.99k
        const size_t nSz = std::min(
1466
1.99k
            nSize, static_cast<size_t>(BKGND_BUFFER_SIZE - nFileOffsetStart));
1467
1.99k
        if (ENABLE_DEBUG)
1468
0
            CPLDebug("VSICURL", "Writing [%d, %d[ in cache for %s",
1469
0
                     static_cast<int>(nFileOffsetStart),
1470
0
                     static_cast<int>(nFileOffsetStart + nSz), m_pszURL);
1471
1.99k
        memcpy(pCachedData + nFileOffsetStart, pData, nSz);
1472
1.99k
        nCachedSize = static_cast<size_t>(nFileOffsetStart + nSz);
1473
1.99k
    }
1474
1.99k
}
1475
1476
/************************************************************************/
1477
/*                               Write()                                */
1478
/************************************************************************/
1479
1480
size_t VSICurlStreamingHandle::Write(const void * /* pBuffer */,
1481
                                     size_t /* nBytes */)
1482
0
{
1483
0
    return 0;
1484
0
}
1485
1486
/************************************************************************/
1487
/*                                Eof()                                 */
1488
/************************************************************************/
1489
1490
int VSICurlStreamingHandle::Eof()
1491
22
{
1492
22
    return bEOF;
1493
22
}
1494
1495
/************************************************************************/
1496
/*                               Error()                                */
1497
/************************************************************************/
1498
1499
int VSICurlStreamingHandle::Error()
1500
1501
0
{
1502
0
    return m_bError;
1503
0
}
1504
1505
/************************************************************************/
1506
/*                              ClearErr()                              */
1507
/************************************************************************/
1508
1509
void VSICurlStreamingHandle::ClearErr()
1510
0
{
1511
0
    bEOF = false;
1512
0
    m_bError = false;
1513
0
}
1514
1515
/************************************************************************/
1516
/*                               Flush()                                */
1517
/************************************************************************/
1518
1519
int VSICurlStreamingHandle::Flush()
1520
0
{
1521
0
    return 0;
1522
0
}
1523
1524
/************************************************************************/
1525
/*                               Close()                                */
1526
/************************************************************************/
1527
1528
int VSICurlStreamingHandle::Close()
1529
1.30k
{
1530
1.30k
    return 0;
1531
1.30k
}
1532
1533
/************************************************************************/
1534
/*                     VSICurlStreamingFSHandler()                      */
1535
/************************************************************************/
1536
1537
VSICurlStreamingFSHandler::VSICurlStreamingFSHandler()
1538
504
    : oCacheFileProp{100 * 1024}
1539
504
{
1540
504
    hMutex = CPLCreateMutex();
1541
504
    CPLReleaseMutex(hMutex);
1542
504
}
1543
1544
/************************************************************************/
1545
/*                     ~VSICurlStreamingFSHandler()                     */
1546
/************************************************************************/
1547
1548
VSICurlStreamingFSHandler::~VSICurlStreamingFSHandler()
1549
0
{
1550
0
    VSICurlStreamingFSHandler::ClearCache();
1551
1552
0
    CPLDestroyMutex(hMutex);
1553
0
    hMutex = nullptr;
1554
0
}
1555
1556
/************************************************************************/
1557
/*                             ClearCache()                             */
1558
/************************************************************************/
1559
1560
void VSICurlStreamingFSHandler::ClearCache()
1561
0
{
1562
0
    CPLMutexHolder oHolder(&hMutex);
1563
1564
0
    {
1565
0
        const auto lambda = [](const lru11::KeyValuePair<std::string, bool> &kv)
1566
0
        { VSICURLInvalidateCachedFileProp(kv.key.c_str()); };
1567
0
        oCacheFileProp.cwalk(lambda);
1568
0
        oCacheFileProp.clear();
1569
0
    }
1570
0
}
1571
1572
/************************************************************************/
1573
/*                            AcquireMutex()                            */
1574
/************************************************************************/
1575
1576
void VSICurlStreamingFSHandler::AcquireMutex()
1577
0
{
1578
0
    CPLAcquireMutex(hMutex, 1000.0);
1579
0
}
1580
1581
/************************************************************************/
1582
/*                            ReleaseMutex()                            */
1583
/************************************************************************/
1584
1585
void VSICurlStreamingFSHandler::ReleaseMutex()
1586
0
{
1587
0
    CPLReleaseMutex(hMutex);
1588
0
}
1589
1590
/************************************************************************/
1591
/*                          CreateFileHandle()                          */
1592
/************************************************************************/
1593
1594
VSICurlStreamingHandle *
1595
VSICurlStreamingFSHandler::CreateFileHandle(const char *pszFilename,
1596
                                            const char *pszURL)
1597
10.4k
{
1598
10.4k
    return new VSICurlStreamingHandle(this, pszFilename, pszURL);
1599
10.4k
}
1600
1601
/************************************************************************/
1602
/*                                Open()                                */
1603
/************************************************************************/
1604
1605
VSIVirtualHandleUniquePtr
1606
VSICurlStreamingFSHandler::Open(const char *pszFilename, const char *pszAccess,
1607
                                bool /* bSetError */, CSLConstList papszOptions)
1608
9.28k
{
1609
9.28k
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
1610
342
        return nullptr;
1611
1612
8.94k
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
1613
0
    {
1614
0
        CPLError(CE_Failure, CPLE_AppDefined,
1615
0
                 "Only read-only mode is supported for %s",
1616
0
                 GetFSPrefix().c_str());
1617
0
        return nullptr;
1618
0
    }
1619
1620
8.94k
    auto poHandle = std::unique_ptr<VSICurlStreamingHandle>(
1621
8.94k
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size()));
1622
    // If we didn't get a filelist, check that the file really exists.
1623
8.94k
    if (poHandle == nullptr || !poHandle->Exists(pszFilename, papszOptions))
1624
7.64k
    {
1625
7.64k
        return nullptr;
1626
7.64k
    }
1627
1628
1.30k
    if (CPLTestBool(CPLGetConfigOption("VSI_CACHE", "FALSE")))
1629
0
        return VSIVirtualHandleUniquePtr(
1630
0
            VSICreateCachedFile(poHandle.release()));
1631
1632
1.30k
    return VSIVirtualHandleUniquePtr(poHandle.release());
1633
1.30k
}
1634
1635
/************************************************************************/
1636
/*                         GetCachedFileProp()                          */
1637
/************************************************************************/
1638
1639
bool VSICurlStreamingFSHandler::GetCachedFileProp(const char *pszURL,
1640
                                                  FileProp &oFileProp)
1641
15.9k
{
1642
15.9k
    CPLMutexHolder oHolder(&hMutex);
1643
15.9k
    bool inCache;
1644
15.9k
    if (oCacheFileProp.tryGet(std::string(pszURL), inCache))
1645
12.3k
    {
1646
12.3k
        if (VSICURLGetCachedFileProp(pszURL, oFileProp))
1647
12.3k
        {
1648
12.3k
            return true;
1649
12.3k
        }
1650
0
        oCacheFileProp.remove(std::string(pszURL));
1651
0
    }
1652
3.62k
    return false;
1653
15.9k
}
1654
1655
/************************************************************************/
1656
/*                         SetCachedFileProp()                          */
1657
/************************************************************************/
1658
1659
void VSICurlStreamingFSHandler::SetCachedFileProp(const char *pszURL,
1660
                                                  FileProp &oFileProp)
1661
15.9k
{
1662
15.9k
    CPLMutexHolder oHolder(&hMutex);
1663
15.9k
    oCacheFileProp.insert(std::string(pszURL), true);
1664
15.9k
    VSICURLSetCachedFileProp(pszURL, oFileProp);
1665
15.9k
}
1666
1667
/************************************************************************/
1668
/*                                Stat()                                */
1669
/************************************************************************/
1670
1671
int VSICurlStreamingFSHandler::Stat(const char *pszFilename,
1672
                                    VSIStatBufL *pStatBuf, int nFlags)
1673
5.30k
{
1674
5.30k
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
1675
272
        return -1;
1676
1677
5.03k
    if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
1678
0
    {
1679
0
        const std::string osVSICURLFilename =
1680
0
            std::string("/vsicurl/") + (pszFilename + GetFSPrefix().size());
1681
0
        return VSIStatExL(osVSICURLFilename.c_str(), pStatBuf, nFlags);
1682
0
    }
1683
1684
5.03k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
1685
1686
5.03k
    VSICurlStreamingHandle *poHandle =
1687
5.03k
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size());
1688
5.03k
    if (poHandle == nullptr)
1689
1.26k
    {
1690
1.26k
        return -1;
1691
1.26k
    }
1692
3.76k
    if (poHandle->IsKnownFileSize() ||
1693
2.45k
        ((nFlags & VSI_STAT_SIZE_FLAG) && !poHandle->IsDirectory() &&
1694
422
         CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_SLOW_GET_SIZE", "YES"))))
1695
1.73k
    {
1696
1.73k
        pStatBuf->st_size = poHandle->GetFileSize();
1697
1.73k
    }
1698
1699
3.76k
    int nRet = (poHandle->Exists(pszFilename, nullptr)) ? 0 : -1;
1700
3.76k
    pStatBuf->st_mode = poHandle->IsDirectory() ? S_IFDIR : S_IFREG;
1701
1702
3.76k
    delete poHandle;
1703
3.76k
    return nRet;
1704
5.03k
}
1705
1706
/************************************************************************/
1707
/*                            GetActualURL()                            */
1708
/************************************************************************/
1709
1710
const char *VSICurlStreamingFSHandler::GetActualURL(const char *pszFilename)
1711
0
{
1712
0
    if (!STARTS_WITH_CI(pszFilename, GetFSPrefix()))
1713
0
        return pszFilename;
1714
0
    auto poHandle = std::unique_ptr<VSICurlStreamingHandle>(
1715
0
        CreateFileHandle(pszFilename, pszFilename + GetFSPrefix().size()));
1716
0
    if (poHandle == nullptr)
1717
0
        return pszFilename;
1718
0
    return CPLSPrintf("%s", poHandle->GetURL());
1719
0
}
1720
1721
/************************************************************************/
1722
/*                      GetNonStreamingFilename()                       */
1723
/************************************************************************/
1724
1725
std::string VSICurlStreamingFSHandler::GetNonStreamingFilename(
1726
    const std::string &osFilename) const
1727
11.7k
{
1728
11.7k
    if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
1729
11.7k
        return GetNonStreamingPrefix() +
1730
11.7k
               osFilename.substr(GetFSPrefix().size());
1731
0
    return osFilename;
1732
11.7k
}
1733
1734
/************************************************************************/
1735
/*                     IVSIS3LikeStreamingFSHandler                     */
1736
/************************************************************************/
1737
1738
class IVSIS3LikeStreamingFSHandler : public VSICurlStreamingFSHandler
1739
{
1740
    CPL_DISALLOW_COPY_ASSIGN(IVSIS3LikeStreamingFSHandler)
1741
1742
  public:
1743
420
    IVSIS3LikeStreamingFSHandler() = default;
1744
1745
    char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
1746
1747
    const char *GetOptions() override
1748
0
    {
1749
0
        return VSIGetFileSystemOptions(GetNonStreamingPrefix().c_str());
1750
0
    }
1751
};
1752
1753
char **IVSIS3LikeStreamingFSHandler::ReadDirEx(const char *pszDirname,
1754
                                               int nMaxFiles)
1755
2
{
1756
2
    if (STARTS_WITH(pszDirname, GetFSPrefix()))
1757
2
    {
1758
2
        return VSIReadDirEx(
1759
2
            (GetNonStreamingPrefix() + (pszDirname + GetFSPrefix().size()))
1760
2
                .c_str(),
1761
2
            nMaxFiles);
1762
2
    }
1763
0
    return nullptr;
1764
2
}
1765
1766
/************************************************************************/
1767
/*                       VSIS3StreamingFSHandler                        */
1768
/************************************************************************/
1769
1770
class VSIS3StreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1771
{
1772
    CPL_DISALLOW_COPY_ASSIGN(VSIS3StreamingFSHandler)
1773
1774
  protected:
1775
    CPLString GetFSPrefix() const override
1776
4.72k
    {
1777
4.72k
        return "/vsis3_streaming/";
1778
4.72k
    }
1779
1780
    std::string GetNonStreamingPrefix() const override
1781
0
    {
1782
0
        return "/vsis3/";
1783
0
    }
1784
1785
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1786
                                             const char *pszURL) override;
1787
1788
  public:
1789
84
    VSIS3StreamingFSHandler() = default;
1790
    ~VSIS3StreamingFSHandler() override = default;
1791
1792
    void ClearCache() override
1793
0
    {
1794
0
        IVSIS3LikeStreamingFSHandler::ClearCache();
1795
0
        VSIS3UpdateParams::ClearCache();
1796
0
    }
1797
};
1798
1799
/************************************************************************/
1800
/*                       VSIS3LikeStreamingHandle                       */
1801
/************************************************************************/
1802
1803
class VSIS3LikeStreamingHandle final : public VSICurlStreamingHandle
1804
{
1805
    CPL_DISALLOW_COPY_ASSIGN(VSIS3LikeStreamingHandle)
1806
1807
    IVSIS3LikeHandleHelper *m_poS3HandleHelper = nullptr;
1808
1809
  protected:
1810
    struct curl_slist *GetCurlHeaders(const CPLString &osVerb,
1811
                                      struct curl_slist *psHeaders) override;
1812
1813
    bool StopReceivingBytesOnError() override
1814
275
    {
1815
275
        return false;
1816
275
    }
1817
1818
    bool CanRestartOnError(const char *pszErrorMsg, const char *pszHeaders,
1819
                           bool bSetError) override;
1820
1821
    bool InterpretRedirect() override
1822
2.46k
    {
1823
2.46k
        return false;
1824
2.46k
    }
1825
1826
  public:
1827
    VSIS3LikeStreamingHandle(IVSIS3LikeStreamingFSHandler *poFS,
1828
                             const char *pszFilename,
1829
                             IVSIS3LikeHandleHelper *poS3HandleHelper);
1830
    ~VSIS3LikeStreamingHandle() override;
1831
};
1832
1833
/************************************************************************/
1834
/*                          CreateFileHandle()                          */
1835
/************************************************************************/
1836
1837
VSICurlStreamingHandle *
1838
VSIS3StreamingFSHandler::CreateFileHandle(const char *pszFilename,
1839
                                          const char *pszURL)
1840
1.18k
{
1841
1.18k
    VSIS3HandleHelper *poS3HandleHelper =
1842
1.18k
        VSIS3HandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str(), false);
1843
1.18k
    if (poS3HandleHelper)
1844
0
    {
1845
0
        return new VSIS3LikeStreamingHandle(this, pszFilename,
1846
0
                                            poS3HandleHelper);
1847
0
    }
1848
1.18k
    return nullptr;
1849
1.18k
}
1850
1851
/************************************************************************/
1852
/*                      VSIS3LikeStreamingHandle()                      */
1853
/************************************************************************/
1854
1855
VSIS3LikeStreamingHandle::VSIS3LikeStreamingHandle(
1856
    IVSIS3LikeStreamingFSHandler *poFS, const char *pszFilename,
1857
    IVSIS3LikeHandleHelper *poS3HandleHelper)
1858
1.28k
    : VSICurlStreamingHandle(poFS, pszFilename,
1859
1.28k
                             poS3HandleHelper->GetURL().c_str()),
1860
1.28k
      m_poS3HandleHelper(poS3HandleHelper)
1861
1.28k
{
1862
1.28k
}
1863
1864
/************************************************************************/
1865
/*                     ~VSIS3LikeStreamingHandle()                      */
1866
/************************************************************************/
1867
1868
VSIS3LikeStreamingHandle::~VSIS3LikeStreamingHandle()
1869
1.28k
{
1870
1.28k
    delete m_poS3HandleHelper;
1871
1.28k
}
1872
1873
/************************************************************************/
1874
/*                           GetCurlHeaders()                           */
1875
/************************************************************************/
1876
1877
struct curl_slist *
1878
VSIS3LikeStreamingHandle::GetCurlHeaders(const CPLString &osVerb,
1879
                                         struct curl_slist *psHeaders)
1880
141
{
1881
141
    return m_poS3HandleHelper->GetCurlHeaders(osVerb, psHeaders);
1882
141
}
1883
1884
/************************************************************************/
1885
/*                         CanRestartOnError()                          */
1886
/************************************************************************/
1887
1888
bool VSIS3LikeStreamingHandle::CanRestartOnError(const char *pszErrorMsg,
1889
                                                 const char *pszHeaders,
1890
                                                 bool bSetError)
1891
137
{
1892
137
    if (m_poS3HandleHelper->CanRestartOnError(pszErrorMsg, pszHeaders,
1893
137
                                              bSetError))
1894
0
    {
1895
0
        SetURL(m_poS3HandleHelper->GetURL().c_str());
1896
0
        return true;
1897
0
    }
1898
137
    return false;
1899
137
}
1900
1901
/************************************************************************/
1902
/*                       VSIGSStreamingFSHandler                        */
1903
/************************************************************************/
1904
1905
class VSIGSStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1906
{
1907
  protected:
1908
    CPLString GetFSPrefix() const override
1909
9.09k
    {
1910
9.09k
        return "/vsigs_streaming/";
1911
9.09k
    }
1912
1913
    std::string GetNonStreamingPrefix() const override
1914
1.29k
    {
1915
1.29k
        return "/vsigs/";
1916
1.29k
    }
1917
1918
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1919
                                             const char *pszURL) override;
1920
1921
  public:
1922
    VSIGSStreamingFSHandler()
1923
84
    {
1924
84
    }
1925
1926
    ~VSIGSStreamingFSHandler() override
1927
0
    {
1928
0
    }
1929
};
1930
1931
/************************************************************************/
1932
/*                          CreateFileHandle()                          */
1933
/************************************************************************/
1934
1935
VSICurlStreamingHandle *
1936
VSIGSStreamingFSHandler::CreateFileHandle(const char *pszFilename,
1937
                                          const char *pszURL)
1938
1.28k
{
1939
1.28k
    VSIGSHandleHelper *poGCHandleHelper =
1940
1.28k
        VSIGSHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
1941
1.28k
    if (poGCHandleHelper)
1942
1.28k
    {
1943
1.28k
        return new VSIS3LikeStreamingHandle(this, pszFilename,
1944
1.28k
                                            poGCHandleHelper);
1945
1.28k
    }
1946
0
    return nullptr;
1947
1.28k
}
1948
1949
/************************************************************************/
1950
/*                      VSIAzureStreamingFSHandler                      */
1951
/************************************************************************/
1952
1953
class VSIAzureStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
1954
{
1955
  protected:
1956
    CPLString GetFSPrefix() const override
1957
2.11k
    {
1958
2.11k
        return "/vsiaz_streaming/";
1959
2.11k
    }
1960
1961
    std::string GetNonStreamingPrefix() const override
1962
0
    {
1963
0
        return "/vsiaz/";
1964
0
    }
1965
1966
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
1967
                                             const char *pszURL) override;
1968
1969
  public:
1970
    VSIAzureStreamingFSHandler()
1971
84
    {
1972
84
    }
1973
1974
    ~VSIAzureStreamingFSHandler() override
1975
0
    {
1976
0
    }
1977
};
1978
1979
/************************************************************************/
1980
/*                          CreateFileHandle()                          */
1981
/************************************************************************/
1982
1983
VSICurlStreamingHandle *
1984
VSIAzureStreamingFSHandler::CreateFileHandle(const char *pszFilename,
1985
                                             const char *pszURL)
1986
284
{
1987
284
    VSIAzureBlobHandleHelper *poHandleHelper =
1988
284
        VSIAzureBlobHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
1989
284
    if (poHandleHelper)
1990
0
    {
1991
0
        return new VSIS3LikeStreamingHandle(this, pszFilename, poHandleHelper);
1992
0
    }
1993
284
    return nullptr;
1994
284
}
1995
1996
/************************************************************************/
1997
/*                       VSIOSSStreamingFSHandler                       */
1998
/************************************************************************/
1999
2000
class VSIOSSStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
2001
{
2002
    CPL_DISALLOW_COPY_ASSIGN(VSIOSSStreamingFSHandler)
2003
2004
  protected:
2005
    CPLString GetFSPrefix() const override
2006
1.38k
    {
2007
1.38k
        return "/vsioss_streaming/";
2008
1.38k
    }
2009
2010
    std::string GetNonStreamingPrefix() const override
2011
0
    {
2012
0
        return "/vsioss/";
2013
0
    }
2014
2015
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
2016
                                             const char *pszURL) override;
2017
2018
  public:
2019
84
    VSIOSSStreamingFSHandler() = default;
2020
    ~VSIOSSStreamingFSHandler() override = default;
2021
2022
    void ClearCache() override
2023
0
    {
2024
0
        IVSIS3LikeStreamingFSHandler::ClearCache();
2025
0
        VSIOSSUpdateParams::ClearCache();
2026
0
    }
2027
};
2028
2029
/************************************************************************/
2030
/*                          CreateFileHandle()                          */
2031
/************************************************************************/
2032
2033
VSICurlStreamingHandle *
2034
VSIOSSStreamingFSHandler::CreateFileHandle(const char *pszFilename,
2035
                                           const char *pszURL)
2036
345
{
2037
345
    VSIOSSHandleHelper *poOSSHandleHelper =
2038
345
        VSIOSSHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str(), false);
2039
345
    if (poOSSHandleHelper)
2040
0
    {
2041
0
        return new VSIS3LikeStreamingHandle(this, pszFilename,
2042
0
                                            poOSSHandleHelper);
2043
0
    }
2044
345
    return nullptr;
2045
345
}
2046
2047
/************************************************************************/
2048
/*                      VSISwiftStreamingFSHandler                      */
2049
/************************************************************************/
2050
2051
class VSISwiftStreamingFSHandler final : public IVSIS3LikeStreamingFSHandler
2052
{
2053
  protected:
2054
    CPLString GetFSPrefix() const override
2055
1.75k
    {
2056
1.75k
        return "/vsiswift_streaming/";
2057
1.75k
    }
2058
2059
    std::string GetNonStreamingPrefix() const override
2060
0
    {
2061
0
        return "/vsiswift/";
2062
0
    }
2063
2064
    VSICurlStreamingHandle *CreateFileHandle(const char *pszFilename,
2065
                                             const char *pszURL) override;
2066
2067
  public:
2068
    VSISwiftStreamingFSHandler()
2069
84
    {
2070
84
    }
2071
2072
    ~VSISwiftStreamingFSHandler() override
2073
0
    {
2074
0
    }
2075
};
2076
2077
/************************************************************************/
2078
/*                          CreateFileHandle()                          */
2079
/************************************************************************/
2080
2081
VSICurlStreamingHandle *
2082
VSISwiftStreamingFSHandler::CreateFileHandle(const char *pszFilename,
2083
                                             const char *pszURL)
2084
400
{
2085
400
    VSISwiftHandleHelper *poHandleHelper =
2086
400
        VSISwiftHandleHelper::BuildFromURI(pszURL, GetFSPrefix().c_str());
2087
400
    if (poHandleHelper)
2088
0
    {
2089
0
        return new VSIS3LikeStreamingHandle(this, pszFilename, poHandleHelper);
2090
0
    }
2091
400
    return nullptr;
2092
400
}
2093
2094
//! @endcond
2095
2096
} /* namespace cpl */
2097
2098
/************************************************************************/
2099
/*                 VSIInstallCurlStreamingFileHandler()                 */
2100
/************************************************************************/
2101
2102
/*!
2103
 \brief Install /vsicurl_streaming/ HTTP/FTP file system handler (requires
2104
 libcurl).
2105
2106
  \verbatim embed:rst
2107
 See :ref:`/vsicurl_streaming/ documentation <vsicurl_streaming>`
2108
 \endverbatim
2109
2110
 */
2111
void VSIInstallCurlStreamingFileHandler(void)
2112
84
{
2113
84
    VSIFileManager::InstallHandler(
2114
84
        "/vsicurl_streaming/",
2115
84
        std::make_shared<cpl::VSICurlStreamingFSHandler>());
2116
84
}
2117
2118
/************************************************************************/
2119
/*                  VSIInstallS3StreamingFileHandler()                  */
2120
/************************************************************************/
2121
2122
/*!
2123
 \brief Install /vsis3_streaming/ Amazon S3 file system handler (requires
2124
 libcurl).
2125
2126
  \verbatim embed:rst
2127
 See :ref:`/vsis3_streaming/ documentation <vsis3_streaming>`
2128
 \endverbatim
2129
2130
 */
2131
void VSIInstallS3StreamingFileHandler(void)
2132
84
{
2133
84
    VSIFileManager::InstallHandler(
2134
84
        "/vsis3_streaming/", std::make_shared<cpl::VSIS3StreamingFSHandler>());
2135
84
}
2136
2137
/************************************************************************/
2138
/*                  VSIInstallGSStreamingFileHandler()                  */
2139
/************************************************************************/
2140
2141
/*!
2142
 \brief Install /vsigs_streaming/ Google Cloud Storage file system handler
2143
 (requires libcurl)
2144
2145
  \verbatim embed:rst
2146
 See :ref:`/vsigs_streaming/ documentation <vsigs_streaming>`
2147
 \endverbatim
2148
2149
 */
2150
2151
void VSIInstallGSStreamingFileHandler(void)
2152
84
{
2153
84
    VSIFileManager::InstallHandler(
2154
84
        "/vsigs_streaming/", std::make_shared<cpl::VSIGSStreamingFSHandler>());
2155
84
}
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
 */
2170
2171
void VSIInstallAzureStreamingFileHandler(void)
2172
84
{
2173
84
    VSIFileManager::InstallHandler(
2174
84
        "/vsiaz_streaming/",
2175
84
        std::make_shared<cpl::VSIAzureStreamingFSHandler>());
2176
84
}
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
 */
2191
2192
void VSIInstallOSSStreamingFileHandler(void)
2193
84
{
2194
84
    VSIFileManager::InstallHandler(
2195
84
        "/vsioss_streaming/",
2196
84
        std::make_shared<cpl::VSIOSSStreamingFSHandler>());
2197
84
}
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
 */
2212
2213
void VSIInstallSwiftStreamingFileHandler(void)
2214
84
{
2215
84
    VSIFileManager::InstallHandler(
2216
84
        "/vsiswift_streaming/",
2217
84
        std::make_shared<cpl::VSISwiftStreamingFSHandler>());
2218
84
}
2219
2220
//! @cond Doxygen_Suppress
2221
2222
/************************************************************************/
2223
/*                     VSICurlStreamingClearCache()                     */
2224
/************************************************************************/
2225
2226
void VSICurlStreamingClearCache(void)
2227
0
{
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
0
    const CPLStringList aosPrefixes(VSIFileManager::GetPrefixes());
2232
0
    for (const char *pszPrefix : aosPrefixes)
2233
0
    {
2234
0
        auto poFSHandler = dynamic_cast<cpl::VSICurlStreamingFSHandler *>(
2235
0
            VSIFileManager::GetHandler(pszPrefix));
2236
2237
0
        if (poFSHandler)
2238
0
            poFSHandler->ClearCache();
2239
0
    }
2240
0
}
2241
2242
//! @endcond
2243
2244
#undef ENABLE_DEBUG
2245
2246
#endif  // !defined(HAVE_CURL) || defined(CPL_MULTIPROC_STUB)