Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_curl.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for HTTP/FTP files
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2018, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "cpl_vsil_curl_priv.h"
15
#include "cpl_vsil_curl_class.h"
16
17
#include <algorithm>
18
#include <array>
19
#include <limits>
20
#include <map>
21
#include <memory>
22
#include <numeric>
23
#include <set>
24
#include <string_view>
25
26
#include "cpl_aws.h"
27
#include "cpl_json.h"
28
#include "cpl_json_header.h"
29
#include "cpl_minixml.h"
30
#include "cpl_multiproc.h"
31
#include "cpl_string.h"
32
#include "cpl_time.h"
33
#include "cpl_vsi.h"
34
#include "cpl_vsi_virtual.h"
35
#include "cpl_http.h"
36
#include "cpl_mem_cache.h"
37
38
#ifndef S_IRUSR
39
#define S_IRUSR 00400
40
#define S_IWUSR 00200
41
#define S_IXUSR 00100
42
#define S_IRGRP 00040
43
#define S_IWGRP 00020
44
#define S_IXGRP 00010
45
#define S_IROTH 00004
46
#define S_IWOTH 00002
47
#define S_IXOTH 00001
48
#endif
49
50
#ifndef HAVE_CURL
51
52
void VSIInstallCurlFileHandler(void)
53
{
54
    // Not supported.
55
}
56
57
void VSICurlClearCache(void)
58
{
59
    // Not supported.
60
}
61
62
void VSICurlPartialClearCache(const char *)
63
{
64
    // Not supported.
65
}
66
67
void VSICurlAuthParametersChanged()
68
{
69
    // Not supported.
70
}
71
72
void VSINetworkStatsReset(void)
73
{
74
    // Not supported
75
}
76
77
char *VSINetworkStatsGetAsSerializedJSON(char ** /* papszOptions */)
78
{
79
    // Not supported
80
    return nullptr;
81
}
82
83
/************************************************************************/
84
/*                       VSICurlInstallReadCbk()                        */
85
/************************************************************************/
86
87
int VSICurlInstallReadCbk(VSILFILE * /* fp */,
88
                          VSICurlReadCbkFunc /* pfnReadCbk */,
89
                          void * /* pfnUserData */,
90
                          int /* bStopOnInterruptUntilUninstall */)
91
{
92
    return FALSE;
93
}
94
95
/************************************************************************/
96
/*                      VSICurlUninstallReadCbk()                       */
97
/************************************************************************/
98
99
int VSICurlUninstallReadCbk(VSILFILE * /* fp */)
100
{
101
    return FALSE;
102
}
103
104
#else
105
106
//! @cond Doxygen_Suppress
107
#ifndef DOXYGEN_SKIP
108
109
25.4k
#define ENABLE_DEBUG 1
110
#define ENABLE_DEBUG_VERBOSE 0
111
112
#define unchecked_curl_easy_setopt(handle, opt, param)                         \
113
1.60M
    CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
114
115
constexpr const char *const VSICURL_PREFIXES[] = {"/vsicurl/", "/vsicurl?"};
116
117
extern "C" bool CPL_DLL GDALIsInGlobalDestructorFromDLLMain();
118
119
/***********************************************************รน************/
120
/*                    VSICurlAuthParametersChanged()                    */
121
/************************************************************************/
122
123
static unsigned int gnGenerationAuthParameters = 0;
124
125
void VSICurlAuthParametersChanged()
126
0
{
127
0
    gnGenerationAuthParameters++;
128
0
}
129
130
// Do not access those variables directly !
131
// Use VSICURLGetDownloadChunkSize() and GetMaxRegions()
132
static int N_MAX_REGIONS_DO_NOT_USE_DIRECTLY = 0;
133
static int DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY = 0;
134
135
/************************************************************************/
136
/*                   VSICURLReadGlobalEnvVariables()                    */
137
/************************************************************************/
138
139
static void VSICURLReadGlobalEnvVariables()
140
178k
{
141
178k
    struct Initializer
142
178k
    {
143
178k
        Initializer()
144
178k
        {
145
16
            constexpr int DOWNLOAD_CHUNK_SIZE_DEFAULT = 16384;
146
16
            const char *pszChunkSize =
147
16
                CPLGetConfigOption("CPL_VSIL_CURL_CHUNK_SIZE", nullptr);
148
16
            GIntBig nChunkSize = DOWNLOAD_CHUNK_SIZE_DEFAULT;
149
150
16
            if (pszChunkSize)
151
0
            {
152
0
                if (CPLParseMemorySize(pszChunkSize, &nChunkSize, nullptr) !=
153
0
                    CE_None)
154
0
                {
155
0
                    CPLError(
156
0
                        CE_Warning, CPLE_AppDefined,
157
0
                        "Could not parse value for CPL_VSIL_CURL_CHUNK_SIZE. "
158
0
                        "Using default value of %d instead.",
159
0
                        DOWNLOAD_CHUNK_SIZE_DEFAULT);
160
0
                }
161
0
            }
162
163
16
            constexpr int MIN_CHUNK_SIZE = 1024;
164
16
            constexpr int MAX_CHUNK_SIZE = 10 * 1024 * 1024;
165
16
            if (nChunkSize < MIN_CHUNK_SIZE || nChunkSize > MAX_CHUNK_SIZE)
166
0
            {
167
0
                nChunkSize = DOWNLOAD_CHUNK_SIZE_DEFAULT;
168
0
                CPLError(CE_Warning, CPLE_AppDefined,
169
0
                         "Invalid value for CPL_VSIL_CURL_CHUNK_SIZE. "
170
0
                         "Allowed range is [%d, %d]. "
171
0
                         "Using CPL_VSIL_CURL_CHUNK_SIZE=%d instead",
172
0
                         MIN_CHUNK_SIZE, MAX_CHUNK_SIZE,
173
0
                         DOWNLOAD_CHUNK_SIZE_DEFAULT);
174
0
            }
175
16
            DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY =
176
16
                static_cast<int>(nChunkSize);
177
178
16
            constexpr int N_MAX_REGIONS_DEFAULT = 1000;
179
16
            constexpr int CACHE_SIZE_DEFAULT =
180
16
                N_MAX_REGIONS_DEFAULT * DOWNLOAD_CHUNK_SIZE_DEFAULT;
181
182
16
            const char *pszCacheSize =
183
16
                CPLGetConfigOption("CPL_VSIL_CURL_CACHE_SIZE", nullptr);
184
16
            GIntBig nCacheSize = CACHE_SIZE_DEFAULT;
185
186
16
            if (pszCacheSize)
187
0
            {
188
0
                if (CPLParseMemorySize(pszCacheSize, &nCacheSize, nullptr) !=
189
0
                    CE_None)
190
0
                {
191
0
                    CPLError(
192
0
                        CE_Warning, CPLE_AppDefined,
193
0
                        "Could not parse value for CPL_VSIL_CURL_CACHE_SIZE. "
194
0
                        "Using default value of " CPL_FRMT_GIB " instead.",
195
0
                        nCacheSize);
196
0
                }
197
0
            }
198
199
16
            const auto nMaxRAM = CPLGetUsablePhysicalRAM();
200
16
            const auto nMinVal = DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
201
16
            auto nMaxVal = static_cast<GIntBig>(INT_MAX) *
202
16
                           DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
203
16
            if (nMaxRAM > 0 && nMaxVal > nMaxRAM)
204
16
                nMaxVal = nMaxRAM;
205
16
            if (nCacheSize < nMinVal || nCacheSize > nMaxVal)
206
0
            {
207
0
                nCacheSize = nCacheSize < nMinVal ? nMinVal : nMaxVal;
208
0
                CPLError(CE_Warning, CPLE_AppDefined,
209
0
                         "Invalid value for CPL_VSIL_CURL_CACHE_SIZE. "
210
0
                         "Allowed range is [%d, " CPL_FRMT_GIB "]. "
211
0
                         "Using CPL_VSIL_CURL_CACHE_SIZE=" CPL_FRMT_GIB
212
0
                         " instead",
213
0
                         nMinVal, nMaxVal, nCacheSize);
214
0
            }
215
16
            N_MAX_REGIONS_DO_NOT_USE_DIRECTLY = std::max(
216
16
                1, static_cast<int>(nCacheSize /
217
16
                                    DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY));
218
16
        }
219
178k
    };
220
221
178k
    static Initializer initializer;
222
178k
}
223
224
/************************************************************************/
225
/*                    VSICURLGetDownloadChunkSize()                     */
226
/************************************************************************/
227
228
int VSICURLGetDownloadChunkSize()
229
153k
{
230
153k
    VSICURLReadGlobalEnvVariables();
231
153k
    return DOWNLOAD_CHUNK_SIZE_DO_NOT_USE_DIRECTLY;
232
153k
}
233
234
/************************************************************************/
235
/*                           GetMaxRegions()                            */
236
/************************************************************************/
237
238
static int GetMaxRegions()
239
24.8k
{
240
24.8k
    VSICURLReadGlobalEnvVariables();
241
24.8k
    return N_MAX_REGIONS_DO_NOT_USE_DIRECTLY;
242
24.8k
}
243
244
/************************************************************************/
245
/*          VSICurlFindStringSensitiveExceptEscapeSequences()           */
246
/************************************************************************/
247
248
static int
249
VSICurlFindStringSensitiveExceptEscapeSequences(CSLConstList papszList,
250
                                                const char *pszTarget)
251
252
164k
{
253
164k
    if (papszList == nullptr)
254
164k
        return -1;
255
256
374
    for (int i = 0; papszList[i] != nullptr; i++)
257
187
    {
258
187
        const char *pszIter1 = papszList[i];
259
187
        const char *pszIter2 = pszTarget;
260
187
        char ch1 = '\0';
261
187
        char ch2 = '\0';
262
        /* The comparison is case-sensitive, except for escaped */
263
        /* sequences where letters of the hexadecimal sequence */
264
        /* can be uppercase or lowercase depending on the quoting algorithm */
265
230
        while (true)
266
230
        {
267
230
            ch1 = *pszIter1;
268
230
            ch2 = *pszIter2;
269
230
            if (ch1 == '\0' || ch2 == '\0')
270
43
                break;
271
187
            if (ch1 == '%' && ch2 == '%' && pszIter1[1] != '\0' &&
272
0
                pszIter1[2] != '\0' && pszIter2[1] != '\0' &&
273
0
                pszIter2[2] != '\0')
274
0
            {
275
0
                if (!EQUALN(pszIter1 + 1, pszIter2 + 1, 2))
276
0
                    break;
277
0
                pszIter1 += 2;
278
0
                pszIter2 += 2;
279
0
            }
280
187
            if (ch1 != ch2)
281
144
                break;
282
43
            pszIter1++;
283
43
            pszIter2++;
284
43
        }
285
187
        if (ch1 == ch2 && ch1 == '\0')
286
0
            return i;
287
187
    }
288
289
187
    return -1;
290
187
}
291
292
/************************************************************************/
293
/*                        VSICurlIsFileInList()                         */
294
/************************************************************************/
295
296
static int VSICurlIsFileInList(CSLConstList papszList, const char *pszTarget)
297
99.3k
{
298
99.3k
    int nRet =
299
99.3k
        VSICurlFindStringSensitiveExceptEscapeSequences(papszList, pszTarget);
300
99.3k
    if (nRet >= 0)
301
0
        return nRet;
302
303
    // If we didn't find anything, try to URL-escape the target filename.
304
99.3k
    char *pszEscaped = CPLEscapeString(pszTarget, -1, CPLES_URL);
305
99.3k
    if (strcmp(pszTarget, pszEscaped) != 0)
306
65.3k
    {
307
65.3k
        nRet = VSICurlFindStringSensitiveExceptEscapeSequences(papszList,
308
65.3k
                                                               pszEscaped);
309
65.3k
    }
310
99.3k
    CPLFree(pszEscaped);
311
99.3k
    return nRet;
312
99.3k
}
313
314
/************************************************************************/
315
/*                      StartsWithVSICurlPrefix()                       */
316
/************************************************************************/
317
318
static bool StartsWithVSICurlPrefix(const char *pszFilename)
319
1.07M
{
320
1.07M
    for (const char *pszPrefix : VSICURL_PREFIXES)
321
1.95M
    {
322
1.95M
        if (STARTS_WITH(pszFilename, pszPrefix))
323
486k
        {
324
486k
            return true;
325
486k
        }
326
1.95M
    }
327
590k
    return false;
328
1.07M
}
329
330
/************************************************************************/
331
/*                     VSICurlGetURLFromFilename()                      */
332
/************************************************************************/
333
334
static std::string VSICurlGetURLFromFilename(
335
    const char *pszFilename, CPLHTTPRetryParameters *poRetryParameters,
336
    bool *pbUseHead, bool *pbUseRedirectURLIfNoQueryStringParams,
337
    bool *pbListDir, bool *pbEmptyDir, CPLStringList *paosHTTPOptions,
338
    bool *pbPlanetaryComputerURLSigning, char **ppszPlanetaryComputerCollection)
339
447k
{
340
447k
    if (ppszPlanetaryComputerCollection)
341
142k
        *ppszPlanetaryComputerCollection = nullptr;
342
343
447k
    if (!StartsWithVSICurlPrefix(pszFilename))
344
84.1k
        return pszFilename;
345
346
363k
    if (pbPlanetaryComputerURLSigning)
347
142k
    {
348
        // It may be more convenient sometimes to store Planetary Computer URL
349
        // signing as a per-path specific option rather than capturing it in
350
        // the filename with the &pc_url_signing=yes option.
351
142k
        if (CPLTestBool(VSIGetPathSpecificOption(
352
142k
                pszFilename, "VSICURL_PC_URL_SIGNING", "FALSE")))
353
0
        {
354
0
            *pbPlanetaryComputerURLSigning = true;
355
0
        }
356
142k
    }
357
358
363k
    pszFilename += strlen("/vsicurl/");
359
363k
    if (!STARTS_WITH(pszFilename, "http://") &&
360
342k
        !STARTS_WITH(pszFilename, "https://") &&
361
338k
        !STARTS_WITH(pszFilename, "ftp://") &&
362
315k
        !STARTS_WITH(pszFilename, "file://"))
363
312k
    {
364
312k
        if (*pszFilename == '?')
365
1.95k
            pszFilename++;
366
312k
        char **papszTokens = CSLTokenizeString2(pszFilename, "&", 0);
367
1.35M
        for (int i = 0; papszTokens[i] != nullptr; i++)
368
1.04M
        {
369
1.04M
            char *pszUnescaped =
370
1.04M
                CPLUnescapeString(papszTokens[i], nullptr, CPLES_URL);
371
1.04M
            CPLFree(papszTokens[i]);
372
1.04M
            papszTokens[i] = pszUnescaped;
373
1.04M
        }
374
375
312k
        std::string osURL;
376
312k
        std::string osHeaders;
377
1.35M
        for (int i = 0; papszTokens[i]; i++)
378
1.04M
        {
379
1.04M
            char *pszKey = nullptr;
380
1.04M
            const char *pszValue = CPLParseNameValue(papszTokens[i], &pszKey);
381
1.04M
            if (pszKey && pszValue)
382
669k
            {
383
669k
                if (EQUAL(pszKey, "max_retry"))
384
2.00k
                {
385
2.00k
                    if (poRetryParameters)
386
735
                        poRetryParameters->nMaxRetry = atoi(pszValue);
387
2.00k
                }
388
667k
                else if (EQUAL(pszKey, "retry_delay"))
389
32.9k
                {
390
32.9k
                    if (poRetryParameters)
391
12.9k
                        poRetryParameters->dfInitialDelay = CPLAtof(pszValue);
392
32.9k
                }
393
634k
                else if (EQUAL(pszKey, "retry_codes"))
394
1.90k
                {
395
1.90k
                    if (poRetryParameters)
396
785
                        poRetryParameters->osRetryCodes = pszValue;
397
1.90k
                }
398
632k
                else if (EQUAL(pszKey, "use_head"))
399
328
                {
400
328
                    if (pbUseHead)
401
107
                        *pbUseHead = CPLTestBool(pszValue);
402
328
                }
403
631k
                else if (EQUAL(pszKey,
404
631k
                               "use_redirect_url_if_no_query_string_params"))
405
2.30k
                {
406
                    /* Undocumented. Used by PLScenes driver */
407
2.30k
                    if (pbUseRedirectURLIfNoQueryStringParams)
408
843
                        *pbUseRedirectURLIfNoQueryStringParams =
409
843
                            CPLTestBool(pszValue);
410
2.30k
                }
411
629k
                else if (EQUAL(pszKey, "list_dir"))
412
501
                {
413
501
                    if (pbListDir)
414
175
                        *pbListDir = CPLTestBool(pszValue);
415
501
                }
416
629k
                else if (EQUAL(pszKey, "empty_dir"))
417
596
                {
418
596
                    if (pbEmptyDir)
419
242
                        *pbEmptyDir = CPLTestBool(pszValue);
420
596
                }
421
628k
                else if (EQUAL(pszKey, "header_file"))
422
8.39k
                {
423
#if defined(CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED)
424
                    constexpr bool CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED =
425
                        true;
426
#else
427
8.39k
                    constexpr bool CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED =
428
8.39k
                        false;
429
8.39k
#endif
430
8.39k
                    if (CPL_VSIL_CURL_HEADER_FILE_KVP_DISABLED)
431
0
                    {
432
0
                        CPLError(CE_Failure, CPLE_AppDefined,
433
0
                                 "Use of 'header_file' key-value pair in "
434
0
                                 "/vsicurl? is disabled in this build");
435
0
                    }
436
8.39k
                    else
437
8.39k
                    {
438
8.39k
                        bool bSetValue = false;
439
8.39k
                        const char *pszAllowHeaderFileKVP = CPLGetConfigOption(
440
8.39k
                            "CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED", nullptr);
441
8.39k
                        if (!pszAllowHeaderFileKVP ||
442
0
                            pszAllowHeaderFileKVP[0] == 0 ||
443
0
                            EQUAL(pszAllowHeaderFileKVP, "ONLY_IN_TEMP"))
444
8.39k
                        {
445
8.39k
                            if (STARTS_WITH(pszValue, "/vsimem/"))
446
6.92k
                            {
447
6.92k
                                bSetValue = !CPLHasUnbalancedPathTraversal(
448
6.92k
                                    pszValue + strlen("/vsimem/"));
449
6.92k
                            }
450
1.46k
                            else if (STARTS_WITH(pszValue, "/tmp/"))
451
0
                            {
452
0
                                bSetValue = !CPLHasUnbalancedPathTraversal(
453
0
                                    pszValue + strlen("/tmp/"));
454
0
                            }
455
1.46k
                            else
456
1.46k
                            {
457
1.46k
                                for (const char *pszEnvVar : {"TEMP", "TMP"})
458
2.93k
                                {
459
2.93k
                                    if (const char *pszTemp =
460
2.93k
                                            CPLGetConfigOption(pszEnvVar,
461
2.93k
                                                               nullptr))
462
0
                                    {
463
0
                                        std::string osTemp = pszTemp;
464
0
                                        if (!osTemp.empty() &&
465
0
                                            (osTemp.back() == '/' ||
466
0
                                             osTemp.back() == '\\'))
467
0
                                            osTemp.pop_back();
468
0
                                        if (!osTemp.empty() &&
469
0
                                            cpl::starts_with(
470
0
                                                std::string_view(pszValue),
471
0
                                                osTemp) &&
472
0
                                            (pszValue[osTemp.size()] == '/' ||
473
0
                                             pszValue[osTemp.size()] == '\\'))
474
0
                                        {
475
0
                                            bSetValue =
476
0
                                                !CPLHasUnbalancedPathTraversal(
477
0
                                                    pszValue + osTemp.size());
478
0
                                            break;
479
0
                                        }
480
0
                                    }
481
2.93k
                                }
482
1.46k
                            }
483
8.39k
                            if (!bSetValue)
484
1.53k
                            {
485
1.53k
                                CPLError(CE_Failure, CPLE_AppDefined,
486
1.53k
                                         "Use of 'header_file=%s' "
487
1.53k
                                         "key-value pair in /vsicurl? is "
488
1.53k
                                         "disabled because it refers to a "
489
1.53k
                                         "file stored in a non-temporary "
490
1.53k
                                         "location. You may set the "
491
1.53k
                                         "CPL_VSIL_CURL_HEADER_FILE_KVP_"
492
1.53k
                                         "ENABLED configuration option to "
493
1.53k
                                         "YES to remove that restriction.",
494
1.53k
                                         pszValue);
495
1.53k
                            }
496
8.39k
                        }
497
0
                        else if (CPLTestBool(pszAllowHeaderFileKVP))
498
0
                        {
499
0
                            bSetValue = true;
500
0
                        }
501
0
                        else
502
0
                        {
503
0
                            CPLError(CE_Failure, CPLE_AppDefined,
504
0
                                     "Use of 'header_file' key-value pair in "
505
0
                                     "/vsicurl? is disabled by the "
506
0
                                     "CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED "
507
0
                                     "configuration option");
508
0
                        }
509
510
8.39k
                        if (bSetValue && paosHTTPOptions)
511
2.50k
                        {
512
2.50k
                            paosHTTPOptions->SetNameValue(pszKey, pszValue);
513
2.50k
                        }
514
8.39k
                    }
515
8.39k
                }
516
620k
                else if (EQUAL(pszKey, "useragent") ||
517
619k
                         EQUAL(pszKey, "referer") || EQUAL(pszKey, "cookie") ||
518
614k
                         EQUAL(pszKey, "unsafessl") ||
519
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
520
                         EQUAL(pszKey, "timeout") ||
521
                         EQUAL(pszKey, "connecttimeout") ||
522
#endif
523
614k
                         EQUAL(pszKey, "low_speed_time") ||
524
613k
                         EQUAL(pszKey, "low_speed_limit") ||
525
612k
                         EQUAL(pszKey, "proxy") || EQUAL(pszKey, "proxyauth") ||
526
581k
                         EQUAL(pszKey, "proxyuserpwd"))
527
40.9k
                {
528
                    // Above names are the ones supported by
529
                    // CPLHTTPSetOptions()
530
40.9k
                    if (paosHTTPOptions)
531
15.4k
                    {
532
15.4k
                        paosHTTPOptions->SetNameValue(pszKey, pszValue);
533
15.4k
                    }
534
40.9k
                }
535
579k
                else if (EQUAL(pszKey, "url"))
536
76.2k
                {
537
76.2k
                    osURL = pszValue;
538
76.2k
                }
539
502k
                else if (EQUAL(pszKey, "pc_url_signing"))
540
8.39k
                {
541
8.39k
                    if (pbPlanetaryComputerURLSigning)
542
3.37k
                        *pbPlanetaryComputerURLSigning = CPLTestBool(pszValue);
543
8.39k
                }
544
494k
                else if (EQUAL(pszKey, "pc_collection"))
545
160
                {
546
160
                    if (ppszPlanetaryComputerCollection)
547
74
                    {
548
74
                        CPLFree(*ppszPlanetaryComputerCollection);
549
74
                        *ppszPlanetaryComputerCollection = CPLStrdup(pszValue);
550
74
                    }
551
160
                }
552
494k
                else if (STARTS_WITH(pszKey, "header."))
553
3.50k
                {
554
3.50k
                    osHeaders += (pszKey + strlen("header."));
555
3.50k
                    osHeaders += ':';
556
3.50k
                    osHeaders += pszValue;
557
3.50k
                    osHeaders += "\r\n";
558
3.50k
                }
559
490k
                else
560
490k
                {
561
490k
                    CPLError(CE_Warning, CPLE_NotSupported,
562
490k
                             "Unsupported option: %s", pszKey);
563
490k
                }
564
669k
            }
565
1.04M
            CPLFree(pszKey);
566
1.04M
        }
567
568
312k
        if (paosHTTPOptions && !osHeaders.empty())
569
1.09k
            paosHTTPOptions->SetNameValue("HEADERS", osHeaders.c_str());
570
571
312k
        CSLDestroy(papszTokens);
572
312k
        if (osURL.empty())
573
295k
        {
574
295k
            CPLError(CE_Failure, CPLE_IllegalArg, "Missing url parameter");
575
295k
            return pszFilename;
576
295k
        }
577
578
17.2k
        return osURL;
579
312k
    }
580
581
50.8k
    return pszFilename;
582
363k
}
583
584
namespace cpl
585
{
586
587
/************************************************************************/
588
/*                           VSICurlHandle()                            */
589
/************************************************************************/
590
591
VSICurlHandle::VSICurlHandle(VSICurlFilesystemHandlerBase *poFSIn,
592
                             const char *pszFilename, const char *pszURLIn)
593
217k
    : poFS(poFSIn), m_osFilename(pszFilename),
594
217k
      m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
595
217k
      m_oRetryParameters(m_aosHTTPOptions),
596
      m_bUseHead(
597
217k
          CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_USE_HEAD", "YES")))
598
217k
{
599
217k
    if (pszURLIn)
600
74.9k
    {
601
74.9k
        m_pszURL = CPLStrdup(pszURLIn);
602
74.9k
    }
603
142k
    else
604
142k
    {
605
142k
        char *pszPCCollection = nullptr;
606
142k
        m_pszURL =
607
142k
            CPLStrdup(VSICurlGetURLFromFilename(
608
142k
                          pszFilename, &m_oRetryParameters, &m_bUseHead,
609
142k
                          &m_bUseRedirectURLIfNoQueryStringParams, nullptr,
610
142k
                          nullptr, &m_aosHTTPOptions,
611
142k
                          &m_bPlanetaryComputerURLSigning, &pszPCCollection)
612
142k
                          .c_str());
613
142k
        if (pszPCCollection)
614
74
            m_osPlanetaryComputerCollection = pszPCCollection;
615
142k
        CPLFree(pszPCCollection);
616
142k
    }
617
618
217k
    m_bCached = poFSIn->AllowCachedDataFor(pszFilename);
619
217k
    poFS->GetCachedFileProp(m_pszURL, oFileProp);
620
217k
}
621
622
/************************************************************************/
623
/*                           ~VSICurlHandle()                           */
624
/************************************************************************/
625
626
VSICurlHandle::~VSICurlHandle()
627
217k
{
628
217k
    if (m_oThreadAdviseRead.joinable())
629
0
    {
630
0
        m_oThreadAdviseRead.join();
631
0
    }
632
217k
    if (m_hCurlMultiHandleForAdviseRead)
633
0
    {
634
0
        VSICURLMultiCleanup(m_hCurlMultiHandleForAdviseRead);
635
0
    }
636
637
217k
    if (!m_bCached)
638
0
    {
639
0
        poFS->InvalidateCachedData(m_pszURL);
640
0
        poFS->InvalidateDirContent(CPLGetDirnameSafe(m_osFilename.c_str()));
641
0
    }
642
217k
    CPLFree(m_pszURL);
643
217k
}
644
645
/************************************************************************/
646
/*                               SetURL()                               */
647
/************************************************************************/
648
649
void VSICurlHandle::SetURL(const char *pszURLIn)
650
0
{
651
0
    CPLFree(m_pszURL);
652
0
    m_pszURL = CPLStrdup(pszURLIn);
653
0
}
654
655
/************************************************************************/
656
/*                           InstallReadCbk()                           */
657
/************************************************************************/
658
659
int VSICurlHandle::InstallReadCbk(VSICurlReadCbkFunc pfnReadCbkIn,
660
                                  void *pfnUserDataIn,
661
                                  int bStopOnInterruptUntilUninstallIn)
662
0
{
663
0
    if (pfnReadCbk != nullptr)
664
0
        return FALSE;
665
666
0
    pfnReadCbk = pfnReadCbkIn;
667
0
    pReadCbkUserData = pfnUserDataIn;
668
0
    bStopOnInterruptUntilUninstall =
669
0
        CPL_TO_BOOL(bStopOnInterruptUntilUninstallIn);
670
0
    bInterrupted = false;
671
0
    return TRUE;
672
0
}
673
674
/************************************************************************/
675
/*                          UninstallReadCbk()                          */
676
/************************************************************************/
677
678
int VSICurlHandle::UninstallReadCbk()
679
0
{
680
0
    if (pfnReadCbk == nullptr)
681
0
        return FALSE;
682
683
0
    pfnReadCbk = nullptr;
684
0
    pReadCbkUserData = nullptr;
685
0
    bStopOnInterruptUntilUninstall = false;
686
0
    bInterrupted = false;
687
0
    return TRUE;
688
0
}
689
690
/************************************************************************/
691
/*                                Seek()                                */
692
/************************************************************************/
693
694
int VSICurlHandle::Seek(vsi_l_offset nOffset, int nWhence)
695
58.9k
{
696
58.9k
    if (nWhence == SEEK_SET)
697
33.8k
    {
698
33.8k
        curOffset = nOffset;
699
33.8k
    }
700
25.1k
    else if (nWhence == SEEK_CUR)
701
0
    {
702
0
        curOffset = curOffset + nOffset;
703
0
    }
704
25.1k
    else
705
25.1k
    {
706
25.1k
        curOffset = GetFileSize(false) + nOffset;
707
25.1k
    }
708
58.9k
    bEOF = false;
709
58.9k
    return 0;
710
58.9k
}
711
712
}  // namespace cpl
713
714
/************************************************************************/
715
/*               VSICurlGetTimeStampFromRFC822DateTime()                */
716
/************************************************************************/
717
718
static GIntBig VSICurlGetTimeStampFromRFC822DateTime(const char *pszDT)
719
2.32k
{
720
    // Sun, 03 Apr 2016 12:07:27 GMT
721
2.32k
    if (strlen(pszDT) >= 5 && pszDT[3] == ',' && pszDT[4] == ' ')
722
2.32k
        pszDT += 5;
723
2.32k
    int nDay = 0;
724
2.32k
    int nYear = 0;
725
2.32k
    int nHour = 0;
726
2.32k
    int nMinute = 0;
727
2.32k
    int nSecond = 0;
728
2.32k
    char szMonth[4] = {};
729
2.32k
    szMonth[3] = 0;
730
2.32k
    if (sscanf(pszDT, "%02d %03s %04d %02d:%02d:%02d GMT", &nDay, szMonth,
731
2.32k
               &nYear, &nHour, &nMinute, &nSecond) == 6)
732
2.32k
    {
733
2.32k
        static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr",
734
2.32k
                                                  "May", "Jun", "Jul", "Aug",
735
2.32k
                                                  "Sep", "Oct", "Nov", "Dec"};
736
737
2.32k
        int nMonthIdx0 = -1;
738
18.6k
        for (int i = 0; i < 12; i++)
739
18.6k
        {
740
18.6k
            if (EQUAL(szMonth, aszMonthStr[i]))
741
2.32k
            {
742
2.32k
                nMonthIdx0 = i;
743
2.32k
                break;
744
2.32k
            }
745
18.6k
        }
746
2.32k
        if (nMonthIdx0 >= 0)
747
2.32k
        {
748
2.32k
            struct tm brokendowntime;
749
2.32k
            brokendowntime.tm_year = nYear - 1900;
750
2.32k
            brokendowntime.tm_mon = nMonthIdx0;
751
2.32k
            brokendowntime.tm_mday = nDay;
752
2.32k
            brokendowntime.tm_hour = nHour;
753
2.32k
            brokendowntime.tm_min = nMinute;
754
2.32k
            brokendowntime.tm_sec = nSecond;
755
2.32k
            return CPLYMDHMSToUnixTime(&brokendowntime);
756
2.32k
        }
757
2.32k
    }
758
0
    return 0;
759
2.32k
}
760
761
/************************************************************************/
762
/*                     VSICURLInitWriteFuncStruct()                     */
763
/************************************************************************/
764
765
void VSICURLInitWriteFuncStruct(cpl::WriteFuncStruct *psStruct, VSILFILE *fp,
766
                                VSICurlReadCbkFunc pfnReadCbk,
767
                                void *pReadCbkUserData)
768
238k
{
769
238k
    psStruct->pBuffer = nullptr;
770
238k
    psStruct->nSize = 0;
771
238k
    psStruct->bIsHTTP = false;
772
238k
    psStruct->bMultiRange = false;
773
238k
    psStruct->nStartOffset = 0;
774
238k
    psStruct->nEndOffset = 0;
775
238k
    psStruct->nHTTPCode = 0;
776
238k
    psStruct->nFirstHTTPCode = 0;
777
238k
    psStruct->nContentLength = 0;
778
238k
    psStruct->bFoundContentRange = false;
779
238k
    psStruct->bError = false;
780
238k
    psStruct->bDetectRangeDownloadingError = true;
781
238k
    psStruct->nTimestampDate = 0;
782
783
238k
    psStruct->fp = fp;
784
238k
    psStruct->pfnReadCbk = pfnReadCbk;
785
238k
    psStruct->pReadCbkUserData = pReadCbkUserData;
786
238k
    psStruct->bInterrupted = false;
787
238k
}
788
789
/************************************************************************/
790
/*                       VSICurlHandleWriteFunc()                       */
791
/************************************************************************/
792
793
size_t VSICurlHandleWriteFunc(void *buffer, size_t count, size_t nmemb,
794
                              void *req)
795
49.6k
{
796
49.6k
    cpl::WriteFuncStruct *psStruct = static_cast<cpl::WriteFuncStruct *>(req);
797
49.6k
    const size_t nSize = count * nmemb;
798
799
49.6k
    if (psStruct->bInterrupted)
800
121
    {
801
121
        return 0;
802
121
    }
803
804
49.5k
    char *pNewBuffer = static_cast<char *>(
805
49.5k
        VSIRealloc(psStruct->pBuffer, psStruct->nSize + nSize + 1));
806
49.5k
    if (pNewBuffer)
807
49.5k
    {
808
49.5k
        psStruct->pBuffer = pNewBuffer;
809
49.5k
        memcpy(psStruct->pBuffer + psStruct->nSize, buffer, nSize);
810
49.5k
        psStruct->pBuffer[psStruct->nSize + nSize] = '\0';
811
49.5k
        if (psStruct->bIsHTTP)
812
20.5k
        {
813
20.5k
            char *pszLine = psStruct->pBuffer + psStruct->nSize;
814
20.5k
            if (STARTS_WITH_CI(pszLine, "HTTP/"))
815
2.32k
            {
816
2.32k
                char *pszSpace = strchr(pszLine, ' ');
817
2.32k
                if (pszSpace)
818
2.32k
                {
819
2.32k
                    const int nHTTPCode = atoi(pszSpace + 1);
820
2.32k
                    if (psStruct->nFirstHTTPCode == 0)
821
2.32k
                        psStruct->nFirstHTTPCode = nHTTPCode;
822
2.32k
                    psStruct->nHTTPCode = nHTTPCode;
823
2.32k
                }
824
2.32k
            }
825
18.2k
            else if (STARTS_WITH_CI(pszLine, "Content-Length: "))
826
2.32k
            {
827
2.32k
                psStruct->nContentLength = CPLScanUIntBig(
828
2.32k
                    pszLine + 16, static_cast<int>(strlen(pszLine + 16)));
829
2.32k
            }
830
15.9k
            else if (STARTS_WITH_CI(pszLine, "Content-Range: "))
831
0
            {
832
0
                psStruct->bFoundContentRange = true;
833
0
            }
834
15.9k
            else if (STARTS_WITH_CI(pszLine, "Date: "))
835
2.32k
            {
836
2.32k
                CPLString osDate = pszLine + strlen("Date: ");
837
2.32k
                size_t nSizeLine = osDate.size();
838
6.98k
                while (nSizeLine && (osDate[nSizeLine - 1] == '\r' ||
839
4.65k
                                     osDate[nSizeLine - 1] == '\n'))
840
4.65k
                {
841
4.65k
                    osDate.resize(nSizeLine - 1);
842
4.65k
                    nSizeLine--;
843
4.65k
                }
844
2.32k
                osDate.Trim();
845
846
2.32k
                GIntBig nTimestampDate =
847
2.32k
                    VSICurlGetTimeStampFromRFC822DateTime(osDate.c_str());
848
#if DEBUG_VERBOSE
849
                CPLDebug("VSICURL", "Timestamp = " CPL_FRMT_GIB,
850
                         nTimestampDate);
851
#endif
852
2.32k
                psStruct->nTimestampDate = nTimestampDate;
853
2.32k
            }
854
            /*if( nSize > 2 && pszLine[nSize - 2] == '\r' &&
855
                  pszLine[nSize - 1] == '\n' )
856
            {
857
                pszLine[nSize - 2] = 0;
858
                CPLDebug("VSICURL", "%s", pszLine);
859
                pszLine[nSize - 2] = '\r';
860
            }*/
861
862
20.5k
            if (pszLine[0] == '\r' && pszLine[1] == '\n')
863
2.32k
            {
864
                // Detect servers that don't support range downloading.
865
2.32k
                if (psStruct->nHTTPCode == 200 &&
866
0
                    psStruct->bDetectRangeDownloadingError &&
867
0
                    !psStruct->bMultiRange && !psStruct->bFoundContentRange &&
868
0
                    (psStruct->nStartOffset != 0 ||
869
0
                     psStruct->nContentLength >
870
0
                         10 * (psStruct->nEndOffset - psStruct->nStartOffset +
871
0
                               1)))
872
0
                {
873
0
                    CPLError(CE_Failure, CPLE_AppDefined,
874
0
                             "Range downloading not supported by this "
875
0
                             "server!");
876
0
                    psStruct->bError = true;
877
0
                    return 0;
878
0
                }
879
2.32k
            }
880
20.5k
        }
881
28.9k
        else
882
28.9k
        {
883
28.9k
            if (psStruct->pfnReadCbk)
884
0
            {
885
0
                if (!psStruct->pfnReadCbk(psStruct->fp, buffer, nSize,
886
0
                                          psStruct->pReadCbkUserData))
887
0
                {
888
0
                    psStruct->bInterrupted = true;
889
0
                    return 0;
890
0
                }
891
0
            }
892
28.9k
        }
893
49.5k
        psStruct->nSize += nSize;
894
49.5k
        return nmemb;
895
49.5k
    }
896
0
    else
897
0
    {
898
0
        return 0;
899
0
    }
900
49.5k
}
901
902
/************************************************************************/
903
/*                      VSICurlIsS3LikeSignedURL()                      */
904
/************************************************************************/
905
906
static bool VSICurlIsS3LikeSignedURL(const char *pszURL)
907
118k
{
908
118k
    return ((strstr(pszURL, ".s3.amazonaws.com/") != nullptr ||
909
117k
             strstr(pszURL, ".s3.amazonaws.com:") != nullptr ||
910
114k
             strstr(pszURL, ".storage.googleapis.com/") != nullptr ||
911
113k
             strstr(pszURL, ".storage.googleapis.com:") != nullptr ||
912
112k
             strstr(pszURL, ".cloudfront.net/") != nullptr ||
913
111k
             strstr(pszURL, ".cloudfront.net:") != nullptr) &&
914
7.35k
            (strstr(pszURL, "&Signature=") != nullptr ||
915
6.14k
             strstr(pszURL, "?Signature=") != nullptr)) ||
916
116k
           strstr(pszURL, "&X-Amz-Signature=") != nullptr ||
917
114k
           strstr(pszURL, "?X-Amz-Signature=") != nullptr;
918
118k
}
919
920
/************************************************************************/
921
/*                VSICurlGetExpiresFromS3LikeSignedURL()                */
922
/************************************************************************/
923
924
static GIntBig VSICurlGetExpiresFromS3LikeSignedURL(const char *pszURL)
925
0
{
926
0
    const auto GetParamValue = [pszURL](const char *pszKey) -> const char *
927
0
    {
928
0
        for (const char *pszPrefix : {"&", "?"})
929
0
        {
930
0
            std::string osNeedle(pszPrefix);
931
0
            osNeedle += pszKey;
932
0
            osNeedle += '=';
933
0
            const char *pszStr = strstr(pszURL, osNeedle.c_str());
934
0
            if (pszStr)
935
0
                return pszStr + osNeedle.size();
936
0
        }
937
0
        return nullptr;
938
0
    };
939
940
0
    {
941
        // Expires= is a Unix timestamp
942
0
        const char *pszExpires = GetParamValue("Expires");
943
0
        if (pszExpires != nullptr)
944
0
            return CPLAtoGIntBig(pszExpires);
945
0
    }
946
947
    // X-Amz-Expires= is a delay, to be combined with X-Amz-Date=
948
0
    const char *pszAmzExpires = GetParamValue("X-Amz-Expires");
949
0
    if (pszAmzExpires == nullptr)
950
0
        return 0;
951
0
    const int nDelay = atoi(pszAmzExpires);
952
953
0
    const char *pszAmzDate = GetParamValue("X-Amz-Date");
954
0
    if (pszAmzDate == nullptr)
955
0
        return 0;
956
    // pszAmzDate should be YYYYMMDDTHHMMSSZ
957
0
    if (strlen(pszAmzDate) < strlen("YYYYMMDDTHHMMSSZ"))
958
0
        return 0;
959
0
    if (pszAmzDate[strlen("YYYYMMDDTHHMMSSZ") - 1] != 'Z')
960
0
        return 0;
961
0
    struct tm brokendowntime;
962
0
    brokendowntime.tm_year =
963
0
        atoi(std::string(pszAmzDate).substr(0, 4).c_str()) - 1900;
964
0
    brokendowntime.tm_mon =
965
0
        atoi(std::string(pszAmzDate).substr(4, 2).c_str()) - 1;
966
0
    brokendowntime.tm_mday = atoi(std::string(pszAmzDate).substr(6, 2).c_str());
967
0
    brokendowntime.tm_hour = atoi(std::string(pszAmzDate).substr(9, 2).c_str());
968
0
    brokendowntime.tm_min = atoi(std::string(pszAmzDate).substr(11, 2).c_str());
969
0
    brokendowntime.tm_sec = atoi(std::string(pszAmzDate).substr(13, 2).c_str());
970
0
    return CPLYMDHMSToUnixTime(&brokendowntime) + nDelay;
971
0
}
972
973
/************************************************************************/
974
/*                        VSICURLMultiPerform()                         */
975
/************************************************************************/
976
977
void VSICURLMultiPerform(CURLM *hCurlMultiHandle, CURL *hEasyHandle,
978
                         std::atomic<bool> *pbInterrupt)
