Coverage Report

Created: 2025-08-28 06:57

/src/gdal/port/cpl_vsil_abstract_archive.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for archive files.
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "cpl_vsi_virtual.h"
15
16
#include <cstring>
17
#include <ctime>
18
#include <fcntl.h>
19
#include <map>
20
#include <set>
21
#include <string>
22
#include <utility>
23
#include <vector>
24
25
#include "cpl_conv.h"
26
#include "cpl_error.h"
27
#include "cpl_multiproc.h"
28
#include "cpl_string.h"
29
#include "cpl_vsi.h"
30
31
//! @cond Doxygen_Suppress
32
33
static bool IsEitherSlash(char c)
34
27.0k
{
35
27.0k
    return c == '/' || c == '\\';
36
27.0k
}
37
38
/************************************************************************/
39
/*                    ~VSIArchiveEntryFileOffset()                      */
40
/************************************************************************/
41
42
0
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
43
44
/************************************************************************/
45
/*                        ~VSIArchiveReader()                           */
46
/************************************************************************/
47
48
2.65k
VSIArchiveReader::~VSIArchiveReader() = default;
49
50
/************************************************************************/
51
/*                        ~VSIArchiveContent()                          */
52
/************************************************************************/
53
54
0
VSIArchiveContent::~VSIArchiveContent() = default;
55
56
/************************************************************************/
57
/*                   VSIArchiveFilesystemHandler()                      */
58
/************************************************************************/
59
60
6
VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler() = default;
61
62
/************************************************************************/
63
/*                   ~VSIArchiveFilesystemHandler()                     */
64
/************************************************************************/
65
66
0
VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler() = default;
67
68
/************************************************************************/
69
/*                       GetStrippedFilename()                          */
70
/************************************************************************/
71
72
static std::string GetStrippedFilename(const std::string &osFileName,
73
                                       bool &bIsDir)
74
0
{
75
0
    bIsDir = false;
76
0
    int nStartPos = 0;
77
78
    // Remove ./ pattern at the beginning of a filename.
79
0
    if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
80
0
    {
81
0
        nStartPos = 2;
82
0
        if (osFileName.size() == 2)
83
0
            return std::string();
84
0
    }
85
86
0
    std::string ret(osFileName, nStartPos);
87
0
    for (char &c : ret)
88
0
    {
89
0
        if (c == '\\')
90
0
            c = '/';
91
0
    }
92
93
0
    bIsDir = !ret.empty() && ret.back() == '/';
94
0
    if (bIsDir)
95
0
    {
96
        // Remove trailing slash.
97
0
        ret.pop_back();
98
0
    }
99
0
    return ret;
100
0
}
101
102
/************************************************************************/
103
/*                        BuildDirectoryIndex()                         */
104
/************************************************************************/
105
106
static void BuildDirectoryIndex(VSIArchiveContent *content)
107
0
{
108
0
    content->dirIndex.clear();
109
0
    const int nEntries = static_cast<int>(content->entries.size());
110
0
    for (int i = 0; i < nEntries; i++)
111
0
    {
112
0
        const char *fileName = content->entries[i].fileName.c_str();
113
0
        std::string parentDir = CPLGetPathSafe(fileName);
114
0
        content->dirIndex[parentDir].push_back(i);
115
0
    }
116
0
}
117
118
/************************************************************************/
119
/*                       GetContentOfArchive()                          */
120
/************************************************************************/
121
122
const VSIArchiveContent *
123
VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
124
                                                 VSIArchiveReader *poReader)
