Coverage Report

Created: 2025-11-15 08:43

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
24.8M
{
36
24.8M
    return c == '/' || c == '\\';
37
24.8M
}
38
39
/************************************************************************/
40
/*                    ~VSIArchiveEntryFileOffset()                      */
41
/************************************************************************/
42
43
3.85M
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44
45
/************************************************************************/
46
/*                        ~VSIArchiveReader()                           */
47
/************************************************************************/
48
49
4.20M
VSIArchiveReader::~VSIArchiveReader() = default;
50
51
/************************************************************************/
52
/*                        ~VSIArchiveContent()                          */
53
/************************************************************************/
54
55
193k
VSIArchiveContent::~VSIArchiveContent() = default;
56
57
/************************************************************************/
58
/*                   VSIArchiveFilesystemHandler()                      */
59
/************************************************************************/
60
61
172
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
4.47M
{
76
4.47M
    bIsDir = false;
77
4.47M
    int nStartPos = 0;
78
79
    // Remove ./ pattern at the beginning of a filename.
80
4.47M
    if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
81
221k
    {
82
221k
        nStartPos = 2;
83
221k
        if (osFileName.size() == 2)
84
11.5k
            return std::string();
85
221k
    }
86
87
4.46M
    std::string ret(osFileName, nStartPos);
88
4.46M
    for (char &c : ret)
89
115M
    {
90
115M
        if (c == '\\')
91
522k
            c = '/';
92
115M
    }
93
94
4.46M
    bIsDir = !ret.empty() && ret.back() == '/';
95
4.46M
    if (bIsDir)
96
254k
    {
97
        // Remove trailing slash.
98
254k
        ret.pop_back();
99
254k
    }
100
4.46M
    return ret;
101
4.47M
}
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.18M
    for (int i = 0; i < nEntries; i++)
112
2.99M
    {
113
2.99M
        const char *fileName = content->entries[i].fileName.c_str();
114
2.99M
        std::string parentDir = CPLGetPathSafe(fileName);
115
2.99M
        content->dirIndex[parentDir].push_back(i);
116
2.99M
    }
117
193k
}
118
119
/************************************************************************/
120
/*                       GetContentOfArchive()                          */
121
/************************************************************************/
122
123
const VSIArchiveContent *
124
VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
125
                                                 VSIArchiveReader *poReader)
