Coverage Report

Created: 2026-02-14 09:00

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
23.3M
{
36
23.3M
    return c == '/' || c == '\\';
37
23.3M
}
38
39
/************************************************************************/
40
/*                     ~VSIArchiveEntryFileOffset()                     */
41
/************************************************************************/
42
43
3.48M
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44
45
/************************************************************************/
46
/*                         ~VSIArchiveReader()                          */
47
/************************************************************************/
48
49
3.75M
VSIArchiveReader::~VSIArchiveReader() = default;
50
51
/************************************************************************/
52
/*                         ~VSIArchiveContent()                         */
53
/************************************************************************/
54
55
192k
VSIArchiveContent::~VSIArchiveContent() = default;
56
57
/************************************************************************/
58
/*                    VSIArchiveFilesystemHandler()                     */
59
/************************************************************************/
60
61
166
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
5.03M
{
76
5.03M
    bIsDir = false;
77
5.03M
    int nStartPos = 0;
78
79
    // Remove ./ pattern at the beginning of a filename.
80
5.03M
    if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
81
250k
    {
82
250k
        nStartPos = 2;
83
250k
        if (osFileName.size() == 2)
84
23.8k
            return std::string();
85
250k
    }
86
87
5.00M
    std::string ret(osFileName, nStartPos);
88
5.00M
    for (char &c : ret)
89
116M
    {
90
116M
        if (c == '\\')
91
330k
            c = '/';
92
116M
    }
93
94
5.00M
    bIsDir = !ret.empty() && ret.back() == '/';
95
5.00M
    if (bIsDir)
96
82.1k
    {
97
        // Remove trailing slash.
98
82.1k
        ret.pop_back();
99
82.1k
    }
100
5.00M
    return ret;
101
5.03M
}
102
103
/************************************************************************/
104
/*                        BuildDirectoryIndex()                         */
105
/************************************************************************/
106
107
static void BuildDirectoryIndex(VSIArchiveContent *content)
108
193k
{
109
193k
    content->dirIndex.clear();
110
193k
    const int nEntries = static_cast<int>(content->entries.size());
111
3.16M
    for (int i = 0; i < nEntries; i++)
112
2.97M
    {
113
2.97M
        const char *fileName = content->entries[i].fileName.c_str();
114
2.97M
        std::string parentDir = CPLGetPathSafe(fileName);
115
2.97M
        content->dirIndex[parentDir].push_back(i);
116
2.97M
    }
117
193k
}
118
119
/************************************************************************/
120
/*                        GetContentOfArchive()                         */
121
/************************************************************************/
122
123
const VSIArchiveContent *
124
VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
125
                                                 VSIArchiveReader *poReader)