125
1.33k
{
126
1.33k
    std::unique_lock oLock(oMutex);
127
128
1.33k
    VSIStatBufL sStat;
129
1.33k
    if (VSIStatL(archiveFilename, &sStat) != 0)
130
112
        return nullptr;
131
132
1.22k
    auto oIter = oFileList.find(archiveFilename);
133
1.22k
    if (oIter != oFileList.end())
134
0
    {
135
0
        const VSIArchiveContent *content = oIter->second.get();
136
0
        if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
137
0
            static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
138
0
        {
139
0
            CPLDebug("VSIArchive",
140
0
                     "The content of %s has changed since it was cached",
141
0
                     archiveFilename);
142
0
            oFileList.erase(archiveFilename);
143
0
        }
144
0
        else
145
0
        {
146
0
            return content;
147
0
        }
148
0
    }
149
150
1.22k
    std::unique_ptr<VSIArchiveReader> temporaryReader;  // keep in that scope
151
1.22k
    if (poReader == nullptr)
152
1.22k
    {
153
1.22k
        temporaryReader = CreateReader(archiveFilename);
154
1.22k
        poReader = temporaryReader.get();
155
1.22k
        if (!poReader)
156
1.22k
            return nullptr;
157
1.22k
    }
158
159
0
    if (poReader->GotoFirstFile() == FALSE)
160
0
    {
161
0
        return nullptr;
162
0
    }
163
164
0
    auto content = std::make_unique<VSIArchiveContent>();
165
0
    content->mTime = sStat.st_mtime;
166
0
    content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
167
168
0
    std::set<std::string> oSet;
169
170
0
    do
171
0
    {
172
0
        bool bIsDir = false;
173
0
        std::string osStrippedFilename =
174
0
            GetStrippedFilename(poReader->GetFileName(), bIsDir);
175
0
        if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
176
0
            osStrippedFilename.find("//") != std::string::npos)
177
0
        {
178
0
            continue;
179
0
        }
180
181
0
        if (oSet.find(osStrippedFilename) == oSet.end())
182
0
        {
183
0
            oSet.insert(osStrippedFilename);
184
185
            // Add intermediate directory structure.
186
0
            for (size_t i = 0; i < osStrippedFilename.size(); ++i)
187
0
            {
188
0
                if (osStrippedFilename[i] == '/')
189
0
                {
190
0
                    std::string osSubdirName(osStrippedFilename, 0, i);
191
0
                    if (oSet.find(osSubdirName) == oSet.end())
192
0
                    {
193
0
                        oSet.insert(osSubdirName);
194
195
0
                        VSIArchiveEntry entry;
196
0
                        entry.fileName = std::move(osSubdirName);
197
0
                        entry.nModifiedTime = poReader->GetModifiedTime();
198
0
                        entry.bIsDir = true;
199
#ifdef DEBUG_VERBOSE
200
                        CPLDebug(
201
                            "VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
202
                            static_cast<int>(content->entries.size() + 1),
203
                            entry.fileName.c_str(), entry.uncompressed_size);
204
#endif
205
0
                        content->entries.push_back(std::move(entry));
206
0
                    }
207
0
                }
208
0
            }
209
210
0
            VSIArchiveEntry entry;
211
0
            entry.fileName = std::move(osStrippedFilename);
212
0
            entry.nModifiedTime = poReader->GetModifiedTime();
213
0
            entry.uncompressed_size = poReader->GetFileSize();
214
0
            entry.bIsDir = bIsDir;
215
0
            entry.file_pos.reset(poReader->GetFileOffset());
216
#ifdef DEBUG_VERBOSE
217
            CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
218
                     static_cast<int>(content->entries.size() + 1),
219
                     entry.fileName.c_str(), entry.uncompressed_size);
220
#endif
221
0
            content->entries.push_back(std::move(entry));
222
0
        }
223
224
0
    } while (poReader->GotoNextFile());
225
226
    // Build directory index for fast lookups
227
0
    BuildDirectoryIndex(content.get());
228
229
0
    return oFileList
230
0
        .insert(std::pair<CPLString, std::unique_ptr<VSIArchiveContent>>(
231
0
            archiveFilename, std::move(content)))
232
0
        .first->second.get();