126
8.26M
{
127
8.26M
    std::unique_lock oLock(oMutex);
128
129
8.26M
    VSIStatBufL sStat;
130
8.26M
    if (VSIStatL(archiveFilename, &sStat) != 0)
131
397
        return nullptr;
132
133
8.26M
    auto oIter = oFileList.find(archiveFilename);
134
8.26M
    if (oIter != oFileList.end())
135
8.26M
    {
136
8.26M
        const VSIArchiveContent *content = oIter->second.get();
137
8.26M
        if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
138
8.25M
            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
8.06M
        else
146
8.06M
        {
147
8.06M
            return content;
148
8.06M
        }
149
8.26M
    }
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.06k
            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
4.47M
    {
173
4.47M
        bool bIsDir = false;
174
4.47M
        std::string osStrippedFilename =
175
4.47M
            GetStrippedFilename(poReader->GetFileName(), bIsDir);
176
4.47M
        if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
177
4.44M
            osStrippedFilename.find("//") != std::string::npos)
178
40.6k
        {
179
40.6k
            continue;
180
40.6k
        }
181
182
4.43M
        if (oSet.find(osStrippedFilename) == oSet.end())
183
1.28M
        {
184
1.28M
            oSet.insert(osStrippedFilename);
185
186
            // Add intermediate directory structure.
187
52.1M
            for (size_t i = 0; i < osStrippedFilename.size(); ++i)
188
50.8M
            {
189
50.8M
                if (osStrippedFilename[i] == '/')
190
2.22M
                {
191
2.22M
                    std::string osSubdirName(osStrippedFilename, 0, i);
192
2.22M
                    if (oSet.find(osSubdirName) == oSet.end())
193
1.70M
                    {
194
1.70M
                        oSet.insert(osSubdirName);
195
196
1.70M
                        VSIArchiveEntry entry;
197
1.70M
                        entry.fileName = std::move(osSubdirName);
198
1.70M
                        entry.nModifiedTime = poReader->GetModifiedTime();
199
1.70M
                        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.70M
                        content->entries.push_back(std::move(entry));
207
1.70M
                    }
208
2.22M
                }
209
50.8M
            }
210
211
1.28M
            VSIArchiveEntry entry;
212
1.28M
            entry.fileName = std::move(osStrippedFilename);
213
1.28M
            entry.nModifiedTime = poReader->GetModifiedTime();
214
1.28M
            entry.uncompressed_size = poReader->GetFileSize();
215
1.28M
            entry.bIsDir = bIsDir;
216
1.28M
            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.28M
            content->entries.push_back(std::move(entry));
223
1.28M
        }
224
225
4.47M
    } 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.82M
{
244
7.82M
    CPLAssert(fileInArchiveName);
245
246
7.82M
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247
7.82M
    if (content)
248
7.81M
    {
249
7.81M
        const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
250
251
        // Use directory index to search within parent directory's children
252
7.81M
        auto dirIter = content->dirIndex.find(parentDir);
253
7.81M
        if (dirIter != content->dirIndex.end())
254
7.65M
        {
255
7.65M
            const std::vector<int> &childIndices = dirIter->second;
256
7.65M
            for (int childIdx : childIndices)
257
78.8M
            {
258
78.8M
                if (content->entries[childIdx].fileName == fileInArchiveName)
259
3.80M
                {
260
3.80M
                    if (archiveEntry)
261
3.80M
                        *archiveEntry = &content->entries[childIdx];
262
3.80M
                    return true;
263
3.80M
                }
264
78.8M
            }
265
7.65M
        }
266
7.81M
    }
267
4.01M
    return false;
268
7.82M
}
269
270
/************************************************************************/
271
/*                           CompactFilename()                          */
272
/************************************************************************/
273
274
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275
8.07M
{
276
8.07M
    std::string osRet(pszArchiveInFileNameIn);
277
278
    // Replace a/../b by b and foo/a/../b by foo/b.
279
27.7M
    while (true)
280
27.7M
    {
281
27.7M
        size_t nSlashDotDot = osRet.find("/../");
282
27.7M
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283
8.07M
            break;
284
19.6M
        size_t nPos = nSlashDotDot - 1;
285
312M
        while (nPos > 0 && osRet[nPos] != '/')
286
292M
            --nPos;
287
19.6M
        if (nPos == 0)
288
19.6M
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289
48.8k
        else
290
48.8k
            osRet = osRet.substr(0, nPos + 1) +
291
48.8k
                    osRet.substr(nSlashDotDot + strlen("/../"));
292
19.6M
    }
293
8.07M
    return osRet;
294
8.07M
}
295
296
/************************************************************************/
297
/*                           SplitFilename()                            */
298
/************************************************************************/
299
300
char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
301
                                                 CPLString &osFileInArchive,
302
                                                 bool bCheckMainFileExists,
303
                                                 bool bSetError) const