126
7.83M
{
127
7.83M
    std::unique_lock oLock(oMutex);
128
129
7.83M
    VSIStatBufL sStat;
130
7.83M
    if (VSIStatL(archiveFilename, &sStat) != 0)
131
247
        return nullptr;
132
133
7.83M
    auto oIter = oFileList.find(archiveFilename);
134
7.83M
    if (oIter != oFileList.end())
135
7.82M
    {
136
7.82M
        const VSIArchiveContent *content = oIter->second.get();
137
7.82M
        if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
138
7.81M
            static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
139
192k
        {
140
192k
            CPLDebug("VSIArchive",
141
192k
                     "The content of %s has changed since it was cached",
142
192k
                     archiveFilename);
143
192k
            oFileList.erase(archiveFilename);
144
192k
        }
145
7.63M
        else
146
7.63M
        {
147
7.63M
            return content;
148
7.63M
        }
149
7.82M
    }
150
151
197k
    std::unique_ptr<VSIArchiveReader> temporaryReader;  // keep in that scope
152
197k
    if (poReader == nullptr)
153
197k
    {
154
197k
        temporaryReader = CreateReader(archiveFilename);
155
197k
        poReader = temporaryReader.get();
156
197k
        if (!poReader)
157
4.59k
            return nullptr;
158
197k
    }
159
160
193k
    if (poReader->GotoFirstFile() == FALSE)
161
0
    {
162
0
        return nullptr;
163
0
    }
164
165
193k
    auto content = std::make_unique<VSIArchiveContent>();
166
193k
    content->mTime = sStat.st_mtime;
167
193k
    content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
168
169
193k
    std::set<std::string> oSet;
170
171
193k
    do
172
5.03M
    {
173
5.03M
        bool bIsDir = false;
174
5.03M
        std::string osStrippedFilename =
175
5.03M
            GetStrippedFilename(poReader->GetFileName(), bIsDir);
176
5.03M
        if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
177
4.97M
            osStrippedFilename.find("//") != std::string::npos)
178
67.6k
        {
179
67.6k
            continue;
180
67.6k
        }
181
182
4.96M
        if (oSet.find(osStrippedFilename) == oSet.end())
183
1.34M
        {
184
1.34M
            oSet.insert(osStrippedFilename);
185
186
            // Add intermediate directory structure.
187
52.7M
            for (size_t i = 0; i < osStrippedFilename.size(); ++i)
188
51.3M
            {
189
51.3M
                if (osStrippedFilename[i] == '/')
190
2.14M
                {
191
2.14M
                    std::string osSubdirName(osStrippedFilename, 0, i);
192
2.14M
                    if (oSet.find(osSubdirName) == oSet.end())
193
1.63M
                    {
194
1.63M
                        oSet.insert(osSubdirName);
195
196
1.63M
                        VSIArchiveEntry entry;
197
1.63M
                        entry.fileName = std::move(osSubdirName);
198
1.63M
                        entry.nModifiedTime = poReader->GetModifiedTime();
199
1.63M
                        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
1.63M
                        content->entries.push_back(std::move(entry));
207
1.63M
                    }
208
2.14M
                }
209
51.3M
            }
210
211
1.34M
            VSIArchiveEntry entry;
212
1.34M
            entry.fileName = std::move(osStrippedFilename);
213
1.34M
            entry.nModifiedTime = poReader->GetModifiedTime();
214
1.34M
            entry.uncompressed_size = poReader->GetFileSize();
215
1.34M
            entry.bIsDir = bIsDir;
216
1.34M
            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
1.34M
            content->entries.push_back(std::move(entry));
223
1.34M
        }
224
225
5.03M
    } while (poReader->GotoNextFile());
226
227
    // Build directory index for fast lookups
228
193k
    BuildDirectoryIndex(content.get());
229
230
193k
    return oFileList
231
193k
        .insert(std::pair<CPLString, std::unique_ptr<VSIArchiveContent>>(
232
193k
            archiveFilename, std::move(content)))
233
193k
        .first->second.get();
234
193k
}
235
236
/************************************************************************/
237
/*                         FindFileInArchive()                          */
238
/************************************************************************/
239
240
bool VSIArchiveFilesystemHandler::FindFileInArchive(
241
    const char *archiveFilename, const char *fileInArchiveName,
242
    const VSIArchiveEntry **archiveEntry)