233
0
}
234
235
/************************************************************************/
236
/*                        FindFileInArchive()                           */
237
/************************************************************************/
238
239
bool VSIArchiveFilesystemHandler::FindFileInArchive(
240
    const char *archiveFilename, const char *fileInArchiveName,
241
    const VSIArchiveEntry **archiveEntry)
242
1.33k
{
243
1.33k
    CPLAssert(fileInArchiveName);
244
245
1.33k
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
246
1.33k
    if (content)
247
0
    {
248
0
        const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
249
250
        // Use directory index to search within parent directory's children
251
0
        auto dirIter = content->dirIndex.find(parentDir);
252
0
        if (dirIter != content->dirIndex.end())
253
0
        {
254
0
            const std::vector<int> &childIndices = dirIter->second;
255
0
            for (int childIdx : childIndices)
256
0
            {
257
0
                if (content->entries[childIdx].fileName == fileInArchiveName)
258
0
                {
259
0
                    if (archiveEntry)
260
0
                        *archiveEntry = &content->entries[childIdx];
261
0
                    return true;
262
0
                }
263
0
            }
264
0
        }
265
0
    }
266
1.33k
    return false;
267
1.33k
}
268
269
/************************************************************************/
270
/*                           CompactFilename()                          */
271
/************************************************************************/
272
273
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
274
3.34k
{
275
3.34k
    std::string osRet(pszArchiveInFileNameIn);
276
277
    // Replace a/../b by b and foo/a/../b by foo/b.
278
9.95k
    while (true)
279
9.95k
    {
280
9.95k
        size_t nSlashDotDot = osRet.find("/../");
281
9.95k
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
282
3.34k
            break;
283
6.61k
        size_t nPos = nSlashDotDot - 1;
284
131k
        while (nPos > 0 && osRet[nPos] != '/')
285
124k
            --nPos;
286
6.61k
        if (nPos == 0)
287
1.97k
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
288
4.63k
        else
289
4.63k
            osRet = osRet.substr(0, nPos + 1) +
290
4.63k
                    osRet.substr(nSlashDotDot + strlen("/../"));
291
6.61k
    }
292
3.34k
    return osRet;
293
3.34k
}
294
295
/************************************************************************/
296
/*                           SplitFilename()                            */
297
/************************************************************************/
298
299
char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
300
                                                 CPLString &osFileInArchive,
301
                                                 bool bCheckMainFileExists,
302
                                                 bool bSetError) const
303
12.8k
{
304
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
305
12.8k
    if (strcmp(pszFilename, GetPrefix()) == 0)
306
61
        return nullptr;
307
308
12.7k
    int i = 0;
309
310
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
311
12.7k
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
312
5.22k
    {
313
5.22k
        pszFilename += strlen(GetPrefix()) + 1;
314
5.22k
        int nCountCurlies = 0;
315
281k
        while (pszFilename[i])
316
281k
        {
317
281k
            if (pszFilename[i] == '{')
318
6.45k
                nCountCurlies++;
319
275k
            else if (pszFilename[i] == '}')
320
6.26k
            {
321
6.26k
                nCountCurlies--;
322
6.26k
                if (nCountCurlies == 0)
323
5.04k
                    break;
324
6.26k
            }
325
276k
            i++;
326
276k
        }
327
5.22k
        if (nCountCurlies > 0)
328
182
            return nullptr;
329
5.04k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
330
5.04k
        archiveFilename[i - 1] = 0;
331
332
5.04k
        bool bArchiveFileExists = false;
333
5.04k
        if (!bCheckMainFileExists)
334
0
        {
335
0
            bArchiveFileExists = true;
336
0
        }
337
5.04k
        else
338
5.04k
        {
339
5.04k
            std::unique_lock oLock(oMutex);
340
341
5.04k
            if (oFileList.find(archiveFilename) != oFileList.end())
342
0
            {
343
0
                bArchiveFileExists = true;
344
0
            }
345
5.04k
        }
346
347
5.04k
        if (!bArchiveFileExists)
348
5.04k
        {
349
5.04k
            VSIStatBufL statBuf;
350
5.04k
            VSIFilesystemHandler *poFSHandler =
351
5.04k
                VSIFileManager::GetHandler(archiveFilename);
352
5.04k
            int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
353
5.04k
            if (bSetError)
354
0
                nFlags |= VSI_STAT_SET_ERROR_FLAG;
355
5.04k
            if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
356
5.04k
                !VSI_ISDIR(statBuf.st_mode))
357
3.64k
            {
358
3.64k
                bArchiveFileExists = true;
359
3.64k
            }
360
5.04k
        }
361
362
5.04k
        if (bArchiveFileExists)
363
3.64k
        {
364
3.64k
            if (IsEitherSlash(pszFilename[i + 1]))
365
2.73k
            {
366
2.73k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
367
2.73k
            }
368
908
            else if (pszFilename[i + 1] == '\0')
369
205
            {
370
205
                osFileInArchive = "";
371
205
            }
372
703
            else
373
703
            {
374
703
                CPLFree(archiveFilename);
375
703
                return nullptr;
376
703
            }
377
378
            // Remove trailing slash.
379
2.94k
            if (!osFileInArchive.empty())
380
2.61k
            {
381
2.61k
                const char lastC = osFileInArchive.back();
382
2.61k
                if (IsEitherSlash(lastC))
383
551
                    osFileInArchive.pop_back();
384
2.61k
            }
385
386
2.94k
            return archiveFilename;
387
3.64k
        }
388
389
1.40k
        CPLFree(archiveFilename);
390
1.40k
        return nullptr;
391
5.04k
    }