304
8.81M
{
305
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
306
8.81M
    if (strcmp(pszFilename, GetPrefix()) == 0)
307
7
        return nullptr;
308
309
8.81M
    int i = 0;
310
311
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
312
8.81M
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
313
629k
    {
314
629k
        pszFilename += strlen(GetPrefix()) + 1;
315
629k
        int nCountCurlies = 0;
316
570M
        while (pszFilename[i])
317
570M
        {
318
570M
            if (pszFilename[i] == '{')
319
25.8M
                nCountCurlies++;
320
544M
            else if (pszFilename[i] == '}')
321
25.8M
            {
322
25.8M
                nCountCurlies--;
323
25.8M
                if (nCountCurlies == 0)
324
628k
                    break;
325
25.8M
            }
326
569M
            i++;
327
569M
        }
328
629k
        if (nCountCurlies > 0)
329
982
            return nullptr;
330
628k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
331
628k
        archiveFilename[i - 1] = 0;
332
333
628k
        bool bArchiveFileExists = false;
334
628k
        if (!bCheckMainFileExists)
335
346
        {
336
346
            bArchiveFileExists = true;
337
346
        }
338
628k
        else
339
628k
        {
340
628k
            std::unique_lock oLock(oMutex);
341
342
628k
            if (oFileList.find(archiveFilename) != oFileList.end())
343
455k
            {
344
455k
                bArchiveFileExists = true;
345
455k
            }
346
628k
        }
347
348
628k
        if (!bArchiveFileExists)
349
172k
        {
350
172k
            VSIStatBufL statBuf;
351
172k
            VSIFilesystemHandler *poFSHandler =
352
172k
                VSIFileManager::GetHandler(archiveFilename);
353
172k
            int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
354
172k
            if (bSetError)
355
507
                nFlags |= VSI_STAT_SET_ERROR_FLAG;
356
172k
            if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
357
1.23k
                !VSI_ISDIR(statBuf.st_mode))
358
1.22k
            {
359
1.22k
                bArchiveFileExists = true;
360
1.22k
            }
361
172k
        }
362
363
628k
        if (bArchiveFileExists)
364
457k
        {
365
457k
            if (IsEitherSlash(pszFilename[i + 1]))
366
430k
            {
367
430k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
368
430k
            }
369
26.6k
            else if (pszFilename[i + 1] == '\0')
370
26.6k
            {
371
26.6k
                osFileInArchive = "";
372
26.6k
            }
373
4
            else
374
4
            {
375
4
                CPLFree(archiveFilename);
376
4
                return nullptr;
377
4
            }
378
379
            // Remove trailing slash.
380
457k
            if (!osFileInArchive.empty())
381
410k
            {
382
410k
                const char lastC = osFileInArchive.back();
383
410k
                if (IsEitherSlash(lastC))
384
58.3k
                    osFileInArchive.pop_back();
385
410k
            }
386
387
457k
            return archiveFilename;
388
457k
        }
389
390
171k
        CPLFree(archiveFilename);
391
171k
        return nullptr;
392
628k
    }
393
394
    // Allow natural chaining of VSI drivers without requiring double slash.
395
396
8.18M
    CPLString osDoubleVsi(GetPrefix());
397
8.18M
    osDoubleVsi += "/vsi";
398
399
8.18M
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
400
21.0k
        pszFilename += strlen(GetPrefix());
401
8.16M
    else
402
8.16M
        pszFilename += strlen(GetPrefix()) + 1;
403
404
    // Parsing strings like
405
    // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
406
    // takes a huge amount of time, so limit the number of nesting of such
407
    // file systems.
408
8.18M
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
409
8.18M
    if (pnCounter == nullptr)
410
29
    {
411
29
        pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
412
29
        *pnCounter = 0;
413
29
        CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
414
29
    }
415
8.18M
    if (*pnCounter == 3)
416
178
    {
417
178
        CPLError(CE_Failure, CPLE_AppDefined,
418
178
                 "Too deep recursion level in "
419
178
                 "VSIArchiveFilesystemHandler::SplitFilename()");
420
178
        return nullptr;
421
178
    }
422
423
8.18M
    const std::vector<CPLString> oExtensions = GetExtensions();
424
8.18M
    int nAttempts = 0;
425
332M
    while (pszFilename[i])
426
332M
    {
427
332M
        int nToSkip = 0;
428
429
332M
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
430
1.70G
             iter != oExtensions.end(); ++iter)
431
1.38G
        {
432
1.38G
            const CPLString &osExtension = *iter;
433
1.38G
            if (EQUALN(pszFilename + i, osExtension.c_str(),
434
1.38G
                       osExtension.size()))
435
8.25M
            {
436
8.25M
                nToSkip = static_cast<int>(osExtension.size());
437
8.25M
                break;
438
8.25M
            }
439
1.38G
        }
440
441
#ifdef DEBUG
442
        // For AFL, so that .cur_input is detected as the archive filename.
443
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
444
        {
445
            nToSkip = static_cast<int>(strlen(".cur_input"));
446
        }
447
#endif
448
449
332M
        if (nToSkip != 0)