243
7.38M
{
244
7.38M
    CPLAssert(fileInArchiveName);
245
246
7.38M
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247
7.38M
    if (content)
248
7.37M
    {
249
7.37M
        const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
250
251
        // Use directory index to search within parent directory's children
252
7.37M
        auto dirIter = content->dirIndex.find(parentDir);
253
7.37M
        if (dirIter != content->dirIndex.end())
254
7.31M
        {
255
7.31M
            const std::vector<int> &childIndices = dirIter->second;
256
7.31M
            for (int childIdx : childIndices)
257
81.6M
            {
258
81.6M
                if (content->entries[childIdx].fileName == fileInArchiveName)
259
3.44M
                {
260
3.44M
                    if (archiveEntry)
261
3.44M
                        *archiveEntry = &content->entries[childIdx];
262
3.44M
                    return true;
263
3.44M
                }
264
81.6M
            }
265
7.31M
        }
266
7.37M
    }
267
3.93M
    return false;
268
7.38M
}
269
270
/************************************************************************/
271
/*                          CompactFilename()                           */
272
/************************************************************************/
273
274
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275
7.61M
{
276
7.61M
    std::string osRet(pszArchiveInFileNameIn);
277
278
    // Replace a/../b by b and foo/a/../b by foo/b.
279
21.5M
    while (true)
280
21.5M
    {
281
21.5M
        size_t nSlashDotDot = osRet.find("/../");
282
21.5M
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283
7.61M
            break;
284
13.8M
        size_t nPos = nSlashDotDot - 1;
285
190M
        while (nPos > 0 && osRet[nPos] != '/')
286
177M
            --nPos;
287
13.8M
        if (nPos == 0)
288
13.8M
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289
26.3k
        else
290
26.3k
            osRet = osRet.substr(0, nPos + 1) +
291
26.3k
                    osRet.substr(nSlashDotDot + strlen("/../"));
292
13.8M
    }
293
7.61M
    return osRet;
294
7.61M
}
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.31M
{
306
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
307
8.31M
    if (strcmp(pszFilename, GetPrefix()) == 0)
308
5
        return nullptr;
309
310
8.31M
    int i = 0;
311
312
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
313
8.31M
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
314
670k
    {
315
670k
        pszFilename += strlen(GetPrefix()) + 1;
316
670k
        int nCountCurlies = 0;
317
397M
        while (pszFilename[i])
318
397M
        {
319
397M
            if (pszFilename[i] == '{')
320
17.6M
                nCountCurlies++;
321
379M
            else if (pszFilename[i] == '}')
322
17.6M
            {
323
17.6M
                nCountCurlies--;
324
17.6M
                if (nCountCurlies == 0)
325
667k
                    break;
326
17.6M
            }
327
396M
            i++;
328
396M
        }
329
670k
        if (nCountCurlies > 0)
330
2.64k
            return nullptr;
331
667k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
332
667k
        archiveFilename[i - 1] = 0;
333
334
667k
        bool bArchiveFileExists = false;
335
667k
        if (!bCheckMainFileExists)
336
226
        {
337
226
            bArchiveFileExists = true;
338
226
        }
339
667k
        else
340
667k
        {
341
667k
            std::unique_lock oLock(oMutex);
342
343
667k
            if (oFileList.find(archiveFilename) != oFileList.end())
344
532k
            {
345
532k
                bArchiveFileExists = true;
346
532k
            }
347
667k
        }
348
349
667k
        if (!bArchiveFileExists)
350
135k
        {
351
135k
            VSIStatBufL statBuf;
352
135k
            VSIFilesystemHandler *poFSHandler =
353
135k
                VSIFileManager::GetHandler(archiveFilename);
354
135k
            int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
355
135k
            if (bSetError)
356
376
                nFlags |= VSI_STAT_SET_ERROR_FLAG;
357
135k
            if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
358
2.46k
                !VSI_ISDIR(statBuf.st_mode))
359
2.36k
            {
360
2.36k
                bArchiveFileExists = true;
361
2.36k
            }
362
135k
        }
363
364
667k
        if (bArchiveFileExists)
365
534k
        {
366
534k
            if (IsEitherSlash(pszFilename[i + 1]))
367
505k
            {
368
505k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
369
505k
            }
370
29.6k
            else if (pszFilename[i + 1] == '\0')
371
29.4k
            {
372
29.4k
                osFileInArchive = "";
373
29.4k
            }
374
184
            else
375
184
            {
376
184
                CPLFree(archiveFilename);
377
184
                return nullptr;
378
184
            }
379
380
            // Remove trailing slash.
381
534k
            if (!osFileInArchive.empty())
382
481k
            {
383
481k
                const char lastC = osFileInArchive.back();
384
481k
                if (IsEitherSlash(lastC))
385
69.1k
                    osFileInArchive.pop_back();
386
481k
            }
387
388
534k
            return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
389
534k
        }
390
391
132k
        CPLFree(archiveFilename);
392
132k
        return nullptr;
393
667k
    }
394
395
    // Allow natural chaining of VSI drivers without requiring double slash.
396
397
7.64M
    CPLString osDoubleVsi(GetPrefix());
398
7.64M
    osDoubleVsi += "/vsi";
399
400
7.64M
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
401
18.2k
        pszFilename += strlen(GetPrefix());
402
7.62M
    else
403
7.62M
        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
7.64M
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
410
7.64M
    if (pnCounter == nullptr)
411
27
    {
412
27
        pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
413
27
        *pnCounter = 0;
414
27
        CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
415
27
    }
416
7.64M
    if (*pnCounter == 3)
417
160
    {
418
160
        CPLError(CE_Failure, CPLE_AppDefined,
419
160
                 "Too deep recursion level in "
420
160
                 "VSIArchiveFilesystemHandler::SplitFilename()");
421
160
        return nullptr;
422
160
    }
423
424
7.64M
    const std::vector<CPLString> oExtensions = GetExtensions();
425
7.64M
    int nAttempts = 0;
426
176M
    while (pszFilename[i])