979
121k
{
980
121k
    if (hEasyHandle)
981
121k
        curl_multi_add_handle(hCurlMultiHandle, hEasyHandle);
982
983
121k
    void *old_handler = CPLHTTPIgnoreSigPipe();
984
130k
    while (true)
985
130k
    {
986
130k
        int still_running;
987
130k
        while (curl_multi_perform(hCurlMultiHandle, &still_running) ==
988
130k
               CURLM_CALL_MULTI_PERFORM)
989
0
        {
990
            // loop
991
0
        }
992
130k
        if (!still_running)
993
121k
        {
994
121k
            break;
995
121k
        }
996
997
#ifdef undef
998
        CURLMsg *msg;
999
        do
1000
        {
1001
            int msgq = 0;
1002
            msg = curl_multi_info_read(hCurlMultiHandle, &msgq);
1003
            if (msg && (msg->msg == CURLMSG_DONE))
1004
            {
1005
                CURL *e = msg->easy_handle;
1006
            }
1007
        } while (msg);
1008
#endif
1009
1010
9.75k
        CPLMultiPerformWait(hCurlMultiHandle);
1011
1012
9.75k
        if (pbInterrupt && *pbInterrupt)
1013
0
            break;
1014
9.75k
    }
1015
121k
    CPLHTTPRestoreSigPipeHandler(old_handler);
1016
1017
121k
    if (hEasyHandle)
1018
121k
        curl_multi_remove_handle(hCurlMultiHandle, hEasyHandle);
1019
121k
}
1020
1021
/************************************************************************/
1022
/*                       VSICurlDummyWriteFunc()                        */
1023
/************************************************************************/
1024
1025
static size_t VSICurlDummyWriteFunc(void *, size_t, size_t, void *)
1026
0
{
1027
0
    return 0;
1028
0
}
1029
1030
/************************************************************************/
1031
/*                VSICURLResetHeaderAndWriterFunctions()                */
1032
/************************************************************************/
1033
1034
void VSICURLResetHeaderAndWriterFunctions(CURL *hCurlHandle)
1035
120k
{
1036
120k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
1037
120k
                               VSICurlDummyWriteFunc);
1038
120k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1039
120k
                               VSICurlDummyWriteFunc);
1040
120k
}
1041
1042
/************************************************************************/
1043
/*                         Iso8601ToUnixTime()                          */
1044
/************************************************************************/
1045
1046
static bool Iso8601ToUnixTime(const char *pszDT, GIntBig *pnUnixTime)
1047
0
{
1048
0
    int nYear;
1049
0
    int nMonth;
1050
0
    int nDay;
1051
0
    int nHour;
1052
0
    int nMinute;
1053
0
    int nSecond;
1054
0
    if (sscanf(pszDT, "%04d-%02d-%02dT%02d:%02d:%02d", &nYear, &nMonth, &nDay,
1055
0
               &nHour, &nMinute, &nSecond) == 6)
1056
0
    {
1057
0
        struct tm brokendowntime;
1058
0
        brokendowntime.tm_year = nYear - 1900;
1059
0
        brokendowntime.tm_mon = nMonth - 1;
1060
0
        brokendowntime.tm_mday = nDay;
1061
0
        brokendowntime.tm_hour = nHour;
1062
0
        brokendowntime.tm_min = nMinute;
1063
0
        brokendowntime.tm_sec = nSecond;
1064
0
        *pnUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
1065
0
        return true;
1066
0
    }
1067
0
    return false;
1068
0
}
1069
1070
namespace cpl
1071
{
1072
1073
/************************************************************************/
1074
/*                   ManagePlanetaryComputerSigning()                   */
1075
/************************************************************************/
1076
1077
void VSICurlHandle::ManagePlanetaryComputerSigning() const
1078
2.52k
{
1079
    // Take global lock
1080
2.52k
    static std::mutex goMutex;
1081
2.52k
    std::lock_guard<std::mutex> oLock(goMutex);
1082
1083
2.52k
    struct PCSigningInfo
1084
2.52k
    {
1085
2.52k
        std::string osQueryString{};
1086
2.52k
        GIntBig nExpireTimestamp = 0;
1087
2.52k
    };
1088
1089
2.52k
    PCSigningInfo sSigningInfo;
1090
2.52k
    constexpr int knExpirationDelayMargin = 60;
1091
1092
2.52k
    if (!m_osPlanetaryComputerCollection.empty())
1093
73
    {
1094
        // key is the name of a collection
1095
73
        static lru11::Cache<std::string, PCSigningInfo> goCacheCollection{1024};
1096
1097
73
        if (goCacheCollection.tryGet(m_osPlanetaryComputerCollection,
1098
73
                                     sSigningInfo) &&
1099
0
            time(nullptr) + knExpirationDelayMargin <=
1100
0
                sSigningInfo.nExpireTimestamp)
1101
0
        {
1102
0
            m_osQueryString = sSigningInfo.osQueryString;
1103
0
        }
1104
73
        else
1105
73
        {
1106
73
            const auto psResult =
1107
73
                CPLHTTPFetch((std::string(CPLGetConfigOption(
1108
73
                                  "VSICURL_PC_SAS_TOKEN_URL",
1109
73
                                  "https://planetarycomputer.microsoft.com/api/"
1110
73
                                  "sas/v1/token/")) +
1111
73
                              m_osPlanetaryComputerCollection)
1112
73
                                 .c_str(),
1113
73
                             nullptr);
1114
73
            if (psResult)
1115
73
            {
1116
73
                const auto aosKeyVals = CPLParseKeyValueJson(
1117
73
                    reinterpret_cast<const char *>(psResult->pabyData));
1118
73
                const char *pszToken = aosKeyVals.FetchNameValue("token");
1119
73
                if (pszToken)
1120
0
                {
1121
0
                    m_osQueryString = '?';
1122
0
                    m_osQueryString += pszToken;
1123
1124
0
                    sSigningInfo.osQueryString = m_osQueryString;
1125
0
                    sSigningInfo.nExpireTimestamp = 0;
1126
0
                    const char *pszExpiry =
1127
0
                        aosKeyVals.FetchNameValue("msft:expiry");
1128
0
                    if (pszExpiry)
1129
0
                    {
1130
0
                        Iso8601ToUnixTime(pszExpiry,
1131
0
                                          &sSigningInfo.nExpireTimestamp);
1132
0
                    }
1133
0
                    goCacheCollection.insert(m_osPlanetaryComputerCollection,
1134
0
                                             sSigningInfo);
1135
1136
0
                    CPLDebug("VSICURL", "Got token from Planetary Computer: %s",
1137
0
                             m_osQueryString.c_str());
1138
0
                }
1139
73
                CPLHTTPDestroyResult(psResult);
1140
73
            }
1141
73
        }
1142
73
    }
1143
2.44k
    else
1144
2.44k
    {
1145
        // key is a URL
1146
2.44k
        static lru11::Cache<std::string, PCSigningInfo> goCacheURL{1024};
1147
1148
2.44k
        if (goCacheURL.tryGet(m_pszURL, sSigningInfo) &&
1149
0
            time(nullptr) + knExpirationDelayMargin <=
1150
0
                sSigningInfo.nExpireTimestamp)
1151
0
        {
1152
0
            m_osQueryString = sSigningInfo.osQueryString;
1153
0
        }
1154
2.44k
        else
1155
2.44k
        {
1156
2.44k
            const auto psResult =
1157
2.44k
                CPLHTTPFetch((std::string(CPLGetConfigOption(
1158
2.44k
                                  "VSICURL_PC_SAS_SIGN_HREF_URL",
1159
2.44k
                                  "https://planetarycomputer.microsoft.com/api/"
1160
2.44k
                                  "sas/v1/sign?href=")) +
1161
2.44k
                              m_pszURL)
1162
2.44k
                                 .c_str(),
1163
2.44k
                             nullptr);
1164
2.44k
            if (psResult)
1165
2.44k
            {
1166
2.44k
                const auto aosKeyVals = CPLParseKeyValueJson(
1167
2.44k
                    reinterpret_cast<const char *>(psResult->pabyData));
1168
2.44k
                const char *pszHref = aosKeyVals.FetchNameValue("href");
1169
2.44k
                if (pszHref && STARTS_WITH(pszHref, m_pszURL))
1170
0
                {
1171
0
                    m_osQueryString = pszHref + strlen(m_pszURL);
1172
1173
0
                    sSigningInfo.osQueryString = m_osQueryString;
1174
0
                    sSigningInfo.nExpireTimestamp = 0;
1175
0
                    const char *pszExpiry =
1176
0
                        aosKeyVals.FetchNameValue("msft:expiry");
1177
0
                    if (pszExpiry)
1178
0
                    {
1179
0
                        Iso8601ToUnixTime(pszExpiry,
1180
0
                                          &sSigningInfo.nExpireTimestamp);
1181
0
                    }
1182
0
                    goCacheURL.insert(m_pszURL, sSigningInfo);
1183
1184
0
                    CPLDebug("VSICURL",
1185
0
                             "Got signature from Planetary Computer: %s",
1186
0
                             m_osQueryString.c_str());
1187
0
                }
1188
2.44k
                CPLHTTPDestroyResult(psResult);
1189
2.44k
            }
1190
2.44k
        }
1191
2.44k
    }
1192
2.52k
}
1193
1194
/************************************************************************/
1195
/*                         UpdateQueryString()                          */
1196
/************************************************************************/
1197
1198
void VSICurlHandle::UpdateQueryString() const
1199
107k
{
1200
107k
    if (m_bPlanetaryComputerURLSigning)
1201
2.52k
    {
1202
2.52k
        ManagePlanetaryComputerSigning();
1203
2.52k
    }
1204
105k
    else
1205
105k
    {
1206
105k
        const char *pszQueryString = VSIGetPathSpecificOption(
1207
105k
            m_osFilename.c_str(), "VSICURL_QUERY_STRING", nullptr);
1208
105k
        if (pszQueryString)
1209
0
        {
1210
0
            if (m_osFilename.back() == '?')
1211
0
            {
1212
0
                if (pszQueryString[0] == '?')
1213
0
                    m_osQueryString = pszQueryString + 1;
1214
0
                else
1215
0
                    m_osQueryString = pszQueryString;
1216
0
            }
1217
0
            else
1218
0
            {
1219
0
                if (pszQueryString[0] == '?')
1220
0
                    m_osQueryString = pszQueryString;
1221
0
                else
1222
0
                {
1223
0
                    m_osQueryString = "?";
1224
0
                    m_osQueryString.append(pszQueryString);
1225
0
                }
1226
0
            }
1227
0
        }
1228
105k
    }
1229
107k
}
1230
1231
/************************************************************************/
1232
/*                        GetFileSizeOrHeaders()                        */
1233
/************************************************************************/
1234
1235
vsi_l_offset VSICurlHandle::GetFileSizeOrHeaders(bool bSetError,
1236
                                                 bool bGetHeaders)
1237
187k
{
1238
187k
    if (oFileProp.bHasComputedFileSize && !bGetHeaders)
1239
92.7k
        return oFileProp.fileSize;
1240
1241
95.1k
    NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
1242
95.1k
    NetworkStatisticsFile oContextFile(m_osFilename.c_str());
1243
95.1k
    NetworkStatisticsAction oContextAction("GetFileSize");
1244
1245
95.1k
    oFileProp.bHasComputedFileSize = true;
1246
1247
95.1k
    CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
1248
1249
95.1k
    UpdateQueryString();
1250
1251
95.1k
    std::string osURL(m_pszURL + m_osQueryString);
1252
95.1k
    int nTryCount = 0;
1253
95.1k
    bool bRetryWithGet = false;
1254
95.1k
    bool bRetryWithLimitedRangeGet = false;
1255
95.1k
    bool bS3LikeRedirect = false;
1256
95.1k
    CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
1257
1258
101k
retry:
1259
101k
    ++nTryCount;
1260
101k
    CURL *hCurlHandle = curl_easy_init();
1261
1262
101k
    struct curl_slist *headers = nullptr;
1263
101k
    if (bS3LikeRedirect)
1264
0
    {
1265
        // Do not propagate authentication sent to the original URL to a S3-like
1266
        // redirect.
1267
0
        CPLStringList aosHTTPOptions{};
1268
0
        for (const auto &pszOption : m_aosHTTPOptions)
1269
0
        {
1270
0
            if (STARTS_WITH_CI(pszOption, "HTTPAUTH") ||
1271
0
                STARTS_WITH_CI(pszOption, "HTTP_BEARER"))
1272
0
                continue;
1273
0
            aosHTTPOptions.AddString(pszOption);
1274
0
        }
1275
0
        headers = VSICurlSetOptions(hCurlHandle, osURL.c_str(),
1276
0
                                    aosHTTPOptions.List());
1277
0
    }
1278
101k
    else
1279
101k
    {
1280
101k
        headers = VSICurlSetOptions(hCurlHandle, osURL.c_str(),
1281
101k
                                    m_aosHTTPOptions.List());
1282
101k
    }
1283
1284
101k
    WriteFuncStruct sWriteFuncHeaderData;
1285
101k
    VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
1286
101k
                               nullptr);
1287
101k
    sWriteFuncHeaderData.bDetectRangeDownloadingError = false;
1288
101k
    sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(osURL.c_str(), "http");
1289
1290
101k
    WriteFuncStruct sWriteFuncData;
1291
101k
    VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
1292
1293
101k
    std::string osVerb;
1294
101k
    std::string osRange;  // leave in this scope !
1295
101k
    int nRoundedBufSize = 0;
1296
101k
    const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
1297
101k
    bool bHasUsedLimitedRangeGet = false;
1298
101k
    if (bRetryWithLimitedRangeGet || UseLimitRangeGetInsteadOfHead())
1299
2.14k
    {
1300
2.14k
        bHasUsedLimitedRangeGet = true;
1301
2.14k
        osVerb = "GET";
1302
2.14k
        const int nBufSize = std::clamp(
1303
2.14k
            atoi(CPLGetConfigOption("GDAL_INGESTED_BYTES_AT_OPEN", "1024")),
1304
2.14k
            1024, 10 * 1024 * 1024);
1305
2.14k
        nRoundedBufSize = cpl::div_round_up(nBufSize, knDOWNLOAD_CHUNK_SIZE) *
1306
2.14k
                          knDOWNLOAD_CHUNK_SIZE;
1307
1308
        // so it gets included in Azure signature
1309
2.14k
        osRange = CPLSPrintf("Range: bytes=0-%d", nRoundedBufSize - 1);
1310
2.14k
        headers = curl_slist_append(headers, osRange.c_str());
1311
2.14k
    }
1312
    // HACK for mbtiles driver: http://a.tiles.mapbox.com/v3/ doesn't accept
1313
    // HEAD, as it is a redirect to AWS S3 signed URL, but those are only valid
1314
    // for a given type of HTTP request, and thus GET. This is valid for any
1315
    // signed URL for AWS S3.
1316
99.8k
    else if (bRetryWithGet ||
1317
99.8k
             strstr(osURL.c_str(), ".tiles.mapbox.com/") != nullptr ||
1318
98.9k
             VSICurlIsS3LikeSignedURL(osURL.c_str()) || !m_bUseHead)
1319
7.23k
    {
1320
7.23k
        sWriteFuncData.bInterrupted = true;
1321
7.23k
        osVerb = "GET";
1322
7.23k
    }
1323
92.5k
    else
1324
92.5k
    {
1325
92.5k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
1326
92.5k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPGET, 0);
1327
92.5k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADER, 1);
1328
92.5k
        osVerb = "HEAD";
1329
92.5k
    }
1330
1331
101k
    bRetryWithLimitedRangeGet = false;
1332
1333
101k
    if (!AllowAutomaticRedirection())
1334
0
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
1335
1336
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
1337
101k
                               &sWriteFuncHeaderData);
1338
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
1339
101k
                               VSICurlHandleWriteFunc);
1340
1341
    // Bug with older curl versions (<=7.16.4) and FTP.
1342
    // See http://curl.haxx.se/mail/lib-2007-08/0312.html
1343
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
1344
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1345
101k
                               VSICurlHandleWriteFunc);
1346
1347
101k
    char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
1348
101k
    szCurlErrBuf[0] = '\0';
1349
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
1350
1351
101k
    headers = GetCurlHeaders(osVerb, headers);
1352
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1353
1354
101k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FILETIME, 1);
1355
1356
101k
    VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle, &m_bInterrupt);
1357
1358
101k
    VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
1359
1360
101k
    curl_slist_free_all(headers);
1361
1362
101k
    oFileProp.eExists = EXIST_UNKNOWN;
1363
1364
101k
    curl_off_t filetime = -1;
1365
101k
    GIntBig mtime = 0;
1366
101k
    if (curl_easy_getinfo(hCurlHandle, CURLINFO_FILETIME_T, &filetime) ==
1367
101k
            CURLE_OK &&
1368
101k
        filetime != -1)
1369
132
    {
1370
132
        mtime = static_cast<GIntBig>(filetime);
1371
132
    }
1372
1373
101k
    if (osVerb == "GET")
1374
9.37k
        NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
1375
92.5k
    else
1376
92.5k
        NetworkStatisticsLogger::LogHEAD();
1377
1378
101k
    if (STARTS_WITH(osURL.c_str(), "ftp"))
1379
16.5k
    {
1380
16.5k
        if (sWriteFuncData.pBuffer != nullptr)
1381
0
        {
1382
0
            const char *pszContentLength =
1383
0
                strstr(const_cast<const char *>(sWriteFuncData.pBuffer),
1384
0
                       "Content-Length: ");
1385
0
            if (pszContentLength)
1386
0
            {
1387
0
                pszContentLength += strlen("Content-Length: ");
1388
0
                oFileProp.eExists = EXIST_YES;
1389
0
                oFileProp.fileSize =
1390
0
                    CPLScanUIntBig(pszContentLength,
1391
0
                                   static_cast<int>(strlen(pszContentLength)));
1392
                if constexpr (ENABLE_DEBUG)
1393
0
                {
1394
0
                    CPLDebug(poFS->GetDebugKey(),
1395
0
                             "GetFileSize(%s)=" CPL_FRMT_GUIB, osURL.c_str(),
1396
0
                             oFileProp.fileSize);
1397
0
                }
1398
0
            }
1399
0
        }
1400
16.5k
    }
1401
1402
101k
    double dfSize = 0;
1403
101k
    long response_code = -1;
1404
101k
    if (oFileProp.eExists != EXIST_YES)