392
393
    // Allow natural chaining of VSI drivers without requiring double slash.
394
395
7.54k
    CPLString osDoubleVsi(GetPrefix());
396
7.54k
    osDoubleVsi += "/vsi";
397
398
7.54k
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
399
4.17k
        pszFilename += strlen(GetPrefix());
400
3.36k
    else
401
3.36k
        pszFilename += strlen(GetPrefix()) + 1;
402
403
    // Parsing strings like
404
    // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
405
    // takes a huge amount of time, so limit the number of nesting of such
406
    // file systems.
407
7.54k
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
408
7.54k
    if (pnCounter == nullptr)
409
1
    {
410
1
        pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
411
1
        *pnCounter = 0;
412
1
        CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
413
1
    }
414
7.54k
    if (*pnCounter == 3)
415
507
    {
416
507
        CPLError(CE_Failure, CPLE_AppDefined,
417
507
                 "Too deep recursion level in "
418
507
                 "VSIArchiveFilesystemHandler::SplitFilename()");
419
507
        return nullptr;
420
507
    }
421
422
7.03k
    const std::vector<CPLString> oExtensions = GetExtensions();
423
7.03k
    int nAttempts = 0;
424
1.59M
    while (pszFilename[i])
425
1.58M
    {
426
1.58M
        int nToSkip = 0;
427
428
1.58M
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
429
8.18M
             iter != oExtensions.end(); ++iter)
430
6.62M
        {
431
6.62M
            const CPLString &osExtension = *iter;
432
6.62M
            if (EQUALN(pszFilename + i, osExtension.c_str(),
433
6.62M
                       osExtension.size()))
434
18.0k
            {
435
18.0k
                nToSkip = static_cast<int>(osExtension.size());
436
18.0k
                break;
437
18.0k
            }
438
6.62M
        }
439
440
1.58M
#ifdef DEBUG
441
        // For AFL, so that .cur_input is detected as the archive filename.
442
1.58M
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
443
1.77k
        {
444
1.77k
            nToSkip = static_cast<int>(strlen(".cur_input"));
445
1.77k
        }
446
1.58M
#endif
447
448
1.58M
        if (nToSkip != 0)