427
176M
    {
428
176M
        int nToSkip = 0;
429
430
176M
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
431
767M
             iter != oExtensions.end(); ++iter)
432
598M
        {
433
598M
            const CPLString &osExtension = *iter;
434
598M
            if (EQUALN(pszFilename + i, osExtension.c_str(),
435
598M
                       osExtension.size()))
436
7.73M
            {
437
7.73M
                nToSkip = static_cast<int>(osExtension.size());
438
7.73M
                break;
439
7.73M
            }
440
598M
        }
441
442
#ifdef DEBUG
443
        // For AFL, so that .cur_input is detected as the archive filename.
444
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
445
        {
446
            nToSkip = static_cast<int>(strlen(".cur_input"));
447
        }
448
#endif
449
450
176M
        if (nToSkip != 0)
451
7.73M
        {
452
7.73M
            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
7.73M
            if (nAttempts == 5)
456
10.6k
            {
457
10.6k
                break;
458
10.6k
            }
459
7.71M
            VSIStatBufL statBuf;
460
7.71M
            char *archiveFilename = CPLStrdup(pszFilename);
461
7.71M
            bool bArchiveFileExists = false;
462
463
7.71M
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
464
7.13M
            {
465
7.13M
                archiveFilename[i + nToSkip] = 0;
466
7.13M
            }
467
468
7.71M
            if (!bCheckMainFileExists)
469
3.10k
            {
470
3.10k
                bArchiveFileExists = true;
471
3.10k
            }
472
7.71M
            else
473
7.71M
            {
474
7.71M
                std::unique_lock oLock(oMutex);
475
476
7.71M
                if (oFileList.find(archiveFilename) != oFileList.end())
477
7.45M
                {
478
7.45M
                    bArchiveFileExists = true;
479
7.45M
                }
480
7.71M
            }
481
482
7.71M
            if (!bArchiveFileExists)
483
263k
            {
484
263k
                (*pnCounter)++;
485
486
263k
                VSIFilesystemHandler *poFSHandler =
487
263k
                    VSIFileManager::GetHandler(archiveFilename);
488
263k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
489
263k
                                      VSI_STAT_EXISTS_FLAG |
490
263k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
491
15.5k
                    !VSI_ISDIR(statBuf.st_mode))
492
15.2k
                {
493
15.2k
                    bArchiveFileExists = true;
494
15.2k
                }
495
496
263k
                (*pnCounter)--;
497
263k
            }
498
499
7.71M
            if (bArchiveFileExists)
500
7.47M
            {
501
7.47M
                if (IsEitherSlash(pszFilename[i + nToSkip]))
502
7.10M
                {
503
7.10M
                    osFileInArchive =
504
7.10M
                        CompactFilename(pszFilename + i + nToSkip + 1);
505
7.10M
                }
506
364k
                else
507
364k
                {
508
364k
                    osFileInArchive = "";
509
364k
                }
510
511
                // Remove trailing slash.
512
7.47M
                if (!osFileInArchive.empty())
513
7.00M
                {
514
7.00M
                    const char lastC = osFileInArchive.back();
515
7.00M
                    if (IsEitherSlash(lastC))
516
35.3k
                        osFileInArchive.resize(osFileInArchive.size() - 1);
517
7.00M
                }
518
519
7.47M
                return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
520
7.47M
            }
521
247k
            CPLFree(archiveFilename);
522
247k
        }
523
168M
        i++;
524
168M
    }
525
174k
    return nullptr;
526
7.64M
}
527
528
/************************************************************************/
529
/*                          OpenArchiveFile()                           */
530
/************************************************************************/
531
532
std::unique_ptr<VSIArchiveReader>
533
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
534
                                             const char *fileInArchiveName)