1405
101k
    {
1406
101k
        curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
1407
1408
101k
        bool bAlreadyLogged = false;
1409
101k
        if (response_code >= 400 && szCurlErrBuf[0] == '\0')
1410
2.05k
        {
1411
2.05k
            const bool bLogResponse =
1412
2.05k
                CPLTestBool(CPLGetConfigOption("CPL_CURL_VERBOSE", "NO"));
1413
2.05k
            if (bLogResponse && sWriteFuncData.pBuffer)
1414
0
            {
1415
0
                const char *pszErrorMsg =
1416
0
                    static_cast<const char *>(sWriteFuncData.pBuffer);
1417
0
                bAlreadyLogged = true;
1418
0
                CPLDebug(
1419
0
                    poFS->GetDebugKey(),
1420
0
                    "GetFileSize(%s): response_code=%d, server error msg=%s",
1421
0
                    osURL.c_str(), static_cast<int>(response_code),
1422
0
                    pszErrorMsg[0] ? pszErrorMsg : "(no message provided)");
1423
0
            }
1424
2.05k
        }
1425
99.9k
        else if (szCurlErrBuf[0] != '\0')
1426
99.5k
        {
1427
99.5k
            bAlreadyLogged = true;
1428
99.5k
            CPLDebug(poFS->GetDebugKey(),
1429
99.5k
                     "GetFileSize(%s): response_code=%d, curl error msg=%s",
1430
99.5k
                     osURL.c_str(), static_cast<int>(response_code),
1431
99.5k
                     szCurlErrBuf);
1432
99.5k
        }
1433
1434
101k
        std::string osEffectiveURL;
1435
101k
        {
1436
101k
            char *pszEffectiveURL = nullptr;
1437
101k
            curl_easy_getinfo(hCurlHandle, CURLINFO_EFFECTIVE_URL,
1438
101k
                              &pszEffectiveURL);
1439
101k
            if (pszEffectiveURL)
1440
101k
                osEffectiveURL = pszEffectiveURL;
1441
101k
        }
1442
1443
101k
        if (!osEffectiveURL.empty() &&
1444
101k
            strstr(osEffectiveURL.c_str(), osURL.c_str()) == nullptr)
1445
18.9k
        {
1446
            // Moved permanently ?
1447
18.9k
            if (sWriteFuncHeaderData.nFirstHTTPCode == 301 ||
1448
18.9k
                (m_bUseRedirectURLIfNoQueryStringParams &&
1449
155
                 osEffectiveURL.find('?') == std::string::npos))
1450
143
            {
1451
143
                CPLDebug(poFS->GetDebugKey(),
1452
143
                         "Using effective URL %s permanently",
1453
143
                         osEffectiveURL.c_str());
1454
143
                oFileProp.osRedirectURL = osEffectiveURL;
1455
143
                poFS->SetCachedFileProp(m_pszURL, oFileProp);
1456
143
            }
1457
18.8k
            else
1458
18.8k
            {
1459
18.8k
                CPLDebug(poFS->GetDebugKey(),
1460
18.8k
                         "Using effective URL %s temporarily",
1461
18.8k
                         osEffectiveURL.c_str());
1462
18.8k
            }
1463
1464
            // Is this is a redirect to a S3 URL?
1465
18.9k
            if (VSICurlIsS3LikeSignedURL(osEffectiveURL.c_str()) &&
1466
391
                !VSICurlIsS3LikeSignedURL(osURL.c_str()))
1467
0
            {
1468
                // Note that this is a redirect as we won't notice after the
1469
                // retry.
1470
0
                bS3LikeRedirect = true;
1471
1472
0
                if (!bRetryWithGet && osVerb == "HEAD" && response_code == 403)
1473
0
                {
1474
0
                    CPLDebug(poFS->GetDebugKey(),
1475
0
                             "Redirected to a AWS S3 signed URL. Retrying "
1476
0
                             "with GET request instead of HEAD since the URL "
1477
0
                             "might be valid only for GET");
1478
0
                    bRetryWithGet = true;
1479
0
                    osURL = std::move(osEffectiveURL);
1480
0
                    CPLFree(sWriteFuncData.pBuffer);
1481
0
                    CPLFree(sWriteFuncHeaderData.pBuffer);
1482
0
                    curl_easy_cleanup(hCurlHandle);
1483
0
                    goto retry;
1484
0
                }
1485
0
            }
1486
18.9k
            else if (oFileProp.osRedirectURL.empty() && nTryCount == 1 &&
1487
13.0k
                     ((response_code >= 300 && response_code < 400) ||
1488
13.0k
                      (osVerb == "HEAD" && response_code == 403)))
1489
0
            {
1490
0
                if (response_code == 403)
1491
0
                {
1492
0
                    CPLDebug(
1493
0
                        poFS->GetDebugKey(),
1494
0
                        "Retrying redirected URL with GET instead of HEAD");
1495
0
                    bRetryWithGet = true;
1496
0
                }
1497
0
                osURL = std::move(osEffectiveURL);
1498
0
                CPLFree(sWriteFuncData.pBuffer);
1499
0
                CPLFree(sWriteFuncHeaderData.pBuffer);
1500
0
                curl_easy_cleanup(hCurlHandle);
1501
0
                goto retry;
1502
0
            }
1503
18.9k
        }
1504
1505
101k
        if (bS3LikeRedirect && response_code >= 200 && response_code < 300 &&
1506
0
            sWriteFuncHeaderData.nTimestampDate > 0 &&
1507
0
            !osEffectiveURL.empty() &&
1508
0
            CPLTestBool(
1509
0
                CPLGetConfigOption("CPL_VSIL_CURL_USE_S3_REDIRECT", "TRUE")))
1510
0
        {
1511
0
            const GIntBig nExpireTimestamp =
1512
0
                VSICurlGetExpiresFromS3LikeSignedURL(osEffectiveURL.c_str());
1513
0
            if (nExpireTimestamp > sWriteFuncHeaderData.nTimestampDate + 10)
1514
0
            {
1515
0
                const int nValidity = static_cast<int>(
1516
0
                    nExpireTimestamp - sWriteFuncHeaderData.nTimestampDate);
1517
0
                CPLDebug(poFS->GetDebugKey(),
1518
0
                         "Will use redirect URL for the next %d seconds",
1519
0
                         nValidity);
1520
                // As our local clock might not be in sync with server clock,
1521
                // figure out the expiration timestamp in local time
1522
0
                oFileProp.bS3LikeRedirect = true;
1523
0
                oFileProp.nExpireTimestampLocal = time(nullptr) + nValidity;
1524
0
                oFileProp.osRedirectURL = osEffectiveURL;
1525
0
                poFS->SetCachedFileProp(m_pszURL, oFileProp);
1526
0
            }
1527
0
        }
1528
1529
        // Split a string with the raw HTTP response headers as a key/value
1530
        // CPLStringList
1531
101k
        const auto TokenizeHeaders = [](const char *pszHeaders) -> CPLStringList
1532
101k
        {
1533
99.7k
            CPLStringList aosHeaders;
1534
99.8k
            while (pszHeaders)
1535
174
            {
1536
174
                const char *pszDelim = strchr(pszHeaders, ':');
1537
174
                if (!pszDelim)
1538
87
                    break;
1539
87
                const char *pszValue = pszDelim + 1;
1540
1541
                // Skip whitespace after colon
1542
174
                while (*pszValue == ' ' || *pszValue == '\t')
1543
87
                    ++pszValue;
1544
1545
                // Find end of value
1546
87
                const char *pszEndOfValue = pszValue;
1547
2.61k
                while (*pszEndOfValue &&
1548
2.61k
                       !(*pszEndOfValue == '\r' && pszEndOfValue[1] == '\n'))
1549
2.52k
                    ++pszEndOfValue;
1550
1551
87
                aosHeaders.SetNameValue(
1552
87
                    std::string(pszHeaders, pszDelim - pszHeaders).c_str(),
1553
87
                    std::string(pszValue, pszEndOfValue - pszValue).c_str());
1554
1555
87
                if (*pszEndOfValue == '\r' && pszEndOfValue[1] == '\n')
1556
87
                    pszHeaders = pszEndOfValue + 2;
1557
0
                else
1558
0
                    break;
1559
87
            }
1560
99.7k
            return aosHeaders;
1561
99.7k
        };
1562
1563
101k
        if (response_code < 300)
1564
99.9k
        {
1565
99.9k
            curl_off_t nSizeTmp = 0;
1566
99.9k
            const CURLcode code = curl_easy_getinfo(
1567
99.9k
                hCurlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &nSizeTmp);
1568
99.9k
            CPL_IGNORE_RET_VAL(dfSize);
1569
99.9k
            dfSize = static_cast<double>(nSizeTmp);
1570
99.9k
            if (code == 0)
1571
99.9k
            {
1572
99.9k
                if (dfSize < 0)
1573
99.8k
                {
1574
99.8k
                    if (osVerb == "HEAD" && !bRetryWithGet &&
1575
92.5k
                        response_code == 200)
1576
0
                    {
1577
0
                        if (sWriteFuncHeaderData.pBuffer)
1578
0
                        {
1579
0
                            const CPLStringList aosHeaders(
1580
0
                                TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1581
0
                            if (strcmp(aosHeaders.FetchNameValueDef(
1582
0
                                           "accept-ranges", ""),
1583
0
                                       "bytes") == 0)
1584
0
                            {
1585
0
                                CPLDebug(
1586
0
                                    poFS->GetDebugKey(),
1587
0
                                    "HEAD did not provide file size. Retrying "
1588
0
                                    "with limited range GET");
1589
0
                                bRetryWithLimitedRangeGet = true;
1590
0
                                CPLFree(sWriteFuncData.pBuffer);
1591
0
                                CPLFree(sWriteFuncHeaderData.pBuffer);
1592
0
                                curl_easy_cleanup(hCurlHandle);
1593
0
                                goto retry;
1594
0
                            }
1595
0
                        }
1596
1597
0
                        CPLDebug(poFS->GetDebugKey(),
1598
0
                                 "HEAD did not provide file size. Retrying "
1599
0
                                 "with GET");
1600
0
                        bRetryWithGet = true;
1601
0
                        CPLFree(sWriteFuncData.pBuffer);
1602
0
                        CPLFree(sWriteFuncHeaderData.pBuffer);
1603
0
                        curl_easy_cleanup(hCurlHandle);
1604
0
                        goto retry;
1605
0
                    }
1606
1607
99.8k
                    if (poFS->GetFSPrefix() == "/vsicurl/" ||
1608
89
                        poFS->GetFSPrefix() == "/vsicurl?")
1609
99.7k
                    {
1610
99.7k
                        const CPLStringList aosHeaders(
1611
99.7k
                            TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1612
99.7k
                        if (strcmp(aosHeaders.FetchNameValueDef(
1613
99.7k
                                       "transfer-encoding", ""),
1614
99.7k
                                   "chunked") == 0)
1615
0
                        {
1616
0
                            CPLError(
1617
0
                                CE_Failure, CPLE_AppDefined,
1618
0
                                "Server does not seem to support range "
1619
0
                                "requests. "
1620
0
                                "Maybe retry with /vsicurl_streaming/ if the "
1621
0
                                "read "
1622
0
                                "access pattern is compatible with sequential "
1623
0
                                "reading, or download the file entirely");
1624
0
                        }
1625
99.7k
                    }
1626
99.8k
                }
1627
45
                else
1628
45
                {
1629
45
                    oFileProp.eExists = EXIST_YES;
1630
45
                    oFileProp.fileSize = static_cast<GUIntBig>(dfSize);
1631
45
                }
1632
99.9k
            }
1633
99.9k
        }
1634
1635
101k
        if (sWriteFuncHeaderData.pBuffer != nullptr &&
1636
2.18k
            (response_code == 200 || response_code == 206))
1637
0
        {
1638
0
            {
1639
0
                const CPLStringList aosHeaders(
1640
0
                    TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1641
0
                for (const auto &[pszKey, pszValue] :
1642
0
                     cpl::IterateNameValue(aosHeaders))
1643
0
                {
1644
0
                    if (bGetHeaders)
1645
0
                    {
1646
0
                        m_aosHeaders.SetNameValue(pszKey, pszValue);
1647
0
                    }
1648
0
                    if (EQUAL(pszKey, "Cache-Control") &&
1649
0
                        EQUAL(pszValue, "no-cache") &&
1650
0
                        CPLTestBool(CPLGetConfigOption(
1651
0
                            "CPL_VSIL_CURL_HONOR_CACHE_CONTROL", "YES")))
1652
0
                    {
1653
0
                        m_bCached = false;
1654
0
                    }
1655
1656
0
                    else if (EQUAL(pszKey, "ETag"))
1657
0
                    {
1658
0
                        std::string osValue(pszValue);
1659
0
                        if (osValue.size() >= 2 && osValue.front() == '"' &&
1660
0
                            osValue.back() == '"')
1661
0
                            osValue = osValue.substr(1, osValue.size() - 2);
1662
0
                        oFileProp.ETag = std::move(osValue);
1663
0
                    }
1664
1665
                    // Azure Data Lake Storage
1666
0
                    else if (EQUAL(pszKey, "x-ms-resource-type"))
1667
0
                    {
1668
0
                        if (EQUAL(pszValue, "file"))
1669
0
                        {
1670
0
                            oFileProp.nMode |= S_IFREG;
1671
0
                        }
1672
0
                        else if (EQUAL(pszValue, "directory"))
1673
0
                        {
1674
0
                            oFileProp.bIsDirectory = true;
1675
0
                            oFileProp.nMode |= S_IFDIR;
1676
0
                        }
1677
0
                    }
1678
0
                    else if (EQUAL(pszKey, "x-ms-permissions"))
1679
0
                    {
1680
0
                        oFileProp.nMode |=
1681
0
                            VSICurlParseUnixPermissions(pszValue);
1682
0
                    }
1683
1684
                    // https://overturemapswestus2.blob.core.windows.net/release/2024-11-13.0/theme%3Ddivisions/type%3Ddivision_area
1685
                    // returns a x-ms-meta-hdi_isfolder: true header
1686
0
                    else if (EQUAL(pszKey, "x-ms-meta-hdi_isfolder") &&
1687
0
                             EQUAL(pszValue, "true"))
1688
0
                    {
1689
0
                        oFileProp.bIsAzureFolder = true;
1690
0
                        oFileProp.bIsDirectory = true;
1691
0
                        oFileProp.nMode |= S_IFDIR;
1692
0
                    }
1693
0
                }
1694
0
            }
1695
0
        }
1696
1697
101k
        if (bHasUsedLimitedRangeGet && response_code == 206)
1698
0
        {
1699
0
            oFileProp.eExists = EXIST_NO;
1700
0
            oFileProp.fileSize = 0;
1701
0
            if (sWriteFuncHeaderData.pBuffer != nullptr)
1702
0
            {
1703
0
                const CPLStringList aosHeaders(
1704
0
                    TokenizeHeaders(sWriteFuncHeaderData.pBuffer));
1705
0
                const char *pszContentRange =
1706
0
                    aosHeaders.FetchNameValue("content-range");
1707
                // Trailing space in string intended
1708
0
                if (pszContentRange &&
1709
0
                    STARTS_WITH_CI(pszContentRange, "bytes "))
1710
0
                {
1711
0
                    pszContentRange += strlen("bytes ");
1712
0
                    pszContentRange = strchr(pszContentRange, '/');
1713
0
                    if (pszContentRange)
1714
0
                    {
1715
0
                        oFileProp.eExists = EXIST_YES;
1716
0
                        oFileProp.fileSize = static_cast<GUIntBig>(
1717
0
                            CPLAtoGIntBig(pszContentRange + 1));
1718
0
                    }
1719
0
                }
1720
1721
                // Add first bytes to cache
1722
0
                if (sWriteFuncData.pBuffer != nullptr)
1723
0
                {
1724
0
                    size_t nOffset = 0;
1725
0
                    while (nOffset < sWriteFuncData.nSize)
1726
0
                    {
1727
0
                        const size_t nToCache =
1728
0
                            std::min<size_t>(sWriteFuncData.nSize - nOffset,
1729
0
                                             knDOWNLOAD_CHUNK_SIZE);
1730
0
                        poFS->AddRegion(m_pszURL, nOffset, nToCache,
1731
0
                                        sWriteFuncData.pBuffer + nOffset);
1732
0
                        nOffset += nToCache;
1733
0
                    }
1734
0
                }
1735
0
            }
1736
0
        }
1737
101k
        else if (IsDirectoryFromExists(osVerb.c_str(),
1738
101k
                                       static_cast<int>(response_code)))
1739
0
        {
1740
0
            oFileProp.eExists = EXIST_YES;
1741
0
            oFileProp.fileSize = 0;
1742
0
            oFileProp.bIsDirectory = true;
1743
0
        }
1744
        // 405 = Method not allowed
1745
101k
        else if (response_code == 405 && !bRetryWithGet && osVerb == "HEAD")
1746
0
        {
1747
0
            CPLDebug(poFS->GetDebugKey(),
1748
0
                     "HEAD not allowed. Retrying with GET");
1749
0
            bRetryWithGet = true;
1750
0
            CPLFree(sWriteFuncData.pBuffer);
1751
0
            CPLFree(sWriteFuncHeaderData.pBuffer);
1752
0
            curl_easy_cleanup(hCurlHandle);
1753
0
            goto retry;
1754
0
        }
1755
101k
        else if (response_code == 416)
1756
0
        {
1757
0
            oFileProp.eExists = EXIST_YES;
1758
0
            oFileProp.fileSize = 0;
1759
0
        }
1760
101k
        else if (response_code != 200)
1761
101k
        {
1762
            // Look if we should attempt a retry
1763
101k
            if (oRetryContext.CanRetry(static_cast<int>(response_code),
1764
101k
                                       sWriteFuncHeaderData.pBuffer,
1765
101k
                                       szCurlErrBuf))
1766
6.78k
            {
1767
6.78k
                CPLError(CE_Warning, CPLE_AppDefined,
1768
6.78k
                         "HTTP error code: %d - %s. "
1769
6.78k
                         "Retrying again in %.1f secs",
1770
6.78k
                         static_cast<int>(response_code), m_pszURL,
1771
6.78k
                         oRetryContext.GetCurrentDelay());
1772
6.78k
                CPLSleep(oRetryContext.GetCurrentDelay());
1773
6.78k
                CPLFree(sWriteFuncData.pBuffer);
1774
6.78k
                CPLFree(sWriteFuncHeaderData.pBuffer);
1775
6.78k
                curl_easy_cleanup(hCurlHandle);
1776
6.78k
                goto retry;
1777
6.78k
            }
1778
1779
95.1k
            if (sWriteFuncData.pBuffer != nullptr)
1780
2.06k
            {
1781
2.06k
                if (UseLimitRangeGetInsteadOfHead() &&
1782
2.05k
                    CanRestartOnError(sWriteFuncData.pBuffer,
1783
2.05k
                                      sWriteFuncHeaderData.pBuffer, bSetError))
1784
0
                {
1785
0
                    oFileProp.bHasComputedFileSize = false;
1786
0
                    CPLFree(sWriteFuncData.pBuffer);
1787
0
                    CPLFree(sWriteFuncHeaderData.pBuffer);
1788
0
                    curl_easy_cleanup(hCurlHandle);
1789
0
                    return GetFileSizeOrHeaders(bSetError, bGetHeaders);
1790
0
                }
1791
2.06k
                else
1792
2.06k
                {
1793
2.06k
                    CPL_IGNORE_RET_VAL(CanRestartOnError(
1794
2.06k
                        sWriteFuncData.pBuffer, sWriteFuncHeaderData.pBuffer,
1795
2.06k
                        bSetError));
1796
2.06k
                }
1797
2.06k
            }
1798
1799
            // If there was no VSI error thrown in the process,
1800
            // fail by reporting the HTTP response code.
1801
95.1k
            if (bSetError && VSIGetLastErrorNo() == 0)
1802
5.55k
            {
1803
5.55k
                if (strlen(szCurlErrBuf) > 0)
1804
5.34k
                {
1805
5.34k
                    if (response_code == 0)
1806
5.34k
                    {
1807
5.34k
                        VSIError(VSIE_HttpError, "CURL error: %s",
1808
5.34k
                                 szCurlErrBuf);
1809
5.34k
                    }
1810
0
                    else
1811
0
                    {
1812
0
                        VSIError(VSIE_HttpError, "HTTP response code: %d - %s",
1813
0
                                 static_cast<int>(response_code), szCurlErrBuf);
1814
0
                    }
1815
5.34k
                }
1816
206
                else
1817
206
                {
1818
206
                    VSIError(VSIE_HttpError, "HTTP response code: %d",
1819
206
                             static_cast<int>(response_code));
1820
206
                }
1821
5.55k
            }
1822
89.6k
            else
1823
89.6k
            {
1824
89.6k
                if (response_code != 400 && response_code != 404)
1825
87.8k
                {
1826
87.8k
                    CPLError(CE_Warning, CPLE_AppDefined,
1827
87.8k
                             "HTTP response code on %s: %d", osURL.c_str(),
1828
87.8k
                             static_cast<int>(response_code));
1829
87.8k
                }
1830
                // else a CPLDebug() is emitted below
1831
89.6k
            }
1832
1833
95.1k
            oFileProp.eExists = EXIST_NO;
1834
95.1k
            oFileProp.nHTTPCode = static_cast<int>(response_code);
1835
95.1k
            oFileProp.fileSize = 0;
1836
95.1k
        }
1837
0
        else if (sWriteFuncData.pBuffer != nullptr)
1838
0
        {
1839
0
            ProcessGetFileSizeResult(
1840
0
                reinterpret_cast<const char *>(sWriteFuncData.pBuffer));
1841
0
        }
1842
1843
        // Try to guess if this is a directory. Generally if this is a
1844
        // directory, curl will retry with an URL with slash added.
1845
95.1k
        if (!osEffectiveURL.empty() &&
1846
95.1k
            strncmp(osURL.c_str(), osEffectiveURL.c_str(), osURL.size()) == 0 &&
1847
67.9k
            osEffectiveURL[osURL.size()] == '/' &&
1848
1.97k
            oFileProp.eExists != EXIST_NO)
1849
0
        {
1850
0
            oFileProp.eExists = EXIST_YES;
1851
0
            oFileProp.fileSize = 0;
1852
0
            oFileProp.bIsDirectory = true;
1853
0
        }
1854
95.1k
        else if (osURL.back() == '/')
1855
568
        {
1856
568
            oFileProp.bIsDirectory = true;
1857
568
        }
1858
1859
95.1k
        if (!bAlreadyLogged)
1860
2.45k
        {
1861
2.45k
            CPLDebug(poFS->GetDebugKey(),
1862
2.45k
                     "GetFileSize(%s)=" CPL_FRMT_GUIB "  response_code=%d",
1863
2.45k
                     osURL.c_str(), oFileProp.fileSize,
1864
2.45k
                     static_cast<int>(response_code));
1865
2.45k
        }
1866
95.1k
    }
1867
1868
95.1k
    CPLFree(sWriteFuncData.pBuffer);
1869
95.1k
    CPLFree(sWriteFuncHeaderData.pBuffer);
1870
95.1k
    curl_easy_cleanup(hCurlHandle);
1871
1872
95.1k
    oFileProp.bHasComputedFileSize = true;
1873
95.1k
    if (mtime > 0)
1874
132
        oFileProp.mTime = mtime;
1875
    // Do not update cached file properties if cURL returned a non-HTTP error
1876
95.1k
    if (response_code != 0)
1877
2.05k
        poFS->SetCachedFileProp(m_pszURL, oFileProp);
1878
1879
95.1k
    return oFileProp.fileSize;
1880
101k
}
1881
1882
/************************************************************************/
1883
/*                               Exists()                               */
1884
/************************************************************************/
1885
1886
bool VSICurlHandle::Exists(bool bSetError)
1887
177k
{
1888
177k
    if (oFileProp.eExists == EXIST_UNKNOWN)
1889
82.0k
    {
1890
82.0k
        GetFileSize(bSetError);
1891
82.0k
    }
1892
95.1k
    else if (oFileProp.eExists == EXIST_NO)
1893
95.1k
    {
1894
        // If there was no VSI error thrown in the process,
1895
        // and we know the HTTP error code of the first request where the
1896
        // file could not be retrieved, fail by reporting the HTTP code.
1897
95.1k
        if (bSetError && VSIGetLastErrorNo() == 0 && oFileProp.nHTTPCode)
1898
173
        {
1899
173
            VSIError(VSIE_HttpError, "HTTP response code: %d",
1900
173
                     oFileProp.nHTTPCode);
1901
173
        }
1902
95.1k
    }
1903
1904
177k
    return oFileProp.eExists == EXIST_YES;
1905
177k
}
1906
1907
/************************************************************************/
1908
/*                                Tell()                                */
1909
/************************************************************************/
1910
1911
vsi_l_offset VSICurlHandle::Tell()
1912
26.0k
{
1913
26.0k
    return curOffset;
1914
26.0k
}
1915
1916
/************************************************************************/
1917
/*                       GetRedirectURLIfValid()                        */
1918
/************************************************************************/
1919
1920
std::string
1921
VSICurlHandle::GetRedirectURLIfValid(bool &bHasExpired,
1922
                                     CPLStringList &aosHTTPOptions) const
1923
12.6k
{
1924
12.6k
    bHasExpired = false;
1925
12.6k
    poFS->GetCachedFileProp(m_pszURL, oFileProp);
1926
1927
12.6k
    std::string osURL(m_pszURL + m_osQueryString);
1928
12.6k
    if (oFileProp.bS3LikeRedirect)
1929
0
    {
1930
0
        if (time(nullptr) + 1 < oFileProp.nExpireTimestampLocal)
1931
0
        {
1932
0
            CPLDebug(poFS->GetDebugKey(),
1933
0
                     "Using redirect URL as it looks to be still valid "
1934
0
                     "(%d seconds left)",
1935
0
                     static_cast<int>(oFileProp.nExpireTimestampLocal -
1936
0
                                      time(nullptr)));
1937
0
            osURL = oFileProp.osRedirectURL;
1938
0
        }
1939
0
        else
1940
0
        {
1941
0
            CPLDebug(poFS->GetDebugKey(),
1942
0
                     "Redirect URL has expired. Using original URL");
1943
0
            oFileProp.bS3LikeRedirect = false;
1944
0
            poFS->SetCachedFileProp(m_pszURL, oFileProp);
1945
0
            bHasExpired = true;
1946
0
        }
1947
0
    }
1948
12.6k
    else if (!oFileProp.osRedirectURL.empty())
1949
0
    {
1950
0
        osURL = oFileProp.osRedirectURL;
1951
0
        bHasExpired = false;
1952
0
    }
1953
1954
12.6k
    if (m_pszURL != osURL)
1955
0
    {
1956
0
        const char *pszAuthorizationHeaderAllowed = VSIGetPathSpecificOption(
1957
0
            m_osFilename.c_str(),
1958
0
            "CPL_VSIL_CURL_AUTHORIZATION_HEADER_ALLOWED_IF_REDIRECT",
1959
0
            "IF_SAME_HOST");
1960
0
        if (EQUAL(pszAuthorizationHeaderAllowed, "IF_SAME_HOST"))
1961
0
        {
1962
0
            const auto ExtractServer = [](const std::string &s)
1963
0
            {
1964
0
                size_t afterHTTPPos = 0;
1965
0
                if (STARTS_WITH(s.c_str(), "http://"))
1966
0
                    afterHTTPPos = strlen("http://");
1967
0
                else if (STARTS_WITH(s.c_str(), "https://"))
1968
0
                    afterHTTPPos = strlen("https://");
1969
0
                const auto posSlash = s.find('/', afterHTTPPos);
1970
0
                if (posSlash != std::string::npos)
1971
0
                    return s.substr(afterHTTPPos, posSlash - afterHTTPPos);
1972
0
                else
1973
0
                    return s.substr(afterHTTPPos);
1974
0
            };
1975
1976
0
            if (ExtractServer(osURL) != ExtractServer(m_pszURL))
1977
0
            {
1978
0
                aosHTTPOptions.SetNameValue("AUTHORIZATION_HEADER_ALLOWED",
1979
0
                                            "NO");
1980
0
            }
1981
0
        }
1982
0
        else if (!CPLTestBool(pszAuthorizationHeaderAllowed))
1983
0
        {
1984
0
            aosHTTPOptions.SetNameValue("AUTHORIZATION_HEADER_ALLOWED", "NO");
1985
0
        }
1986
0
    }
1987
1988
12.6k
    return osURL;
1989
12.6k
}
1990
1991
/************************************************************************/
1992
/*                           CurrentDownload                            */
1993
/************************************************************************/
1994
1995
namespace
1996
{
1997
struct CurrentDownload
1998
{
1999
    VSICurlFilesystemHandlerBase *m_poFS = nullptr;
2000
    std::string m_osURL{};
2001
    vsi_l_offset m_nStartOffset = 0;
2002
    int m_nBlocks = 0;
2003
    std::string m_osAlreadyDownloadedData{};
2004
    bool m_bHasAlreadyDownloadedData = false;
2005
2006
    CurrentDownload(VSICurlFilesystemHandlerBase *poFS, const char *pszURL,
2007
                    vsi_l_offset startOffset, int nBlocks)
2008
12.6k
        : m_poFS(poFS), m_osURL(pszURL), m_nStartOffset(startOffset),
2009
12.6k
          m_nBlocks(nBlocks)
2010
12.6k
    {
2011
12.6k
        auto res = m_poFS->NotifyStartDownloadRegion(m_osURL, m_nStartOffset,
2012
12.6k
                                                     m_nBlocks);
2013
12.6k
        m_bHasAlreadyDownloadedData = res.first;
2014
12.6k
        m_osAlreadyDownloadedData = std::move(res.second);
2015
12.6k
    }
2016
2017
    bool HasAlreadyDownloadedData() const
2018
12.6k
    {
2019
12.6k
        return m_bHasAlreadyDownloadedData;
2020
12.6k
    }
2021
2022
    const std::string &GetAlreadyDownloadedData() const
2023
0
    {
2024
0
        return m_osAlreadyDownloadedData;
2025
0
    }
2026
2027
    void SetData(const std::string &osData)
2028
0
    {
2029
0
        CPLAssert(!m_bHasAlreadyDownloadedData);
2030
0
        m_bHasAlreadyDownloadedData = true;
2031
0
        m_poFS->NotifyStopDownloadRegion(m_osURL, m_nStartOffset, m_nBlocks,
2032
0
                                         osData);
2033
0
    }
2034
2035
    ~CurrentDownload()
2036
12.6k
    {
2037
12.6k
        if (!m_bHasAlreadyDownloadedData)
2038
12.6k
            m_poFS->NotifyStopDownloadRegion(m_osURL, m_nStartOffset, m_nBlocks,
2039
12.6k
                                             std::string());
2040
12.6k
    }
2041
2042
    CurrentDownload(const CurrentDownload &) = delete;
2043
    CurrentDownload &operator=(const CurrentDownload &) = delete;
2044
};
2045
}  // namespace
2046
2047
/************************************************************************/
2048
/*                     NotifyStartDownloadRegion()                      */
2049
/************************************************************************/
2050
2051
/** Indicate intent at downloading a new region.
2052
 *
2053
 * If the region is already in download in another thread, then wait for its
2054
 * completion.
2055
 *
2056
 * Returns:
2057
 * - (false, empty string) if a new download is needed
2058
 * - (true, region_content) if we have been waiting for a download of the same
2059
 *   region to be completed and got its result. Note that region_content will be
2060
 *   empty if the download of that region failed.
2061
 */
2062
std::pair<bool, std::string>
2063
VSICurlFilesystemHandlerBase::NotifyStartDownloadRegion(
2064
    const std::string &osURL, vsi_l_offset startOffset, int nBlocks)
2065
12.6k
{
2066
12.6k
    std::string osId(osURL);
2067
12.6k
    osId += '_';
2068
12.6k
    osId += std::to_string(startOffset);
2069
12.6k
    osId += '_';
2070
12.6k
    osId += std::to_string(nBlocks);
2071
2072
12.6k
    m_oMutex.lock();
2073
12.6k
    auto oIter = m_oMapRegionInDownload.find(osId);
2074
12.6k
    if (oIter != m_oMapRegionInDownload.end())
2075
0
    {
2076
0
        auto &region = *(oIter->second);
2077
0
        std::unique_lock<std::mutex> oRegionLock(region.oMutex);
2078
0
        m_oMutex.unlock();
2079
0
        region.nWaiters++;
2080
0
        while (region.bDownloadInProgress)
2081
0
        {
2082
0
            region.oCond.wait(oRegionLock);
2083
0
        }
2084
0
        std::string osRet = region.osData;
2085
0
        region.nWaiters--;
2086
0
        region.oCond.notify_one();
2087
0
        return std::pair<bool, std::string>(true, osRet);
2088
0
    }
2089
12.6k
    else
2090
12.6k
    {
2091
12.6k
        auto poRegionInDownload = std::make_unique<RegionInDownload>();
2092
12.6k
        poRegionInDownload->bDownloadInProgress = true;
2093
12.6k
        m_oMapRegionInDownload[osId] = std::move(poRegionInDownload);
2094
12.6k
        m_oMutex.unlock();
2095
12.6k
        return std::pair<bool, std::string>(false, std::string());
2096
12.6k
    }
2097
12.6k
}
2098
2099
/************************************************************************/
2100
/*                      NotifyStopDownloadRegion()                      */
2101
/************************************************************************/
2102
2103
void VSICurlFilesystemHandlerBase::NotifyStopDownloadRegion(
2104
    const std::string &osURL, vsi_l_offset startOffset, int nBlocks,
2105
    const std::string &osData)
2106
12.6k
{
2107
12.6k
    std::string osId(osURL);
2108
12.6k
    osId += '_';
2109
12.6k
    osId += std::to_string(startOffset);
2110
12.6k
    osId += '_';
2111
12.6k
    osId += std::to_string(nBlocks);
2112
2113
12.6k
    m_oMutex.lock();
2114
12.6k
    auto oIter = m_oMapRegionInDownload.find(osId);
2115
12.6k
    CPLAssert(oIter != m_oMapRegionInDownload.end());
2116
12.6k
    auto &region = *(oIter->second);
2117
12.6k
    {
2118
12.6k
        std::unique_lock<std::mutex> oRegionLock(region.oMutex);
2119
12.6k
        if (region.nWaiters)
2120
0
        {
2121
0
            region.osData = osData;
2122
0
            region.bDownloadInProgress = false;
2123
0
            region.oCond.notify_all();
2124
2125
0
            while (region.nWaiters)
2126
0
            {
2127
0
                region.oCond.wait(oRegionLock);
2128
0
            }
2129
0
        }
2130
12.6k
    }
2131
12.6k
    m_oMapRegionInDownload.erase(oIter);
2132
12.6k
    m_oMutex.unlock();
2133
12.6k
}
2134
2135
/************************************************************************/
2136
/*                           DownloadRegion()                           */
2137
/************************************************************************/
2138
2139
std::string VSICurlHandle::DownloadRegion(const vsi_l_offset startOffset,
2140
                                          const int nBlocks)
2141
12.6k
{
2142
12.6k
    if (bInterrupted && bStopOnInterruptUntilUninstall)
2143
0
        return std::string();
2144
2145
12.6k
    if (oFileProp.eExists == EXIST_NO)
2146
1
        return std::string();
2147
2148
    // Check if there is not a download of the same region in progress in
2149
    // another thread, and if so wait for it to be completed
2150
12.6k
    CurrentDownload currentDownload(poFS, m_pszURL, startOffset, nBlocks);
2151
12.6k
    if (currentDownload.HasAlreadyDownloadedData())
2152
0
    {
2153
0
        return currentDownload.GetAlreadyDownloadedData();
2154
0
    }
2155
2156
12.6k
begin:
2157
12.6k
    CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
2158
2159
12.6k
    UpdateQueryString();
2160
2161
12.6k
    bool bHasExpired = false;
2162
2163
12.6k
    CPLStringList aosHTTPOptions(m_aosHTTPOptions);
2164
12.6k
    std::string osURL(GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
2165
12.6k
    bool bUsedRedirect = osURL != m_pszURL;
2166
2167
12.6k
    WriteFuncStruct sWriteFuncData;
2168
12.6k
    WriteFuncStruct sWriteFuncHeaderData;
2169
12.6k
    CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
2170
2171
12.7k
retry:
2172
12.7k
    CURL *hCurlHandle = curl_easy_init();
2173
12.7k
    struct curl_slist *headers =
2174
12.7k
        VSICurlSetOptions(hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
2175
2176
12.7k
    if (!AllowAutomaticRedirection())
2177
0
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
2178
2179
12.7k
    VSICURLInitWriteFuncStruct(&sWriteFuncData, this, pfnReadCbk,
2180
12.7k
                               pReadCbkUserData);
2181
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
2182
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
2183
12.7k
                               VSICurlHandleWriteFunc);
2184
2185
12.7k
    VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
2186
12.7k
                               nullptr);
2187
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
2188
12.7k
                               &sWriteFuncHeaderData);
2189
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
2190
12.7k
                               VSICurlHandleWriteFunc);
2191
12.7k
    sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
2192
12.7k
    sWriteFuncHeaderData.nStartOffset = startOffset;
2193
12.7k
    sWriteFuncHeaderData.nEndOffset =
2194
12.7k
        startOffset +
2195
12.7k
        static_cast<vsi_l_offset>(nBlocks) * VSICURLGetDownloadChunkSize() - 1;
2196
    // Some servers don't like we try to read after end-of-file (#5786).
2197
12.7k
    if (oFileProp.bHasComputedFileSize &&
2198
0
        sWriteFuncHeaderData.nEndOffset >= oFileProp.fileSize)
2199
0
    {
2200
0
        sWriteFuncHeaderData.nEndOffset = oFileProp.fileSize - 1;
2201
0
    }
2202
2203
12.7k
    char rangeStr[512] = {};
2204
12.7k
    snprintf(rangeStr, sizeof(rangeStr), CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2205
12.7k
             startOffset, sWriteFuncHeaderData.nEndOffset);
2206
2207
    if constexpr (ENABLE_DEBUG)
2208
12.7k
    {
2209
12.7k
        CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...", rangeStr,
2210
12.7k
                 osURL.c_str());
2211
12.7k
    }
2212
2213
12.7k
    std::string osHeaderRange;  // leave in this scope
2214
12.7k
    if (sWriteFuncHeaderData.bIsHTTP)
2215
10.0k
    {
2216
10.0k
        osHeaderRange = CPLSPrintf("Range: bytes=%s", rangeStr);
2217
        // So it gets included in Azure signature
2218
10.0k
        headers = curl_slist_append(headers, osHeaderRange.c_str());
2219
10.0k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
2220
10.0k
    }
2221
2.69k
    else
2222
2.69k
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, rangeStr);
2223
2224
12.7k
    char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
2225
12.7k
    szCurlErrBuf[0] = '\0';
2226
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
2227
2228
12.7k
    headers = GetCurlHeaders("GET", headers);
2229
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
2230
2231
12.7k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FILETIME, 1);
2232
2233
12.7k
    VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle, &m_bInterrupt);
2234
2235
12.7k
    VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
2236
2237
12.7k
    curl_slist_free_all(headers);
2238
2239
12.7k
    NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
2240
2241
12.7k
    if (sWriteFuncData.bInterrupted || m_bInterrupt)
2242
0
    {
2243
0
        bInterrupted = true;
2244
2245
        // Notify that the download of the current region is finished
2246
0
        currentDownload.SetData(std::string());
2247
2248
0
        CPLFree(sWriteFuncData.pBuffer);
2249
0
        CPLFree(sWriteFuncHeaderData.pBuffer);
2250
0
        curl_easy_cleanup(hCurlHandle);
2251
2252
0
        return std::string();
2253
0
    }
2254
2255
12.7k
    long response_code = 0;
2256
12.7k
    curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
2257
2258
12.7k
    if (ENABLE_DEBUG && szCurlErrBuf[0] != '\0')
2259
12.4k
    {
2260
12.4k
        CPLDebug(poFS->GetDebugKey(),
2261
12.4k
                 "DownloadRegion(%s): response_code=%d, msg=%s", osURL.c_str(),
2262
12.4k
                 static_cast<int>(response_code), szCurlErrBuf);
2263
12.4k
    }
2264
2265
12.7k
    long mtime = 0;
2266
12.7k
    curl_easy_getinfo(hCurlHandle, CURLINFO_FILETIME, &mtime);
2267
12.7k
    if (mtime > 0)
2268
0
    {
2269
0
        oFileProp.mTime = mtime;
2270
0
        poFS->SetCachedFileProp(m_pszURL, oFileProp);
2271
0
    }
2272
2273
    if constexpr (ENABLE_DEBUG)
2274
12.7k
    {
2275
12.7k
        CPLDebug(poFS->GetDebugKey(), "Got response_code=%ld", response_code);
2276
12.7k
    }
2277
2278
12.7k
    if (bUsedRedirect &&
2279
0
        (response_code == 403 ||
2280
         // Below case is in particular for
2281
         // gdalinfo
2282
         // /vsicurl/https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif
2283
         // --config GDAL_DISABLE_READDIR_ON_OPEN EMPTY_DIR --config
2284
         // GDAL_HTTP_COOKIEFILE /tmp/cookie.txt --config GDAL_HTTP_COOKIEJAR
2285
         // /tmp/cookie.txt We got the redirect URL from a HEAD request, but it
2286
         // is not valid for a GET. So retry with GET on original URL to get a
2287
         // redirect URL valid for it.
2288
0
         (response_code == 400 &&
2289
0
          osURL.find(".cloudfront.net") != std::string::npos)))
2290
0
    {
2291
0
        CPLDebug(poFS->GetDebugKey(),
2292
0
                 "Got an error with redirect URL. Retrying with original one");
2293
0
        oFileProp.bS3LikeRedirect = false;
2294
0
        poFS->SetCachedFileProp(m_pszURL, oFileProp);
2295
0
        bUsedRedirect = false;
2296
0
        osURL = m_pszURL;
2297
0
        CPLFree(sWriteFuncData.pBuffer);
2298
0
        CPLFree(sWriteFuncHeaderData.pBuffer);
2299
0
        curl_easy_cleanup(hCurlHandle);
2300
0
        goto retry;
2301
0
    }
2302
2303
12.7k
    if (response_code == 401 && oRetryContext.CanRetry())
2304
0
    {
2305
0
        CPLDebug(poFS->GetDebugKey(), "Unauthorized, trying to authenticate");
2306
0
        CPLFree(sWriteFuncData.pBuffer);
2307
0
        CPLFree(sWriteFuncHeaderData.pBuffer);
2308
0
        curl_easy_cleanup(hCurlHandle);
2309
0
        if (Authenticate(m_osFilename.c_str()))
2310
0
            goto retry;
2311
0
        return std::string();
2312
0
    }
2313
2314
12.7k
    UpdateRedirectInfo(hCurlHandle, sWriteFuncHeaderData);
2315
2316
12.7k
    if ((response_code != 200 && response_code != 206 && response_code != 225 &&
2317
12.7k
         response_code != 226 && response_code != 426) ||
2318
0
        sWriteFuncHeaderData.bError)
2319
12.7k
    {
2320
12.7k
        if (sWriteFuncData.pBuffer != nullptr &&
2321
275
            CanRestartOnError(
2322
275
                reinterpret_cast<const char *>(sWriteFuncData.pBuffer),
2323
275
                reinterpret_cast<const char *>(sWriteFuncHeaderData.pBuffer),
2324
275
                true))
2325
0
        {
2326
0
            CPLFree(sWriteFuncData.pBuffer);
2327
0
            CPLFree(sWriteFuncHeaderData.pBuffer);
2328
0
            curl_easy_cleanup(hCurlHandle);
2329
0
            goto begin;
2330
0
        }
2331
2332
        // Look if we should attempt a retry
2333
12.7k
        if (oRetryContext.CanRetry(static_cast<int>(response_code),
2334
12.7k
                                   sWriteFuncHeaderData.pBuffer, szCurlErrBuf))
2335
91
        {
2336
91
            CPLError(CE_Warning, CPLE_AppDefined,
2337
91
                     "HTTP error code: %d - %s. "
2338
91
                     "Retrying again in %.1f secs",
2339
91
                     static_cast<int>(response_code), m_pszURL,
2340
91
                     oRetryContext.GetCurrentDelay());
2341
91
            CPLSleep(oRetryContext.GetCurrentDelay());
2342
91
            CPLFree(sWriteFuncData.pBuffer);
2343
91
            CPLFree(sWriteFuncHeaderData.pBuffer);
2344
91
            curl_easy_cleanup(hCurlHandle);
2345
91
            goto retry;
2346
91
        }
2347
2348
12.6k
        if (response_code >= 400 && szCurlErrBuf[0] != '\0')
2349
0
        {
2350
0
            if (strcmp(szCurlErrBuf, "Couldn't use REST") == 0)
2351
0
                CPLError(
2352
0
                    CE_Failure, CPLE_AppDefined,
2353
0
                    "%d: %s, Range downloading not supported by this server!",
2354
0
                    static_cast<int>(response_code), szCurlErrBuf);
2355
0
            else
2356
0
                CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
2357
0
                         static_cast<int>(response_code), szCurlErrBuf);
2358
0
        }
2359
12.6k
        else if (response_code == 416) /* Range Not Satisfiable */
2360
0
        {
2361
0
            if (sWriteFuncData.pBuffer)
2362
0
            {
2363
0
                CPLError(
2364
0
                    CE_Failure, CPLE_AppDefined,
2365
0
                    "%d: Range downloading not supported by this server: %s",
2366
0
                    static_cast<int>(response_code), sWriteFuncData.pBuffer);
2367
0
            }
2368
0
            else
2369
0
            {
2370
0
                CPLError(CE_Failure, CPLE_AppDefined,
2371
0
                         "%d: Range downloading not supported by this server",
2372
0
                         static_cast<int>(response_code));
2373
0
            }
2374
0
        }
2375
12.6k
        if (!oFileProp.bHasComputedFileSize && startOffset == 0)
2376
12.6k
        {
2377
12.6k
            oFileProp.bHasComputedFileSize = true;
2378
12.6k
            oFileProp.fileSize = 0;
2379
12.6k
            oFileProp.eExists = EXIST_NO;
2380
12.6k
            poFS->SetCachedFileProp(m_pszURL, oFileProp);
2381
12.6k
        }
2382
12.6k
        CPLFree(sWriteFuncData.pBuffer);
2383
12.6k
        CPLFree(sWriteFuncHeaderData.pBuffer);
2384
12.6k
        curl_easy_cleanup(hCurlHandle);
2385
12.6k
        return std::string();
2386
12.7k
    }
2387
2388
0
    if (!oFileProp.bHasComputedFileSize && sWriteFuncHeaderData.pBuffer)
2389
0
    {
2390
        // Try to retrieve the filesize from the HTTP headers
2391
        // if in the form: "Content-Range: bytes x-y/filesize".
2392
0
        char *pszContentRange =
2393
0
            strstr(sWriteFuncHeaderData.pBuffer, "Content-Range: bytes ");
2394
0
        if (pszContentRange == nullptr)
2395
0
            pszContentRange =
2396
0
                strstr(sWriteFuncHeaderData.pBuffer, "content-range: bytes ");
2397
0
        if (pszContentRange)
2398
0
        {
2399
0
            char *pszEOL = strchr(pszContentRange, '\n');
2400
0
            if (pszEOL)
2401
0
            {
2402
0
                *pszEOL = 0;
2403
0
                pszEOL = strchr(pszContentRange, '\r');
2404
0
                if (pszEOL)
2405
0
                    *pszEOL = 0;
2406
0
                char *pszSlash = strchr(pszContentRange, '/');
2407
0
                if (pszSlash)
2408
0
                {
2409
0
                    pszSlash++;
2410
0
                    oFileProp.fileSize = CPLScanUIntBig(
2411
0
                        pszSlash, static_cast<int>(strlen(pszSlash)));
2412
0
                }
2413
0
            }
2414
0
        }
2415
0
        else if (STARTS_WITH(m_pszURL, "ftp"))
2416
0
        {
2417
            // Parse 213 answer for FTP protocol.
2418
0
            char *pszSize = strstr(sWriteFuncHeaderData.pBuffer, "213 ");
2419
0
            if (pszSize)
2420
0
            {
2421
0
                pszSize += 4;
2422
0
                char *pszEOL = strchr(pszSize, '\n');
2423
0
                if (pszEOL)
2424
0
                {
2425
0
                    *pszEOL = 0;
2426
0
                    pszEOL = strchr(pszSize, '\r');
2427
0
                    if (pszEOL)
2428
0
                        *pszEOL = 0;
2429
2430
0
                    oFileProp.fileSize = CPLScanUIntBig(
2431
0
                        pszSize, static_cast<int>(strlen(pszSize)));
2432
0
                }
2433
0
            }
2434
0
        }
2435
2436
0
        if (oFileProp.fileSize != 0)
2437
0
        {
2438
0
            oFileProp.eExists = EXIST_YES;
2439
2440
            if constexpr (ENABLE_DEBUG)
2441
0
            {
2442
0
                CPLDebug(poFS->GetDebugKey(),
2443
0
                         "GetFileSize(%s)=" CPL_FRMT_GUIB "  response_code=%d",
2444
0
                         m_pszURL, oFileProp.fileSize,
2445
0
                         static_cast<int>(response_code));
2446
0
            }
2447
2448
0
            oFileProp.bHasComputedFileSize = true;
2449
0
            poFS->SetCachedFileProp(m_pszURL, oFileProp);
2450
0
        }
2451
0
    }
2452
2453
0
    DownloadRegionPostProcess(startOffset, nBlocks, sWriteFuncData.pBuffer,
2454
0
                              sWriteFuncData.nSize);
2455
2456
0
    std::string osRet;
2457
0
    osRet.assign(sWriteFuncData.pBuffer, sWriteFuncData.nSize);
2458
2459
    // Notify that the download of the current region is finished
2460
0
    currentDownload.SetData(osRet);
2461
2462
0
    CPLFree(sWriteFuncData.pBuffer);
2463
0
    CPLFree(sWriteFuncHeaderData.pBuffer);
2464
0
    curl_easy_cleanup(hCurlHandle);
2465
2466
0
    return osRet;
2467
12.7k
}
2468
2469
/************************************************************************/
2470
/*                         UpdateRedirectInfo()                         */
2471
/************************************************************************/
2472
2473
void VSICurlHandle::UpdateRedirectInfo(
2474
    CURL *hCurlHandle, const WriteFuncStruct &sWriteFuncHeaderData)
2475
12.7k
{
2476
12.7k
    std::string osEffectiveURL;
2477
12.7k
    {
2478
12.7k
        char *pszEffectiveURL = nullptr;
2479
12.7k
        curl_easy_getinfo(hCurlHandle, CURLINFO_EFFECTIVE_URL,
2480
12.7k
                          &pszEffectiveURL);
2481
12.7k
        if (pszEffectiveURL)
2482
12.7k
            osEffectiveURL = pszEffectiveURL;
2483
12.7k
    }
2484
2485
12.7k
    if (!oFileProp.bS3LikeRedirect && !osEffectiveURL.empty() &&
2486
12.7k
        strstr(osEffectiveURL.c_str(), m_pszURL) == nullptr)
2487
325
    {
2488
325
        CPLDebug(poFS->GetDebugKey(), "Effective URL: %s",
2489
325
                 osEffectiveURL.c_str());
2490
2491
325
        long response_code = 0;
2492
325
        curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
2493
325
        if (response_code >= 200 && response_code < 300 &&
2494
0
            sWriteFuncHeaderData.nTimestampDate > 0 &&
2495
0
            VSICurlIsS3LikeSignedURL(osEffectiveURL.c_str()) &&
2496
0
            !VSICurlIsS3LikeSignedURL(m_pszURL) &&
2497
0
            CPLTestBool(
2498
0
                CPLGetConfigOption("CPL_VSIL_CURL_USE_S3_REDIRECT", "TRUE")))
2499
0
        {
2500
0
            GIntBig nExpireTimestamp =
2501
0
                VSICurlGetExpiresFromS3LikeSignedURL(osEffectiveURL.c_str());
2502
0
            if (nExpireTimestamp > sWriteFuncHeaderData.nTimestampDate + 10)
2503
0
            {
2504
0
                const int nValidity = static_cast<int>(
2505
0
                    nExpireTimestamp - sWriteFuncHeaderData.nTimestampDate);
2506
0
                CPLDebug(poFS->GetDebugKey(),
2507
0
                         "Will use redirect URL for the next %d seconds",
2508
0
                         nValidity);
2509
                // As our local clock might not be in sync with server clock,
2510
                // figure out the expiration timestamp in local time.
2511
0
                oFileProp.bS3LikeRedirect = true;
2512
0
                oFileProp.nExpireTimestampLocal = time(nullptr) + nValidity;
2513
0
                oFileProp.osRedirectURL = std::move(osEffectiveURL);
2514
0
                poFS->SetCachedFileProp(m_pszURL, oFileProp);
2515
0
            }
2516
0
        }
2517
325
    }
2518
12.7k
}
2519
2520
/************************************************************************/
2521
/*                     DownloadRegionPostProcess()                      */
2522
/************************************************************************/
2523
2524
void VSICurlHandle::DownloadRegionPostProcess(const vsi_l_offset startOffset,
2525
                                              const int nBlocks,
2526
                                              const char *pBuffer, size_t nSize)
2527
0
{
2528
0
    const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
2529
0
    lastDownloadedOffset = startOffset + static_cast<vsi_l_offset>(nBlocks) *
2530
0
                                             knDOWNLOAD_CHUNK_SIZE;
2531
2532
0
    if (nSize > static_cast<size_t>(nBlocks) * knDOWNLOAD_CHUNK_SIZE)
2533
0
    {
2534
        if constexpr (ENABLE_DEBUG)
2535
0
        {
2536
0
            CPLDebug(
2537
0
                poFS->GetDebugKey(),
2538
0
                "Got more data than expected : %u instead of %u",
2539
0
                static_cast<unsigned int>(nSize),
2540
0
                static_cast<unsigned int>(nBlocks * knDOWNLOAD_CHUNK_SIZE));
2541
0
        }
2542
0
    }
2543
2544
0
    vsi_l_offset l_startOffset = startOffset;
2545
0
    while (nSize > 0)
2546
0
    {
2547
#if DEBUG_VERBOSE
2548
        if constexpr (ENABLE_DEBUG)
2549
        {
2550
            CPLDebug(poFS->GetDebugKey(), "Add region %u - %u",
2551
                     static_cast<unsigned int>(startOffset),
2552
                     static_cast<unsigned int>(std::min(
2553
                         static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE), nSize)));
2554
        }
2555
#endif
2556
0
        const size_t nChunkSize =
2557
0
            std::min(static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE), nSize);