449
19.7k
        {
450
19.7k
            nAttempts++;
451
            // Arbitrary threshold to avoid DoS with things like
452
            // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
453
19.7k
            if (nAttempts == 5)
454
696
            {
455
696
                break;
456
696
            }
457
19.0k
            VSIStatBufL statBuf;
458
19.0k
            char *archiveFilename = CPLStrdup(pszFilename);
459
19.0k
            bool bArchiveFileExists = false;
460
461
19.0k
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
462
1.98k
            {
463
1.98k
                archiveFilename[i + nToSkip] = 0;
464
1.98k
            }
465
466
19.0k
            if (!bCheckMainFileExists)
467
0
            {
468
0
                bArchiveFileExists = true;
469
0
            }
470
19.0k
            else
471
19.0k
            {
472
19.0k
                std::unique_lock oLock(oMutex);
473
474
19.0k
                if (oFileList.find(archiveFilename) != oFileList.end())
475
0
                {
476
0
                    bArchiveFileExists = true;
477
0
                }
478
19.0k
            }
479
480
19.0k
            if (!bArchiveFileExists)
481
19.0k
            {
482
19.0k
                (*pnCounter)++;
483
484
19.0k
                VSIFilesystemHandler *poFSHandler =
485
19.0k
                    VSIFileManager::GetHandler(archiveFilename);
486
19.0k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
487
19.0k
                                      VSI_STAT_EXISTS_FLAG |
488
19.0k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
489
19.0k
                    !VSI_ISDIR(statBuf.st_mode))
490
1.13k
                {
491
1.13k
                    bArchiveFileExists = true;
492
1.13k
                }
493
494
19.0k
                (*pnCounter)--;
495
19.0k
            }
496
497
19.0k
            if (bArchiveFileExists)
498
1.13k
            {
499
1.13k
                if (IsEitherSlash(pszFilename[i + nToSkip]))
500
613
                {
501
613
                    osFileInArchive =
502
613
                        CompactFilename(pszFilename + i + nToSkip + 1);
503
613
                }
504
524
                else
505
524
                {
506
524
                    osFileInArchive = "";
507
524
                }
508
509
                // Remove trailing slash.
510
1.13k
                if (!osFileInArchive.empty())
511
603
                {
512
603
                    const char lastC = osFileInArchive.back();
513
603
                    if (IsEitherSlash(lastC))
514
235
                        osFileInArchive.resize(osFileInArchive.size() - 1);
515
603
                }
516
517
1.13k
                return archiveFilename;
518
1.13k
            }
519
17.9k
            CPLFree(archiveFilename);
520
17.9k
        }
521
1.58M
        i++;
522
1.58M
    }
523
5.89k
    return nullptr;
524
7.03k
}
525
526
/************************************************************************/
527
/*                           OpenArchiveFile()                          */
528
/************************************************************************/
529
530
std::unique_ptr<VSIArchiveReader>
531
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
532
                                             const char *fileInArchiveName)
533
921
{
534
921
    auto poReader = CreateReader(archiveFilename);
535
536
921
    if (poReader == nullptr)
537
921
    {
538
921
        return nullptr;
539
921
    }
540
541
0
    if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
542
0
    {
543
0
        if (poReader->GotoFirstFile() == FALSE)
544
0
        {
545
0
            return nullptr;
546
0
        }
547
548
        // Skip optional leading subdir.
549
0
        const CPLString osFileName = poReader->GetFileName();
550
0
        if (osFileName.empty() || IsEitherSlash(osFileName.back()))
551
0
        {
552
0
            if (poReader->GotoNextFile() == FALSE)
553
0
            {
554
0
                return nullptr;
555
0
            }
556
0
        }
557
558
0
        if (poReader->GotoNextFile())
559
0
        {
560
0
            CPLString msg;
561
0
            msg.Printf("Support only 1 file in archive file %s when "
562
0
                       "no explicit in-archive filename is specified",
563
0
                       archiveFilename);
564
0
            const VSIArchiveContent *content =
565
0
                GetContentOfArchive(archiveFilename, poReader.get());
566
0
            if (content)
567
0
            {
568
0
                msg += "\nYou could try one of the following :\n";
569
0
                for (const auto &entry : content->entries)
570
0
                {
571
0
                    msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
572
0
                                              archiveFilename,
573
0
                                              entry.fileName.c_str());
574
0
                }
575
0
            }
576
577
0
            CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
578
579
0
            return nullptr;
580
0
        }
581
0
    }