450
8.25M
        {
451
8.25M
            nAttempts++;
452
            // Arbitrary threshold to avoid DoS with things like
453
            // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
454
8.25M
            if (nAttempts == 5)
455
11.2k
            {
456
11.2k
                break;
457
11.2k
            }
458
8.24M
            VSIStatBufL statBuf;
459
8.24M
            char *archiveFilename = CPLStrdup(pszFilename);
460
8.24M
            bool bArchiveFileExists = false;
461
462
8.24M
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
463
7.67M
            {
464
7.67M
                archiveFilename[i + nToSkip] = 0;
465
7.67M
            }
466
467
8.24M
            if (!bCheckMainFileExists)
468
3.24k
            {
469
3.24k
                bArchiveFileExists = true;
470
3.24k
            }
471
8.24M
            else
472
8.24M
            {
473
8.24M
                std::unique_lock oLock(oMutex);
474
475
8.24M
                if (oFileList.find(archiveFilename) != oFileList.end())
476
7.99M
                {
477
7.99M
                    bArchiveFileExists = true;
478
7.99M
                }
479
8.24M
            }
480
481
8.24M
            if (!bArchiveFileExists)
482
250k
            {
483
250k
                (*pnCounter)++;
484
485
250k
                VSIFilesystemHandler *poFSHandler =
486
250k
                    VSIFileManager::GetHandler(archiveFilename);
487
250k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
488
250k
                                      VSI_STAT_EXISTS_FLAG |
489
250k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
490
15.7k
                    !VSI_ISDIR(statBuf.st_mode))
491
14.2k
                {
492
14.2k
                    bArchiveFileExists = true;
493
14.2k
                }
494
495
250k
                (*pnCounter)--;
496
250k
            }
497
498
8.24M
            if (bArchiveFileExists)
499
8.00M
            {
500
8.00M
                if (IsEitherSlash(pszFilename[i + nToSkip]))
501
7.64M
                {
502
7.64M
                    osFileInArchive =
503
7.64M
                        CompactFilename(pszFilename + i + nToSkip + 1);
504
7.64M
                }
505
364k
                else
506
364k
                {
507
364k
                    osFileInArchive = "";
508
364k
                }
509
510
                // Remove trailing slash.
511
8.00M
                if (!osFileInArchive.empty())
512
7.52M
                {
513
7.52M
                    const char lastC = osFileInArchive.back();
514
7.52M
                    if (IsEitherSlash(lastC))
515
38.9k
                        osFileInArchive.resize(osFileInArchive.size() - 1);
516
7.52M
                }
517
518
8.00M
                return archiveFilename;
519
8.00M
            }
520
236k
            CPLFree(archiveFilename);
521
236k
        }
522
324M
        i++;
523
324M
    }
524
175k
    return nullptr;
525
8.18M
}
526
527
/************************************************************************/
528
/*                           OpenArchiveFile()                          */
529
/************************************************************************/
530
531
std::unique_ptr<VSIArchiveReader>
532
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
533
                                             const char *fileInArchiveName)
534
3.93M
{
535
3.93M
    auto poReader = CreateReader(archiveFilename);
536
537
3.93M
    if (poReader == nullptr)
538
10.0k
    {
539
10.0k
        return nullptr;
540
10.0k
    }
541
542
3.92M
    if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
543
101k
    {
544
101k
        if (poReader->GotoFirstFile() == FALSE)
545
0
        {
546
0
            return nullptr;
547
0
        }
548
549
        // Skip optional leading subdir.
550
101k
        const CPLString osFileName = poReader->GetFileName();
551
101k
        if (osFileName.empty() || IsEitherSlash(osFileName.back()))
552
3.57k
        {
553
3.57k
            if (poReader->GotoNextFile() == FALSE)
554
125
            {
555
125
                return nullptr;
556
125
            }
557
3.57k
        }
558
559
101k
        if (poReader->GotoNextFile())
560
2.61k
        {
561
2.61k
            CPLString msg;
562
2.61k
            msg.Printf("Support only 1 file in archive file %s when "
563
2.61k
                       "no explicit in-archive filename is specified",
564
2.61k
                       archiveFilename);
565
2.61k
            const VSIArchiveContent *content =
566
2.61k
                GetContentOfArchive(archiveFilename, poReader.get());
567
2.61k
            if (content)
568
2.61k
            {
569
2.61k
                msg += "\nYou could try one of the following :\n";
570
2.61k
                for (const auto &entry : content->entries)
571
3.23M
                {
572
3.23M
                    msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
573
3.23M
                                              archiveFilename,
574
3.23M
                                              entry.fileName.c_str());
575
3.23M
                }
576
2.61k
            }
577
578
2.61k
            CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
579
580
2.61k
            return nullptr;
581
2.61k
        }
582
101k
    }