2558
0
        poFS->AddRegion(m_pszURL, l_startOffset, nChunkSize, pBuffer);
2559
0
        l_startOffset += nChunkSize;
2560
0
        pBuffer += nChunkSize;
2561
0
        nSize -= nChunkSize;
2562
0
    }
2563
0
}
2564
2565
/************************************************************************/
2566
/*                                Read()                                */
2567
/************************************************************************/
2568
2569
size_t VSICurlHandle::Read(void *const pBufferIn, size_t const nBytes)
2570
43.3k
{
2571
43.3k
    NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
2572
43.3k
    NetworkStatisticsFile oContextFile(m_osFilename.c_str());
2573
43.3k
    NetworkStatisticsAction oContextAction("Read");
2574
2575
43.3k
    size_t nBufferRequestSize = nBytes;
2576
43.3k
    if (nBufferRequestSize == 0)
2577
18.5k
        return 0;
2578
2579
24.7k
    void *pBuffer = pBufferIn;
2580
2581
#if DEBUG_VERBOSE
2582
    CPLDebug(poFS->GetDebugKey(), "offset=%d, size=%d",
2583
             static_cast<int>(curOffset), static_cast<int>(nBufferRequestSize));
2584
#endif
2585
2586
24.7k
    vsi_l_offset iterOffset = curOffset;
2587
24.7k
    const int knMAX_REGIONS = GetMaxRegions();
2588
24.7k
    const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
2589
24.7k
    while (nBufferRequestSize)
2590
24.7k
    {
2591
        // Don't try to read after end of file.
2592
24.7k
        poFS->GetCachedFileProp(m_pszURL, oFileProp);
2593
24.7k
        if (oFileProp.bHasComputedFileSize && iterOffset >= oFileProp.fileSize)
2594
11.6k
        {
2595
11.6k
            if (iterOffset == curOffset)
2596
11.6k
            {
2597
11.6k
                CPLDebug(poFS->GetDebugKey(),
2598
11.6k
                         "Request at offset " CPL_FRMT_GUIB
2599
11.6k
                         ", after end of file",
2600
11.6k
                         iterOffset);
2601
11.6k
            }
2602
11.6k
            break;
2603
11.6k
        }
2604
2605
13.1k
        const vsi_l_offset nOffsetToDownload =
2606
13.1k
            (iterOffset / knDOWNLOAD_CHUNK_SIZE) * knDOWNLOAD_CHUNK_SIZE;
2607
13.1k
        std::string osRegion;
2608
13.1k
        std::shared_ptr<std::string> psRegion =
2609
13.1k
            poFS->GetRegion(m_pszURL, nOffsetToDownload);
2610
13.1k
        if (psRegion != nullptr)
2611
0
        {
2612
0
            osRegion = *psRegion;
2613
0
        }
2614
13.1k
        else
2615
13.1k
        {
2616
13.1k
            if (nOffsetToDownload == lastDownloadedOffset)
2617
0
            {
2618
                // In case of consecutive reads (of small size), we use a
2619
                // heuristic that we will read the file sequentially, so
2620
                // we double the requested size to decrease the number of
2621
                // client/server roundtrips.
2622
0
                constexpr int MAX_CHUNK_SIZE_INCREASE_FACTOR = 128;
2623
0
                if (nBlocksToDownload < MAX_CHUNK_SIZE_INCREASE_FACTOR)
2624
0
                    nBlocksToDownload *= 2;
2625
0
            }
2626
13.1k
            else
2627
13.1k
            {
2628
                // Random reads. Cancel the above heuristics.
2629
13.1k
                nBlocksToDownload = 1;
2630
13.1k
            }
2631
2632
            // Ensure that we will request at least the number of blocks
2633
            // to satisfy the remaining buffer size to read.
2634
13.1k
            const vsi_l_offset nEndOffsetToDownload =
2635
13.1k
                ((iterOffset + nBufferRequestSize + knDOWNLOAD_CHUNK_SIZE - 1) /
2636
13.1k
                 knDOWNLOAD_CHUNK_SIZE) *
2637
13.1k
                knDOWNLOAD_CHUNK_SIZE;
2638
13.1k
            const int nMinBlocksToDownload =
2639
13.1k
                static_cast<int>((nEndOffsetToDownload - nOffsetToDownload) /
2640
13.1k
                                 knDOWNLOAD_CHUNK_SIZE);
2641
13.1k
            if (nBlocksToDownload < nMinBlocksToDownload)
2642
74
                nBlocksToDownload = nMinBlocksToDownload;
2643
2644
            // Avoid reading already cached data.
2645
            // Note: this might get evicted if concurrent reads are done, but
2646
            // this should not cause bugs. Just missed optimization.
2647
13.3k
            for (int i = 1; i < nBlocksToDownload; i++)
2648
240
            {
2649
240
                if (poFS->GetRegion(m_pszURL, nOffsetToDownload +
2650
240
                                                  static_cast<vsi_l_offset>(i) *
2651
240
                                                      knDOWNLOAD_CHUNK_SIZE) !=
2652
240
                    nullptr)
2653
0
                {
2654
0
                    nBlocksToDownload = i;
2655
0
                    break;
2656
0
                }
2657
240
            }
2658
2659
            // We can't download more than knMAX_REGIONS chunks at a time,
2660
            // otherwise the cache will not be big enough to store them and
2661
            // copy their content to the target buffer.
2662
13.1k
            if (nBlocksToDownload > knMAX_REGIONS)
2663
0
                nBlocksToDownload = knMAX_REGIONS;
2664
2665
13.1k
            osRegion = DownloadRegion(nOffsetToDownload, nBlocksToDownload);
2666
13.1k
            if (osRegion.empty())
2667
13.1k
            {
2668
13.1k
                if (!bInterrupted)
2669
13.1k
                    bError = true;
2670
13.1k
                return 0;
2671
13.1k
            }
2672
13.1k
        }
2673
2674
0
        const vsi_l_offset nRegionOffset = iterOffset - nOffsetToDownload;
2675
0
        if (osRegion.size() < nRegionOffset)
2676
0
        {
2677
0
            if (iterOffset == curOffset)
2678
0
            {
2679
0
                CPLDebug(poFS->GetDebugKey(),
2680
0
                         "Request at offset " CPL_FRMT_GUIB
2681
0
                         ", after end of file",
2682
0
                         iterOffset);
2683
0
            }
2684
0
            break;
2685
0
        }
2686
2687
0
        const int nToCopy = static_cast<int>(
2688
0
            std::min(static_cast<vsi_l_offset>(nBufferRequestSize),
2689
0
                     osRegion.size() - nRegionOffset));
2690
0
        memcpy(pBuffer, osRegion.data() + nRegionOffset, nToCopy);
2691
0
        pBuffer = static_cast<char *>(pBuffer) + nToCopy;
2692
0
        iterOffset += nToCopy;
2693
0
        nBufferRequestSize -= nToCopy;
2694
0
        if (osRegion.size() < static_cast<size_t>(knDOWNLOAD_CHUNK_SIZE) &&
2695
0
            nBufferRequestSize != 0)
2696
0
        {
2697
0
            break;
2698
0
        }
2699
0
    }
2700
2701
11.6k
    const size_t ret = static_cast<size_t>(iterOffset - curOffset);
2702
11.6k
    if (ret != nBytes)
2703
11.6k
        bEOF = true;
2704
2705
11.6k
    curOffset = iterOffset;
2706
2707
11.6k
    return ret;
2708
24.7k
}
2709
2710
/************************************************************************/
2711
/*                           ReadMultiRange()                           */
2712
/************************************************************************/
2713
2714
int VSICurlHandle::ReadMultiRange(int const nRanges, void **const ppData,
2715
                                  const vsi_l_offset *const panOffsets,
2716
                                  const size_t *const panSizes)
2717
0
{
2718
0
    if (bInterrupted && bStopOnInterruptUntilUninstall)
2719
0
        return FALSE;
2720
2721
0
    poFS->GetCachedFileProp(m_pszURL, oFileProp);
2722
0
    if (oFileProp.eExists == EXIST_NO)
2723
0
        return -1;
2724
2725
0
    NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
2726
0
    NetworkStatisticsFile oContextFile(m_osFilename.c_str());
2727
0
    NetworkStatisticsAction oContextAction("ReadMultiRange");
2728
2729
0
    const char *pszMultiRangeStrategy =
2730
0
        CPLGetConfigOption("GDAL_HTTP_MULTIRANGE", "");
2731
0
    if (EQUAL(pszMultiRangeStrategy, "SINGLE_GET"))
2732
0
    {
2733
        // Just in case someone needs it, but the interest of this mode is
2734
        // rather dubious now. We could probably remove it
2735
0
        return ReadMultiRangeSingleGet(nRanges, ppData, panOffsets, panSizes);
2736
0
    }
2737
0
    else if (nRanges == 1 || EQUAL(pszMultiRangeStrategy, "SERIAL"))
2738
0
    {
2739
0
        return VSIVirtualHandle::ReadMultiRange(nRanges, ppData, panOffsets,
2740
0
                                                panSizes);
2741
0
    }
2742
2743
0
    UpdateQueryString();
2744
2745
0
    bool bHasExpired = false;
2746
2747
0
    CPLStringList aosHTTPOptions(m_aosHTTPOptions);
2748
0
    std::string osURL(GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
2749
0
    if (bHasExpired)
2750
0
    {
2751
0
        return VSIVirtualHandle::ReadMultiRange(nRanges, ppData, panOffsets,
2752
0
                                                panSizes);
2753
0
    }
2754
2755
0
    CURLM *hMultiHandle = poFS->GetCurlMultiHandleFor(osURL);
2756
0
#ifdef CURLPIPE_MULTIPLEX
2757
    // Enable HTTP/2 multiplexing (ignored if an older version of HTTP is
2758
    // used)
2759
    // Not that this does not enable HTTP/1.1 pipeling, which is not
2760
    // recommended for example by Google Cloud Storage.
2761
    // For HTTP/1.1, parallel connections work better since you can get
2762
    // results out of order.
2763
0
    if (CPLTestBool(CPLGetConfigOption("GDAL_HTTP_MULTIPLEX", "YES")))
2764
0
    {
2765
0
        curl_multi_setopt(hMultiHandle, CURLMOPT_PIPELINING,
2766
0
                          CURLPIPE_MULTIPLEX);
2767
0
    }
2768
0
#endif
2769
2770
0
    struct CurlErrBuffer
2771
0
    {
2772
0
        std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
2773
0
    };
2774
2775
    // Sort ranges by file offset so the merge loop below can coalesce
2776
    // adjacent ranges regardless of the order the caller passed them.
2777
    // The ppData buffer pointers travel with their offsets, so the
2778
    // distribute logic fills the correct caller buffers after reading.
2779
0
    std::vector<int> anSortOrder(nRanges);
2780
0
    std::iota(anSortOrder.begin(), anSortOrder.end(), 0);
2781
0
    std::sort(anSortOrder.begin(), anSortOrder.end(), [panOffsets](int a, int b)
2782
0
              { return panOffsets[a] < panOffsets[b]; });
2783
2784
0
    std::vector<void *> apSortedData(nRanges);
2785
0
    std::vector<vsi_l_offset> anSortedOffsets(nRanges);
2786
0
    std::vector<size_t> anSortedSizes(nRanges);
2787
0
    for (int i = 0; i < nRanges; ++i)
2788
0
    {
2789
0
        apSortedData[i] = ppData[anSortOrder[i]];
2790
0
        anSortedOffsets[i] = panOffsets[anSortOrder[i]];
2791
0
        anSortedSizes[i] = panSizes[anSortOrder[i]];
2792
0
    }
2793
2794
0
    const bool bMergeConsecutiveRanges = CPLTestBool(
2795
0
        CPLGetConfigOption("GDAL_HTTP_MERGE_CONSECUTIVE_RANGES", "TRUE"));
2796
2797
    // Build list of merged requests upfront, each with its own retry context
2798
0
    struct MergedRequest
2799
0
    {
2800
0
        int iFirstRange;
2801
0
        int iLastRange;
2802
0
        vsi_l_offset nStartOffset;
2803
0
        size_t nSize;
2804
0
        CPLHTTPRetryContext retryContext;
2805
0
        bool bToRetry = true;  // true initially to trigger first attempt
2806
2807
0
        MergedRequest(int first, int last, vsi_l_offset start, size_t size,
2808
0
                      const CPLHTTPRetryParameters &params)
2809
0
            : iFirstRange(first), iLastRange(last), nStartOffset(start),
2810
0
              nSize(size), retryContext(params)
2811
0
        {
2812
0
        }
2813
0
    };
2814
2815
0
    std::vector<MergedRequest> asMergedRequests;
2816
0
    for (int i = 0; i < nRanges;)
2817
0
    {
2818
0
        size_t nSize = 0;
2819
0
        int iNext = i;
2820
        // Identify consecutive ranges
2821
0
        while (bMergeConsecutiveRanges && iNext + 1 < nRanges &&
2822
0
               anSortedOffsets[iNext] + anSortedSizes[iNext] ==
2823
0
                   anSortedOffsets[iNext + 1])
2824
0
        {
2825
0
            nSize += anSortedSizes[iNext];
2826
0
            iNext++;
2827
0
        }
2828
0
        nSize += anSortedSizes[iNext];
2829
2830
0
        if (nSize == 0)
2831
0
        {
2832
0
            i = iNext + 1;
2833
0
            continue;
2834
0
        }
2835
2836
0
        asMergedRequests.emplace_back(i, iNext, anSortedOffsets[i], nSize,
2837
0
                                      m_oRetryParameters);
2838
0
        i = iNext + 1;
2839
0
    }
2840
2841
0
    if (asMergedRequests.empty())
2842
0
        return 0;
2843
2844
0
    int nRet = 0;
2845
0
    size_t nTotalDownloaded = 0;
2846
2847
    // Retry loop: re-issue only failed requests that are retryable
2848
0
    while (true)
2849
0
    {
2850
0
        const size_t nRequests = asMergedRequests.size();
2851
0
        std::vector<CURL *> aHandles(nRequests, nullptr);
2852
0
        std::vector<WriteFuncStruct> asWriteFuncData(nRequests);
2853
0
        std::vector<WriteFuncStruct> asWriteFuncHeaderData(nRequests);
2854
0
        std::vector<char *> apszRanges(nRequests, nullptr);
2855
0
        std::vector<struct curl_slist *> aHeaders(nRequests, nullptr);
2856
0
        std::vector<CurlErrBuffer> asCurlErrors(nRequests);
2857
2858
0
        bool bAnyHandle = false;
2859
0
        for (size_t iReq = 0; iReq < nRequests; iReq++)
2860
0
        {
2861
0
            if (!asMergedRequests[iReq].bToRetry)
2862
0
                continue;
2863
0
            asMergedRequests[iReq].bToRetry = false;
2864
2865
0
            CURL *hCurlHandle = curl_easy_init();
2866
0
            aHandles[iReq] = hCurlHandle;
2867
0
            bAnyHandle = true;
2868
2869
0
            struct curl_slist *headers = VSICurlSetOptions(
2870
0
                hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
2871
2872
0
            VSICURLInitWriteFuncStruct(&asWriteFuncData[iReq], this, pfnReadCbk,
2873
0
                                       pReadCbkUserData);
2874
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
2875
0
                                       &asWriteFuncData[iReq]);
2876
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
2877
0
                                       VSICurlHandleWriteFunc);
2878
2879
0
            VSICURLInitWriteFuncStruct(&asWriteFuncHeaderData[iReq], nullptr,
2880
0
                                       nullptr, nullptr);
2881
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
2882
0
                                       &asWriteFuncHeaderData[iReq]);
2883
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
2884
0
                                       VSICurlHandleWriteFunc);
2885
0
            asWriteFuncHeaderData[iReq].bIsHTTP = STARTS_WITH(m_pszURL, "http");
2886
0
            asWriteFuncHeaderData[iReq].nStartOffset =
2887
0
                asMergedRequests[iReq].nStartOffset;
2888
0
            asWriteFuncHeaderData[iReq].nEndOffset =
2889
0
                asMergedRequests[iReq].nStartOffset +
2890
0
                asMergedRequests[iReq].nSize - 1;
2891
2892
0
            char rangeStr[512] = {};
2893
0
            snprintf(rangeStr, sizeof(rangeStr),
2894
0
                     CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2895
0
                     asWriteFuncHeaderData[iReq].nStartOffset,
2896
0
                     asWriteFuncHeaderData[iReq].nEndOffset);
2897
2898
            if constexpr (ENABLE_DEBUG)
2899
0
            {
2900
0
                CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
2901
0
                         rangeStr, osURL.c_str());
2902
0
            }
2903
2904
0
            if (asWriteFuncHeaderData[iReq].bIsHTTP)
2905
0
            {
2906
                // So it gets included in Azure signature
2907
0
                char *pszRange =
2908
0
                    CPLStrdup(CPLSPrintf("Range: bytes=%s", rangeStr));
2909
0
                apszRanges[iReq] = pszRange;
2910
0
                headers = curl_slist_append(headers, pszRange);
2911
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
2912
0
            }
2913
0
            else
2914
0
            {
2915
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
2916
0
                                           rangeStr);
2917
0
            }
2918
2919
0
            asCurlErrors[iReq].szCurlErrBuf[0] = '\0';
2920
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
2921
0
                                       &asCurlErrors[iReq].szCurlErrBuf[0]);
2922
2923
0
            headers = GetCurlHeaders("GET", headers);
2924
0
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
2925
0
                                       headers);
2926
0
            aHeaders[iReq] = headers;
2927
0
            curl_multi_add_handle(hMultiHandle, hCurlHandle);
2928
0
        }
2929
2930
0
        if (bAnyHandle)
2931
0
        {
2932
0
            VSICURLMultiPerform(hMultiHandle);
2933
0
        }
2934
2935
        // Process results
2936
0
        bool bRetry = false;
2937
0
        double dfMaxDelay = 0.0;
2938
0
        for (size_t iReq = 0; iReq < nRequests; iReq++)
2939
0
        {
2940
0
            if (!aHandles[iReq])
2941
0
                continue;
2942
2943
0
            long response_code = 0;
2944
0
            curl_easy_getinfo(aHandles[iReq], CURLINFO_HTTP_CODE,
2945
0
                              &response_code);
2946
2947
0
            if (ENABLE_DEBUG && asCurlErrors[iReq].szCurlErrBuf[0] != '\0')
2948
0
            {
2949
0
                char rangeStr[512] = {};
2950
0
                snprintf(rangeStr, sizeof(rangeStr),
2951
0
                         CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2952
0
                         asWriteFuncHeaderData[iReq].nStartOffset,
2953
0
                         asWriteFuncHeaderData[iReq].nEndOffset);
2954
2955
0
                const char *pszErrorMsg = &asCurlErrors[iReq].szCurlErrBuf[0];
2956
0
                CPLDebug(poFS->GetDebugKey(),
2957
0
                         "ReadMultiRange(%s), %s: response_code=%d, msg=%s",
2958
0
                         osURL.c_str(), rangeStr,
2959
0
                         static_cast<int>(response_code), pszErrorMsg);
2960
0
            }
2961
2962
0
            if ((response_code != 206 && response_code != 225) ||
2963
0
                asWriteFuncHeaderData[iReq].nEndOffset + 1 !=
2964
0
                    asWriteFuncHeaderData[iReq].nStartOffset +
2965
0
                        asWriteFuncData[iReq].nSize)
2966
0
            {
2967
0
                char rangeStr[512] = {};
2968
0
                snprintf(rangeStr, sizeof(rangeStr),
2969
0
                         CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
2970
0
                         asWriteFuncHeaderData[iReq].nStartOffset,
2971
0
                         asWriteFuncHeaderData[iReq].nEndOffset);
2972
2973
                // Look if we should attempt a retry
2974
0
                if (asMergedRequests[iReq].retryContext.CanRetry(
2975
0
                        static_cast<int>(response_code),
2976
0
                        asWriteFuncData[iReq].pBuffer,
2977
0
                        &asCurlErrors[iReq].szCurlErrBuf[0]))
2978
0
                {
2979
0
                    CPLError(
2980
0
                        CE_Warning, CPLE_AppDefined,
2981
0
                        "HTTP error code for %s range %s: %d. "
2982
0
                        "Retrying again in %.1f secs",
2983
0
                        osURL.c_str(), rangeStr,
2984
0
                        static_cast<int>(response_code),
2985
0
                        asMergedRequests[iReq].retryContext.GetCurrentDelay());
2986
0
                    dfMaxDelay = std::max(
2987
0
                        dfMaxDelay,
2988
0
                        asMergedRequests[iReq].retryContext.GetCurrentDelay());
2989
0
                    asMergedRequests[iReq].bToRetry = true;
2990
0
                    bRetry = true;
2991
0
                }
2992
0
                else
2993
0
                {
2994
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2995
0
                             "Request for %s failed with response_code=%ld",
2996
0
                             rangeStr, response_code);
2997
0
                    nRet = -1;
2998
0
                }
2999
0
            }
3000
0
            else if (nRet == 0)
3001
0
            {
3002
0
                size_t nOffset = 0;
3003
0
                size_t nRemainingSize = asWriteFuncData[iReq].nSize;
3004
0
                nTotalDownloaded += nRemainingSize;
3005
0
                for (int iRange = asMergedRequests[iReq].iFirstRange;
3006
0
                     iRange <= asMergedRequests[iReq].iLastRange; iRange++)
3007
0
                {
3008
0
                    if (nRemainingSize < anSortedSizes[iRange])
3009
0
                    {
3010
0
                        nRet = -1;
3011
0
                        break;
3012
0
                    }
3013
3014
0
                    if (anSortedSizes[iRange] > 0)
3015
0
                    {
3016
0
                        memcpy(apSortedData[iRange],
3017
0
                               asWriteFuncData[iReq].pBuffer + nOffset,
3018
0
                               anSortedSizes[iRange]);
3019
0
                    }
3020
0
                    nOffset += anSortedSizes[iRange];
3021
0
                    nRemainingSize -= anSortedSizes[iRange];
3022
0
                }
3023
0
            }
3024
3025
0
            curl_multi_remove_handle(hMultiHandle, aHandles[iReq]);
3026
0
            VSICURLResetHeaderAndWriterFunctions(aHandles[iReq]);
3027
0
            curl_easy_cleanup(aHandles[iReq]);
3028
0
            CPLFree(apszRanges[iReq]);
3029
0
            CPLFree(asWriteFuncData[iReq].pBuffer);
3030
0
            CPLFree(asWriteFuncHeaderData[iReq].pBuffer);
3031
0
            if (aHeaders[iReq])
3032
0
                curl_slist_free_all(aHeaders[iReq]);
3033
0
        }
3034
3035
0
        if (!bRetry || nRet != 0)
3036
0
            break;
3037
0
        CPLSleep(dfMaxDelay);
3038
0
    }
3039
3040
0
    NetworkStatisticsLogger::LogGET(nTotalDownloaded);
3041
3042
    if constexpr (ENABLE_DEBUG)
3043
0
    {
3044
0
        CPLDebug(poFS->GetDebugKey(), "Download completed");
3045
0
    }
3046
3047
0
    return nRet;
3048
0
}
3049
3050
/************************************************************************/
3051
/*                      ReadMultiRangeSingleGet()                       */
3052
/************************************************************************/
3053
3054
// TODO: the interest of this mode is rather dubious now. We could probably
3055
// remove it
3056
int VSICurlHandle::ReadMultiRangeSingleGet(int const nRanges,
3057
                                           void **const ppData,
3058
                                           const vsi_l_offset *const panOffsets,
3059
                                           const size_t *const panSizes)
3060
0
{
3061
0
    std::string osRanges;
3062
0
    std::string osFirstRange;
3063
0
    std::string osLastRange;
3064
0
    int nMergedRanges = 0;
3065
0
    vsi_l_offset nTotalReqSize = 0;
3066
0
    for (int i = 0; i < nRanges; i++)
3067
0
    {
3068
0
        std::string osCurRange;
3069
0
        if (i != 0)
3070
0
            osRanges.append(",");
3071
0
        osCurRange = CPLSPrintf(CPL_FRMT_GUIB "-", panOffsets[i]);
3072
0
        while (i + 1 < nRanges &&
3073
0
               panOffsets[i] + panSizes[i] == panOffsets[i + 1])
3074
0
        {
3075
0
            nTotalReqSize += panSizes[i];
3076
0
            i++;
3077
0
        }
3078
0
        nTotalReqSize += panSizes[i];
3079
0
        osCurRange.append(
3080
0
            CPLSPrintf(CPL_FRMT_GUIB, panOffsets[i] + panSizes[i] - 1));
3081
0
        nMergedRanges++;
3082
3083
0
        osRanges += osCurRange;
3084
3085
0
        if (nMergedRanges == 1)
3086
0
            osFirstRange = osCurRange;
3087
0
        osLastRange = std::move(osCurRange);
3088
0
    }
3089
3090
0
    const char *pszMaxRanges =
3091
0
        CPLGetConfigOption("CPL_VSIL_CURL_MAX_RANGES", "250");
3092
0
    int nMaxRanges = atoi(pszMaxRanges);
3093
0
    if (nMaxRanges <= 0)
3094
0
        nMaxRanges = 250;
3095
0
    if (nMergedRanges > nMaxRanges)
3096
0
    {
3097
0
        const int nHalf = nRanges / 2;
3098
0
        const int nRet = ReadMultiRange(nHalf, ppData, panOffsets, panSizes);
3099
0
        if (nRet != 0)
3100
0
            return nRet;
3101
0
        return ReadMultiRange(nRanges - nHalf, ppData + nHalf,
3102
0
                              panOffsets + nHalf, panSizes + nHalf);
3103
0
    }
3104
3105
0
    CURLM *hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
3106
0
    CURL *hCurlHandle = curl_easy_init();
3107
3108
0
    struct curl_slist *headers =
3109
0
        VSICurlSetOptions(hCurlHandle, m_pszURL, m_aosHTTPOptions.List());
3110
3111
0
    WriteFuncStruct sWriteFuncData;
3112
0
    WriteFuncStruct sWriteFuncHeaderData;
3113
3114
0
    VSICURLInitWriteFuncStruct(&sWriteFuncData, this, pfnReadCbk,
3115
0
                               pReadCbkUserData);
3116
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
3117
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3118
0
                               VSICurlHandleWriteFunc);
3119
3120
0
    VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
3121
0
                               nullptr);
3122
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3123
0
                               &sWriteFuncHeaderData);
3124
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3125
0
                               VSICurlHandleWriteFunc);
3126
0
    sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
3127
0
    sWriteFuncHeaderData.bMultiRange = nMergedRanges > 1;
3128
0
    if (nMergedRanges == 1)
3129
0
    {
3130
0
        sWriteFuncHeaderData.nStartOffset = panOffsets[0];
3131
0
        sWriteFuncHeaderData.nEndOffset = panOffsets[0] + nTotalReqSize - 1;
3132
0
    }
3133
3134
    if constexpr (ENABLE_DEBUG)
3135
0
    {
3136
0
        if (nMergedRanges == 1)
3137
0
            CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
3138
0
                     osRanges.c_str(), m_pszURL);
3139
0
        else
3140
0
            CPLDebug(poFS->GetDebugKey(),
3141
0
                     "Downloading %s, ..., %s (" CPL_FRMT_GUIB " bytes, %s)...",
3142
0
                     osFirstRange.c_str(), osLastRange.c_str(),
3143
0
                     static_cast<GUIntBig>(nTotalReqSize), m_pszURL);
3144
0
    }
3145
3146
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, osRanges.c_str());
3147
3148
0
    char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
3149
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
3150
3151
0
    headers = GetCurlHeaders("GET", headers);
3152
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
3153
3154
0
    VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
3155
3156
0
    VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
3157
3158
0
    curl_slist_free_all(headers);
3159
3160
0
    NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
3161
3162
0
    if (sWriteFuncData.bInterrupted)
3163
0
    {
3164
0
        bInterrupted = true;
3165
3166
0
        CPLFree(sWriteFuncData.pBuffer);
3167
0
        CPLFree(sWriteFuncHeaderData.pBuffer);
3168
0
        curl_easy_cleanup(hCurlHandle);
3169
3170
0
        return -1;
3171
0
    }
3172
3173
0
    long response_code = 0;
3174
0
    curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
3175
3176
0
    if ((response_code != 200 && response_code != 206 && response_code != 225 &&
3177
0
         response_code != 226 && response_code != 426) ||
3178
0
        sWriteFuncHeaderData.bError)
3179
0
    {
3180
0
        if (response_code >= 400 && szCurlErrBuf[0] != '\0')
3181
0
        {
3182
0
            if (strcmp(szCurlErrBuf, "Couldn't use REST") == 0)
3183
0
                CPLError(
3184
0
                    CE_Failure, CPLE_AppDefined,
3185
0
                    "%d: %s, Range downloading not supported by this server!",
3186
0
                    static_cast<int>(response_code), szCurlErrBuf);
3187
0
            else
3188
0
                CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
3189
0
                         static_cast<int>(response_code), szCurlErrBuf);
3190
0
        }
3191
        /*
3192
        if( !bHasComputedFileSize && startOffset == 0 )
3193
        {
3194
            cachedFileProp->bHasComputedFileSize = bHasComputedFileSize = true;
3195
            cachedFileProp->fileSize = fileSize = 0;
3196
            cachedFileProp->eExists = eExists = EXIST_NO;
3197
        }
3198
        */
3199
0
        CPLFree(sWriteFuncData.pBuffer);
3200
0
        CPLFree(sWriteFuncHeaderData.pBuffer);
3201
0
        curl_easy_cleanup(hCurlHandle);
3202
0
        return -1;
3203
0
    }
3204
3205
0
    char *pBuffer = sWriteFuncData.pBuffer;
3206
0
    size_t nSize = sWriteFuncData.nSize;
3207
3208
    // TODO(schwehr): Localize after removing gotos.
3209
0
    int nRet = -1;
3210
0
    char *pszBoundary;
3211
0
    std::string osBoundary;
3212
0
    char *pszNext = nullptr;
3213
0
    int iRange = 0;
3214
0
    int iPart = 0;
3215
0
    char *pszEOL = nullptr;
3216
3217
    /* -------------------------------------------------------------------- */
3218
    /*      No multipart if a single range has been requested               */
3219
    /* -------------------------------------------------------------------- */
3220
3221
0
    if (nMergedRanges == 1)
3222
0
    {
3223
0
        size_t nAccSize = 0;
3224
0
        if (static_cast<vsi_l_offset>(nSize) < nTotalReqSize)
3225
0
            goto end;
3226
3227
0
        for (int i = 0; i < nRanges; i++)
3228
0
        {
3229
0
            memcpy(ppData[i], pBuffer + nAccSize, panSizes[i]);
3230
0
            nAccSize += panSizes[i];
3231
0
        }
3232
3233
0
        nRet = 0;
3234
0
        goto end;
3235
0
    }
3236
3237
    /* -------------------------------------------------------------------- */
3238
    /*      Extract boundary name                                           */
3239
    /* -------------------------------------------------------------------- */
3240
3241
0
    pszBoundary = strstr(sWriteFuncHeaderData.pBuffer,
3242
0
                         "Content-Type: multipart/byteranges; boundary=");
3243
0
    if (pszBoundary == nullptr)
3244
0
    {
3245
0
        CPLError(CE_Failure, CPLE_AppDefined, "Could not find '%s'",
3246
0
                 "Content-Type: multipart/byteranges; boundary=");
3247
0
        goto end;
3248
0
    }
3249
3250
0
    pszBoundary += strlen("Content-Type: multipart/byteranges; boundary=");
3251
3252
0
    pszEOL = strchr(pszBoundary, '\r');
3253
0
    if (pszEOL)
3254
0
        *pszEOL = 0;
3255
0
    pszEOL = strchr(pszBoundary, '\n');
3256
0
    if (pszEOL)
3257
0
        *pszEOL = 0;
3258
3259
    /* Remove optional double-quote character around boundary name */
3260
0
    if (pszBoundary[0] == '"')
3261
0
    {
3262
0
        pszBoundary++;
3263
0
        char *pszLastDoubleQuote = strrchr(pszBoundary, '"');
3264
0
        if (pszLastDoubleQuote)
3265
0
            *pszLastDoubleQuote = 0;
3266
0
    }
3267
3268
0
    osBoundary = "--";
3269
0
    osBoundary += pszBoundary;
3270
3271
    /* -------------------------------------------------------------------- */
3272
    /*      Find the start of the first chunk.                              */
3273
    /* -------------------------------------------------------------------- */
3274
0
    pszNext = strstr(pBuffer, osBoundary.c_str());
3275
0
    if (pszNext == nullptr)
3276
0
    {
3277
0
        CPLError(CE_Failure, CPLE_AppDefined, "No parts found.");
3278
0
        goto end;
3279
0
    }
3280
3281
0
    pszNext += osBoundary.size();
3282
0
    while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
3283
0
        pszNext++;
3284
0
    if (*pszNext == '\r')
3285
0
        pszNext++;
3286
0
    if (*pszNext == '\n')
3287
0
        pszNext++;
3288
3289
    /* -------------------------------------------------------------------- */
3290
    /*      Loop over parts...                                              */
3291
    /* -------------------------------------------------------------------- */
3292
0
    while (iPart < nRanges)
3293
0
    {
3294
        /* --------------------------------------------------------------------
3295
         */
3296
        /*      Collect headers. */
3297
        /* --------------------------------------------------------------------
3298
         */
3299
0
        bool bExpectedRange = false;
3300
3301
0
        while (*pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0')
3302
0
        {
3303
0
            pszEOL = strstr(pszNext, "\n");
3304
3305
0
            if (pszEOL == nullptr)
3306
0
            {
3307
0
                CPLError(CE_Failure, CPLE_AppDefined,
3308
0
                         "Error while parsing multipart content (at line %d)",
3309
0
                         __LINE__);
3310
0
                goto end;
3311
0
            }
3312
3313
0
            *pszEOL = '\0';
3314
0
            bool bRestoreAntislashR = false;
3315
0
            if (pszEOL - pszNext > 1 && pszEOL[-1] == '\r')
3316
0
            {
3317
0
                bRestoreAntislashR = true;
3318
0
                pszEOL[-1] = '\0';
3319
0
            }
3320
3321
0
            if (STARTS_WITH_CI(pszNext, "Content-Range: bytes "))
3322
0
            {
3323
0
                bExpectedRange = true; /* FIXME */
3324
0
            }
3325
3326
0
            if (bRestoreAntislashR)
3327
0
                pszEOL[-1] = '\r';
3328
0
            *pszEOL = '\n';
3329
3330
0
            pszNext = pszEOL + 1;
3331
0
        }
3332
3333
0
        if (!bExpectedRange)
3334
0
        {
3335
0
            CPLError(CE_Failure, CPLE_AppDefined,
3336
0
                     "Error while parsing multipart content (at line %d)",
3337
0
                     __LINE__);
3338
0
            goto end;
3339
0
        }
3340
3341
0
        if (*pszNext == '\r')
3342
0
            pszNext++;
3343
0
        if (*pszNext == '\n')
3344
0
            pszNext++;
3345
3346
        /* --------------------------------------------------------------------
3347
         */
3348
        /*      Work out the data block size. */
3349
        /* --------------------------------------------------------------------
3350
         */
3351
0
        size_t nBytesAvail = nSize - (pszNext - pBuffer);
3352
3353
0
        while (true)
3354
0
        {
3355
0
            if (nBytesAvail < panSizes[iRange])
3356
0
            {
3357
0
                CPLError(CE_Failure, CPLE_AppDefined,
3358
0
                         "Error while parsing multipart content (at line %d)",
3359
0
                         __LINE__);
3360
0
                goto end;
3361
0
            }
3362
3363
0
            memcpy(ppData[iRange], pszNext, panSizes[iRange]);
3364
0
            pszNext += panSizes[iRange];
3365
0
            nBytesAvail -= panSizes[iRange];
3366
0
            if (iRange + 1 < nRanges &&
3367
0
                panOffsets[iRange] + panSizes[iRange] == panOffsets[iRange + 1])
3368
0
            {
3369
0
                iRange++;
3370
0
            }
3371
0
            else
3372
0
            {
3373
0
                break;
3374
0
            }
3375
0
        }
3376
3377
0
        iPart++;
3378
0
        iRange++;
3379
3380
0
        while (nBytesAvail > 0 &&
3381
0
               (*pszNext != '-' ||
3382
0
                strncmp(pszNext, osBoundary.c_str(), osBoundary.size()) != 0))
3383
0
        {
3384
0
            pszNext++;
3385
0
            nBytesAvail--;
3386
0
        }
3387
3388
0
        if (nBytesAvail == 0)
3389
0
        {
3390
0
            CPLError(CE_Failure, CPLE_AppDefined,
3391
0
                     "Error while parsing multipart content (at line %d)",
3392
0
                     __LINE__);
3393
0
            goto end;
3394
0
        }
3395
3396
0
        pszNext += osBoundary.size();
3397
0
        if (STARTS_WITH(pszNext, "--"))
3398
0
        {
3399
            // End of multipart.
3400
0
            break;
3401
0
        }
3402
3403
0
        if (*pszNext == '\r')
3404
0
            pszNext++;
3405
0
        if (*pszNext == '\n')
3406
0
            pszNext++;
3407
0
        else
3408
0
        {
3409
0
            CPLError(CE_Failure, CPLE_AppDefined,
3410
0
                     "Error while parsing multipart content (at line %d)",
3411
0
                     __LINE__);
3412
0
            goto end;
3413
0
        }
3414
0
    }
3415
3416
0
    if (iPart == nMergedRanges)
3417
0
        nRet = 0;
3418
0
    else
3419
0
        CPLError(CE_Failure, CPLE_AppDefined,
3420
0
                 "Got only %d parts, where %d were expected", iPart,
3421
0
                 nMergedRanges);
3422
3423
0
end:
3424
0
    CPLFree(sWriteFuncData.pBuffer);
3425
0
    CPLFree(sWriteFuncHeaderData.pBuffer);
3426
0
    curl_easy_cleanup(hCurlHandle);
3427
3428
0
    return nRet;
3429
0
}
3430
3431
/************************************************************************/
3432
/*                               PRead()                                */
3433
/************************************************************************/
3434
3435
size_t VSICurlHandle::PRead(void *pBuffer, size_t nSize,
3436
                            vsi_l_offset nOffset) const