582
0
    else
583
0
    {
584
        // Optimization: instead of iterating over all files which can be
585
        // slow on .tar.gz files, try reading the first one first.
586
        // This can help if it is really huge.
587
0
        {
588
0
            std::unique_lock oLock(oMutex);
589
590
0
            if (oFileList.find(archiveFilename) == oFileList.end())
591
0
            {
592
0
                if (poReader->GotoFirstFile() == FALSE)
593
0
                {
594
0
                    return nullptr;
595
0
                }
596
597
0
                const CPLString osFileName = poReader->GetFileName();
598
0
                bool bIsDir = false;
599
0
                const CPLString osStrippedFilename =
600
0
                    GetStrippedFilename(osFileName, bIsDir);
601
0
                if (!osStrippedFilename.empty())
602
0
                {
603
0
                    const bool bMatch =
604
0
                        strcmp(osStrippedFilename, fileInArchiveName) == 0;
605
0
                    if (bMatch)
606
0
                    {
607
0
                        if (bIsDir)
608
0
                        {
609
0
                            return nullptr;
610
0
                        }
611
0
                        return poReader;
612
0
                    }
613
0
                }
614
0
            }
615
0
        }
616
617
0
        const VSIArchiveEntry *archiveEntry = nullptr;
618
0
        if (FindFileInArchive(archiveFilename, fileInArchiveName,
619
0
                              &archiveEntry) == FALSE ||
620
0
            archiveEntry->bIsDir)
621
0
        {
622
0
            return nullptr;
623
0
        }
624
0
        if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
625
0
        {
626
0
            return nullptr;
627
0
        }
628
0
    }
629
0
    return poReader;
630
0
}
631
632
/************************************************************************/
633
/*                                 Stat()                               */
634
/************************************************************************/
635
636
int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
637
                                      VSIStatBufL *pStatBuf, int nFlags)
638
6.51k
{
639
6.51k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
640
641
6.51k
    CPLString osFileInArchive;
642
6.51k
    char *archiveFilename =
643
6.51k
        SplitFilename(pszFilename, osFileInArchive, true,
644
6.51k
                      (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
645
6.51k
    if (archiveFilename == nullptr)
646
4.67k
        return -1;
647
648
1.84k
    int ret = -1;
649
1.84k
    if (!osFileInArchive.empty())
650
1.33k
    {
651
#ifdef DEBUG_VERBOSE
652
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
653
                 osFileInArchive.c_str());
654
#endif
655
656
1.33k
        const VSIArchiveEntry *archiveEntry = nullptr;
657
1.33k
        if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
658
0
        {
659
            // Patching st_size with uncompressed file size.
660
0
            pStatBuf->st_size = archiveEntry->uncompressed_size;
661
0
            pStatBuf->st_mtime =
662
0
                static_cast<time_t>(archiveEntry->nModifiedTime);
663
0
            if (archiveEntry->bIsDir)
664
0
                pStatBuf->st_mode = S_IFDIR;
665
0
            else
666
0
                pStatBuf->st_mode = S_IFREG;
667
0
            ret = 0;
668
0
        }
669
1.33k
    }
670
509
    else
671
509
    {
672
509
        auto poReader = CreateReader(archiveFilename);
673
509
        CPLFree(archiveFilename);
674
509
        archiveFilename = nullptr;
675
676
509
        if (poReader != nullptr && poReader->GotoFirstFile())
677
0
        {
678
            // Skip optional leading subdir.
679
0
            const CPLString osFileName = poReader->GetFileName();
680
0
            if (IsEitherSlash(osFileName.back()))
681
0
            {
682
0
                if (poReader->GotoNextFile() == FALSE)
683
0
                {
684
0
                    return -1;
685
0
                }
686
0
            }
687
688
0
            if (poReader->GotoNextFile())
689
0
            {
690
                // Several files in archive --> treat as dir.
691
0
                pStatBuf->st_size = 0;
692
0
                pStatBuf->st_mode = S_IFDIR;
693
0
            }
694
0
            else
695
0
            {
696
                // Patching st_size with uncompressed file size.
697
0
                pStatBuf->st_size = poReader->GetFileSize();
698
0
                pStatBuf->st_mtime =
699
0
                    static_cast<time_t>(poReader->GetModifiedTime());
700
0
                pStatBuf->st_mode = S_IFREG;
701
0
            }
702
703
0
            ret = 0;
704
0
        }
705
509
    }
706
707
1.84k
    CPLFree(archiveFilename);
708
1.84k
    return ret;
709
1.84k
}
710
711
/************************************************************************/
712
/*                             ReadDirEx()                              */
713
/************************************************************************/
714
715
char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
716
                                              int nMaxFiles)