583
3.81M
    else
584
3.81M
    {
585
        // Optimization: instead of iterating over all files which can be
586
        // slow on .tar.gz files, try reading the first one first.
587
        // This can help if it is really huge.
588
3.81M
        {
589
3.81M
            std::unique_lock oLock(oMutex);
590
591
3.81M
            if (oFileList.find(archiveFilename) == oFileList.end())
592
1.16k
            {
593
1.16k
                if (poReader->GotoFirstFile() == FALSE)
594
0
                {
595
0
                    return nullptr;
596
0
                }
597
598
1.16k
                const CPLString osFileName = poReader->GetFileName();
599
1.16k
                bool bIsDir = false;
600
1.16k
                const CPLString osStrippedFilename =
601
1.16k
                    GetStrippedFilename(osFileName, bIsDir);
602
1.16k
                if (!osStrippedFilename.empty())
603
1.15k
                {
604
1.15k
                    const bool bMatch =
605
1.15k
                        strcmp(osStrippedFilename, fileInArchiveName) == 0;
606
1.15k
                    if (bMatch)
607
551
                    {
608
551
                        if (bIsDir)
609
1
                        {
610
1
                            return nullptr;
611
1
                        }
612
550
                        return poReader;
613
551
                    }
614
1.15k
                }
615
1.16k
            }
616
3.81M
        }
617
618
3.81M
        const VSIArchiveEntry *archiveEntry = nullptr;
619
3.81M
        if (FindFileInArchive(archiveFilename, fileInArchiveName,
620
3.81M
                              &archiveEntry) == FALSE ||
621
2.51M
            archiveEntry->bIsDir)
622
1.33M
        {
623
1.33M
            return nullptr;
624
1.33M
        }
625
2.48M
        if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
626
116
        {
627
116
            return nullptr;
628
116
        }
629
2.48M
    }
630
2.58M
    return poReader;
631
3.92M
}
632
633
/************************************************************************/
634
/*                                 Stat()                               */
635
/************************************************************************/
636
637
int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
638
                                      VSIStatBufL *pStatBuf, int nFlags)
639
4.17M
{
640
4.17M
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
641
642
4.17M
    CPLString osFileInArchive;
643
4.17M
    char *archiveFilename =
644
4.17M
        SplitFilename(pszFilename, osFileInArchive, true,
645
4.17M
                      (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
646
4.17M
    if (archiveFilename == nullptr)
647
86.7k
        return -1;
648
649
4.08M
    int ret = -1;
650
4.08M
    if (!osFileInArchive.empty())
651
4.00M
    {
652
#ifdef DEBUG_VERBOSE
653
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
654
                 osFileInArchive.c_str());
655
#endif
656
657
4.00M
        const VSIArchiveEntry *archiveEntry = nullptr;
658
4.00M
        if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
659
1.28M
        {
660
            // Patching st_size with uncompressed file size.
661
1.28M
            pStatBuf->st_size = archiveEntry->uncompressed_size;
662
1.28M
            pStatBuf->st_mtime =
663
1.28M
                static_cast<time_t>(archiveEntry->nModifiedTime);
664
1.28M
            if (archiveEntry->bIsDir)
665
118k
                pStatBuf->st_mode = S_IFDIR;
666
1.16M
            else
667
1.16M
                pStatBuf->st_mode = S_IFREG;
668
1.28M
            ret = 0;
669
1.28M
        }
670
4.00M
    }
671
81.8k
    else
672
81.8k
    {
673
81.8k
        auto poReader = CreateReader(archiveFilename);
674
81.8k
        CPLFree(archiveFilename);
675
81.8k
        archiveFilename = nullptr;
676
677
81.8k
        if (poReader != nullptr && poReader->GotoFirstFile())
678
80.6k
        {
679
            // Skip optional leading subdir.
680
80.6k
            const CPLString osFileName = poReader->GetFileName();
681
80.6k
            if (IsEitherSlash(osFileName.back()))
682
2.37k
            {
683
2.37k
                if (poReader->GotoNextFile() == FALSE)
684
122
                {
685
122
                    return -1;
686
122
                }
687
2.37k
            }
688
689
80.5k
            if (poReader->GotoNextFile())
690
59.6k
            {
691
                // Several files in archive --> treat as dir.
692
59.6k
                pStatBuf->st_size = 0;
693
59.6k
                pStatBuf->st_mode = S_IFDIR;
694
59.6k
            }
695
20.8k
            else
696
20.8k
            {
697
                // Patching st_size with uncompressed file size.
698
20.8k
                pStatBuf->st_size = poReader->GetFileSize();
699
20.8k
                pStatBuf->st_mtime =
700
20.8k
                    static_cast<time_t>(poReader->GetModifiedTime());
701
20.8k
                pStatBuf->st_mode = S_IFREG;
702
20.8k
            }
703
704
80.5k
            ret = 0;
705
80.5k
        }
706
81.8k
    }
707
708
4.08M
    CPLFree(archiveFilename);
709
4.08M
    return ret;
710
4.08M
}
711
712
/************************************************************************/
713
/*                             ReadDirEx()                              */
714
/************************************************************************/
715
716
char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
717
                                              int nMaxFiles)