3437
0
{
3438
    // Try to use AdviseRead ranges fetched asynchronously
3439
0
    if (!m_aoAdviseReadRanges.empty())
3440
0
    {
3441
0
        for (auto &poRange : m_aoAdviseReadRanges)
3442
0
        {
3443
0
            if (nOffset >= poRange->nStartOffset &&
3444
0
                nOffset + nSize <= poRange->nStartOffset + poRange->nSize)
3445
0
            {
3446
0
                {
3447
0
                    std::unique_lock<std::mutex> oLock(poRange->oMutex);
3448
                    // coverity[missing_lock:FALSE]
3449
0
                    while (!poRange->bDone)
3450
0
                    {
3451
0
                        poRange->oCV.wait(oLock);
3452
0
                    }
3453
0
                }
3454
0
                if (poRange->abyData.empty())
3455
0
                    return 0;
3456
3457
0
                auto nEndOffset =
3458
0
                    poRange->nStartOffset + poRange->abyData.size();
3459
0
                if (nOffset >= nEndOffset)
3460
0
                    return 0;
3461
0
                const size_t nToCopy = static_cast<size_t>(
3462
0
                    std::min<vsi_l_offset>(nSize, nEndOffset - nOffset));
3463
0
                memcpy(pBuffer,
3464
0
                       poRange->abyData.data() +
3465
0
                           static_cast<size_t>(nOffset - poRange->nStartOffset),
3466
0
                       nToCopy);
3467
0
                return nToCopy;
3468
0
            }
3469
0
        }
3470
0
    }
3471
3472
    // poFS has a global mutex
3473
0
    poFS->GetCachedFileProp(m_pszURL, oFileProp);
3474
0
    if (oFileProp.eExists == EXIST_NO)
3475
0
        return static_cast<size_t>(-1);
3476
3477
0
    NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
3478
0
    NetworkStatisticsFile oContextFile(m_osFilename.c_str());
3479
0
    NetworkStatisticsAction oContextAction("PRead");
3480
3481
0
    CPLStringList aosHTTPOptions(m_aosHTTPOptions);
3482
0
    std::string osURL;
3483
0
    {
3484
0
        std::lock_guard<std::mutex> oLock(m_oMutex);
3485
0
        UpdateQueryString();
3486
0
        bool bHasExpired;
3487
0
        osURL = GetRedirectURLIfValid(bHasExpired, aosHTTPOptions);
3488
0
    }
3489
3490
0
    CURL *hCurlHandle = curl_easy_init();
3491
3492
0
    struct curl_slist *headers =
3493
0
        VSICurlSetOptions(hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
3494
3495
0
    WriteFuncStruct sWriteFuncData;
3496
0
    VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
3497
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
3498
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3499
0
                               VSICurlHandleWriteFunc);
3500
3501
0
    WriteFuncStruct sWriteFuncHeaderData;
3502
0
    VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
3503
0
                               nullptr);
3504
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3505
0
                               &sWriteFuncHeaderData);
3506
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3507
0
                               VSICurlHandleWriteFunc);
3508
0
    sWriteFuncHeaderData.bIsHTTP = STARTS_WITH(m_pszURL, "http");
3509
0
    sWriteFuncHeaderData.nStartOffset = nOffset;
3510
3511
0
    sWriteFuncHeaderData.nEndOffset = nOffset + nSize - 1;
3512
3513
0
    char rangeStr[512] = {};
3514
0
    snprintf(rangeStr, sizeof(rangeStr), CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3515
0
             sWriteFuncHeaderData.nStartOffset,
3516
0
             sWriteFuncHeaderData.nEndOffset);
3517
3518
    if constexpr (ENABLE_DEBUG)
3519
0
    {
3520
0
        CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...", rangeStr,
3521
0
                 osURL.c_str());
3522
0
    }
3523
3524
0
    std::string osHeaderRange;
3525
0
    if (sWriteFuncHeaderData.bIsHTTP)
3526
0
    {
3527
0
        osHeaderRange = CPLSPrintf("Range: bytes=%s", rangeStr);
3528
        // So it gets included in Azure signature
3529
0
        headers = curl_slist_append(headers, osHeaderRange.data());
3530
0
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
3531
0
    }
3532
0
    else
3533
0
    {
3534
0
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, rangeStr);
3535
0
    }
3536
3537
0
    std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
3538
0
    szCurlErrBuf[0] = '\0';
3539
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
3540
0
                               &szCurlErrBuf[0]);
3541
3542
0
    {
3543
0
        std::lock_guard<std::mutex> oLock(m_oMutex);
3544
0
        headers =
3545
0
            const_cast<VSICurlHandle *>(this)->GetCurlHeaders("GET", headers);
3546
0
    }
3547
0
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
3548
3549
0
    CURLM *hMultiHandle = poFS->GetCurlMultiHandleFor(osURL);
3550
0
    VSICURLMultiPerform(hMultiHandle, hCurlHandle, &m_bInterrupt);
3551
3552
0
    {
3553
0
        std::lock_guard<std::mutex> oLock(m_oMutex);
3554
0
        const_cast<VSICurlHandle *>(this)->UpdateRedirectInfo(
3555
0
            hCurlHandle, sWriteFuncHeaderData);
3556
0
    }
3557
3558
0
    long response_code = 0;
3559
0
    curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
3560
3561
0
    if (ENABLE_DEBUG && szCurlErrBuf[0] != '\0')
3562
0
    {
3563
0
        const char *pszErrorMsg = &szCurlErrBuf[0];
3564
0
        CPLDebug(poFS->GetDebugKey(), "PRead(%s), %s: response_code=%d, msg=%s",
3565
0
                 osURL.c_str(), rangeStr, static_cast<int>(response_code),
3566
0
                 pszErrorMsg);
3567
0
    }
3568
3569
0
    size_t nRet;
3570
0
    if ((response_code != 206 && response_code != 225) ||
3571
0
        sWriteFuncData.nSize == 0)
3572
0
    {
3573
0
        if (!m_bInterrupt)
3574
0
        {
3575
0
            CPLDebug(poFS->GetDebugKey(),
3576
0
                     "Request for %s failed with response_code=%ld", rangeStr,
3577
0
                     response_code);
3578
0
        }
3579
0
        nRet = static_cast<size_t>(-1);
3580
0
    }
3581
0
    else
3582
0
    {
3583
0
        nRet = std::min(sWriteFuncData.nSize, nSize);
3584
0
        if (nRet > 0)
3585
0
            memcpy(pBuffer, sWriteFuncData.pBuffer, nRet);
3586
0
    }
3587
3588
0
    VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
3589
0
    curl_easy_cleanup(hCurlHandle);
3590
0
    CPLFree(sWriteFuncData.pBuffer);
3591
0
    CPLFree(sWriteFuncHeaderData.pBuffer);
3592
0
    curl_slist_free_all(headers);
3593
3594
0
    NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
3595
3596
#if 0
3597
    if( ENABLE_DEBUG )
3598
        CPLDebug(poFS->GetDebugKey(), "Download completed");
3599
#endif
3600
3601
0
    return nRet;
3602
0
}
3603
3604
/************************************************************************/
3605
/*                    GetAdviseReadTotalBytesLimit()                    */
3606
/************************************************************************/
3607
3608
size_t VSICurlHandle::GetAdviseReadTotalBytesLimit() const
3609
0
{
3610
0
    return static_cast<size_t>(std::min<unsigned long long>(
3611
0
        std::numeric_limits<size_t>::max(),
3612
        // 100 MB
3613
0
        std::strtoull(
3614
0
            CPLGetConfigOption("CPL_VSIL_CURL_ADVISE_READ_TOTAL_BYTES_LIMIT",
3615
0
                               "104857600"),
3616
0
            nullptr, 10)));
3617
0
}
3618
3619
/************************************************************************/
3620
/*                          VSICURLMultiInit()                          */
3621
/************************************************************************/
3622
3623
static CURLM *VSICURLMultiInit()
3624
43
{
3625
43
    CURLM *hCurlMultiHandle = curl_multi_init();
3626
3627
43
    if (const char *pszMAXCONNECTS =
3628
43
            CPLGetConfigOption("GDAL_HTTP_MAX_CACHED_CONNECTIONS", nullptr))
3629
0
    {
3630
0
        curl_multi_setopt(hCurlMultiHandle, CURLMOPT_MAXCONNECTS,
3631
0
                          atoi(pszMAXCONNECTS));
3632
0
    }
3633
3634
43
    if (const char *pszMAX_TOTAL_CONNECTIONS =
3635
43
            CPLGetConfigOption("GDAL_HTTP_MAX_TOTAL_CONNECTIONS", nullptr))
3636
0
    {
3637
0
        curl_multi_setopt(hCurlMultiHandle, CURLMOPT_MAX_TOTAL_CONNECTIONS,
3638
0
                          atoi(pszMAX_TOTAL_CONNECTIONS));
3639
0
    }
3640
3641
43
    return hCurlMultiHandle;
3642
43
}
3643
3644
/************************************************************************/
3645
/*                             AdviseRead()                             */
3646
/************************************************************************/
3647
3648
void VSICurlHandle::AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
3649
                               const size_t *panSizes)
3650
0
{
3651
0
    if (!CPLTestBool(
3652
0
            CPLGetConfigOption("GDAL_HTTP_ENABLE_ADVISE_READ", "TRUE")))
3653
0
        return;
3654
3655
0
    if (m_oThreadAdviseRead.joinable())
3656
0
    {
3657
0
        m_oThreadAdviseRead.join();
3658
0
    }
3659
3660
    // Give up if we need to allocate too much memory
3661
0
    vsi_l_offset nMaxSize = 0;
3662
0
    const size_t nLimit = GetAdviseReadTotalBytesLimit();
3663
0
    for (int i = 0; i < nRanges; ++i)
3664
0
    {
3665
0
        if (panSizes[i] > nLimit - nMaxSize)
3666
0
        {
3667
0
            CPLDebug(poFS->GetDebugKey(),
3668
0
                     "Trying to request too many bytes in AdviseRead()");
3669
0
            return;
3670
0
        }
3671
0
        nMaxSize += panSizes[i];
3672
0
    }
3673
3674
0
    UpdateQueryString();
3675
3676
0
    bool bHasExpired = false;
3677
0
    CPLStringList aosHTTPOptions(m_aosHTTPOptions);
3678
0
    const std::string l_osURL(
3679
0
        GetRedirectURLIfValid(bHasExpired, aosHTTPOptions));
3680
0
    if (bHasExpired)
3681
0
    {
3682
0
        return;
3683
0
    }
3684
3685
0
    const bool bMergeConsecutiveRanges = CPLTestBool(
3686
0
        CPLGetConfigOption("GDAL_HTTP_MERGE_CONSECUTIVE_RANGES", "TRUE"));
3687
3688
0
    try
3689
0
    {
3690
0
        m_aoAdviseReadRanges.clear();
3691
0
        m_aoAdviseReadRanges.reserve(nRanges);
3692
0
        for (int i = 0; i < nRanges;)
3693
0
        {
3694
0
            int iNext = i;
3695
            // Identify consecutive ranges
3696
0
            constexpr size_t SIZE_COG_MARKERS = 2 * sizeof(uint32_t);
3697
0
            auto nEndOffset = panOffsets[iNext] + panSizes[iNext];
3698
0
            while (bMergeConsecutiveRanges && iNext + 1 < nRanges &&
3699
0
                   panOffsets[iNext + 1] > panOffsets[iNext] &&
3700
0
                   panOffsets[iNext] + panSizes[iNext] + SIZE_COG_MARKERS >=
3701
0
                       panOffsets[iNext + 1] &&
3702
0
                   panOffsets[iNext + 1] + panSizes[iNext + 1] > nEndOffset)
3703
0
            {
3704
0
                iNext++;
3705
0
                nEndOffset = panOffsets[iNext] + panSizes[iNext];
3706
0
            }
3707
0
            CPLAssert(panOffsets[i] <= nEndOffset);
3708
0
            const size_t nSize =
3709
0
                static_cast<size_t>(nEndOffset - panOffsets[i]);
3710
3711
0
            if (nSize == 0)
3712
0
            {
3713
0
                i = iNext + 1;
3714
0
                continue;
3715
0
            }
3716
3717
0
            auto newAdviseReadRange =
3718
0
                std::make_unique<AdviseReadRange>(m_oRetryParameters);
3719
0
            newAdviseReadRange->nStartOffset = panOffsets[i];
3720
0
            newAdviseReadRange->nSize = nSize;
3721
0
            newAdviseReadRange->abyData.resize(nSize);
3722
0
            m_aoAdviseReadRanges.push_back(std::move(newAdviseReadRange));
3723
3724
0
            i = iNext + 1;
3725
0
        }
3726
0
    }
3727
0
    catch (const std::exception &)
3728
0
    {
3729
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
3730
0
                 "Out of memory in VSICurlHandle::AdviseRead()");
3731
0
        m_aoAdviseReadRanges.clear();
3732
0
    }
3733
3734
0
    if (m_aoAdviseReadRanges.empty())
3735
0
        return;
3736
3737
#ifdef DEBUG
3738
    CPLDebug(poFS->GetDebugKey(), "AdviseRead(): fetching %u ranges",
3739
             static_cast<unsigned>(m_aoAdviseReadRanges.size()));
3740
#endif
3741
3742
0
    const auto task = [this, aosHTTPOptions = std::move(aosHTTPOptions)](
3743
0
                          const std::string &osURL)
3744
0
    {
3745
0
        if (!m_hCurlMultiHandleForAdviseRead)
3746
0
            m_hCurlMultiHandleForAdviseRead = VSICURLMultiInit();
3747
3748
0
        NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix().c_str());
3749
0
        NetworkStatisticsFile oContextFile(m_osFilename.c_str());
3750
0
        NetworkStatisticsAction oContextAction("AdviseRead");
3751
3752
0
#ifdef CURLPIPE_MULTIPLEX
3753
        // Enable HTTP/2 multiplexing (ignored if an older version of HTTP is
3754
        // used)
3755
        // Not that this does not enable HTTP/1.1 pipeling, which is not
3756
        // recommended for example by Google Cloud Storage.
3757
        // For HTTP/1.1, parallel connections work better since you can get
3758
        // results out of order.
3759
0
        if (CPLTestBool(CPLGetConfigOption("GDAL_HTTP_MULTIPLEX", "YES")))
3760
0
        {
3761
0
            curl_multi_setopt(m_hCurlMultiHandleForAdviseRead,
3762
0
                              CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
3763
0
        }
3764
0
#endif
3765
3766
0
        size_t nTotalDownloaded = 0;
3767
3768
0
        while (true)
3769
0
        {
3770
3771
0
            std::vector<CURL *> aHandles;
3772
0
            std::vector<WriteFuncStruct> asWriteFuncData(
3773
0
                m_aoAdviseReadRanges.size());
3774
0
            std::vector<WriteFuncStruct> asWriteFuncHeaderData(
3775
0
                m_aoAdviseReadRanges.size());
3776
0
            std::vector<char *> apszRanges;
3777
0
            std::vector<struct curl_slist *> aHeaders;
3778
3779
0
            struct CurlErrBuffer
3780
0
            {
3781
0
                std::array<char, CURL_ERROR_SIZE + 1> szCurlErrBuf;
3782
0
            };
3783
0
            std::vector<CurlErrBuffer> asCurlErrors(
3784
0
                m_aoAdviseReadRanges.size());
3785
3786
0
            std::map<CURL *, size_t> oMapHandleToIdx;
3787
0
            for (size_t i = 0; i < m_aoAdviseReadRanges.size(); ++i)
3788
0
            {
3789
0
                if (!m_aoAdviseReadRanges[i]->bToRetry)
3790
0
                {
3791
0
                    aHandles.push_back(nullptr);
3792
0
                    apszRanges.push_back(nullptr);
3793
0
                    aHeaders.push_back(nullptr);
3794
0
                    continue;
3795
0
                }
3796
0
                m_aoAdviseReadRanges[i]->bToRetry = false;
3797
3798
0
                CURL *hCurlHandle = curl_easy_init();
3799
0
                oMapHandleToIdx[hCurlHandle] = i;
3800
0
                aHandles.push_back(hCurlHandle);
3801
3802
                // As the multi-range request is likely not the first one, we don't
3803
                // need to wait as we already know if pipelining is possible
3804
                // unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_PIPEWAIT, 1);
3805
3806
0
                struct curl_slist *headers = VSICurlSetOptions(
3807
0
                    hCurlHandle, osURL.c_str(), aosHTTPOptions.List());
3808
3809
0
                VSICURLInitWriteFuncStruct(&asWriteFuncData[i], this,
3810
0
                                           pfnReadCbk, pReadCbkUserData);
3811
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
3812
0
                                           &asWriteFuncData[i]);
3813
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
3814
0
                                           VSICurlHandleWriteFunc);
3815
3816
0
                VSICURLInitWriteFuncStruct(&asWriteFuncHeaderData[i], nullptr,
3817
0
                                           nullptr, nullptr);
3818
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
3819
0
                                           &asWriteFuncHeaderData[i]);
3820
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
3821
0
                                           VSICurlHandleWriteFunc);
3822
0
                asWriteFuncHeaderData[i].bIsHTTP =
3823
0
                    STARTS_WITH(m_pszURL, "http");
3824
0
                asWriteFuncHeaderData[i].nStartOffset =
3825
0
                    m_aoAdviseReadRanges[i]->nStartOffset;
3826
3827
0
                asWriteFuncHeaderData[i].nEndOffset =
3828
0
                    m_aoAdviseReadRanges[i]->nStartOffset +
3829
0
                    m_aoAdviseReadRanges[i]->nSize - 1;
3830
3831
0
                char rangeStr[512] = {};
3832
0
                snprintf(rangeStr, sizeof(rangeStr),
3833
0
                         CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3834
0
                         asWriteFuncHeaderData[i].nStartOffset,
3835
0
                         asWriteFuncHeaderData[i].nEndOffset);
3836
3837
                if constexpr (ENABLE_DEBUG)
3838
0
                {
3839
0
                    CPLDebug(poFS->GetDebugKey(), "Downloading %s (%s)...",
3840
0
                             rangeStr, osURL.c_str());
3841
0
                }
3842
3843
0
                if (asWriteFuncHeaderData[i].bIsHTTP)
3844
0
                {
3845
0
                    std::string osHeaderRange(
3846
0
                        CPLSPrintf("Range: bytes=%s", rangeStr));
3847
                    // So it gets included in Azure signature
3848
0
                    char *pszRange = CPLStrdup(osHeaderRange.c_str());
3849
0
                    apszRanges.push_back(pszRange);
3850
0
                    headers = curl_slist_append(headers, pszRange);
3851
0
                    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
3852
0
                                               nullptr);
3853
0
                }
3854
0
                else
3855
0
                {
3856
0
                    apszRanges.push_back(nullptr);
3857
0
                    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE,
3858
0
                                               rangeStr);
3859
0
                }
3860
3861
0
                asCurlErrors[i].szCurlErrBuf[0] = '\0';
3862
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
3863
0
                                           &asCurlErrors[i].szCurlErrBuf[0]);
3864
3865
0
                headers = GetCurlHeaders("GET", headers);
3866
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
3867
0
                                           headers);
3868
0
                aHeaders.push_back(headers);
3869
0
                curl_multi_add_handle(m_hCurlMultiHandleForAdviseRead,
3870
0
                                      hCurlHandle);
3871
0
            }
3872
3873
0
            const auto DealWithRequest = [this, &osURL, &nTotalDownloaded,
3874
0
                                          &oMapHandleToIdx, &asCurlErrors,
3875
0
                                          &asWriteFuncHeaderData,
3876
0
                                          &asWriteFuncData](CURL *hCurlHandle)
3877
0
            {
3878
0
                auto oIter = oMapHandleToIdx.find(hCurlHandle);
3879
0
                CPLAssert(oIter != oMapHandleToIdx.end());
3880
0
                const auto iReq = oIter->second;
3881
3882
0
                long response_code = 0;
3883
0
                curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE,
3884
0
                                  &response_code);
3885
3886
0
                if (ENABLE_DEBUG && asCurlErrors[iReq].szCurlErrBuf[0] != '\0')
3887
0
                {
3888
0
                    char rangeStr[512] = {};
3889
0
                    snprintf(rangeStr, sizeof(rangeStr),
3890
0
                             CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3891
0
                             asWriteFuncHeaderData[iReq].nStartOffset,
3892
0
                             asWriteFuncHeaderData[iReq].nEndOffset);
3893
3894
0
                    const char *pszErrorMsg =
3895
0
                        &asCurlErrors[iReq].szCurlErrBuf[0];
3896
0
                    CPLDebug(poFS->GetDebugKey(),
3897
0
                             "ReadMultiRange(%s), %s: response_code=%d, msg=%s",
3898
0
                             osURL.c_str(), rangeStr,
3899
0
                             static_cast<int>(response_code), pszErrorMsg);
3900
0
                }
3901
3902
0
                bool bToRetry = false;
3903
0
                if ((response_code != 206 && response_code != 225) ||
3904
0
                    asWriteFuncHeaderData[iReq].nEndOffset + 1 !=
3905
0
                        asWriteFuncHeaderData[iReq].nStartOffset +
3906
0
                            asWriteFuncData[iReq].nSize)
3907
0
                {
3908
0
                    char rangeStr[512] = {};
3909
0
                    snprintf(rangeStr, sizeof(rangeStr),
3910
0
                             CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
3911
0
                             asWriteFuncHeaderData[iReq].nStartOffset,
3912
0
                             asWriteFuncHeaderData[iReq].nEndOffset);
3913
3914
                    // Look if we should attempt a retry
3915
0
                    if (m_aoAdviseReadRanges[iReq]->retryContext.CanRetry(
3916
0
                            static_cast<int>(response_code),
3917
0
                            asWriteFuncData[iReq].pBuffer,
3918
0
                            &asCurlErrors[iReq].szCurlErrBuf[0]))
3919
0
                    {
3920
0
                        CPLError(CE_Warning, CPLE_AppDefined,
3921
0
                                 "HTTP error code for %s range %s: %d. "
3922
0
                                 "Retrying again in %.1f secs",
3923
0
                                 osURL.c_str(), rangeStr,
3924
0
                                 static_cast<int>(response_code),
3925
0
                                 m_aoAdviseReadRanges[iReq]
3926
0
                                     ->retryContext.GetCurrentDelay());
3927
0
                        m_aoAdviseReadRanges[iReq]->dfSleepDelay =
3928
0
                            m_aoAdviseReadRanges[iReq]
3929
0
                                ->retryContext.GetCurrentDelay();
3930
0
                        bToRetry = true;
3931
0
                    }
3932
0
                    else
3933
0
                    {
3934
0
                        CPLError(CE_Failure, CPLE_AppDefined,
3935
0
                                 "Request for %s range %s failed with "
3936
0
                                 "response_code=%ld",
3937
0
                                 osURL.c_str(), rangeStr, response_code);
3938
0
                    }
3939
0
                }
3940
0
                else
3941
0
                {
3942
0
                    const size_t nSize = asWriteFuncData[iReq].nSize;
3943
0
                    memcpy(&m_aoAdviseReadRanges[iReq]->abyData[0],
3944
0
                           asWriteFuncData[iReq].pBuffer, nSize);
3945
0
                    m_aoAdviseReadRanges[iReq]->abyData.resize(nSize);
3946
3947
0
                    nTotalDownloaded += nSize;
3948
0
                }
3949
3950
0
                m_aoAdviseReadRanges[iReq]->bToRetry = bToRetry;
3951
3952
0
                if (!bToRetry)
3953
0
                {
3954
0
                    std::lock_guard<std::mutex> oLock(
3955
0
                        m_aoAdviseReadRanges[iReq]->oMutex);
3956
0
                    m_aoAdviseReadRanges[iReq]->bDone = true;
3957
0
                    m_aoAdviseReadRanges[iReq]->oCV.notify_all();
3958
0
                }
3959
0
            };
3960
3961
0
            void *old_handler = CPLHTTPIgnoreSigPipe();
3962
0
            while (true)
3963
0
            {
3964
0
                int still_running;
3965
0
                while (curl_multi_perform(m_hCurlMultiHandleForAdviseRead,
3966
0
                                          &still_running) ==
3967
0
                       CURLM_CALL_MULTI_PERFORM)
3968
0
                {
3969
                    // loop
3970
0
                }
3971
0
                if (!still_running)
3972
0
                {
3973
0
                    break;
3974
0
                }
3975
3976
0
                CURLMsg *msg;
3977
0
                do
3978
0
                {
3979
0
                    int msgq = 0;
3980
0
                    msg = curl_multi_info_read(m_hCurlMultiHandleForAdviseRead,
3981
0
                                               &msgq);
3982
0
                    if (msg && (msg->msg == CURLMSG_DONE))
3983
0
                    {
3984
0
                        DealWithRequest(msg->easy_handle);
3985
0
                    }
3986
0
                } while (msg);
3987
3988
0
                CPLMultiPerformWait(m_hCurlMultiHandleForAdviseRead);
3989
0
            }
3990
0
            CPLHTTPRestoreSigPipeHandler(old_handler);
3991
3992
0
            bool bRetry = false;
3993
0
            double dfDelay = 0.0;
3994
0
            for (size_t i = 0; i < m_aoAdviseReadRanges.size(); ++i)
3995
0
            {
3996
0
                bool bReqDone;
3997
0
                {
3998
                    // To please Coverity Scan
3999
0
                    std::lock_guard<std::mutex> oLock(
4000
0
                        m_aoAdviseReadRanges[i]->oMutex);
4001
0
                    bReqDone = m_aoAdviseReadRanges[i]->bDone;
4002
0
                }
4003
0
                if (!bReqDone && !m_aoAdviseReadRanges[i]->bToRetry)
4004
0
                {
4005
0
                    DealWithRequest(aHandles[i]);
4006
0
                }
4007
0
                if (m_aoAdviseReadRanges[i]->bToRetry)
4008
0
                    dfDelay = std::max(dfDelay,
4009
0
                                       m_aoAdviseReadRanges[i]->dfSleepDelay);
4010
0
                bRetry = bRetry || m_aoAdviseReadRanges[i]->bToRetry;
4011
0
                if (aHandles[i])
4012
0
                {
4013
0
                    curl_multi_remove_handle(m_hCurlMultiHandleForAdviseRead,
4014
0
                                             aHandles[i]);
4015
0
                    VSICURLResetHeaderAndWriterFunctions(aHandles[i]);
4016
0
                    curl_easy_cleanup(aHandles[i]);
4017
0
                }
4018
0
                CPLFree(apszRanges[i]);
4019
0
                CPLFree(asWriteFuncData[i].pBuffer);
4020
0
                CPLFree(asWriteFuncHeaderData[i].pBuffer);
4021
0
                if (aHeaders[i])
4022
0
                    curl_slist_free_all(aHeaders[i]);
4023
0
            }
4024
0
            if (!bRetry)
4025
0
                break;
4026
0
            CPLSleep(dfDelay);
4027
0
        }
4028
4029
0
        NetworkStatisticsLogger::LogGET(nTotalDownloaded);
4030
0
    };
4031
4032
0
    m_oThreadAdviseRead = std::thread(task, l_osURL);
4033
0
}
4034
4035
/************************************************************************/
4036
/*                               Write()                                */
4037
/************************************************************************/
4038
4039
size_t VSICurlHandle::Write(const void * /* pBuffer */, size_t /* nBytes */)
4040
0
{
4041
0
    return 0;
4042
0
}
4043
4044
/************************************************************************/
4045
/*                              ClearErr()                              */
4046
/************************************************************************/
4047
4048
void VSICurlHandle::ClearErr()
4049
4050
0
{
4051
0
    bEOF = false;
4052
0
    bError = false;
4053
0
}
4054
4055
/************************************************************************/
4056
/*                               Error()                                */
4057
/************************************************************************/
4058
4059
int VSICurlHandle::Error()
4060
4061
0
{
4062
0
    return bError ? TRUE : FALSE;
4063
0
}
4064
4065
/************************************************************************/
4066
/*                                Eof()                                 */
4067
/************************************************************************/
4068
4069
int VSICurlHandle::Eof()
4070
4071
37
{
4072
37
    return bEOF ? TRUE : FALSE;
4073
37
}
4074
4075
/************************************************************************/
4076
/*                               Flush()                                */
4077
/************************************************************************/
4078
4079
int VSICurlHandle::Flush()
4080
0
{
4081
0
    return 0;
4082
0
}
4083
4084
/************************************************************************/
4085
/*                               Close()                                */
4086
/************************************************************************/
4087
4088
int VSICurlHandle::Close()
4089
40.1k
{
4090
40.1k
    return 0;
4091
40.1k
}
4092
4093
/************************************************************************/
4094
/*                    VSICurlFilesystemHandlerBase()                    */
4095
/************************************************************************/
4096
4097
VSICurlFilesystemHandlerBase::VSICurlFilesystemHandlerBase()
4098
656
    : oCacheDirList{1024, 0}
4099
656
{
4100
656
}
4101
4102
/************************************************************************/
4103
/*                           CachedConnection                           */
4104
/************************************************************************/
4105
4106
namespace
4107
{
4108
struct CachedConnection
4109
{
4110
    CURLM *hCurlMultiHandle = nullptr;
4111
    void clear();
4112
4113
    ~CachedConnection()
4114
43
    {
4115
43
        clear();
4116
43
    }
4117
};
4118
}  // namespace
4119
4120
#ifdef _WIN32
4121
// Currently thread_local and C++ objects don't work well with DLL on Windows
4122
static void FreeCachedConnection(void *pData)
4123
{
4124
    delete static_cast<
4125
        std::map<VSICurlFilesystemHandlerBase *, CachedConnection> *>(pData);
4126
}
4127
4128
// Per-thread and per-filesystem Curl connection cache.
4129
static std::map<VSICurlFilesystemHandlerBase *, CachedConnection> &
4130
GetConnectionCache()
4131
{
4132
    static std::map<VSICurlFilesystemHandlerBase *, CachedConnection>
4133
        dummyCache;
4134
    int bMemoryErrorOccurred = false;
4135
    void *pData =
4136
        CPLGetTLSEx(CTLS_VSICURL_CACHEDCONNECTION, &bMemoryErrorOccurred);
4137
    if (bMemoryErrorOccurred)
4138
    {
4139
        return dummyCache;
4140
    }
4141
    if (pData == nullptr)
4142
    {
4143
        auto cachedConnection =
4144
            new std::map<VSICurlFilesystemHandlerBase *, CachedConnection>();
4145
        CPLSetTLSWithFreeFuncEx(CTLS_VSICURL_CACHEDCONNECTION, cachedConnection,
4146
                                FreeCachedConnection, &bMemoryErrorOccurred);
4147
        if (bMemoryErrorOccurred)
4148
        {
4149
            delete cachedConnection;
4150
            return dummyCache;
4151
        }
4152
        return *cachedConnection;
4153
    }
4154
    return *static_cast<
4155
        std::map<VSICurlFilesystemHandlerBase *, CachedConnection> *>(pData);
4156
}
4157
#else
4158
static thread_local std::map<VSICurlFilesystemHandlerBase *, CachedConnection>
4159
    g_tls_connectionCache;
4160
4161
static std::map<VSICurlFilesystemHandlerBase *, CachedConnection> &
4162
GetConnectionCache()
4163
114k
{
4164
114k
    return g_tls_connectionCache;
4165
114k
}
4166
#endif
4167
4168
/************************************************************************/
4169
/*                               clear()                                */
4170
/************************************************************************/
4171
4172
void CachedConnection::clear()
4173
43
{
4174
43
    if (hCurlMultiHandle)
4175
43
    {
4176
43
        VSICURLMultiCleanup(hCurlMultiHandle);
4177
43
        hCurlMultiHandle = nullptr;
4178
43
    }
4179
43
}
4180
4181
/************************************************************************/
4182
/*                   ~VSICurlFilesystemHandlerBase()                    */
4183
/************************************************************************/
4184
4185
VSICurlFilesystemHandlerBase::~VSICurlFilesystemHandlerBase()
4186
0
{
4187
0
    VSICurlFilesystemHandlerBase::ClearCache();
4188
0
    GetConnectionCache().erase(this);
4189
4190
0
    if (hMutex != nullptr)
4191
0
        CPLDestroyMutex(hMutex);
4192
0
    hMutex = nullptr;
4193
0
}
4194
4195
/************************************************************************/
4196
/*                         AllowCachedDataFor()                         */
4197
/************************************************************************/
4198
4199
bool VSICurlFilesystemHandlerBase::AllowCachedDataFor(const char *pszFilename)
4200
467k
{
4201
467k
    bool bCachedAllowed = true;
4202
467k
    char **papszTokens = CSLTokenizeString2(
4203
467k
        CPLGetConfigOption("CPL_VSIL_CURL_NON_CACHED", ""), ":", 0);
4204
467k
    for (int i = 0; papszTokens && papszTokens[i]; i++)
4205
0
    {
4206
0
        if (STARTS_WITH(pszFilename, papszTokens[i]))
4207
0
        {
4208
0
            bCachedAllowed = false;
4209
0
            break;
4210
0
        }
4211
0
    }
4212
467k
    CSLDestroy(papszTokens);
4213
467k
    return bCachedAllowed;
4214
467k
}
4215
4216
/************************************************************************/
4217
/*                       GetCurlMultiHandleFor()                        */
4218
/************************************************************************/
4219
4220
CURLM *VSICurlFilesystemHandlerBase::GetCurlMultiHandleFor(
4221
    const std::string & /*osURL*/)
4222
114k
{
4223
114k
    auto &conn = GetConnectionCache()[this];
4224
114k
    if (conn.hCurlMultiHandle == nullptr)
4225
43
    {
4226
43
        conn.hCurlMultiHandle = VSICURLMultiInit();
4227
43
    }
4228
114k
    return conn.hCurlMultiHandle;
4229
114k
}
4230
4231
/************************************************************************/
4232
/*                           GetRegionCache()                           */
4233
/************************************************************************/
4234
4235
VSICurlFilesystemHandlerBase::RegionCacheType *
4236
VSICurlFilesystemHandlerBase::GetRegionCache()
4237
13.3k
{
4238
    // should be called under hMutex taken
4239
13.3k
    if (m_poRegionCacheDoNotUseDirectly == nullptr)
4240
32
    {
4241
32
        m_poRegionCacheDoNotUseDirectly.reset(
4242
32
            new RegionCacheType(static_cast<size_t>(GetMaxRegions())));
4243
32
    }
4244
13.3k
    return m_poRegionCacheDoNotUseDirectly.get();
4245
13.3k
}
4246
4247
/************************************************************************/
4248
/*                             GetRegion()                              */
4249
/************************************************************************/
4250
4251
std::shared_ptr<std::string>
4252
VSICurlFilesystemHandlerBase::GetRegion(const char *pszURL,
4253
                                        vsi_l_offset nFileOffsetStart)
4254
13.3k
{
4255
13.3k
    CPLMutexHolder oHolder(&hMutex);
4256
4257
13.3k
    const int knDOWNLOAD_CHUNK_SIZE = VSICURLGetDownloadChunkSize();
4258
13.3k
    nFileOffsetStart =
4259
13.3k
        (nFileOffsetStart / knDOWNLOAD_CHUNK_SIZE) * knDOWNLOAD_CHUNK_SIZE;
4260
4261
13.3k
    std::shared_ptr<std::string> out;
4262
13.3k
    if (GetRegionCache()->tryGet(
4263
13.3k
            FilenameOffsetPair(std::string(pszURL), nFileOffsetStart), out))
4264
0
    {
4265
0
        return out;
4266
0
    }
4267
4268
13.3k
    return nullptr;
4269
13.3k
}
4270
4271
/************************************************************************/
4272
/*                             AddRegion()                              */
4273
/************************************************************************/
4274
4275
void VSICurlFilesystemHandlerBase::AddRegion(const char *pszURL,
4276
                                             vsi_l_offset nFileOffsetStart,
4277
                                             size_t nSize, const char *pData)
4278
0
{
4279
0
    CPLMutexHolder oHolder(&hMutex);
4280
4281
0
    auto value = std::make_shared<std::string>();
4282
0
    value->assign(pData, nSize);
4283
0
    GetRegionCache()->insert(
4284
0
        FilenameOffsetPair(std::string(pszURL), nFileOffsetStart),
4285
0
        std::move(value));
4286
0
}
4287
4288
/************************************************************************/
4289
/*                         GetCachedFileProp()                          */
4290
/************************************************************************/
4291
4292
bool VSICurlFilesystemHandlerBase::GetCachedFileProp(const char *pszURL,
4293
                                                     FileProp &oFileProp)
4294
678k
{
4295
678k
    return VSICURLGetCachedFileProp(pszURL, oFileProp);
4296
678k
}
4297
4298
/************************************************************************/
4299
/*                         SetCachedFileProp()                          */
4300
/************************************************************************/
4301
4302
void VSICurlFilesystemHandlerBase::SetCachedFileProp(const char *pszURL,
4303
                                                     FileProp &oFileProp)
4304
17.0k
{
4305
17.0k
    VSICURLSetCachedFileProp(pszURL, oFileProp);
4306
17.0k
}
4307
4308
/************************************************************************/
4309
/*                          GetCachedDirList()                          */
4310
/************************************************************************/
4311
4312
bool VSICurlFilesystemHandlerBase::GetCachedDirList(
4313
    const char *pszURL, CachedDirList &oCachedDirList)
4314
245k
{
4315
245k
    CPLMutexHolder oHolder(&hMutex);
4316
4317
245k
    return oCacheDirList.tryGet(std::string(pszURL), oCachedDirList) &&
4318
           // Let a chance to use new auth parameters
4319
195k
           gnGenerationAuthParameters ==
4320
195k
               oCachedDirList.nGenerationAuthParameters;
4321
245k
}
4322
4323
/************************************************************************/
4324
/*                          SetCachedDirList()                          */
4325
/************************************************************************/
4326
4327
void VSICurlFilesystemHandlerBase::SetCachedDirList(
4328
    const char *pszURL, CachedDirList &oCachedDirList)
