Coverage Report

Created: 2026-02-14 06:52

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