718
444k
{
719
444k
    CPLString osInArchiveSubDir;
720
444k
    char *archiveFilename =
721
444k
        SplitFilename(pszDirname, osInArchiveSubDir, true, true);
722
444k
    if (archiveFilename == nullptr)
723
3.02k
        return nullptr;
724
725
441k
    const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
726
727
441k
    CPLStringList oDir;
728
729
441k
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
730
441k
    if (!content)
731
1.18k
    {
732
1.18k
        CPLFree(archiveFilename);
733
1.18k
        return nullptr;
734
1.18k
    }
735
736
#ifdef DEBUG_VERBOSE
737
    CPLDebug("VSIArchive", "Read dir %s", pszDirname);
738
#endif
739
740
439k
    std::string searchDir;
741
439k
    if (lenInArchiveSubDir != 0)
742
93.5k
        searchDir = std::move(osInArchiveSubDir);
743
744
    // Use directory index to find the list of children for this directory
745
439k
    auto dirIter = content->dirIndex.find(searchDir);
746
439k
    if (dirIter == content->dirIndex.end())
747
7.86k
    {
748
        // Directory not found in index - no children
749
7.86k
        CPLFree(archiveFilename);
750
7.86k
        return oDir.StealList();
751
7.86k
    }
752
432k
    const std::vector<int> &childIndices = dirIter->second;
753
754
    // Scan the children of this directory
755
432k
    for (int childIdx : childIndices)
756
5.66M
    {
757
5.66M
        const char *fileName = content->entries[childIdx].fileName.c_str();
758
759
5.66M
        const char *baseName = fileName;
760
5.66M
        if (lenInArchiveSubDir != 0)
761
640k
        {
762
            // Skip the directory prefix and slash to get just the child name
763
640k
            baseName = fileName + lenInArchiveSubDir + 1;
764
640k
        }
765
5.66M
        oDir.AddStringDirectly(CPLStrdup(baseName));
766
767
5.66M
        if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
768
0
            break;
769
5.66M
    }
770
432k
    CPLFree(archiveFilename);
771
432k
    return oDir.StealList();
772
439k
}
773
774
/************************************************************************/
775
/*                               IsLocal()                              */
776
/************************************************************************/
777
778
bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
779
1.09k
{
780
1.09k
    if (!STARTS_WITH(pszPath, GetPrefix()))
781
0
        return false;
782
1.09k
    const char *pszBaseFileName = pszPath + strlen(GetPrefix());
783
1.09k
    VSIFilesystemHandler *poFSHandler =
784
1.09k
        VSIFileManager::GetHandler(pszBaseFileName);
785
1.09k
    return poFSHandler->IsLocal(pszPath);
786
1.09k
}
787
788
/************************************************************************/
789
/*                               IsArchive()                            */
790
/************************************************************************/
791
792
bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
793
0
{
794
0
    if (!STARTS_WITH(pszPath, GetPrefix()))
795
0
        return false;
796
0
    CPLString osFileInArchive;
797
0
    return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
798
0
           osFileInArchive.empty();
799
0
}
800
801
//! @endcond