4329
12.7k
{
4330
12.7k
    CPLMutexHolder oHolder(&hMutex);
4331
4332
12.7k
    std::string key(pszURL);
4333
12.7k
    CachedDirList oldValue;
4334
12.7k
    if (oCacheDirList.tryGet(key, oldValue))
4335
0
    {
4336
0
        nCachedFilesInDirList -= oldValue.oFileList.size();
4337
0
        oCacheDirList.remove(key);
4338
0
    }
4339
4340
13.2k
    while ((!oCacheDirList.empty() &&
4341
13.1k
            nCachedFilesInDirList + oCachedDirList.oFileList.size() >
4342
13.1k
                1024 * 1024) ||
4343
13.2k
           oCacheDirList.size() == oCacheDirList.getMaxAllowedSize())
4344
479
    {
4345
479
        std::string oldestKey;
4346
479
        oCacheDirList.getOldestEntry(oldestKey, oldValue);
4347
479
        nCachedFilesInDirList -= oldValue.oFileList.size();
4348
479
        oCacheDirList.remove(oldestKey);
4349
479
    }
4350
12.7k
    oCachedDirList.nGenerationAuthParameters = gnGenerationAuthParameters;
4351
4352
12.7k
    nCachedFilesInDirList += oCachedDirList.oFileList.size();
4353
12.7k
    oCacheDirList.insert(key, oCachedDirList);
4354
12.7k
}
4355
4356
/************************************************************************/
4357
/*                        ExistsInCacheDirList()                        */
4358
/************************************************************************/
4359
4360
bool VSICurlFilesystemHandlerBase::ExistsInCacheDirList(
4361
    const std::string &osDirname, bool *pbIsDir)
4362
0
{
4363
0
    CachedDirList cachedDirList;
4364
0
    if (GetCachedDirList(osDirname.c_str(), cachedDirList))
4365
0
    {
4366
0
        if (pbIsDir)
4367
0
            *pbIsDir = !cachedDirList.oFileList.empty();
4368
0
        return false;
4369
0
    }
4370
0
    else
4371
0
    {
4372
0
        if (pbIsDir)
4373
0
            *pbIsDir = false;
4374
0
        return false;
4375
0
    }
4376
0
}
4377
4378
/************************************************************************/
4379
/*                        InvalidateCachedData()                        */
4380
/************************************************************************/
4381
4382
void VSICurlFilesystemHandlerBase::InvalidateCachedData(const char *pszURL)
4383
0
{
4384
0
    CPLMutexHolder oHolder(&hMutex);
4385
4386
0
    VSICURLInvalidateCachedFileProp(pszURL);
4387
4388
    // Invalidate all cached regions for this URL
4389
0
    std::list<FilenameOffsetPair> keysToRemove;
4390
0
    std::string osURL(pszURL);
4391
0
    auto lambda =
4392
0
        [&keysToRemove,
4393
0
         &osURL](const lru11::KeyValuePair<FilenameOffsetPair,
4394
0
                                           std::shared_ptr<std::string>> &kv)
4395
0
    {
4396
0
        if (kv.key.filename_ == osURL)
4397
0
            keysToRemove.push_back(kv.key);
4398
0
    };
4399
0
    auto *poRegionCache = GetRegionCache();
4400
0
    poRegionCache->cwalk(lambda);
4401
0
    for (const auto &key : keysToRemove)
4402
0
        poRegionCache->remove(key);
4403
0
}
4404
4405
/************************************************************************/
4406
/*                             ClearCache()                             */
4407
/************************************************************************/
4408
4409
void VSICurlFilesystemHandlerBase::ClearCache()
4410
0
{
4411
0
    CPLMutexHolder oHolder(&hMutex);
4412
4413
0
    GetRegionCache()->clear();
4414
4415
0
    VSICURLDestroyCacheFileProp();
4416
4417
0
    oCacheDirList.clear();
4418
0
    nCachedFilesInDirList = 0;
4419
4420
0
    GetConnectionCache()[this].clear();
4421
0
}
4422
4423
/************************************************************************/
4424
/*                         PartialClearCache()                          */
4425
/************************************************************************/
4426
4427
void VSICurlFilesystemHandlerBase::PartialClearCache(
4428
    const char *pszFilenamePrefix)
4429
0
{
4430
0
    CPLMutexHolder oHolder(&hMutex);
4431
4432
0
    std::string osURL = GetURLFromFilename(pszFilenamePrefix);
4433
0
    {
4434
0
        std::list<FilenameOffsetPair> keysToRemove;
4435
0
        auto lambda =
4436
0
            [&keysToRemove, &osURL](
4437
0
                const lru11::KeyValuePair<FilenameOffsetPair,
4438
0
                                          std::shared_ptr<std::string>> &kv)
4439
0
        {
4440
0
            if (strncmp(kv.key.filename_.c_str(), osURL.c_str(),
4441
0
                        osURL.size()) == 0)
4442
0
                keysToRemove.push_back(kv.key);
4443
0
        };
4444
0
        auto *poRegionCache = GetRegionCache();
4445
0
        poRegionCache->cwalk(lambda);
4446
0
        for (const auto &key : keysToRemove)
4447
0
            poRegionCache->remove(key);
4448
0
    }
4449
4450
0
    VSICURLInvalidateCachedFilePropPrefix(osURL.c_str());
4451
4452
0
    {
4453
0
        const size_t nLen = strlen(pszFilenamePrefix);
4454
0
        std::list<std::string> keysToRemove;
4455
0
        auto lambda =
4456
0
            [this, &keysToRemove, pszFilenamePrefix,
4457
0
             nLen](const lru11::KeyValuePair<std::string, CachedDirList> &kv)
4458
0
        {
4459
0
            if (strncmp(kv.key.c_str(), pszFilenamePrefix, nLen) == 0)
4460
0
            {
4461
0
                keysToRemove.push_back(kv.key);
4462
0
                nCachedFilesInDirList -= kv.value.oFileList.size();
4463
0
            }
4464
0
        };
4465
0
        oCacheDirList.cwalk(lambda);
4466
0
        for (const auto &key : keysToRemove)
4467
0
            oCacheDirList.remove(key);
4468
0
    }
4469
0
}
4470
4471
/************************************************************************/
4472
/*                          CreateFileHandle()                          */
4473
/************************************************************************/
4474
4475
VSICurlHandle *
4476
VSICurlFilesystemHandlerBase::CreateFileHandle(const char *pszFilename)
4477
142k
{
4478
142k
    return new VSICurlHandle(this, pszFilename);
4479
142k
}
4480
4481
/************************************************************************/
4482
/*                            GetActualURL()                            */
4483
/************************************************************************/
4484
4485
const char *VSICurlFilesystemHandlerBase::GetActualURL(const char *pszFilename)
4486
0
{
4487
0
    VSICurlHandle *poHandle = CreateFileHandle(pszFilename);
4488
0
    if (poHandle == nullptr)
4489
0
        return pszFilename;
4490
0
    std::string osURL(poHandle->GetURL());
4491
0
    delete poHandle;
4492
0
    return CPLSPrintf("%s", osURL.c_str());
4493
0
}
4494
4495
/************************************************************************/
4496
/*                             GetOptions()                             */
4497
/************************************************************************/
4498
4499
#define VSICURL_OPTIONS                                                        \
4500
0
    "  <Option name='GDAL_HTTP_MAX_RETRY' type='int' "                         \
4501
0
    "description='Maximum number of retries' default='0'/>"                    \
4502
0
    "  <Option name='GDAL_HTTP_RETRY_DELAY' type='double' "                    \
4503
0
    "description='Retry delay in seconds' default='30'/>"                      \
4504
0
    "  <Option name='GDAL_HTTP_HEADER_FILE' type='string' "                    \
4505
0
    "description='Filename of a file that contains HTTP headers to "           \
4506
0
    "forward to the server'/>"                                                 \
4507
0
    "  <Option name='CPL_VSIL_CURL_USE_HEAD' type='boolean' "                  \
4508
0
    "description='Whether to use HTTP HEAD verb to retrieve "                  \
4509
0
    "file information' default='YES'/>"                                        \
4510
0
    "  <Option name='GDAL_HTTP_MULTIRANGE' type='string-select' "              \
4511
0
    "description='Strategy to apply to run multi-range requests' "             \
4512
0
    "default='PARALLEL'>"                                                      \
4513
0
    "       <Value>PARALLEL</Value>"                                           \
4514
0
    "       <Value>SERIAL</Value>"                                             \
4515
0
    "  </Option>"                                                              \
4516
0
    "  <Option name='GDAL_HTTP_MULTIPLEX' type='boolean' "                     \
4517
0
    "description='Whether to enable HTTP/2 multiplexing' default='YES'/>"      \
4518
0
    "  <Option name='GDAL_HTTP_MERGE_CONSECUTIVE_RANGES' type='boolean' "      \
4519
0
    "description='Whether to merge consecutive ranges in multirange "          \
4520
0
    "requests' default='YES'/>"                                                \
4521
0
    "  <Option name='CPL_VSIL_CURL_NON_CACHED' type='string' "                 \
4522
0
    "description='Colon-separated list of filenames whose content"             \
4523
0
    "must not be cached across open attempts'/>"                               \
4524
0
    "  <Option name='CPL_VSIL_CURL_ALLOWED_FILENAME' type='string' "           \
4525
0
    "description='Single filename that is allowed to be opened'/>"             \
4526
0
    "  <Option name='CPL_VSIL_CURL_ALLOWED_EXTENSIONS' type='string' "         \
4527
0
    "description='Comma or space separated list of allowed file "              \
4528
0
    "extensions'/>"                                                            \
4529
0
    "  <Option name='GDAL_DISABLE_READDIR_ON_OPEN' type='string-select' "      \
4530
0
    "description='Whether to disable establishing the list of files in "       \
4531
0
    "the directory of the current filename' default='NO'>"                     \
4532
0
    "       <Value>NO</Value>"                                                 \
4533
0
    "       <Value>YES</Value>"                                                \
4534
0
    "       <Value>EMPTY_DIR</Value>"                                          \
4535
0
    "  </Option>"                                                              \
4536
0
    "  <Option name='VSI_CACHE' type='boolean' "                               \
4537
0
    "description='Whether to cache in memory the contents of the opened "      \
4538
0
    "file as soon as they are read' default='NO'/>"                            \
4539
0
    "  <Option name='CPL_VSIL_CURL_CHUNK_SIZE' type='integer' "                \
4540
0
    "description='Size in bytes of the minimum amount of data read in a "      \
4541
0
    "file' default='16384' min='1024' max='10485760'/>"                        \
4542
0
    "  <Option name='CPL_VSIL_CURL_CACHE_SIZE' type='integer' "                \
4543
0
    "description='Size in bytes of the global /vsicurl/ cache' "               \
4544
0
    "default='16384000'/>"                                                     \
4545
0
    "  <Option name='CPL_VSIL_CURL_IGNORE_GLACIER_STORAGE' type='boolean' "    \
4546
0
    "description='Whether to skip files with Glacier storage class in "        \
4547
0
    "directory listing.' default='YES'/>"                                      \
4548
0
    "  <Option name='CPL_VSIL_CURL_ADVISE_READ_TOTAL_BYTES_LIMIT' "            \
4549
0
    "type='integer' description='Maximum number of bytes AdviseRead() is "     \
4550
0
    "allowed to fetch at once' default='104857600'/>"                          \
4551
0
    "  <Option name='CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED' "                  \
4552
0
    "type='string-select' description='Whether the header-file key-value "     \
4553
0
    "pair can be used in /vsicurl? filenames' default='ONLY_IN_TEMP'>"         \
4554
0
    "       <Value>ONLY_IN_TEMP</Value>"                                       \
4555
0
    "       <Value>NO</Value>"                                                 \
4556
0
    "       <Value>YES</Value>"                                                \
4557
0
    "  </Option>"                                                              \
4558
0
    "  <Option name='GDAL_HTTP_MAX_CACHED_CONNECTIONS' type='integer' "        \
4559
0
    "description='Maximum amount of connections that libcurl may keep alive "  \
4560
0
    "in its connection cache after use'/>"                                     \
4561
0
    "  <Option name='GDAL_HTTP_MAX_TOTAL_CONNECTIONS' type='integer' "         \
4562
0
    "description='Maximum number of simultaneously open connections in "       \
4563
0
    "total'/>"
4564
4565
const char *VSICurlFilesystemHandlerBase::GetOptionsStatic()
4566
0
{
4567
0
    return VSICURL_OPTIONS;
4568
0
}
4569
4570
const char *VSICurlFilesystemHandlerBase::GetOptions()
4571
0
{
4572
0
    static std::string osOptions(std::string("<Options>") + GetOptionsStatic() +
4573
0
                                 "</Options>");
4574
0
    return osOptions.c_str();
4575
0
}
4576
4577
/************************************************************************/
4578
/*                         IsAllowedFilename()                          */
4579
/************************************************************************/
4580
4581
bool VSICurlFilesystemHandlerBase::IsAllowedFilename(const char *pszFilename)
4582
321k
{
4583
321k
    const char *pszAllowedFilename =
4584
321k
        CPLGetConfigOption("CPL_VSIL_CURL_ALLOWED_FILENAME", nullptr);
4585
321k
    if (pszAllowedFilename != nullptr)
4586
0
    {
4587
0
        return strcmp(pszFilename, pszAllowedFilename) == 0;
4588
0
    }
4589
4590
    // Consider that only the files whose extension ends up with one that is
4591
    // listed in CPL_VSIL_CURL_ALLOWED_EXTENSIONS exist on the server.  This can
4592
    // speeds up dramatically open experience, in case the server cannot return
4593
    // a file list.  {noext} can be used as a special token to mean file with no
4594
    // extension.
4595
    // For example:
4596
    // gdalinfo --config CPL_VSIL_CURL_ALLOWED_EXTENSIONS ".tif"
4597
    // /vsicurl/http://igskmncngs506.cr.usgs.gov/gmted/Global_tiles_GMTED/075darcsec/bln/W030/30N030W_20101117_gmted_bln075.tif
4598
321k
    const char *pszAllowedExtensions =
4599
321k
        CPLGetConfigOption("CPL_VSIL_CURL_ALLOWED_EXTENSIONS", nullptr);
4600
321k
    if (pszAllowedExtensions)
4601
0
    {
4602
0
        char **papszExtensions =
4603
0
            CSLTokenizeString2(pszAllowedExtensions, ", ", 0);
4604
0
        const char *queryStart = strchr(pszFilename, '?');
4605
0
        char *pszFilenameWithoutQuery = nullptr;
4606
0
        if (queryStart != nullptr)
4607
0
        {
4608
0
            pszFilenameWithoutQuery = CPLStrdup(pszFilename);
4609
0
            pszFilenameWithoutQuery[queryStart - pszFilename] = '\0';
4610
0
            pszFilename = pszFilenameWithoutQuery;
4611
0
        }
4612
0
        const size_t nURLLen = strlen(pszFilename);
4613
0
        bool bFound = false;
4614
0
        for (int i = 0; papszExtensions[i] != nullptr; i++)
4615
0
        {
4616
0
            const size_t nExtensionLen = strlen(papszExtensions[i]);
4617
0
            if (EQUAL(papszExtensions[i], "{noext}"))
4618
0
            {
4619
0
                const char *pszLastSlash = strrchr(pszFilename, '/');
4620
0
                if (pszLastSlash != nullptr &&
4621
0
                    strchr(pszLastSlash, '.') == nullptr)
4622
0
                {
4623
0
                    bFound = true;
4624
0
                    break;
4625
0
                }
4626
0
            }
4627
0
            else if (nURLLen > nExtensionLen &&
4628
0
                     EQUAL(pszFilename + nURLLen - nExtensionLen,
4629
0
                           papszExtensions[i]))
4630
0
            {
4631
0
                bFound = true;
4632
0
                break;
4633
0
            }
4634
0
        }
4635
4636
0
        CSLDestroy(papszExtensions);
4637
0
        if (pszFilenameWithoutQuery)
4638
0
        {
4639
0
            CPLFree(pszFilenameWithoutQuery);
4640
0
        }
4641
4642
0
        return bFound;
4643
0
    }
4644
321k
    return TRUE;
4645
321k
}
4646
4647
/************************************************************************/
4648
/*                                Open()                                */
4649
/************************************************************************/
4650
4651
VSIVirtualHandleUniquePtr
4652
VSICurlFilesystemHandlerBase::Open(const char *pszFilename,
4653
                                   const char *pszAccess, bool bSetError,
4654
                                   CSLConstList papszOptions)
4655
124k
{
4656
124k
    const bool bStartsWithVSICurlPrefix = StartsWithVSICurlPrefix(pszFilename);
4657
124k
    if (!bStartsWithVSICurlPrefix &&
4658
21.9k
        !cpl::starts_with(std::string_view(pszFilename), GetFSPrefix()))
4659
46
    {
4660
46
        return nullptr;
4661
46
    }
4662
4663
124k
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
4664
0
    {
4665
0
        if (bSetError)
4666
0
        {
4667
0
            VSIError(VSIE_FileError,
4668
0
                     "Only read-only mode is supported for /vsicurl");
4669
0
        }
4670
0
        return nullptr;
4671
0
    }
4672
124k
    if (!papszOptions ||
4673
0
        !CPLTestBool(CSLFetchNameValueDef(
4674
0
            papszOptions, "IGNORE_FILENAME_RESTRICTIONS", "NO")))
4675
124k
    {
4676
124k
        if (!IsAllowedFilename(pszFilename))
4677
0
            return nullptr;
4678
124k
    }
4679
4680
124k
    bool bListDir = true;
4681
124k
    bool bEmptyDir = false;
4682
124k
    std::string osURL =
4683
124k
        bStartsWithVSICurlPrefix
4684
124k
            ? VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr, nullptr,
4685
102k
                                        &bListDir, &bEmptyDir, nullptr, nullptr,
4686
102k
                                        nullptr)
4687
124k
            : GetURLFromFilename(pszFilename);
4688
4689
124k
    const char *pszOptionVal = CSLFetchNameValueDef(
4690
124k
        papszOptions, "DISABLE_READDIR_ON_OPEN",
4691
124k
        VSIGetPathSpecificOption(pszFilename, "GDAL_DISABLE_READDIR_ON_OPEN",
4692
124k
                                 "NO"));
4693
124k
    const bool bCache = CPLTestBool(CSLFetchNameValueDef(
4694
124k
        papszOptions, "CACHE", AllowCachedDataFor(pszFilename) ? "YES" : "NO"));
4695
124k
    const bool bSkipReadDir = !bListDir || bEmptyDir ||
4696
123k
                              EQUAL(pszOptionVal, "EMPTY_DIR") ||
4697
123k
                              CPLTestBool(pszOptionVal) || !bCache;
4698
4699
124k
    std::string osFilename(pszFilename);
4700
124k
    bool bGotFileList = !bSkipReadDir;
4701
124k
    bool bForceExistsCheck = false;
4702
124k
    FileProp cachedFileProp;
4703
124k
    if (!bSkipReadDir &&
4704
123k
        !(GetCachedFileProp(osURL.c_str(), cachedFileProp) &&
4705
24.5k
          cachedFileProp.eExists == EXIST_YES) &&
4706
123k
        strchr(CPLGetFilename(osFilename.c_str()), '.') != nullptr &&
4707
93.9k
        !STARTS_WITH(CPLGetExtensionSafe(osFilename.c_str()).c_str(), "zip") &&
4708
        // Likely a Kerchunk JSON reference file: no need to list siblings
4709
80.3k
        !cpl::ends_with(osFilename, ".nc.zarr"))
4710
80.2k
    {
4711
        // 1000 corresponds to the default page size of S3.
4712
80.2k
        constexpr int FILE_COUNT_LIMIT = 1000;
4713
80.2k
        const CPLStringList aosFileList(ReadDirInternal(
4714
80.2k
            (CPLGetDirnameSafe(osFilename.c_str()) + '/').c_str(),
4715
80.2k
            FILE_COUNT_LIMIT, &bGotFileList));
4716
80.2k
        const bool bFound =
4717
80.2k
            VSICurlIsFileInList(aosFileList.List(),
4718
80.2k
                                CPLGetFilename(osFilename.c_str())) != -1;
4719
80.2k
        if (bGotFileList && !bFound && aosFileList.size() < FILE_COUNT_LIMIT)
4720
1.71k
        {
4721
            // Some file servers are case insensitive, so in case there is a
4722
            // match with case difference, do a full check just in case.
4723
            // e.g.
4724
            // http://pds-geosciences.wustl.edu/mgs/mgs-m-mola-5-megdr-l3-v1/mgsl_300x/meg004/MEGA90N000CB.IMG
4725
            // that is queried by
4726
            // gdalinfo
4727
            // /vsicurl/http://pds-geosciences.wustl.edu/mgs/mgs-m-mola-5-megdr-l3-v1/mgsl_300x/meg004/mega90n000cb.lbl
4728
1.71k
            if (aosFileList.FindString(CPLGetFilename(osFilename.c_str())) !=
4729
1.71k
                -1)
4730
0
            {
4731
0
                bForceExistsCheck = true;
4732
0
            }
4733
1.71k
            else
4734
1.71k
            {
4735
1.71k
                return nullptr;
4736
1.71k
            }
4737
1.71k
        }
4738
80.2k
    }
4739
122k
    if (!bStartsWithVSICurlPrefix)
4740
21.9k
        osURL = GetURLFromFilename(pszFilename);
4741
122k
    if (GetCachedFileProp(osURL.c_str(), cachedFileProp) &&
4742
24.6k
        cachedFileProp.eExists == EXIST_YES && cachedFileProp.bIsDirectory)
4743
0
    {
4744
0
        return nullptr;
4745
0
    }
4746
4747
122k
    auto poHandle =
4748
122k
        std::unique_ptr<VSICurlHandle>(CreateFileHandle(osFilename.c_str()));
4749
122k
    if (poHandle == nullptr)
4750
8.64k
        return nullptr;
4751
113k
    poHandle->SetCache(bCache);
4752
113k
    if (!bGotFileList || bForceExistsCheck)
4753
73.5k
    {
4754
        // If we didn't get a filelist, check that the file really exists.
4755
73.5k
        if (!poHandle->Exists(bSetError))
4756
73.5k
        {
4757
73.5k
            return nullptr;
4758
73.5k
        }
4759
73.5k
    }
4760
4761
40.1k
    if (CPLTestBool(CPLGetConfigOption("VSI_CACHE", "FALSE")))
4762
0
        return VSIVirtualHandleUniquePtr(
4763
0
            VSICreateCachedFile(poHandle.release()));
4764
40.1k
    else
4765
40.1k
        return VSIVirtualHandleUniquePtr(poHandle.release());
4766
40.1k
}
4767
4768
/************************************************************************/
4769
/*                        VSICurlParserFindEOL()                        */
4770
/*                                                                      */
4771
/*      Small helper function for VSICurlPaseHTMLFileList() to find     */
4772
/*      the end of a line in the directory listing.  Either a <br>      */
4773
/*      or newline.                                                     */
4774
/************************************************************************/
4775
4776
static char *VSICurlParserFindEOL(char *pszData)
4777
4778
0
{
4779
0
    while (*pszData != '\0' && *pszData != '\n' &&
4780
0
           !STARTS_WITH_CI(pszData, "<br>"))
4781
0
        pszData++;
4782
4783
0
    if (*pszData == '\0')
4784
0
        return nullptr;
4785
4786
0
    return pszData;
4787
0
}
4788
4789
/************************************************************************/
4790
/*                           ParseFileSize()                            */
4791
/************************************************************************/
4792
4793
static GUIntBig ParseFileSize(const char *pszStr)
4794
0
{
4795
0
    GUIntBig nFileSize = 0;
4796
0
    while (*pszStr == ' ')
4797
0
        pszStr++;
4798
0
    if (*pszStr >= '1' && *pszStr <= '9')
4799
0
    {
4800
0
        const char *pszIter = pszStr + 1;
4801
0
        while (*pszIter >= '0' && *pszIter <= '9')
4802
0
            ++pszIter;
4803
0
        if (*pszIter == 0 || *pszIter == ' ' || *pszIter == '\t' ||
4804
0
            *pszIter == '\r' || *pszIter == '\n')
4805
0
        {
4806
0
            nFileSize =
4807
0
                CPLScanUIntBig(pszStr, static_cast<int>(pszIter - pszStr));
4808
0
        }
4809
0
    }
4810
0
    return nFileSize;
4811
0
}
4812
4813
/************************************************************************/
4814
/*                  VSICurlParseHTMLDateTimeFileSize()                  */
4815
/************************************************************************/
4816
4817
static const char *const apszMonths[] = {
4818
    "January", "February", "March",     "April",   "May",      "June",
4819
    "July",    "August",   "September", "October", "November", "December"};
4820
4821
static bool VSICurlParseHTMLDateTimeFileSize(const char *pszStr,
4822
                                             struct tm &brokendowntime,
4823
                                             GUIntBig &nFileSize,
4824
                                             GIntBig &mTime)
4825
0
{
4826
0
    for (int iMonth = 0; iMonth < 12; iMonth++)
4827
0
    {
4828
0
        nFileSize = 0;
4829
4830
0
        char szMonth[32] = {};
4831
0
        szMonth[0] = '-';
4832
0
        memcpy(szMonth + 1, apszMonths[iMonth], 3);
4833
0
        szMonth[4] = '-';
4834
0
        szMonth[5] = '\0';
4835
0
        const char *pszMonthFound = strstr(pszStr, szMonth);
4836
0
        if (pszMonthFound)
4837
0
        {
4838
            // Format of Apache, like in
4839
            // http://download.osgeo.org/gdal/data/gtiff/
4840
            // "17-May-2010 12:26"
4841
0
            const auto nMonthFoundLen = strlen(pszMonthFound);
4842
0
            if (pszMonthFound - pszStr > 2 && nMonthFoundLen > 15 &&
4843
0
                pszMonthFound[-2 + 11] == ' ' && pszMonthFound[-2 + 14] == ':')
4844
0
            {
4845
0
                pszMonthFound -= 2;
4846
0
                int nDay = atoi(pszMonthFound);
4847
0
                int nYear = atoi(pszMonthFound + 7);
4848
0
                int nHour = atoi(pszMonthFound + 12);
4849
0
                int nMin = atoi(pszMonthFound + 15);
4850
0
                if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4851
0
                    nHour <= 24 && nMin >= 0 && nMin < 60)
4852
0
                {
4853
0
                    brokendowntime.tm_year = nYear - 1900;
4854
0
                    brokendowntime.tm_mon = iMonth;
4855
0
                    brokendowntime.tm_mday = nDay;
4856
0
                    brokendowntime.tm_hour = nHour;
4857
0
                    brokendowntime.tm_min = nMin;
4858
0
                    mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4859
4860
0
                    if (nMonthFoundLen > 15 + 2)
4861
0
                    {
4862
0
                        const char *pszFilesize = pszMonthFound + 15 + 2;
4863
0
                        nFileSize = ParseFileSize(pszFilesize);
4864
0
                    }
4865
0
                }
4866
0
            }
4867
0
            return nFileSize > 0;
4868
0
        }
4869
4870
        /* Microsoft IIS */
4871
0
        snprintf(szMonth, sizeof(szMonth), " %s ", apszMonths[iMonth]);
4872
0
        pszMonthFound = strstr(pszStr, szMonth);
4873
0
        if (pszMonthFound)
4874
0
        {
4875
0
            int nLenMonth = static_cast<int>(strlen(apszMonths[iMonth]));
4876
0
            if (pszMonthFound - pszStr > 2 && pszMonthFound[-1] != ',' &&
4877
0
                pszMonthFound[-2] != ' ' &&
4878
0
                static_cast<int>(strlen(pszMonthFound - 2)) >
4879
0
                    2 + 1 + nLenMonth + 1 + 4 + 1 + 5 + 1 + 4)
4880
0
            {
4881
                /* Format of http://ortho.linz.govt.nz/tifs/1994_95/ */
4882
                /* "        Friday, 21 April 2006 12:05 p.m.     48062343
4883
                 * m35a_fy_94_95.tif" */
4884
0
                pszMonthFound -= 2;
4885
0
                int nDay = atoi(pszMonthFound);
4886
0
                int nCurOffset = 2 + 1 + nLenMonth + 1;
4887
0
                int nYear = atoi(pszMonthFound + nCurOffset);
4888
0
                nCurOffset += 4 + 1;
4889
0
                int nHour = atoi(pszMonthFound + nCurOffset);
4890
0
                if (nHour < 10)
4891
0
                    nCurOffset += 1 + 1;
4892
0
                else
4893
0
                    nCurOffset += 2 + 1;
4894
0
                const int nMin = atoi(pszMonthFound + nCurOffset);
4895
0
                nCurOffset += 2 + 1;
4896
0
                if (STARTS_WITH(pszMonthFound + nCurOffset, "p.m."))
4897
0
                    nHour += 12;
4898
0
                else if (!STARTS_WITH(pszMonthFound + nCurOffset, "a.m."))
4899
0
                    nHour = -1;
4900
0
                nCurOffset += 4;
4901
4902
0
                if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4903
0
                    nHour <= 24 && nMin >= 0 && nMin < 60)
4904
0
                {
4905
0
                    brokendowntime.tm_year = nYear - 1900;
4906
0
                    brokendowntime.tm_mon = iMonth;
4907
0
                    brokendowntime.tm_mday = nDay;
4908
0
                    brokendowntime.tm_hour = nHour;
4909
0
                    brokendowntime.tm_min = nMin;
4910
0
                    mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4911
4912
0
                    const char *pszFilesize = pszMonthFound + nCurOffset;
4913
0
                    nFileSize = ParseFileSize(pszFilesize);
4914
0
                }
4915
0
            }
4916
0
            else if (pszMonthFound - pszStr > 1 && pszMonthFound[-1] == ',' &&
4917
0
                     static_cast<int>(strlen(pszMonthFound)) >
4918
0
                         1 + nLenMonth + 1 + 2 + 1 + 1 + 4 + 1 + 5 + 1 + 2)
4919
0
            {
4920
                // Format of
4921
                // http://publicfiles.dep.state.fl.us/dear/BWR_GIS/2007NWFLULC/
4922
                // "        Sunday, June 20, 2010  6:46 PM    233170905
4923
                // NWF2007LULCForSDE.zip"
4924
0
                pszMonthFound += 1;
4925
0
                int nCurOffset = nLenMonth + 1;
4926
0
                int nDay = atoi(pszMonthFound + nCurOffset);
4927
0
                nCurOffset += 2 + 1 + 1;
4928
0
                int nYear = atoi(pszMonthFound + nCurOffset);
4929
0
                nCurOffset += 4 + 1;
4930
0
                int nHour = atoi(pszMonthFound + nCurOffset);
4931
0
                nCurOffset += 2 + 1;
4932
0
                const int nMin = atoi(pszMonthFound + nCurOffset);
4933
0
                nCurOffset += 2 + 1;
4934
0
                if (STARTS_WITH(pszMonthFound + nCurOffset, "PM"))
4935
0
                    nHour += 12;
4936
0
                else if (!STARTS_WITH(pszMonthFound + nCurOffset, "AM"))
4937
0
                    nHour = -1;
4938
0
                nCurOffset += 2;
4939
4940
0
                if (nDay >= 1 && nDay <= 31 && nYear >= 1900 && nHour >= 0 &&
4941
0
                    nHour <= 24 && nMin >= 0 && nMin < 60)
4942
0
                {
4943
0
                    brokendowntime.tm_year = nYear - 1900;
4944
0
                    brokendowntime.tm_mon = iMonth;
4945
0
                    brokendowntime.tm_mday = nDay;
4946
0
                    brokendowntime.tm_hour = nHour;
4947
0
                    brokendowntime.tm_min = nMin;
4948
0
                    mTime = CPLYMDHMSToUnixTime(&brokendowntime);
4949
4950
0
                    const char *pszFilesize = pszMonthFound + nCurOffset;
4951
0
                    nFileSize = ParseFileSize(pszFilesize);
4952
0
                }
4953
0
            }
4954
4955
0
            return nFileSize > 0;
4956
0
        }
4957
0
    }
4958
4959
0
    return false;
4960
0
}
4961
4962
/************************************************************************/
4963
/*                          ParseHTMLFileList()                         */
4964
/*                                                                      */
4965
/*      Parse a file list document and return all the components.       */
4966
/************************************************************************/
4967
4968
char **VSICurlFilesystemHandlerBase::ParseHTMLFileList(const char *pszFilename,
4969
                                                       int nMaxFiles,
4970
                                                       char *pszData,
4971
                                                       bool *pbGotFileList)
4972
0
{
4973
0
    *pbGotFileList = false;
4974
4975
0
    std::string osURL(VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr,
4976
0
                                                nullptr, nullptr, nullptr,
4977
0
                                                nullptr, nullptr, nullptr));
4978
0
    const char *pszDir = nullptr;
4979
0
    if (STARTS_WITH_CI(osURL.c_str(), "http://"))
4980
0
        pszDir = strchr(osURL.c_str() + strlen("http://"), '/');
4981
0
    else if (STARTS_WITH_CI(osURL.c_str(), "https://"))
4982
0
        pszDir = strchr(osURL.c_str() + strlen("https://"), '/');
4983
0
    else if (STARTS_WITH_CI(osURL.c_str(), "ftp://"))
4984
0
        pszDir = strchr(osURL.c_str() + strlen("ftp://"), '/');
4985
0
    if (pszDir == nullptr)
4986
0
        pszDir = "";
4987
4988
    /* Apache / Nginx */
4989
    /* Most of the time the format is <title>Index of {pszDir[/]}</title>, but
4990
     * there are special cases like https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/GEOCOLOR/
4991
     * where a CDN stuff makes that the title is <title>Index of /ma-cdn02/GOES/data/GOES18/ABI/MESO/M1/GEOCOLOR/</title>
4992
     */
4993
0
    const std::string osTitleIndexOfPrefix = "<title>Index of ";
4994
0
    const std::string osExpectedSuffix = std::string(pszDir).append("</title>");
4995
0
    const std::string osExpectedSuffixWithSlash =
4996
0
        std::string(pszDir).append("/</title>");
4997
    /* FTP */
4998
0
    const std::string osExpectedStringFTP =
4999
0
        std::string("FTP Listing of ").append(pszDir).append("/");
5000
    /* Apache 1.3.33 */
5001
0
    const std::string osExpectedStringOldApache =
5002
0
        std::string("<TITLE>Index of ").append(pszDir).append("</TITLE>");
5003
5004
    // The listing of
5005
    // http://dds.cr.usgs.gov/srtm/SRTM_image_sample/picture%20examples/
5006
    // has
5007
    // "<title>Index of /srtm/SRTM_image_sample/picture examples</title>"
5008
    // so we must try unescaped %20 also.
5009
    // Similar with
5010
    // http://datalib.usask.ca/gis/Data/Central_America_goodbutdoweown%3f/
5011
0
    std::string osExpectedString_unescaped;
5012
0
    if (strchr(pszDir, '%'))
5013
0
    {
5014
0
        char *pszUnescapedDir = CPLUnescapeString(pszDir, nullptr, CPLES_URL);
5015
0
        osExpectedString_unescaped = osTitleIndexOfPrefix;
5016
0
        osExpectedString_unescaped += pszUnescapedDir;
5017
0
        osExpectedString_unescaped += "</title>";
5018
0
        CPLFree(pszUnescapedDir);
5019
0
    }
5020
5021
0
    char *c = nullptr;
5022
0
    int nCount = 0;
5023
0
    int nCountTable = 0;
5024
0
    CPLStringList oFileList;
5025
0
    char *pszLine = pszData;
5026
0
    bool bIsHTMLDirList = false;
5027
5028
0
    while ((c = VSICurlParserFindEOL(pszLine)) != nullptr)