535
3.47M
{
536
3.47M
    auto poReader = CreateReader(archiveFilename);
537
538
3.47M
    if (poReader == nullptr)
539
10.4k
    {
540
10.4k
        return nullptr;
541
10.4k
    }
542
543
3.46M
    if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
544
82.1k
    {
545
82.1k
        if (poReader->GotoFirstFile() == FALSE)
546
0
        {
547
0
            return nullptr;
548
0
        }
549
550
        // Skip optional leading subdir.
551
82.1k
        const CPLString osFileName = poReader->GetFileName();
552
82.1k
        if (osFileName.empty() || IsEitherSlash(osFileName.back()))
553
4.05k
        {
554
4.05k
            if (poReader->GotoNextFile() == FALSE)
555
235
            {
556
235
                return nullptr;
557
235
            }
558
4.05k
        }
559
560
81.8k
        if (poReader->GotoNextFile())
561
3.06k
        {
562
3.06k
            CPLString msg;
563
3.06k
            msg.Printf("Support only 1 file in archive file %s when "
564
3.06k
                       "no explicit in-archive filename is specified",
565
3.06k
                       archiveFilename);
566
3.06k
            const VSIArchiveContent *content =
567
3.06k
                GetContentOfArchive(archiveFilename, poReader.get());
568
3.06k
            if (content)
569
3.06k
            {
570
3.06k
                msg += "\nYou could try one of the following :\n";
571
3.06k
                for (const auto &entry : content->entries)
572
2.98M
                {
573
2.98M
                    msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
574
2.98M
                                              archiveFilename,
575
2.98M
                                              entry.fileName.c_str());
576
2.98M
                }
577
3.06k
            }
578
579
3.06k
            CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
580
581
3.06k
            return nullptr;
582
3.06k
        }
583
81.8k
    }
584
3.38M
    else
585
3.38M
    {
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
3.38M
        {
590
3.38M
            std::unique_lock oLock(oMutex);
591
592
3.38M
            if (oFileList.find(archiveFilename) == oFileList.end())
593
1.43k
            {
594
1.43k
                if (poReader->GotoFirstFile() == FALSE)
595
0
                {
596
0
                    return nullptr;
597
0
                }
598
599
1.43k
                const CPLString osFileName = poReader->GetFileName();
600
1.43k
                bool bIsDir = false;
601
1.43k
                const CPLString osStrippedFilename =
602
1.43k
                    GetStrippedFilename(osFileName, bIsDir);
603
1.43k
                if (!osStrippedFilename.empty())
604
1.42k
                {
605
1.42k
                    const bool bMatch =
606
1.42k
                        strcmp(osStrippedFilename, fileInArchiveName) == 0;
607
1.42k
                    if (bMatch)
608
738
                    {
609
738
                        if (bIsDir)
610
0
                        {
611
0
                            return nullptr;
612
0
                        }
613
738
                        return poReader;
614
738
                    }
615
1.42k
                }
616
1.43k
            }
617
3.38M
        }
618
619
3.38M
        const VSIArchiveEntry *archiveEntry = nullptr;
620
3.38M
        if (FindFileInArchive(archiveFilename, fileInArchiveName,
621
3.38M
                              &archiveEntry) == FALSE ||
622
2.10M
            archiveEntry->bIsDir)
623
1.31M
        {
624
1.31M
            return nullptr;
625
1.31M
        }
626
2.07M
        if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
627
187
        {
628
187
            return nullptr;
629
187
        }
630
2.07M
    }
631
2.15M
    return poReader;
632
3.46M
}
633
634
/************************************************************************/
635
/*                                Stat()                                */
636
/************************************************************************/
637
638
int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
639
                                      VSIStatBufL *pStatBuf, int nFlags)