717
0
{
718
0
    CPLString osInArchiveSubDir;
719
0
    char *archiveFilename =
720
0
        SplitFilename(pszDirname, osInArchiveSubDir, true, true);
721
0
    if (archiveFilename == nullptr)
722
0
        return nullptr;
723
724
0
    const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
725
726
0
    CPLStringList oDir;
727
728
0
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
729
0
    if (!content)
730
0
    {
731
0
        CPLFree(archiveFilename);
732
0
        return nullptr;
733
0
    }
734
735
#ifdef DEBUG_VERBOSE
736
    CPLDebug("VSIArchive", "Read dir %s", pszDirname);
737
#endif
738
739
0
    std::string searchDir;
740
0
    if (lenInArchiveSubDir != 0)
741
0
        searchDir = std::move(osInArchiveSubDir);
742
743
    // Use directory index to find the list of children for this directory
744
0
    auto dirIter = content->dirIndex.find(searchDir);
745
0
    if (dirIter == content->dirIndex.end())
746
0
    {
747
        // Directory not found in index - no children
748
0
        CPLFree(archiveFilename);
749
0
        return oDir.StealList();
750
0
    }
751
0
    const std::vector<int> &childIndices = dirIter->second;
752
753
    // Scan the children of this directory
754
0
    for (int childIdx : childIndices)
755
0
    {
756
0
        const char *fileName = content->entries[childIdx].fileName.c_str();
757
758
0
        const char *baseName = fileName;
759
0
        if (lenInArchiveSubDir != 0)
760
0
        {
761
            // Skip the directory prefix and slash to get just the child name
762
0
            baseName = fileName + lenInArchiveSubDir + 1;
763
0
        }
764
0
        oDir.AddStringDirectly(CPLStrdup(baseName));
765
766
0
        if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
767
0
            break;
768
0
    }
769
0
    CPLFree(archiveFilename);
770
0
    return oDir.StealList();
771
0
}
772
773
/************************************************************************/
774
/*                               IsLocal()                              */
775
/************************************************************************/
776
777
bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
778
0
{
779
0
    if (!STARTS_WITH(pszPath, GetPrefix()))
780
0
        return false;
781
0
    const char *pszBaseFileName = pszPath + strlen(GetPrefix());
782
0
    VSIFilesystemHandler *poFSHandler =
783
0
        VSIFileManager::GetHandler(pszBaseFileName);
784
0
    return poFSHandler->IsLocal(pszPath);
785
0
}
786
787
/************************************************************************/
788
/*                               IsArchive()                            */
789
/************************************************************************/
790
791
bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
792
0
{
793
0
    if (!STARTS_WITH(pszPath, GetPrefix()))
794
0
        return false;
795
0
    CPLString osFileInArchive;
796
0
    return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
797
0
           osFileInArchive.empty();
798
0
}
799
800
//! @endcond