5029
0
    {
5030
0
        *c = '\0';
5031
5032
        // To avoid false positive on pages such as
5033
        // http://www.ngs.noaa.gov/PC_PROD/USGG2009BETA
5034
        // This is a heuristics, but normal HTML listing of files have not more
5035
        // than one table.
5036
0
        if (strstr(pszLine, "<table"))
5037
0
        {
5038
0
            nCountTable++;
5039
0
            if (nCountTable == 2)
5040
0
            {
5041
0
                *pbGotFileList = false;
5042
0
                return nullptr;
5043
0
            }
5044
0
        }
5045
5046
0
        if (!bIsHTMLDirList &&
5047
0
            ((strstr(pszLine, osTitleIndexOfPrefix.c_str()) &&
5048
0
              (strstr(pszLine, osExpectedSuffix.c_str()) ||
5049
0
               strstr(pszLine, osExpectedSuffixWithSlash.c_str()))) ||
5050
0
             strstr(pszLine, osExpectedStringFTP.c_str()) ||
5051
0
             strstr(pszLine, osExpectedStringOldApache.c_str()) ||
5052
0
             (!osExpectedString_unescaped.empty() &&
5053
0
              strstr(pszLine, osExpectedString_unescaped.c_str()))))
5054
0
        {
5055
0
            bIsHTMLDirList = true;
5056
0
            *pbGotFileList = true;
5057
0
        }
5058
        // Subversion HTTP listing
5059
        // or Microsoft-IIS/6.0 listing
5060
        // (e.g. http://ortho.linz.govt.nz/tifs/2005_06/) */
5061
0
        else if (!bIsHTMLDirList && strstr(pszLine, "<title>"))
5062
0
        {
5063
            // Detect something like:
5064
            // <html><head><title>gdal - Revision 20739:
5065
            // /trunk/autotest/gcore/data</title></head> */ The annoying thing
5066
            // is that what is after ': ' is a subpart of what is after
5067
            // http://server/
5068
0
            char *pszSubDir = strstr(pszLine, ": ");
5069
0
            if (pszSubDir == nullptr)
5070
                // or <title>ortho.linz.govt.nz - /tifs/2005_06/</title>
5071
0
                pszSubDir = strstr(pszLine, "- ");
5072
0
            if (pszSubDir)
5073
0
            {
5074
0
                pszSubDir += 2;
5075
0
                char *pszTmp = strstr(pszSubDir, "</title>");
5076
0
                if (pszTmp)
5077
0
                {
5078
0
                    if (pszTmp[-1] == '/')
5079
0
                        pszTmp[-1] = 0;
5080
0
                    else
5081
0
                        *pszTmp = 0;
5082
0
                    if (strstr(pszDir, pszSubDir))
5083
0
                    {
5084
0
                        bIsHTMLDirList = true;
5085
0
                        *pbGotFileList = true;
5086
0
                    }
5087
0
                }
5088
0
            }
5089
0
        }
5090
0
        else if (bIsHTMLDirList &&
5091
0
                 (strstr(pszLine, "<a href=\"") != nullptr ||
5092
0
                  strstr(pszLine, "<A HREF=\"") != nullptr) &&
5093
                 // Exclude absolute links, like to subversion home.
5094
0
                 strstr(pszLine, "<a href=\"http://") == nullptr &&
5095
                 // exclude parent directory.
5096
0
                 strstr(pszLine, "Parent Directory") == nullptr)
5097
0
        {
5098
0
            char *beginFilename = strstr(pszLine, "<a href=\"");
5099
0
            if (beginFilename == nullptr)
5100
0
                beginFilename = strstr(pszLine, "<A HREF=\"");
5101
0
            beginFilename += strlen("<a href=\"");
5102
0
            char *endQuote = strchr(beginFilename, '"');
5103
0
            if (endQuote && !STARTS_WITH(beginFilename, "?C=") &&
5104
0
                !STARTS_WITH(beginFilename, "?N="))
5105
0
            {
5106
0
                struct tm brokendowntime;
5107
0
                memset(&brokendowntime, 0, sizeof(brokendowntime));
5108
0
                GUIntBig nFileSize = 0;
5109
0
                GIntBig mTime = 0;
5110
5111
0
                VSICurlParseHTMLDateTimeFileSize(pszLine, brokendowntime,
5112
0
                                                 nFileSize, mTime);
5113
5114
0
                *endQuote = '\0';
5115
5116
                // Remove trailing slash, that are returned for directories by
5117
                // Apache.
5118
0
                bool bIsDirectory = false;
5119
0
                if (endQuote[-1] == '/')
5120
0
                {
5121
0
                    bIsDirectory = true;
5122
0
                    endQuote[-1] = 0;
5123
0
                }
5124
5125
                // shttpd links include slashes from the root directory.
5126
                // Skip them.
5127
0
                while (strchr(beginFilename, '/'))
5128
0
                    beginFilename = strchr(beginFilename, '/') + 1;
5129
5130
0
                if (strcmp(beginFilename, ".") != 0 &&
5131
0
                    strcmp(beginFilename, "..") != 0)
5132
0
                {
5133
0
                    std::string osCachedFilename =
5134
0
                        CPLSPrintf("%s/%s", osURL.c_str(), beginFilename);
5135
5136
0
                    FileProp cachedFileProp;
5137
0
                    GetCachedFileProp(osCachedFilename.c_str(), cachedFileProp);
5138
0
                    cachedFileProp.eExists = EXIST_YES;
5139
0
                    cachedFileProp.bIsDirectory = bIsDirectory;
5140
0
                    if (mTime > 0)
5141
0
                    {
5142
0
                        cachedFileProp.mTime = static_cast<time_t>(mTime);
5143
0
                    }
5144
0
                    if (!cachedFileProp.bHasComputedFileSize)
5145
0
                    {
5146
0
                        cachedFileProp.bHasComputedFileSize = nFileSize > 0;
5147
0
                        cachedFileProp.fileSize = nFileSize;
5148
0
                    }
5149
0
                    SetCachedFileProp(osCachedFilename.c_str(), cachedFileProp);
5150
5151
0
                    oFileList.AddString(beginFilename);
5152
                    if constexpr (ENABLE_DEBUG_VERBOSE)
5153
                    {
5154
                        CPLDebug(
5155
                            GetDebugKey(),
5156
                            "File[%d] = %s, is_dir = %d, size = " CPL_FRMT_GUIB
5157
                            ", time = %04d/%02d/%02d %02d:%02d:%02d",
5158
                            nCount, osCachedFilename.c_str(),
5159
                            bIsDirectory ? 1 : 0, nFileSize,
5160
                            brokendowntime.tm_year + 1900,
5161
                            brokendowntime.tm_mon + 1, brokendowntime.tm_mday,
5162
                            brokendowntime.tm_hour, brokendowntime.tm_min,
5163
                            brokendowntime.tm_sec);
5164
                    }
5165
0
                    nCount++;
5166
5167
0
                    if (nMaxFiles > 0 && oFileList.Count() > nMaxFiles)
5168
0
                        break;
5169
0
                }
5170
0
            }
5171
0
        }
5172
0
        pszLine = c + 1;
5173
0
    }
5174
5175
0
    return oFileList.StealList();
5176
0
}
5177
5178
/************************************************************************/
5179
/*                        GetStreamingFilename()                        */
5180
/************************************************************************/
5181
5182
std::string VSICurlFilesystemHandler::GetStreamingFilename(
5183
    const std::string &osFilename) const
5184
484k
{
5185
484k
    if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
5186
484k
        return "/vsicurl_streaming/" + osFilename.substr(GetFSPrefix().size());
5187
0
    return osFilename;
5188
484k
}
5189
5190
/************************************************************************/
5191
/*                GetHintForPotentiallyRecognizedPath()                 */
5192
/************************************************************************/
5193
5194
std::string VSICurlFilesystemHandler::GetHintForPotentiallyRecognizedPath(
5195
    const std::string &osPath)
5196
487k
{
5197
487k
    if (!StartsWithVSICurlPrefix(osPath.c_str()) &&
5198
484k
        !cpl::starts_with(osPath, GetStreamingFilename(GetFSPrefix())))
5199
484k
    {
5200
484k
        for (const char *pszPrefix : {"http://", "https://"})
5201
968k
        {
5202
968k
            if (cpl::starts_with(osPath, pszPrefix))
5203
328
            {
5204
328
                return GetFSPrefix() + osPath;
5205
328
            }
5206
968k
        }
5207
484k
    }
5208
487k
    return std::string();
5209
487k
}
5210
5211
/************************************************************************/
5212
/*                          VSICurlGetToken()                           */
5213
/************************************************************************/
5214
5215
static char *VSICurlGetToken(char *pszCurPtr, char **ppszNextToken)
5216
0
{
5217
0
    if (pszCurPtr == nullptr)
5218
0
        return nullptr;
5219
5220
0
    while ((*pszCurPtr) == ' ')
5221
0
        pszCurPtr++;
5222
0
    if (*pszCurPtr == '\0')
5223
0
        return nullptr;
5224
5225
0
    char *pszToken = pszCurPtr;
5226
0
    while ((*pszCurPtr) != ' ' && (*pszCurPtr) != '\0')
5227
0
        pszCurPtr++;
5228
0
    if (*pszCurPtr == '\0')
5229
0
    {
5230
0
        *ppszNextToken = nullptr;
5231
0
    }
5232
0
    else
5233
0
    {
5234
0
        *pszCurPtr = '\0';
5235
0
        pszCurPtr++;
5236
0
        while ((*pszCurPtr) == ' ')
5237
0
            pszCurPtr++;
5238
0
        *ppszNextToken = pszCurPtr;
5239
0
    }
5240
5241
0
    return pszToken;
5242
0
}
5243
5244
/************************************************************************/
5245
/*                      VSICurlParseFullFTPLine()                       */
5246
/************************************************************************/
5247
5248
/* Parse lines like the following ones :
5249
-rw-r--r--    1 10003    100           430 Jul 04  2008 COPYING
5250
lrwxrwxrwx    1 ftp      ftp            28 Jun 14 14:13 MPlayer ->
5251
mirrors/mplayerhq.hu/MPlayer -rw-r--r--    1 ftp      ftp      725614592 May 13
5252
20:13 Fedora-15-x86_64-Live-KDE.iso drwxr-xr-x  280 1003  1003  6656 Aug 26
5253
04:17 gnu
5254
*/
5255
5256
static bool VSICurlParseFullFTPLine(char *pszLine, char *&pszFilename,
5257
                                    bool &bSizeValid, GUIntBig &nSize,
5258
                                    bool &bIsDirectory, GIntBig &nUnixTime)
5259
0
{
5260
0
    char *pszNextToken = pszLine;
5261
0
    char *pszPermissions = VSICurlGetToken(pszNextToken, &pszNextToken);
5262
0
    if (pszPermissions == nullptr || strlen(pszPermissions) != 10)
5263
0
        return false;
5264
0
    bIsDirectory = pszPermissions[0] == 'd';
5265
5266
0
    for (int i = 0; i < 3; i++)
5267
0
    {
5268
0
        if (VSICurlGetToken(pszNextToken, &pszNextToken) == nullptr)
5269
0
            return false;
5270
0
    }
5271
5272
0
    char *pszSize = VSICurlGetToken(pszNextToken, &pszNextToken);
5273
0
    if (pszSize == nullptr)
5274
0
        return false;
5275
5276
0
    if (pszPermissions[0] == '-')
5277
0
    {
5278
        // Regular file.
5279
0
        bSizeValid = true;
5280
0
        nSize = CPLScanUIntBig(pszSize, static_cast<int>(strlen(pszSize)));
5281
0
    }
5282
5283
0
    struct tm brokendowntime;
5284
0
    memset(&brokendowntime, 0, sizeof(brokendowntime));
5285
0
    bool bBrokenDownTimeValid = true;
5286
5287
0
    char *pszMonth = VSICurlGetToken(pszNextToken, &pszNextToken);
5288
0
    if (pszMonth == nullptr || strlen(pszMonth) != 3)
5289
0
        return false;
5290
5291
0
    int i = 0;  // Used after for.
5292
0
    for (; i < 12; i++)
5293
0
    {
5294
0
        if (EQUALN(pszMonth, apszMonths[i], 3))
5295
0
            break;
5296
0
    }
5297
0
    if (i < 12)
5298
0
        brokendowntime.tm_mon = i;
5299
0
    else
5300
0
        bBrokenDownTimeValid = false;
5301
5302
0
    char *pszDay = VSICurlGetToken(pszNextToken, &pszNextToken);
5303
0
    if (pszDay == nullptr || (strlen(pszDay) != 1 && strlen(pszDay) != 2))
5304
0
        return false;
5305
0
    int nDay = atoi(pszDay);
5306
0
    if (nDay >= 1 && nDay <= 31)
5307
0
        brokendowntime.tm_mday = nDay;
5308
0
    else
5309
0
        bBrokenDownTimeValid = false;
5310
5311
0
    char *pszHourOrYear = VSICurlGetToken(pszNextToken, &pszNextToken);
5312
0
    if (pszHourOrYear == nullptr ||
5313
0
        (strlen(pszHourOrYear) != 4 && strlen(pszHourOrYear) != 5))
5314
0
        return false;
5315
0
    if (strlen(pszHourOrYear) == 4)
5316
0
    {
5317
0
        brokendowntime.tm_year = atoi(pszHourOrYear) - 1900;
5318
0
    }
5319
0
    else
5320
0
    {
5321
0
        time_t sTime;
5322
0
        time(&sTime);
5323
0
        struct tm currentBrokendowntime;
5324
0
        CPLUnixTimeToYMDHMS(static_cast<GIntBig>(sTime),
5325
0
                            &currentBrokendowntime);
5326
0
        brokendowntime.tm_year = currentBrokendowntime.tm_year;
5327
0
        brokendowntime.tm_hour = atoi(pszHourOrYear);
5328
0
        brokendowntime.tm_min = atoi(pszHourOrYear + 3);
5329
0
    }
5330
5331
0
    if (bBrokenDownTimeValid)
5332
0
        nUnixTime = CPLYMDHMSToUnixTime(&brokendowntime);
5333
0
    else
5334
0
        nUnixTime = 0;
5335
5336
0
    if (pszNextToken == nullptr)
5337
0
        return false;
5338
5339
0
    pszFilename = pszNextToken;
5340
5341
0
    char *pszCurPtr = pszFilename;
5342
0
    while (*pszCurPtr != '\0')
5343
0
    {
5344
        // In case of a link, stop before the pointed part of the link.
5345
0
        if (pszPermissions[0] == 'l' && STARTS_WITH(pszCurPtr, " -> "))
5346
0
        {
5347
0
            break;
5348
0
        }
5349
0
        pszCurPtr++;
5350
0
    }
5351
0
    *pszCurPtr = '\0';
5352
5353
0
    return true;
5354
0
}
5355
5356
/************************************************************************/
5357
/*                         GetURLFromFilename()                         */
5358
/************************************************************************/
5359
5360
std::string VSICurlFilesystemHandlerBase::GetURLFromFilename(
5361
    const std::string &osFilename) const
5362
70.9k
{
5363
70.9k
    return VSICurlGetURLFromFilename(osFilename.c_str(), nullptr, nullptr,
5364
70.9k
                                     nullptr, nullptr, nullptr, nullptr,
5365
70.9k
                                     nullptr, nullptr);
5366
70.9k
}
5367
5368
/************************************************************************/
5369
/*                          RegisterEmptyDir()                          */
5370
/************************************************************************/
5371
5372
void VSICurlFilesystemHandlerBase::RegisterEmptyDir(
5373
    const std::string &osDirname)
5374
0
{
5375
0
    CachedDirList cachedDirList;
5376
0
    cachedDirList.bGotFileList = true;
5377
0
    cachedDirList.oFileList.AddString(".");
5378
0
    SetCachedDirList(osDirname.c_str(), cachedDirList);
5379
0
}
5380
5381
/************************************************************************/
5382
/*                            GetFileList()                             */
5383
/************************************************************************/
5384
5385
char **VSICurlFilesystemHandlerBase::GetFileList(const char *pszDirname,
5386
                                                 int nMaxFiles,
5387
                                                 bool *pbGotFileList)
5388
6.47k
{
5389
    if constexpr (ENABLE_DEBUG)
5390
6.47k
    {
5391
6.47k
        CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
5392
6.47k
    }
5393
5394
6.47k
    *pbGotFileList = false;
5395
5396
6.47k
    bool bListDir = true;
5397
6.47k
    bool bEmptyDir = false;
5398
6.47k
    std::string osURL(VSICurlGetURLFromFilename(pszDirname, nullptr, nullptr,
5399
6.47k
                                                nullptr, &bListDir, &bEmptyDir,
5400
6.47k
                                                nullptr, nullptr, nullptr));
5401
6.47k
    if (bEmptyDir)
5402
10
    {
5403
10
        *pbGotFileList = true;
5404
10
        return CSLAddString(nullptr, ".");
5405
10
    }
5406
6.46k
    if (!bListDir)
5407
1
        return nullptr;
5408
5409
    // Deal with publicly visible Azure directories.
5410
6.46k
    if (STARTS_WITH(osURL.c_str(), "https://"))
5411
79
    {
5412
79
        const char *pszBlobCore =
5413
79
            strstr(osURL.c_str(), ".blob.core.windows.net/");
5414
79
        if (pszBlobCore)
5415
1
        {
5416
1
            FileProp cachedFileProp;
5417
1
            GetCachedFileProp(osURL.c_str(), cachedFileProp);
5418
1
            if (cachedFileProp.bIsAzureFolder)
5419
0
            {
5420
0
                const char *pszURLWithoutHTTPS =
5421
0
                    osURL.c_str() + strlen("https://");
5422
0
                const std::string osStorageAccount(
5423
0
                    pszURLWithoutHTTPS, pszBlobCore - pszURLWithoutHTTPS);
5424
0
                CPLConfigOptionSetter oSetter1("AZURE_NO_SIGN_REQUEST", "YES",
5425
0
                                               false);
5426
0
                CPLConfigOptionSetter oSetter2("AZURE_STORAGE_ACCOUNT",
5427
0
                                               osStorageAccount.c_str(), false);
5428
0
                const std::string osVSIAZ(std::string("/vsiaz/").append(
5429
0
                    pszBlobCore + strlen(".blob.core.windows.net/")));
5430
0
                char **papszFileList = VSIReadDirEx(osVSIAZ.c_str(), nMaxFiles);
5431
0
                if (papszFileList)
5432
0
                {
5433
0
                    *pbGotFileList = true;
5434
0
                    return papszFileList;
5435
0
                }
5436
0
            }
5437
1
        }
5438
79
    }
5439
5440
    // HACK (optimization in fact) for MBTiles driver.
5441
6.46k
    if (strstr(pszDirname, ".tiles.mapbox.com") != nullptr)
5442
225
        return nullptr;
5443
5444
6.23k
    if (STARTS_WITH(osURL.c_str(), "ftp://"))
5445
208
    {
5446
208
        WriteFuncStruct sWriteFuncData;
5447
208
        sWriteFuncData.pBuffer = nullptr;
5448
5449
208
        std::string osDirname(osURL);
5450
208
        osDirname += '/';
5451
5452
208
        char **papszFileList = nullptr;
5453
5454
208
        CURLM *hCurlMultiHandle = GetCurlMultiHandleFor(osDirname);
5455
208
        CURL *hCurlHandle = curl_easy_init();
5456
5457
208
        for (int iTry = 0; iTry < 2; iTry++)
5458
208
        {
5459
208
            struct curl_slist *headers =
5460
208
                VSICurlSetOptions(hCurlHandle, osDirname.c_str(), nullptr);
5461
5462
            // On the first pass, we want to try fetching all the possible
5463
            // information (filename, file/directory, size). If that does not
5464
            // work, then try again with CURLOPT_DIRLISTONLY set.
5465
208
            if (iTry == 1)
5466
0
            {
5467
0
                unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_DIRLISTONLY, 1);
5468
0
            }
5469
5470
208
            VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr,
5471
208
                                       nullptr);
5472
208
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
5473
208
                                       &sWriteFuncData);
5474
208
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
5475
208
                                       VSICurlHandleWriteFunc);
5476
5477
208
            char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
5478
208
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
5479
208
                                       szCurlErrBuf);
5480
5481
208
            unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
5482
208
                                       headers);
5483
5484
208
            VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
5485
5486
208
            curl_slist_free_all(headers);
5487
5488
208
            if (sWriteFuncData.pBuffer == nullptr)
5489
208
            {
5490
208
                curl_easy_cleanup(hCurlHandle);
5491
208
                return nullptr;
5492
208
            }
5493
5494
0
            char *pszLine = sWriteFuncData.pBuffer;
5495
0
            char *c = nullptr;
5496
0
            int nCount = 0;
5497
5498
0
            if (STARTS_WITH_CI(pszLine, "<!DOCTYPE HTML") ||
5499
0
                STARTS_WITH_CI(pszLine, "<HTML>"))
5500
0
            {
5501
0
                papszFileList =
5502
0
                    ParseHTMLFileList(pszDirname, nMaxFiles,
5503
0
                                      sWriteFuncData.pBuffer, pbGotFileList);
5504
0
                break;
5505
0
            }
5506
0
            else if (iTry == 0)
5507
0
            {
5508
0
                CPLStringList oFileList;
5509
0
                *pbGotFileList = true;
5510
5511
0
                while ((c = strchr(pszLine, '\n')) != nullptr)
5512
0
                {
5513
0
                    *c = 0;
5514
0
                    if (c - pszLine > 0 && c[-1] == '\r')
5515
0
                        c[-1] = 0;
5516
5517
0
                    char *pszFilename = nullptr;
5518
0
                    bool bSizeValid = false;
5519
0
                    GUIntBig nFileSize = 0;
5520
0
                    bool bIsDirectory = false;
5521
0
                    GIntBig mUnixTime = 0;
5522
0
                    if (!VSICurlParseFullFTPLine(pszLine, pszFilename,
5523
0
                                                 bSizeValid, nFileSize,
5524
0
                                                 bIsDirectory, mUnixTime))
5525
0
                        break;
5526
5527
0
                    if (strcmp(pszFilename, ".") != 0 &&
5528
0
                        strcmp(pszFilename, "..") != 0)
5529
0
                    {
5530
0
                        if (CPLHasUnbalancedPathTraversal(pszFilename))
5531
0
                        {
5532
0
                            CPLError(CE_Warning, CPLE_AppDefined,
5533
0
                                     "Ignoring '%s' that has a path traversal "
5534
0
                                     "pattern",
5535
0
                                     pszFilename);
5536
0
                        }
5537
0
                        else
5538
0
                        {
5539
0
                            std::string osCachedFilename =
5540
0
                                CPLSPrintf("%s/%s", osURL.c_str(), pszFilename);
5541
5542
0
                            FileProp cachedFileProp;
5543
0
                            GetCachedFileProp(osCachedFilename.c_str(),
5544
0
                                              cachedFileProp);
5545
0
                            cachedFileProp.eExists = EXIST_YES;
5546
0
                            cachedFileProp.bIsDirectory = bIsDirectory;
5547
0
                            cachedFileProp.mTime =
5548
0
                                static_cast<time_t>(mUnixTime);
5549
0
                            cachedFileProp.bHasComputedFileSize = bSizeValid;
5550
0
                            cachedFileProp.fileSize = nFileSize;
5551
0
                            SetCachedFileProp(osCachedFilename.c_str(),
5552
0
                                              cachedFileProp);
5553
5554
0
                            oFileList.AddString(pszFilename);
5555
                            if constexpr (ENABLE_DEBUG_VERBOSE)
5556
                            {
5557
                                struct tm brokendowntime;
5558
                                CPLUnixTimeToYMDHMS(mUnixTime, &brokendowntime);
5559
                                CPLDebug(
5560
                                    GetDebugKey(),
5561
                                    "File[%d] = %s, is_dir = %d, size "
5562
                                    "= " CPL_FRMT_GUIB
5563
                                    ", time = %04d/%02d/%02d %02d:%02d:%02d",
5564
                                    nCount, pszFilename, bIsDirectory ? 1 : 0,
5565
                                    nFileSize, brokendowntime.tm_year + 1900,
5566
                                    brokendowntime.tm_mon + 1,
5567
                                    brokendowntime.tm_mday,
5568
                                    brokendowntime.tm_hour,
5569
                                    brokendowntime.tm_min,
5570
                                    brokendowntime.tm_sec);
5571
                            }
5572
5573
0
                            nCount++;
5574
5575
0
                            if (nMaxFiles > 0 && oFileList.Count() > nMaxFiles)
5576
0
                                break;
5577
0
                        }
5578
0
                    }
5579
5580
0
                    pszLine = c + 1;
5581
0
                }
5582
5583
0
                if (c == nullptr)
5584
0
                {
5585
0
                    papszFileList = oFileList.StealList();
5586
0
                    break;
5587
0
                }
5588
0
            }
5589
0
            else
5590
0
            {
5591
0
                CPLStringList oFileList;
5592
0
                *pbGotFileList = true;
5593
5594
0
                while ((c = strchr(pszLine, '\n')) != nullptr)
5595
0
                {
5596
0
                    *c = 0;
5597
0
                    if (c - pszLine > 0 && c[-1] == '\r')
5598
0
                        c[-1] = 0;
5599
5600
0
                    if (strcmp(pszLine, ".") != 0 && strcmp(pszLine, "..") != 0)
5601
0
                    {
5602
0
                        oFileList.AddString(pszLine);
5603
                        if constexpr (ENABLE_DEBUG_VERBOSE)
5604
                        {
5605
                            CPLDebug(GetDebugKey(), "File[%d] = %s", nCount,
5606
                                     pszLine);
5607
                        }
5608
0
                        nCount++;
5609
0
                    }
5610
5611
0
                    pszLine = c + 1;
5612
0
                }
5613
5614
0
                papszFileList = oFileList.StealList();
5615
0
            }
5616
5617
0
            CPLFree(sWriteFuncData.pBuffer);
5618
0
            sWriteFuncData.pBuffer = nullptr;
5619
0
        }
5620
5621
0
        CPLFree(sWriteFuncData.pBuffer);
5622
0
        curl_easy_cleanup(hCurlHandle);
5623
5624
0
        return papszFileList;
5625
208
    }
5626
5627
    // Try to recognize HTML pages that list the content of a directory.
5628
    // Currently this supports what Apache and shttpd can return.
5629
6.02k
    else if (STARTS_WITH(osURL.c_str(), "http://") ||
5630
5.74k
             STARTS_WITH(osURL.c_str(), "https://"))
5631
358
    {
5632
358
        std::string osDirname(std::move(osURL));
5633
358
        osDirname += '/';
5634
5635
358
        CURLM *hCurlMultiHandle = GetCurlMultiHandleFor(osDirname);
5636
358
        CURL *hCurlHandle = curl_easy_init();
5637
5638
358
        struct curl_slist *headers =
5639
358
            VSICurlSetOptions(hCurlHandle, osDirname.c_str(), nullptr);
5640
5641
358
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_RANGE, nullptr);
5642
5643
358
        WriteFuncStruct sWriteFuncData;
5644
358
        VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
5645
358
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
5646
358
                                   &sWriteFuncData);
5647
358
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
5648
358
                                   VSICurlHandleWriteFunc);
5649
5650
358
        char szCurlErrBuf[CURL_ERROR_SIZE + 1] = {};
5651
358
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
5652
358
                                   szCurlErrBuf);
5653
5654
358
        unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
5655
5656
358
        VSICURLMultiPerform(hCurlMultiHandle, hCurlHandle);
5657
5658
358
        curl_slist_free_all(headers);
5659
5660
358
        NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
5661
5662
358
        if (sWriteFuncData.pBuffer == nullptr)
5663
358
        {
5664
358
            curl_easy_cleanup(hCurlHandle);
5665
358
            return nullptr;
5666
358
        }
5667
5668
0
        char **papszFileList = nullptr;
5669
0
        if (STARTS_WITH_CI(sWriteFuncData.pBuffer, "<?xml") &&
5670
0
            strstr(sWriteFuncData.pBuffer, "<ListBucketResult") != nullptr)
5671
0
        {
5672
0
            CPLStringList osFileList;
5673
0
            std::string osBaseURL(pszDirname);
5674
0
            osBaseURL += "/";
5675
0
            bool bIsTruncated = true;
5676
0
            bool ret = AnalyseS3FileList(
5677
0
                osBaseURL, sWriteFuncData.pBuffer, osFileList, nMaxFiles,
5678
0
                GetS3IgnoredStorageClasses(), bIsTruncated);
5679
            // If the list is truncated, then don't report it.
5680
0
            if (ret && !bIsTruncated)
5681
0
            {
5682
0
                if (osFileList.empty())
5683
0
                {
5684
                    // To avoid an error to be reported
5685
0
                    osFileList.AddString(".");
5686
0
                }
5687
0
                papszFileList = osFileList.StealList();
5688
0
                *pbGotFileList = true;
5689
0
            }
5690
0
        }
5691
0
        else
5692
0
        {
5693
0
            papszFileList = ParseHTMLFileList(
5694
0
                pszDirname, nMaxFiles, sWriteFuncData.pBuffer, pbGotFileList);
5695
0
        }
5696
5697
0
        CPLFree(sWriteFuncData.pBuffer);
5698
0
        curl_easy_cleanup(hCurlHandle);
5699
0
        return papszFileList;
5700
358
    }
5701
5702
5.67k
    return nullptr;
5703
6.23k
}
5704
5705
/************************************************************************/
5706
/*                     GetS3IgnoredStorageClasses()                     */
5707
/************************************************************************/
5708
5709
std::set<std::string> VSICurlFilesystemHandlerBase::GetS3IgnoredStorageClasses()
5710
0
{
5711
0
    std::set<std::string> oSetIgnoredStorageClasses;
5712
0
    const char *pszIgnoredStorageClasses =
5713
0
        CPLGetConfigOption("CPL_VSIL_CURL_IGNORE_STORAGE_CLASSES", nullptr);
5714
0
    const char *pszIgnoreGlacierStorage =
5715
0
        CPLGetConfigOption("CPL_VSIL_CURL_IGNORE_GLACIER_STORAGE", nullptr);
5716
0
    CPLStringList aosIgnoredStorageClasses(
5717
0
        CSLTokenizeString2(pszIgnoredStorageClasses ? pszIgnoredStorageClasses
5718
0
                                                    : "GLACIER,DEEP_ARCHIVE",
5719
0
                           ",", 0));
5720
0
    for (int i = 0; i < aosIgnoredStorageClasses.size(); ++i)
5721
0
        oSetIgnoredStorageClasses.insert(aosIgnoredStorageClasses[i]);
5722
0
    if (pszIgnoredStorageClasses == nullptr &&
5723
0
        pszIgnoreGlacierStorage != nullptr &&
5724
0
        !CPLTestBool(pszIgnoreGlacierStorage))
5725
0
    {
5726
0
        oSetIgnoredStorageClasses.clear();
5727
0
    }
5728
0
    return oSetIgnoredStorageClasses;
5729
0
}
5730
5731
/************************************************************************/
5732
/*                                Stat()                                */
5733
/************************************************************************/
5734
5735
int VSICurlFilesystemHandlerBase::Stat(const char *pszFilename,
5736
                                       VSIStatBufL *pStatBuf, int nFlags)
5737
126k
{
5738
126k
    if (!cpl::starts_with(std::string_view(pszFilename), GetFSPrefix()) &&
5739
17.9k
        !StartsWithVSICurlPrefix(pszFilename))
5740
193
    {
5741
193
        return -1;
5742
193
    }
5743
5744
126k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
5745
5746
126k
    if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
5747
0
    {
5748
0
        cpl::FileProp oFileProp;
5749
0
        if (!GetCachedFileProp(GetURLFromFilename(pszFilename).c_str(),
5750
0
                               oFileProp) ||
5751
0
            oFileProp.eExists != EXIST_YES)
5752
0
        {
5753
0
            return -1;
5754
0
        }
5755
0
        pStatBuf->st_mode = static_cast<unsigned short>(oFileProp.nMode);
5756
0
        pStatBuf->st_mtime = oFileProp.mTime;
5757
0
        pStatBuf->st_size = oFileProp.fileSize;
5758
0
        return 0;
5759
0
    }
5760
5761
126k
    NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5762
126k
    NetworkStatisticsAction oContextAction("Stat");
5763
5764
126k
    const std::string osFilename(pszFilename);
5765
5766
126k
    if (!IsAllowedFilename(pszFilename))
5767
0
        return -1;
5768
5769
126k
    bool bListDir = true;
5770
126k
    bool bEmptyDir = false;
5771
126k
    std::string osURL(VSICurlGetURLFromFilename(pszFilename, nullptr, nullptr,
5772
126k
                                                nullptr, &bListDir, &bEmptyDir,
5773
126k
                                                nullptr, nullptr, nullptr));
5774
5775
126k
    const char *pszOptionVal = VSIGetPathSpecificOption(
5776
126k
        pszFilename, "GDAL_DISABLE_READDIR_ON_OPEN", "NO");
5777
126k
    const bool bSkipReadDir =
5778
126k
        !bListDir || bEmptyDir || EQUAL(pszOptionVal, "EMPTY_DIR") ||
5779
126k
        CPLTestBool(pszOptionVal) || !AllowCachedDataFor(pszFilename);
5780
5781
    // Does it look like a FTP directory?
5782
126k
    if (STARTS_WITH(osURL.c_str(), "ftp://") && osFilename.back() == '/' &&
5783
0
        !bSkipReadDir)
5784
0
    {
5785
0
        char **papszFileList = ReadDirEx(osFilename.c_str(), 0);
5786
0
        if (papszFileList)
5787
0
        {
5788
0
            pStatBuf->st_mode = S_IFDIR;
5789
0
            pStatBuf->st_size = 0;
5790
5791
0
            CSLDestroy(papszFileList);
5792
5793
0
            return 0;
5794
0
        }
5795
0
        return -1;
5796
0
    }
5797
126k
    else if (strchr(CPLGetFilename(osFilename.c_str()), '.') != nullptr &&
5798
126k
             !STARTS_WITH_CI(CPLGetExtensionSafe(osFilename.c_str()).c_str(),
5799
66.9k
                             "zip") &&
5800
66.9k
             strstr(osFilename.c_str(), ".zip.") != nullptr &&
5801
27.8k
             strstr(osFilename.c_str(), ".ZIP.") != nullptr && !bSkipReadDir)
5802
19.1k
    {
5803
19.1k
        bool bGotFileList = false;
5804
19.1k
        char **papszFileList = ReadDirInternal(
5805
19.1k
            CPLGetDirnameSafe(osFilename.c_str()).c_str(), 0, &bGotFileList);
5806
19.1k
        const bool bFound =
5807
19.1k
            VSICurlIsFileInList(papszFileList,
5808
19.1k
                                CPLGetFilename(osFilename.c_str())) != -1;
5809
19.1k
        CSLDestroy(papszFileList);
5810
19.1k
        if (bGotFileList && !bFound)
5811
2
        {
5812
2
            return -1;
5813
2
        }
5814
19.1k
    }
5815
5816
126k
    VSICurlHandle *poHandle = CreateFileHandle(osFilename.c_str());
5817
126k
    if (poHandle == nullptr)
5818
22.3k
        return -1;
5819
5820
103k
    if (poHandle->IsKnownFileSize() ||
5821
21.7k
        ((nFlags & VSI_STAT_SIZE_FLAG) && !poHandle->IsDirectory() &&
5822
6.50k
         CPLTestBool(CPLGetConfigOption("CPL_VSIL_CURL_SLOW_GET_SIZE", "YES"))))
5823
88.4k
    {
5824
88.4k
        pStatBuf->st_size = poHandle->GetFileSize(true);
5825
88.4k
    }
5826
5827
103k
    const int nRet =
5828
103k
        poHandle->Exists((nFlags & VSI_STAT_SET_ERROR_FLAG) > 0) ? 0 : -1;
5829
103k
    pStatBuf->st_mtime = poHandle->GetMTime();
5830
103k
    pStatBuf->st_mode = static_cast<unsigned short>(poHandle->GetMode());
5831
103k
    if (pStatBuf->st_mode == 0)
5832
103k
        pStatBuf->st_mode = poHandle->IsDirectory() ? S_IFDIR : S_IFREG;
5833
103k
    delete poHandle;
5834
103k
    return nRet;
5835
126k
}
5836
5837
/************************************************************************/
5838
/*                          ReadDirInternal()                           */
5839
/************************************************************************/
5840
5841
char **VSICurlFilesystemHandlerBase::ReadDirInternal(const char *pszDirname,
5842
                                                     int nMaxFiles,
5843
                                                     bool *pbGotFileList)
5844
178k
{
5845
178k
    std::string osDirname(pszDirname);
5846
5847
    // Replace a/b/../c by a/c
5848
178k
    const auto posSlashDotDot = osDirname.find("/..");
5849
178k
    if (posSlashDotDot != std::string::npos && posSlashDotDot >= 1)
5850
8.50k
    {
5851
8.50k
        const auto posPrecedingSlash =
5852
8.50k
            osDirname.find_last_of('/', posSlashDotDot - 1);
5853
8.50k
        if (posPrecedingSlash != std::string::npos && posPrecedingSlash >= 1)
5854
8.04k
        {
5855
8.04k
            osDirname.erase(osDirname.begin() + posPrecedingSlash,
5856
8.04k
                            osDirname.begin() + posSlashDotDot + strlen("/.."));
5857
8.04k
        }
5858
8.50k
    }
5859
5860
178k
    std::string osDirnameOri(osDirname);
5861
178k
    if (osDirname + "/" == GetFSPrefix())
5862
885
    {
5863
885
        osDirname += "/";
5864
885
    }
5865
178k
    else if (osDirname != GetFSPrefix())
5866
174k
    {
5867
284k
        while (!osDirname.empty() && osDirname.back() == '/')
5868
110k
            osDirname.erase(osDirname.size() - 1);
5869
174k
    }
5870
5871
178k
    if (osDirname.size() < GetFSPrefix().size())
5872
1.80k
    {
5873
1.80k
        if (pbGotFileList)
5874
1.60k
            *pbGotFileList = true;
5875
1.80k
        return nullptr;
5876
1.80k
    }
5877
5878
177k
    NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5879
177k
    NetworkStatisticsAction oContextAction("ReadDir");
5880
5881
177k
    CPLMutexHolder oHolder(&hMutex);
5882
5883
    // If we know the file exists and is not a directory,
5884
    // then don't try to list its content.
5885
177k
    FileProp cachedFileProp;
5886
177k
    if (GetCachedFileProp(GetURLFromFilename(osDirname.c_str()).c_str(),
5887
177k
                          cachedFileProp) &&
5888
72.9k
        cachedFileProp.eExists == EXIST_YES && !cachedFileProp.bIsDirectory)
5889
0
    {
5890
0
        if (osDirnameOri != osDirname)
5891
0
        {
5892
0
            if (GetCachedFileProp((GetURLFromFilename(osDirname) + "/").c_str(),
5893
0
                                  cachedFileProp) &&
5894
0
                cachedFileProp.eExists == EXIST_YES &&
5895
0
                !cachedFileProp.bIsDirectory)
5896
0
            {
5897
0
                if (pbGotFileList)
5898
0
                    *pbGotFileList = true;
5899
0
                return nullptr;
5900
0
            }
5901
0
        }
5902
0
        else
5903
0
        {
5904
0
            if (pbGotFileList)
5905
0
                *pbGotFileList = true;
5906
0
            return nullptr;
5907
0
        }
5908
0
    }
5909
5910
177k
    CachedDirList cachedDirList;
5911
177k
    if (!GetCachedDirList(osDirname.c_str(), cachedDirList))
5912
12.7k
    {
5913
12.7k
        cachedDirList.oFileList.Assign(GetFileList(osDirname.c_str(), nMaxFiles,
5914
12.7k
                                                   &cachedDirList.bGotFileList),
5915
12.7k
                                       true);
5916
12.7k
        if (cachedDirList.bGotFileList && cachedDirList.oFileList.empty())
5917
0
        {
5918
            // To avoid an error to be reported
5919
0
            cachedDirList.oFileList.AddString(".");
5920
0
        }
5921
12.7k
        if (nMaxFiles <= 0 || cachedDirList.oFileList.size() < nMaxFiles)
5922
12.7k
        {
5923
            // Only cache content if we didn't hit the limitation
5924
12.7k
            SetCachedDirList(osDirname.c_str(), cachedDirList);
5925
12.7k
        }
5926
12.7k
    }
5927
5928
177k
    if (pbGotFileList)
5929
97.7k
        *pbGotFileList = cachedDirList.bGotFileList;
5930
5931
177k
    return CSLDuplicate(cachedDirList.oFileList.List());
5932
177k
}
5933
5934
/************************************************************************/
5935
/*                        InvalidateDirContent()                        */
5936
/************************************************************************/
5937
5938
void VSICurlFilesystemHandlerBase::InvalidateDirContent(
5939
    const std::string &osDirname)
5940
0
{
5941
0
    CPLMutexHolder oHolder(&hMutex);
5942
5943
0
    CachedDirList oCachedDirList;
5944
0
    if (oCacheDirList.tryGet(osDirname, oCachedDirList))
5945
0
    {
5946
0
        nCachedFilesInDirList -= oCachedDirList.oFileList.size();
5947
0
        oCacheDirList.remove(osDirname);
5948
0
    }
5949
0
}
5950
5951
/************************************************************************/
5952
/*                             ReadDirEx()                              */
5953
/************************************************************************/
5954
5955
char **VSICurlFilesystemHandlerBase::ReadDirEx(const char *pszDirname,
5956
                                               int nMaxFiles)