640
4.15M
{
641
4.15M
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
642
643
4.15M
    CPLString osFileInArchive;
644
4.15M
    auto archiveFilename =
645
4.15M
        SplitFilename(pszFilename, osFileInArchive, true,
646
4.15M
                      (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
647
4.15M
    if (archiveFilename == nullptr)
648
83.5k
        return -1;
649
650
4.07M
    int ret = -1;
651
4.07M
    if (!osFileInArchive.empty())
652
3.99M
    {
653
#ifdef DEBUG_VERBOSE
654
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename.get(),
655
                 osFileInArchive.c_str());
656
#endif
657
658
3.99M
        const VSIArchiveEntry *archiveEntry = nullptr;
659
3.99M
        if (FindFileInArchive(archiveFilename.get(), osFileInArchive,
660
3.99M
                              &archiveEntry))
661
1.34M
        {
662
            // Patching st_size with uncompressed file size.
663
1.34M
            pStatBuf->st_size = archiveEntry->uncompressed_size;
664
1.34M
            pStatBuf->st_mtime =
665
1.34M
                static_cast<time_t>(archiveEntry->nModifiedTime);
666
1.34M
            if (archiveEntry->bIsDir)
667
122k
                pStatBuf->st_mode = S_IFDIR;
668
1.21M
            else
669
1.21M
                pStatBuf->st_mode = S_IFREG;
670
1.34M
            ret = 0;
671
1.34M
        }
672
3.99M
    }
673
78.7k
    else
674
78.7k
    {
675
78.7k
        auto poReader = CreateReader(archiveFilename.get());
676
677
78.7k
        if (poReader != nullptr && poReader->GotoFirstFile())
678
77.2k
        {
679
            // Skip optional leading subdir.
680
77.2k
            const CPLString osFileName = poReader->GetFileName();
681
77.2k
            if (IsEitherSlash(osFileName.back()))
682
2.64k
            {
683
2.64k
                if (poReader->GotoNextFile() == FALSE)
684
148
                {
685
148
                    return -1;
686
148
                }
687
2.64k
            }
688
689
77.0k
            if (poReader->GotoNextFile())
690
56.3k
            {
691
                // Several files in archive --> treat as dir.
692
56.3k
                pStatBuf->st_size = 0;
693
56.3k
                pStatBuf->st_mode = S_IFDIR;
694
56.3k
            }
695
20.7k
            else
696
20.7k
            {
697
                // Patching st_size with uncompressed file size.
698
20.7k
                pStatBuf->st_size = poReader->GetFileSize();
699
20.7k
                pStatBuf->st_mtime =
700
20.7k
                    static_cast<time_t>(poReader->GetModifiedTime());
701
20.7k
                pStatBuf->st_mode = S_IFREG;
702
20.7k
            }
703
704
77.0k
            ret = 0;
705
77.0k
        }
706
78.7k
    }
707
708
4.07M
    return ret;
709
4.07M
}
710
711
/************************************************************************/
712
/*                             ReadDirEx()                              */
713
/************************************************************************/
714
715
char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
716
                                              int nMaxFiles)
717
447k
{
718
447k
    CPLString osInArchiveSubDir;
719
447k
    auto archiveFilename =
720
447k
        SplitFilename(pszDirname, osInArchiveSubDir, true, true);
721
447k
    if (archiveFilename == nullptr)
722
3.01k
        return nullptr;
723
724
444k
    const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
725
726
444k
    CPLStringList oDir;
727
728
444k
    const VSIArchiveContent *content =
729
444k
        GetContentOfArchive(archiveFilename.get());
730
444k
    if (!content)
731
967
    {
732
967
        return nullptr;
733
967
    }
734
735
#ifdef DEBUG_VERBOSE
736
    CPLDebug("VSIArchive", "Read dir %s", pszDirname);
737
#endif
738
739
443k
    std::string searchDir;
740
443k
    if (lenInArchiveSubDir != 0)
741
90.2k
        searchDir = std::move(osInArchiveSubDir);
742
743
    // Use directory index to find the list of children for this directory
744
443k
    auto dirIter = content->dirIndex.find(searchDir);
745
443k
    if (dirIter == content->dirIndex.end())
746
4.44k
    {
747
        // Directory not found in index - no children
748
4.44k
        return oDir.StealList();
749
4.44k
    }
750
438k
    const std::vector<int> &childIndices = dirIter->second;
751
752
    // Scan the children of this directory
753
438k
    for (int childIdx : childIndices)
754
5.95M
    {
755
5.95M
        const char *fileName = content->entries[childIdx].fileName.c_str();
756
757
5.95M
        const char *baseName = fileName;
758
5.95M
        if (lenInArchiveSubDir != 0)
759
614k
        {
760
            // Skip the directory prefix and slash to get just the child name
761
614k
            baseName = fileName + lenInArchiveSubDir + 1;
762
614k
        }
763
5.95M
        oDir.AddStringDirectly(CPLStrdup(baseName));
764
765
5.95M
        if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
766
0
            break;
767
5.95M
    }
768
438k
    return oDir.StealList();
769
443k
}
770
771
/************************************************************************/
772
/*                              IsLocal()                               */
773
/************************************************************************/
774
775
bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
776
15.2k
{
777
15.2k
    if (!STARTS_WITH(pszPath, GetPrefix()))
778
0
        return false;
779
15.2k
    const char *pszBaseFileName = pszPath + strlen(GetPrefix());
780
15.2k
    VSIFilesystemHandler *poFSHandler =
781
15.2k
        VSIFileManager::GetHandler(pszBaseFileName);
782
15.2k
    return poFSHandler->IsLocal(pszPath);
783
15.2k
}
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