5957
11.4k
{
5958
11.4k
    return ReadDirInternal(pszDirname, nMaxFiles, nullptr);
5959
11.4k
}
5960
5961
/************************************************************************/
5962
/*                            SiblingFiles()                            */
5963
/************************************************************************/
5964
5965
char **VSICurlFilesystemHandlerBase::SiblingFiles(const char *pszFilename)
5966
14.1k
{
5967
    /* Small optimization to avoid unnecessary stat'ing from PAux or ENVI */
5968
    /* drivers. The MBTiles driver needs no companion file. */
5969
14.1k
    if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "mbtiles"))
5970
0
    {
5971
0
        return static_cast<char **>(CPLCalloc(1, sizeof(char *)));
5972
0
    }
5973
14.1k
    return nullptr;
5974
14.1k
}
5975
5976
/************************************************************************/
5977
/*                          GetFileMetadata()                           */
5978
/************************************************************************/
5979
5980
char **VSICurlFilesystemHandlerBase::GetFileMetadata(const char *pszFilename,
5981
                                                     const char *pszDomain,
5982
                                                     CSLConstList)
5983
0
{
5984
0
    if (pszDomain == nullptr || !EQUAL(pszDomain, "HEADERS"))
5985
0
        return nullptr;
5986
0
    std::unique_ptr<VSICurlHandle> poHandle(CreateFileHandle(pszFilename));
5987
0
    if (poHandle == nullptr)
5988
0
        return nullptr;
5989
5990
0
    NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
5991
0
    NetworkStatisticsAction oContextAction("GetFileMetadata");
5992
5993
0
    poHandle->GetFileSizeOrHeaders(true, true);
5994
0
    return CSLDuplicate(poHandle->GetHeaders().List());
5995
0
}
5996
5997
/************************************************************************/
5998
/*                        VSIAppendWriteHandle()                        */
5999
/************************************************************************/
6000
6001
VSIAppendWriteHandle::VSIAppendWriteHandle(VSICurlFilesystemHandlerBase *poFS,
6002
                                           const char *pszFSPrefix,
6003
                                           const char *pszFilename,
6004
                                           int nChunkSize)
6005
0
    : m_poFS(poFS), m_osFSPrefix(pszFSPrefix), m_osFilename(pszFilename),
6006
0
      m_oRetryParameters(CPLStringList(CPLHTTPGetOptionsFromEnv(pszFilename))),
6007
0
      m_nBufferSize(nChunkSize)
6008
0
{
6009
0
    m_pabyBuffer = static_cast<GByte *>(VSIMalloc(m_nBufferSize));
6010
0
    if (m_pabyBuffer == nullptr)
6011
0
    {
6012
0
        CPLError(CE_Failure, CPLE_AppDefined,
6013
0
                 "Cannot allocate working buffer for %s writing",
6014
0
                 m_osFSPrefix.c_str());
6015
0
    }
6016
0
}
6017
6018
/************************************************************************/
6019
/*                       ~VSIAppendWriteHandle()                        */
6020
/************************************************************************/
6021
6022
VSIAppendWriteHandle::~VSIAppendWriteHandle()
6023
0
{
6024
    /* WARNING: implementation should call Close() themselves */
6025
    /* cannot be done safely from here, since Send() can be called. */
6026
0
    CPLFree(m_pabyBuffer);
6027
0
}
6028
6029
/************************************************************************/
6030
/*                                Seek()                                */
6031
/************************************************************************/
6032
6033
int VSIAppendWriteHandle::Seek(vsi_l_offset nOffset, int nWhence)
6034
0
{
6035
0
    if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) ||
6036
0
          (nWhence == SEEK_CUR && nOffset == 0) ||
6037
0
          (nWhence == SEEK_END && nOffset == 0)))
6038
0
    {
6039
0
        CPLError(CE_Failure, CPLE_NotSupported,
6040
0
                 "Seek not supported on writable %s files",
6041
0
                 m_osFSPrefix.c_str());
6042
0
        m_bError = true;
6043
0
        return -1;
6044
0
    }
6045
0
    return 0;
6046
0
}
6047
6048
/************************************************************************/
6049
/*                                Tell()                                */
6050
/************************************************************************/
6051
6052
vsi_l_offset VSIAppendWriteHandle::Tell()
6053
0
{
6054
0
    return m_nCurOffset;
6055
0
}
6056
6057
/************************************************************************/
6058
/*                                Read()                                */
6059
/************************************************************************/
6060
6061
size_t VSIAppendWriteHandle::Read(void * /* pBuffer */, size_t /* nBytes */)
6062
0
{
6063
0
    CPLError(CE_Failure, CPLE_NotSupported,
6064
0
             "Read not supported on writable %s files", m_osFSPrefix.c_str());
6065
0
    m_bError = true;
6066
0
    return 0;
6067
0
}
6068
6069
/************************************************************************/
6070
/*                         ReadCallBackBuffer()                         */
6071
/************************************************************************/
6072
6073
size_t VSIAppendWriteHandle::ReadCallBackBuffer(char *buffer, size_t size,
6074
                                                size_t nitems, void *instream)
6075
0
{
6076
0
    VSIAppendWriteHandle *poThis =
6077
0
        static_cast<VSIAppendWriteHandle *>(instream);
6078
0
    const int nSizeMax = static_cast<int>(size * nitems);
6079
0
    const int nSizeToWrite = std::min(
6080
0
        nSizeMax, poThis->m_nBufferOff - poThis->m_nBufferOffReadCallback);
6081
0
    memcpy(buffer, poThis->m_pabyBuffer + poThis->m_nBufferOffReadCallback,
6082
0
           nSizeToWrite);
6083
0
    poThis->m_nBufferOffReadCallback += nSizeToWrite;
6084
0
    return nSizeToWrite;
6085
0
}
6086
6087
/************************************************************************/
6088
/*                               Write()                                */
6089
/************************************************************************/
6090
6091
size_t VSIAppendWriteHandle::Write(const void *pBuffer, size_t nBytes)
6092
0
{
6093
0
    if (m_bError)
6094
0
        return 0;
6095
6096
0
    size_t nBytesToWrite = nBytes;
6097
0
    if (nBytesToWrite == 0)
6098
0
        return 0;
6099
6100
0
    const GByte *pabySrcBuffer = reinterpret_cast<const GByte *>(pBuffer);
6101
0
    while (nBytesToWrite > 0)
6102
0
    {
6103
0
        if (m_nBufferOff == m_nBufferSize)
6104
0
        {
6105
0
            if (!Send(false))
6106
0
            {
6107
0
                m_bError = true;
6108
0
                return 0;
6109
0
            }
6110
0
            m_nBufferOff = 0;
6111
0
        }
6112
6113
0
        const int nToWriteInBuffer = static_cast<int>(std::min(
6114
0
            static_cast<size_t>(m_nBufferSize - m_nBufferOff), nBytesToWrite));
6115
0
        memcpy(m_pabyBuffer + m_nBufferOff, pabySrcBuffer, nToWriteInBuffer);
6116
0
        pabySrcBuffer += nToWriteInBuffer;
6117
0
        m_nBufferOff += nToWriteInBuffer;
6118
0
        m_nCurOffset += nToWriteInBuffer;
6119
0
        nBytesToWrite -= nToWriteInBuffer;
6120
0
    }
6121
0
    return nBytes;
6122
0
}
6123
6124
/************************************************************************/
6125
/*                               Close()                                */
6126
/************************************************************************/
6127
6128
int VSIAppendWriteHandle::Close()
6129
0
{
6130
0
    int nRet = 0;
6131
0
    if (!m_bClosed)
6132
0
    {
6133
0
        m_bClosed = true;
6134
0
        if (!m_bError && !Send(true))
6135
0
            nRet = -1;
6136
0
    }
6137
0
    return nRet;
6138
0
}
6139
6140
/************************************************************************/
6141
/*                         CurlRequestHelper()                          */
6142
/************************************************************************/
6143
6144
CurlRequestHelper::CurlRequestHelper()
6145
2.66k
{
6146
2.66k
    VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
6147
2.66k
    VSICURLInitWriteFuncStruct(&sWriteFuncHeaderData, nullptr, nullptr,
6148
2.66k
                               nullptr);
6149
2.66k
}
6150
6151
/************************************************************************/
6152
/*                         ~CurlRequestHelper()                         */
6153
/************************************************************************/
6154
6155
CurlRequestHelper::~CurlRequestHelper()
6156
2.66k
{
6157
2.66k
    CPLFree(sWriteFuncData.pBuffer);
6158
2.66k
    CPLFree(sWriteFuncHeaderData.pBuffer);
6159
2.66k
}
6160
6161
/************************************************************************/
6162
/*                              perform()                               */
6163
/************************************************************************/
6164
6165
long CurlRequestHelper::perform(CURL *hCurlHandle, struct curl_slist *headers,
6166
                                VSICurlFilesystemHandlerBase *poFS,
6167
                                IVSIS3LikeHandleHelper *poS3HandleHelper)
6168
2.66k
{
6169
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
6170
6171
2.66k
    poS3HandleHelper->ResetQueryParameters();
6172
6173
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
6174
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
6175
2.66k
                               VSICurlHandleWriteFunc);
6176
6177
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
6178
2.66k
                               &sWriteFuncHeaderData);
6179
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
6180
2.66k
                               VSICurlHandleWriteFunc);
6181
6182
2.66k
    szCurlErrBuf[0] = '\0';
6183
2.66k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf);
6184
6185
2.66k
    VSICURLMultiPerform(poFS->GetCurlMultiHandleFor(poS3HandleHelper->GetURL()),
6186
2.66k
                        hCurlHandle);
6187
6188
2.66k
    VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
6189
6190
2.66k
    curl_slist_free_all(headers);
6191
6192
2.66k
    long response_code = 0;
6193
2.66k
    curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
6194
2.66k
    return response_code;
6195
2.66k
}
6196
6197
/************************************************************************/
6198
/*                       NetworkStatisticsLogger                        */
6199
/************************************************************************/
6200
6201
// Global variable
6202
NetworkStatisticsLogger NetworkStatisticsLogger::gInstance{};
6203
int NetworkStatisticsLogger::gnEnabled = -1;  // unknown state
6204
6205
static void ShowNetworkStats()
6206
0
{
6207
0
    printf("Network statistics:\n%s\n",  // ok
6208
0
           NetworkStatisticsLogger::GetReportAsSerializedJSON().c_str());
6209
0
}
6210
6211
void NetworkStatisticsLogger::ReadEnabled()
6212
16
{
6213
16
    const bool bShowNetworkStats =
6214
16
        CPLTestBool(CPLGetConfigOption("CPL_VSIL_SHOW_NETWORK_STATS", "NO"));
6215
16
    gnEnabled =
6216
16
        (bShowNetworkStats || CPLTestBool(CPLGetConfigOption(
6217
16
                                  "CPL_VSIL_NETWORK_STATS_ENABLED", "NO")))
6218
16
            ? TRUE
6219
16
            : FALSE;
6220
16
    if (bShowNetworkStats)
6221
0
    {
6222
0
        static bool bRegistered = false;
6223
0
        if (!bRegistered)
6224
0
        {
6225
0
            bRegistered = true;
6226
0
            atexit(ShowNetworkStats);
6227
0
        }
6228
0
    }
6229
16
}
6230
6231
void NetworkStatisticsLogger::EnterFileSystem(const char *pszName)
6232
521k
{
6233
521k
    if (!IsEnabled())
6234
521k
        return;
6235
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6236
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6237
0
        ContextPathItem(ContextPathType::FILESYSTEM, pszName));
6238
0
}
6239
6240
void NetworkStatisticsLogger::LeaveFileSystem()
6241
521k
{
6242
521k
    if (!IsEnabled())
6243
521k
        return;
6244
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6245
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6246
0
}
6247
6248
void NetworkStatisticsLogger::EnterFile(const char *pszName)
6249
140k
{
6250
140k
    if (!IsEnabled())
6251
140k
        return;
6252
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6253
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6254
0
        ContextPathItem(ContextPathType::FILE, pszName));
6255
0
}
6256
6257
void NetworkStatisticsLogger::LeaveFile()
6258
140k
{
6259
140k
    if (!IsEnabled())
6260
140k
        return;
6261
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6262
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6263
0
}
6264
6265
void NetworkStatisticsLogger::EnterAction(const char *pszName)
6266
521k
{
6267
521k
    if (!IsEnabled())
6268
521k
        return;
6269
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6270
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].push_back(
6271
0
        ContextPathItem(ContextPathType::ACTION, pszName));
6272
0
}
6273
6274
void NetworkStatisticsLogger::LeaveAction()
6275
521k
{
6276
521k
    if (!IsEnabled())
6277
521k
        return;
6278
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6279
0
    gInstance.m_mapThreadIdToContextPath[CPLGetPID()].pop_back();
6280
0
}
6281
6282
std::vector<NetworkStatisticsLogger::Counters *>
6283
NetworkStatisticsLogger::GetCountersForContext()
6284
0
{
6285
0
    std::vector<Counters *> v;
6286
0
    const auto &contextPath = gInstance.m_mapThreadIdToContextPath[CPLGetPID()];
6287
6288
0
    Stats *curStats = &m_stats;
6289
0
    v.push_back(&(curStats->counters));
6290
6291
0
    bool inFileSystem = false;
6292
0
    bool inFile = false;
6293
0
    bool inAction = false;
6294
0
    for (const auto &item : contextPath)
6295
0
    {
6296
0
        if (item.eType == ContextPathType::FILESYSTEM)
6297
0
        {
6298
0
            if (inFileSystem)
6299
0
                continue;
6300
0
            inFileSystem = true;
6301
0
        }
6302
0
        else if (item.eType == ContextPathType::FILE)
6303
0
        {
6304
0
            if (inFile)
6305
0
                continue;
6306
0
            inFile = true;
6307
0
        }
6308
0
        else if (item.eType == ContextPathType::ACTION)
6309
0
        {
6310
0
            if (inAction)
6311
0
                continue;
6312
0
            inAction = true;
6313
0
        }
6314
6315
0
        curStats = &(curStats->children[item]);
6316
0
        v.push_back(&(curStats->counters));
6317
0
    }
6318
6319
0
    return v;
6320
0
}
6321
6322
void NetworkStatisticsLogger::LogGET(size_t nDownloadedBytes)
6323
28.2k
{
6324
28.2k
    if (!IsEnabled())
6325
28.2k
        return;
6326
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6327
0
    for (auto counters : gInstance.GetCountersForContext())
6328
0
    {
6329
0
        counters->nGET++;
6330
0
        counters->nGETDownloadedBytes += nDownloadedBytes;
6331
0
    }
6332
0
}
6333
6334
void NetworkStatisticsLogger::LogPUT(size_t nUploadedBytes)
6335
0
{
6336
0
    if (!IsEnabled())
6337
0
        return;
6338
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6339
0
    for (auto counters : gInstance.GetCountersForContext())
6340
0
    {
6341
0
        counters->nPUT++;
6342
0
        counters->nPUTUploadedBytes += nUploadedBytes;
6343
0
    }
6344
0
}
6345
6346
void NetworkStatisticsLogger::LogHEAD()
6347
92.5k
{
6348
92.5k
    if (!IsEnabled())
6349
92.5k
        return;
6350
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6351
0
    for (auto counters : gInstance.GetCountersForContext())
6352
0
    {
6353
0
        counters->nHEAD++;
6354
0
    }
6355
0
}
6356
6357
void NetworkStatisticsLogger::LogPOST(size_t nUploadedBytes,
6358
                                      size_t nDownloadedBytes)
6359
0
{
6360
0
    if (!IsEnabled())
6361
0
        return;
6362
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6363
0
    for (auto counters : gInstance.GetCountersForContext())
6364
0
    {
6365
0
        counters->nPOST++;
6366
0
        counters->nPOSTUploadedBytes += nUploadedBytes;
6367
0
        counters->nPOSTDownloadedBytes += nDownloadedBytes;
6368
0
    }
6369
0
}
6370
6371
void NetworkStatisticsLogger::LogDELETE()
6372
0
{
6373
0
    if (!IsEnabled())
6374
0
        return;
6375
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6376
0
    for (auto counters : gInstance.GetCountersForContext())
6377
0
    {
6378
0
        counters->nDELETE++;
6379
0
    }
6380
0
}
6381
6382
void NetworkStatisticsLogger::Reset()
6383
0
{
6384
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6385
0
    gInstance.m_stats = Stats();
6386
0
    gnEnabled = -1;
6387
0
}
6388
6389
void NetworkStatisticsLogger::Stats::AsJSON(CPLJSONObject &oJSON) const
6390
0
{
6391
0
    CPLJSONObject oMethods;
6392
0
    if (counters.nHEAD)
6393
0
        oMethods.Add("HEAD/count", counters.nHEAD);
6394
0
    if (counters.nGET)
6395
0
        oMethods.Add("GET/count", counters.nGET);
6396
0
    if (counters.nGETDownloadedBytes)
6397
0
        oMethods.Add("GET/downloaded_bytes", counters.nGETDownloadedBytes);
6398
0
    if (counters.nPUT)
6399
0
        oMethods.Add("PUT/count", counters.nPUT);
6400
0
    if (counters.nPUTUploadedBytes)
6401
0
        oMethods.Add("PUT/uploaded_bytes", counters.nPUTUploadedBytes);
6402
0
    if (counters.nPOST)
6403
0
        oMethods.Add("POST/count", counters.nPOST);
6404
0
    if (counters.nPOSTUploadedBytes)
6405
0
        oMethods.Add("POST/uploaded_bytes", counters.nPOSTUploadedBytes);
6406
0
    if (counters.nPOSTDownloadedBytes)
6407
0
        oMethods.Add("POST/downloaded_bytes", counters.nPOSTDownloadedBytes);
6408
0
    if (counters.nDELETE)
6409
0
        oMethods.Add("DELETE/count", counters.nDELETE);
6410
0
    oJSON.Add("methods", oMethods);
6411
0
    CPLJSONObject oFiles;
6412
0
    bool bFilesAdded = false;
6413
0
    for (const auto &kv : children)
6414
0
    {
6415
0
        CPLJSONObject childJSON;
6416
0
        kv.second.AsJSON(childJSON);
6417
0
        if (kv.first.eType == ContextPathType::FILESYSTEM)
6418
0
        {
6419
0
            std::string osName(kv.first.osName);
6420
0
            if (!osName.empty() && osName[0] == '/')
6421
0
                osName = osName.substr(1);
6422
0
            if (!osName.empty() && osName.back() == '/')
6423
0
                osName.pop_back();
6424
0
            oJSON.Add(("handlers/" + osName).c_str(), childJSON);
6425
0
        }
6426
0
        else if (kv.first.eType == ContextPathType::FILE)
6427
0
        {
6428
0
            if (!bFilesAdded)
6429
0
            {
6430
0
                bFilesAdded = true;
6431
0
                oJSON.Add("files", oFiles);
6432
0
            }
6433
0
            oFiles.AddNoSplitName(kv.first.osName.c_str(), childJSON);
6434
0
        }
6435
0
        else if (kv.first.eType == ContextPathType::ACTION)
6436
0
        {
6437
0
            oJSON.Add(("actions/" + kv.first.osName).c_str(), childJSON);
6438
0
        }
6439
0
    }
6440
0
}
6441
6442
std::string NetworkStatisticsLogger::GetReportAsSerializedJSON()
6443
0
{
6444
0
    std::lock_guard<std::mutex> oLock(gInstance.m_mutex);
6445
6446
0
    CPLJSONObject oJSON;
6447
0
    gInstance.m_stats.AsJSON(oJSON);
6448
0
    return oJSON.Format(CPLJSONObject::PrettyFormat::Pretty);
6449
0
}
6450
6451
} /* end of namespace cpl */
6452
6453
/************************************************************************/
6454
/*                    VSICurlParseUnixPermissions()                     */
6455
/************************************************************************/
6456
6457
int VSICurlParseUnixPermissions(const char *pszPermissions)
6458
0
{
6459
0
    if (strlen(pszPermissions) != 9)
6460
0
        return 0;
6461
0
    int nMode = 0;
6462
0
    if (pszPermissions[0] == 'r')
6463
0
        nMode |= S_IRUSR;
6464
0
    if (pszPermissions[1] == 'w')
6465
0
        nMode |= S_IWUSR;
6466
0
    if (pszPermissions[2] == 'x')
6467
0
        nMode |= S_IXUSR;
6468
0
    if (pszPermissions[3] == 'r')
6469
0
        nMode |= S_IRGRP;
6470
0
    if (pszPermissions[4] == 'w')
6471
0
        nMode |= S_IWGRP;
6472
0
    if (pszPermissions[5] == 'x')
6473
0
        nMode |= S_IXGRP;
6474
0
    if (pszPermissions[6] == 'r')
6475
0
        nMode |= S_IROTH;
6476
0
    if (pszPermissions[7] == 'w')
6477
0
        nMode |= S_IWOTH;
6478
0
    if (pszPermissions[8] == 'x')
6479
0
        nMode |= S_IXOTH;
6480
0
    return nMode;
6481
0
}
6482
6483
/************************************************************************/
6484
/*                      Cache of file properties.                       */
6485
/************************************************************************/
6486
6487
static std::mutex oCacheFilePropMutex;
6488
static lru11::Cache<std::string, cpl::FileProp> *poCacheFileProp = nullptr;
6489
6490
/************************************************************************/
6491
/*                      VSICURLGetCachedFileProp()                      */
6492
/************************************************************************/
6493
6494
bool VSICURLGetCachedFileProp(const char *pszURL, cpl::FileProp &oFileProp)
6495
692k
{
6496
692k
    std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6497
692k
    return poCacheFileProp != nullptr &&
6498
690k
           poCacheFileProp->tryGet(std::string(pszURL), oFileProp) &&
6499
           // Let a chance to use new auth parameters
6500
249k
           !(oFileProp.eExists == cpl::EXIST_NO &&
6501
243k
             gnGenerationAuthParameters != oFileProp.nGenerationAuthParameters);
6502
692k
}
6503
6504
/************************************************************************/
6505
/*                      VSICURLSetCachedFileProp()                      */
6506
/************************************************************************/
6507
6508
void VSICURLSetCachedFileProp(const char *pszURL, cpl::FileProp &oFileProp)
6509
34.9k
{
6510
34.9k
    std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6511
34.9k
    if (poCacheFileProp == nullptr)
6512
16
        poCacheFileProp =
6513
16
            new lru11::Cache<std::string, cpl::FileProp>(100 * 1024);
6514
34.9k
    oFileProp.nGenerationAuthParameters = gnGenerationAuthParameters;
6515
34.9k
    poCacheFileProp->insert(std::string(pszURL), oFileProp);
6516
34.9k
}
6517
6518
/************************************************************************/
6519
/*                  VSICURLInvalidateCachedFileProp()                   */
6520
/************************************************************************/
6521
6522
void VSICURLInvalidateCachedFileProp(const char *pszURL)
6523
0
{
6524
0
    std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6525
0
    if (poCacheFileProp != nullptr)
6526
0
        poCacheFileProp->remove(std::string(pszURL));
6527
0
}
6528
6529
/************************************************************************/
6530
/*               VSICURLInvalidateCachedFilePropPrefix()                */
6531
/************************************************************************/
6532
6533
void VSICURLInvalidateCachedFilePropPrefix(const char *pszURL)
6534
0
{
6535
0
    std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6536
0
    if (poCacheFileProp != nullptr)
6537
0
    {
6538
0
        std::list<std::string> keysToRemove;
6539
0
        const size_t nURLSize = strlen(pszURL);
6540
0
        auto lambda =
6541
0
            [&keysToRemove, &pszURL, nURLSize](
6542
0
                const lru11::KeyValuePair<std::string, cpl::FileProp> &kv)
6543
0
        {
6544
0
            if (strncmp(kv.key.c_str(), pszURL, nURLSize) == 0)
6545
0
                keysToRemove.push_back(kv.key);
6546
0
        };
6547
0
        poCacheFileProp->cwalk(lambda);
6548
0
        for (const auto &key : keysToRemove)
6549
0
            poCacheFileProp->remove(key);
6550
0
    }
6551
0
}
6552
6553
/************************************************************************/
6554
/*                    VSICURLDestroyCacheFileProp()                     */
6555
/************************************************************************/
6556
6557
void VSICURLDestroyCacheFileProp()
6558
0
{
6559
0
    std::lock_guard<std::mutex> oLock(oCacheFilePropMutex);
6560
0
    delete poCacheFileProp;
6561
0
    poCacheFileProp = nullptr;
6562
0
}
6563
6564
/************************************************************************/
6565
/*                        VSICURLMultiCleanup()                         */
6566
/************************************************************************/
6567
6568
void VSICURLMultiCleanup(CURLM *hCurlMultiHandle)
6569
43
{
6570
#if defined(CURL_AT_LEAST_VERSION) && defined(_WIN32)
6571
    // Since curl 8.20.0, auxiliary threads are used for DNS resolution
6572
    // Trying to join them when detaching the DLL results in a hang.
6573
    // See https://github.com/curl/curl/issues/21466#issuecomment-4372138595
6574
#if CURL_AT_LEAST_VERSION(8, 20, 0)
6575
    if (GDALIsInGlobalDestructorFromDLLMain())
6576
        curl_multi_setopt(hCurlMultiHandle, CURLMOPT_QUICK_EXIT, 1L);
6577
#endif
6578
#endif
6579
6580
43
    void *old_handler = CPLHTTPIgnoreSigPipe();
6581
43
    curl_multi_cleanup(hCurlMultiHandle);
6582
43
    CPLHTTPRestoreSigPipeHandler(old_handler);
6583
43
}
6584
6585
/************************************************************************/
6586
/*                       VSICurlInstallReadCbk()                        */
6587
/************************************************************************/
6588
6589
int VSICurlInstallReadCbk(VSILFILE *fp, VSICurlReadCbkFunc pfnReadCbk,
6590
                          void *pfnUserData, int bStopOnInterruptUntilUninstall)
6591
0
{
6592
0
    return reinterpret_cast<cpl::VSICurlHandle *>(fp)->InstallReadCbk(
6593
0
        pfnReadCbk, pfnUserData, bStopOnInterruptUntilUninstall);
6594
0
}
6595
6596
/************************************************************************/
6597
/*                      VSICurlUninstallReadCbk()                       */
6598
/************************************************************************/
6599
6600
int VSICurlUninstallReadCbk(VSILFILE *fp)
6601
0
{
6602
0
    return reinterpret_cast<cpl::VSICurlHandle *>(fp)->UninstallReadCbk();
6603
0
}
6604
6605
/************************************************************************/
6606
/*                         VSICurlSetOptions()                          */
6607
/************************************************************************/
6608
6609
struct curl_slist *VSICurlSetOptions(CURL *hCurlHandle, const char *pszURL,
6610
                                     const char *const *papszOptions)
6611
125k
{
6612
125k
    struct curl_slist *headers = static_cast<struct curl_slist *>(
6613
125k
        CPLHTTPSetOptions(hCurlHandle, pszURL, papszOptions));
6614
6615
125k
    long option = CURLFTPMETHOD_SINGLECWD;
6616
125k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FTP_FILEMETHOD, option);
6617
6618
    // ftp://ftp2.cits.rncan.gc.ca/pub/cantopo/250k_tif/
6619
    // doesn't like EPSV command,
6620
125k
    unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_FTP_USE_EPSV, 0);
6621
6622
125k
    return headers;
6623
125k
}
6624
6625
/************************************************************************/
6626
/*                    VSICurlSetContentTypeFromExt()                    */
6627
/************************************************************************/
6628
6629
struct curl_slist *VSICurlSetContentTypeFromExt(struct curl_slist *poList,
6630
                                                const char *pszPath)
6631
0
{
6632
0
    struct curl_slist *iter = poList;
6633
0
    while (iter != nullptr)
6634
0
    {
6635
0
        if (STARTS_WITH_CI(iter->data, "Content-Type"))
6636
0
        {
6637
0
            return poList;
6638
0
        }
6639
0
        iter = iter->next;
6640
0
    }
6641
6642
0
    static const struct
6643
0
    {
6644
0
        const char *ext;
6645
0
        const char *mime;
6646
0
    } aosExtMimePairs[] = {
6647
0
        {"txt", "text/plain"}, {"json", "application/json"},
6648
0
        {"tif", "image/tiff"}, {"tiff", "image/tiff"},
6649
0
        {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"},
6650
0
        {"jp2", "image/jp2"},  {"jpx", "image/jp2"},
6651
0
        {"j2k", "image/jp2"},  {"jpc", "image/jp2"},
6652
0
        {"png", "image/png"},
6653
0
    };
6654
6655
0
    const std::string osExt = CPLGetExtensionSafe(pszPath);
6656
0
    if (!osExt.empty())
6657
0
    {
6658
0
        for (const auto &pair : aosExtMimePairs)
6659
0
        {
6660
0
            if (EQUAL(osExt.c_str(), pair.ext))
6661
0
            {
6662
6663
0
                const std::string osContentType(
6664
0
                    CPLSPrintf("Content-Type: %s", pair.mime));
6665
0
                poList = curl_slist_append(poList, osContentType.c_str());
6666
#ifdef DEBUG_VERBOSE
6667
                CPLDebug("HTTP", "Setting %s, based on lookup table.",
6668
                         osContentType.c_str());
6669
#endif
6670
0
                break;
6671
0
            }
6672
0
        }
6673
0
    }
6674
6675
0
    return poList;
6676
0
}
6677
6678
/************************************************************************/
6679
/*                VSICurlSetCreationHeadersFromOptions()                */
6680
/************************************************************************/
6681
6682
struct curl_slist *VSICurlSetCreationHeadersFromOptions(
6683
    struct curl_slist *headers, CSLConstList papszOptions, const char *pszPath)
6684
0
{
6685
0
    bool bContentTypeFound = false;
6686
0
    for (CSLConstList papszIter = papszOptions; papszIter && *papszIter;
6687
0
         ++papszIter)
6688
0
    {
6689
0
        char *pszKey = nullptr;
6690
0
        const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
6691
0
        if (pszKey && pszValue)
6692
0
        {
6693
0
            if (EQUAL(pszKey, "Content-Type"))
6694
0
            {
6695
0
                bContentTypeFound = true;
6696
0
            }
6697
0
            headers = curl_slist_append(headers,
6698
0
                                        CPLSPrintf("%s: %s", pszKey, pszValue));
6699
0
        }
6700
0
        CPLFree(pszKey);
6701
0
    }
6702
6703
    // If Content-type not found in papszOptions, try to set it from the
6704
    // filename exstension.
6705
0
    if (!bContentTypeFound)
6706
0
    {
6707
0
        headers = VSICurlSetContentTypeFromExt(headers, pszPath);
6708
0
    }
6709
6710
0
    return headers;
6711
0
}
6712
6713
#endif  // DOXYGEN_SKIP
6714
//! @endcond
6715
6716
/************************************************************************/
6717
/*                     VSIInstallCurlFileHandler()                      */
6718
/************************************************************************/
6719
6720
/*!
6721
 \brief Install /vsicurl/ HTTP/FTP file system handler (requires libcurl)
6722
6723
 \verbatim embed:rst
6724
 See :ref:`/vsicurl/ documentation <vsicurl>`
6725
 \endverbatim
6726
6727
 */
6728
void VSIInstallCurlFileHandler(void)
6729
82
{
6730
82
    auto poHandler = std::make_shared<cpl::VSICurlFilesystemHandler>();
6731
82
    for (const char *pszPrefix : VSICURL_PREFIXES)
6732
164
    {
6733
164
        VSIFileManager::InstallHandler(pszPrefix, poHandler);
6734
164
    }
6735
82
}
6736
6737
/************************************************************************/
6738
/*                         VSICurlClearCache()                          */
6739
/************************************************************************/
6740
6741
/**
6742
 * \brief Clean local cache associated with /vsicurl/ (and related file systems)
6743
 *
6744
 * /vsicurl (and related file systems like /vsis3/, /vsigs/, /vsiaz/, /vsioss/,
6745
 * /vsiswift/) cache a number of
6746
 * metadata and data for faster execution in read-only scenarios. But when the
6747
 * content on the server-side may change during the same process, those
6748
 * mechanisms can prevent opening new files, or give an outdated version of
6749
 * them.
6750
 *
6751
 */
6752
6753
void VSICurlClearCache(void)
6754
0
{
6755
    // FIXME ? Currently we have different filesystem instances for
6756
    // vsicurl/, /vsis3/, /vsigs/ . So each one has its own cache of regions.
6757
    // File properties cache are now shared
6758
0
    char **papszPrefix = VSIFileManager::GetPrefixes();
6759
0
    for (size_t i = 0; papszPrefix && papszPrefix[i]; ++i)
6760
0
    {
6761
0
        auto poFSHandler = dynamic_cast<cpl::VSICurlFilesystemHandlerBase *>(
6762
0
            VSIFileManager::GetHandler(papszPrefix[i]));
6763
6764
0
        if (poFSHandler)
6765
0
            poFSHandler->ClearCache();
6766
0
    }
6767
0
    CSLDestroy(papszPrefix);
6768
6769
0
    VSICurlStreamingClearCache();
6770
0
}
6771
6772
/************************************************************************/
6773
/*                      VSICurlPartialClearCache()                      */
6774
/************************************************************************/
6775
6776
/**
6777
 * \brief Clean local cache associated with /vsicurl/ (and related file systems)
6778
 * for a given filename (and its subfiles and subdirectories if it is a
6779
 * directory)
6780
 *
6781
 * /vsicurl (and related file systems like /vsis3/, /vsigs/, /vsiaz/, /vsioss/,
6782
 * /vsiswift/) cache a number of
6783
 * metadata and data for faster execution in read-only scenarios. But when the
6784
 * content on the server-side may change during the same process, those
6785
 * mechanisms can prevent opening new files, or give an outdated version of
6786
 * them.
6787
 *
6788
 * The filename prefix must start with the name of a known virtual file system
6789
 * (such as "/vsicurl/", "/vsis3/")
6790
 *
6791
 * VSICurlPartialClearCache("/vsis3/b") will clear all cached state for any file
6792
 * or directory starting with that prefix, so potentially "/vsis3/bucket",
6793
 * "/vsis3/basket/" or "/vsis3/basket/object".
6794
 *
6795
 * @param pszFilenamePrefix Filename prefix
6796
 */
6797
6798
void VSICurlPartialClearCache(const char *pszFilenamePrefix)
6799
0
{
6800
0
    auto poFSHandler = dynamic_cast<cpl::VSICurlFilesystemHandlerBase *>(
6801
0
        VSIFileManager::GetHandler(pszFilenamePrefix));
6802
6803
0
    if (poFSHandler)
6804
0
        poFSHandler->PartialClearCache(pszFilenamePrefix);
6805
0
}
6806
6807
/************************************************************************/
6808
/*                        VSINetworkStatsReset()                        */
6809
/************************************************************************/
6810
6811
/**
6812
 * \brief Clear network related statistics.
6813
 *
6814
 * The effect of the CPL_VSIL_NETWORK_STATS_ENABLED configuration option
6815
 * will also be reset. That is, that the next network access will check its
6816
 * value again.
6817
 *
6818
 * @since GDAL 3.2.0
6819
 */
6820
6821
void VSINetworkStatsReset(void)
6822
0
{
6823
0
    cpl::NetworkStatisticsLogger::Reset();
6824
0
}
6825
6826
/************************************************************************/
6827
/*                 VSINetworkStatsGetAsSerializedJSON()                 */
6828
/************************************************************************/
6829
6830
/**
6831
 * \brief Return network related statistics, as a JSON serialized object.
6832
 *
6833
 * Statistics collecting should be enabled with the
6834
 CPL_VSIL_NETWORK_STATS_ENABLED
6835
 * configuration option set to YES before any network activity starts
6836
 * (for efficiency, reading it is cached on first access, until
6837
 VSINetworkStatsReset() is called)
6838
 *
6839
 * Statistics can also be emitted on standard output at process termination if
6840
 * the CPL_VSIL_SHOW_NETWORK_STATS configuration option is set to YES.
6841
 *
6842
 * Example of output:
6843
 * \code{.js}
6844
 * {
6845
 *   "methods":{
6846
 *     "GET":{
6847
 *       "count":6,
6848
 *       "downloaded_bytes":40825
6849
 *     },
6850
 *     "PUT":{
6851
 *       "count":1,
6852
 *       "uploaded_bytes":35472
6853
 *     }
6854
 *   },
6855
 *   "handlers":{
6856
 *     "vsigs":{
6857
 *       "methods":{
6858
 *         "GET":{
6859
 *           "count":2,
6860
 *           "downloaded_bytes":446
6861
 *         },
6862
 *         "PUT":{
6863
 *           "count":1,
6864
 *           "uploaded_bytes":35472
6865
 *         }
6866
 *       },
6867
 *       "files":{
6868
 *         "\/vsigs\/spatialys\/byte.tif":{
6869
 *           "methods":{
6870
 *             "PUT":{
6871
 *               "count":1,
6872
 *               "uploaded_bytes":35472
6873
 *             }
6874
 *           },
6875
 *           "actions":{
6876
 *             "Write":{
6877
 *               "methods":{
6878
 *                 "PUT":{
6879
 *                   "count":1,
6880
 *                   "uploaded_bytes":35472
6881
 *                 }
6882
 *               }
6883
 *             }
6884
 *           }
6885
 *         }
6886
 *       },
6887
 *       "actions":{
6888
 *         "Stat":{
6889
 *           "methods":{
6890
 *             "GET":{
6891
 *               "count":2,
6892
 *               "downloaded_bytes":446
6893
 *             }
6894
 *           },
6895
 *           "files":{
6896
 *             "\/vsigs\/spatialys\/byte.tif\/":{
6897
 *               "methods":{
6898
 *                 "GET":{
6899
 *                   "count":1,
6900
 *                   "downloaded_bytes":181
6901
 *                 }
6902
 *               }
6903
 *             }
6904
 *           }
6905
 *         }
6906
 *       }
6907
 *     },
6908
 *     "vsis3":{
6909
 *          [...]
6910
 *     }
6911
 *   }
6912
 * }
6913
 * \endcode
6914
 *
6915
 * @param papszOptions Unused.
6916
 * @return a JSON serialized string to free with VSIFree(), or nullptr
6917
 * @since GDAL 3.2.0
6918
 */
6919
6920
char *VSINetworkStatsGetAsSerializedJSON(CPL_UNUSED char **papszOptions)
6921
0
{
6922
0
    return CPLStrdup(
6923
0
        cpl::NetworkStatisticsLogger::GetReportAsSerializedJSON().c_str());
6924
0
}
6925
6926
#endif /* HAVE_CURL */
6927
6928
#undef ENABLE_DEBUG