Coverage Report

Created: 2025-12-31 06:48

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.7k
{
36
24.7k
    return c == '/' || c == '\\';
37
24.7k
}
38
39
/************************************************************************/
40
/*                    ~VSIArchiveEntryFileOffset()                      */
41
/************************************************************************/
42
43
0
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44
45
/************************************************************************/
46
/*                        ~VSIArchiveReader()                           */
47
/************************************************************************/
48
49
2.43k
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.51k
{
127
1.51k
    std::unique_lock oLock(oMutex);
128
129
1.51k
    VSIStatBufL sStat;
130
1.51k
    if (VSIStatL(archiveFilename, &sStat) != 0)
131
176
        return nullptr;
132
133
1.33k
    auto oIter = oFileList.find(archiveFilename);
134
1.33k
    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
1.33k
    std::unique_ptr<VSIArchiveReader> temporaryReader;  // keep in that scope
152
1.33k
    if (poReader == nullptr)
153
1.33k
    {
154
1.33k
        temporaryReader = CreateReader(archiveFilename);
155
1.33k
        poReader = temporaryReader.get();
156
1.33k
        if (!poReader)
157
1.33k
            return nullptr;
158
1.33k
    }
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.51k
{
244
1.51k
    CPLAssert(fileInArchiveName);
245
246
1.51k
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247
1.51k
    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.51k
    return false;
268
1.51k
}
269
270
/************************************************************************/
271
/*                           CompactFilename()                          */
272
/************************************************************************/
273
274
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275
3.41k
{
276
3.41k
    std::string osRet(pszArchiveInFileNameIn);
277
278
    // Replace a/../b by b and foo/a/../b by foo/b.
279
8.49k
    while (true)
280
8.49k
    {
281
8.49k
        size_t nSlashDotDot = osRet.find("/../");
282
8.49k
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283
3.41k
            break;
284
5.08k
        size_t nPos = nSlashDotDot - 1;
285
91.5k
        while (nPos > 0 && osRet[nPos] != '/')
286
86.4k
            --nPos;
287
5.08k
        if (nPos == 0)
288
1.76k
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289
3.32k
        else
290
3.32k
            osRet = osRet.substr(0, nPos + 1) +
291
3.32k
                    osRet.substr(nSlashDotDot + strlen("/../"));
292
5.08k
    }
293
3.41k
    return osRet;
294
3.41k
}
295
296
/************************************************************************/
297
/*                           SplitFilename()                            */
298
/************************************************************************/
299
300
char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
301
                                                 CPLString &osFileInArchive,
302
                                                 bool bCheckMainFileExists,
303
                                                 bool bSetError) const
304
12.1k
{
305
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
306
12.1k
    if (strcmp(pszFilename, GetPrefix()) == 0)
307
162
        return nullptr;
308
309
12.0k
    int i = 0;
310
311
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
312
12.0k
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
313
5.19k
    {
314
5.19k
        pszFilename += strlen(GetPrefix()) + 1;
315
5.19k
        int nCountCurlies = 0;
316
321k
        while (pszFilename[i])
317
321k
        {
318
321k
            if (pszFilename[i] == '{')
319
6.70k
                nCountCurlies++;
320
314k
            else if (pszFilename[i] == '}')
321
6.55k
            {
322
6.55k
                nCountCurlies--;
323
6.55k
                if (nCountCurlies == 0)
324
5.05k
                    break;
325
6.55k
            }
326
316k
            i++;
327
316k
        }
328
5.19k
        if (nCountCurlies > 0)
329
143
            return nullptr;
330
5.05k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
331
5.05k
        archiveFilename[i - 1] = 0;
332
333
5.05k
        bool bArchiveFileExists = false;
334
5.05k
        if (!bCheckMainFileExists)
335
0
        {
336
0
            bArchiveFileExists = true;
337
0
        }
338
5.05k
        else
339
5.05k
        {
340
5.05k
            std::unique_lock oLock(oMutex);
341
342
5.05k
            if (oFileList.find(archiveFilename) != oFileList.end())
343
0
            {
344
0
                bArchiveFileExists = true;
345
0
            }
346
5.05k
        }
347
348
5.05k
        if (!bArchiveFileExists)
349
5.05k
        {
350
5.05k
            VSIStatBufL statBuf;
351
5.05k
            VSIFilesystemHandler *poFSHandler =
352
5.05k
                VSIFileManager::GetHandler(archiveFilename);
353
5.05k
            int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
354
5.05k
            if (bSetError)
355
0
                nFlags |= VSI_STAT_SET_ERROR_FLAG;
356
5.05k
            if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
357
3.49k
                !VSI_ISDIR(statBuf.st_mode))
358
3.34k
            {
359
3.34k
                bArchiveFileExists = true;
360
3.34k
            }
361
5.05k
        }
362
363
5.05k
        if (bArchiveFileExists)
364
3.34k
        {
365
3.34k
            if (IsEitherSlash(pszFilename[i + 1]))
366
3.08k
            {
367
3.08k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
368
3.08k
            }
369
265
            else if (pszFilename[i + 1] == '\0')
370
19
            {
371
19
                osFileInArchive = "";
372
19
            }
373
246
            else
374
246
            {
375
246
                CPLFree(archiveFilename);
376
246
                return nullptr;
377
246
            }
378
379
            // Remove trailing slash.
380
3.09k
            if (!osFileInArchive.empty())
381
3.07k
            {
382
3.07k
                const char lastC = osFileInArchive.back();
383
3.07k
                if (IsEitherSlash(lastC))
384
813
                    osFileInArchive.pop_back();
385
3.07k
            }
386
387
3.09k
            return archiveFilename;
388
3.34k
        }
389
390
1.70k
        CPLFree(archiveFilename);
391
1.70k
        return nullptr;
392
5.05k
    }
393
394
    // Allow natural chaining of VSI drivers without requiring double slash.
395
396
6.81k
    CPLString osDoubleVsi(GetPrefix());
397
6.81k
    osDoubleVsi += "/vsi";
398
399
6.81k
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
400
3.97k
        pszFilename += strlen(GetPrefix());
401
2.84k
    else
402
2.84k
        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
6.81k
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
409
6.81k
    if (pnCounter == nullptr)
410
1
    {
411
1
        pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
412
1
        *pnCounter = 0;
413
1
        CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
414
1
    }
415
6.81k
    if (*pnCounter == 3)
416
489
    {
417
489
        CPLError(CE_Failure, CPLE_AppDefined,
418
489
                 "Too deep recursion level in "
419
489
                 "VSIArchiveFilesystemHandler::SplitFilename()");
420
489
        return nullptr;
421
489
    }
422
423
6.32k
    const std::vector<CPLString> oExtensions = GetExtensions();
424
6.32k
    int nAttempts = 0;
425
1.33M
    while (pszFilename[i])
426
1.33M
    {
427
1.33M
        int nToSkip = 0;
428
429
1.33M
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
430
6.55M
             iter != oExtensions.end(); ++iter)
431
5.23M
        {
432
5.23M
            const CPLString &osExtension = *iter;
433
5.23M
            if (EQUALN(pszFilename + i, osExtension.c_str(),
434
5.23M
                       osExtension.size()))
435
16.8k
            {
436
16.8k
                nToSkip = static_cast<int>(osExtension.size());
437
16.8k
                break;
438
16.8k
            }
439
5.23M
        }
440
441
1.33M
#ifdef DEBUG
442
        // For AFL, so that .cur_input is detected as the archive filename.
443
1.33M
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
444
1.14k
        {
445
1.14k
            nToSkip = static_cast<int>(strlen(".cur_input"));
446
1.14k
        }
447
1.33M
#endif
448
449
1.33M
        if (nToSkip != 0)
450
17.9k
        {
451
17.9k
            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
17.9k
            if (nAttempts == 5)
455
653
            {
456
653
                break;
457
653
            }
458
17.3k
            VSIStatBufL statBuf;
459
17.3k
            char *archiveFilename = CPLStrdup(pszFilename);
460
17.3k
            bool bArchiveFileExists = false;
461
462
17.3k
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
463
1.55k
            {
464
1.55k
                archiveFilename[i + nToSkip] = 0;
465
1.55k
            }
466
467
17.3k
            if (!bCheckMainFileExists)
468
0
            {
469
0
                bArchiveFileExists = true;
470
0
            }
471
17.3k
            else
472
17.3k
            {
473
17.3k
                std::unique_lock oLock(oMutex);
474
475
17.3k
                if (oFileList.find(archiveFilename) != oFileList.end())
476
0
                {
477
0
                    bArchiveFileExists = true;
478
0
                }
479
17.3k
            }
480
481
17.3k
            if (!bArchiveFileExists)
482
17.3k
            {
483
17.3k
                (*pnCounter)++;
484
485
17.3k
                VSIFilesystemHandler *poFSHandler =
486
17.3k
                    VSIFileManager::GetHandler(archiveFilename);
487
17.3k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
488
17.3k
                                      VSI_STAT_EXISTS_FLAG |
489
17.3k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
490
876
                    !VSI_ISDIR(statBuf.st_mode))
491
721
                {
492
721
                    bArchiveFileExists = true;
493
721
                }
494
495
17.3k
                (*pnCounter)--;
496
17.3k
            }
497
498
17.3k
            if (bArchiveFileExists)
499
721
            {
500
721
                if (IsEitherSlash(pszFilename[i + nToSkip]))
501
334
                {
502
334
                    osFileInArchive =
503
334
                        CompactFilename(pszFilename + i + nToSkip + 1);
504
334
                }
505
387
                else
506
387
                {
507
387
                    osFileInArchive = "";
508
387
                }
509
510
                // Remove trailing slash.
511
721
                if (!osFileInArchive.empty())
512
288
                {
513
288
                    const char lastC = osFileInArchive.back();
514
288
                    if (IsEitherSlash(lastC))
515
132
                        osFileInArchive.resize(osFileInArchive.size() - 1);
516
288
                }
517
518
721
                return archiveFilename;
519
721
            }
520
16.5k
            CPLFree(archiveFilename);
521
16.5k
        }
522
1.33M
        i++;
523
1.33M
    }
524
5.60k
    return nullptr;
525
6.32k
}
526
527
/************************************************************************/
528
/*                           OpenArchiveFile()                          */
529
/************************************************************************/
530
531
std::unique_ptr<VSIArchiveReader>
532
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
533
                                             const char *fileInArchiveName)
534
781
{
535
781
    auto poReader = CreateReader(archiveFilename);
536
537
781
    if (poReader == nullptr)
538
781
    {
539
781
        return nullptr;
540
781
    }
541
542
0
    if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
543
0
    {
544
0
        if (poReader->GotoFirstFile() == FALSE)
545
0
        {
546
0
            return nullptr;
547
0
        }
548
549
        // Skip optional leading subdir.
550
0
        const CPLString osFileName = poReader->GetFileName();
551
0
        if (osFileName.empty() || IsEitherSlash(osFileName.back()))
552
0
        {
553
0
            if (poReader->GotoNextFile() == FALSE)
554
0
            {
555
0
                return nullptr;
556
0
            }
557
0
        }
558
559
0
        if (poReader->GotoNextFile())
560
0
        {
561
0
            CPLString msg;
562
0
            msg.Printf("Support only 1 file in archive file %s when "
563
0
                       "no explicit in-archive filename is specified",
564
0
                       archiveFilename);
565
0
            const VSIArchiveContent *content =
566
0
                GetContentOfArchive(archiveFilename, poReader.get());
567
0
            if (content)
568
0
            {
569
0
                msg += "\nYou could try one of the following :\n";
570
0
                for (const auto &entry : content->entries)
571
0
                {
572
0
                    msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
573
0
                                              archiveFilename,
574
0
                                              entry.fileName.c_str());
575
0
                }
576
0
            }
577
578
0
            CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
579
580
0
            return nullptr;
581
0
        }
582
0
    }
583
0
    else
584
0
    {
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
0
        {
589
0
            std::unique_lock oLock(oMutex);
590
591
0
            if (oFileList.find(archiveFilename) == oFileList.end())
592
0
            {
593
0
                if (poReader->GotoFirstFile() == FALSE)
594
0
                {
595
0
                    return nullptr;
596
0
                }
597
598
0
                const CPLString osFileName = poReader->GetFileName();
599
0
                bool bIsDir = false;
600
0
                const CPLString osStrippedFilename =
601
0
                    GetStrippedFilename(osFileName, bIsDir);
602
0
                if (!osStrippedFilename.empty())
603
0
                {
604
0
                    const bool bMatch =
605
0
                        strcmp(osStrippedFilename, fileInArchiveName) == 0;
606
0
                    if (bMatch)
607
0
                    {
608
0
                        if (bIsDir)
609
0
                        {
610
0
                            return nullptr;
611
0
                        }
612
0
                        return poReader;
613
0
                    }
614
0
                }
615
0
            }
616
0
        }
617
618
0
        const VSIArchiveEntry *archiveEntry = nullptr;
619
0
        if (FindFileInArchive(archiveFilename, fileInArchiveName,
620
0
                              &archiveEntry) == FALSE ||
621
0
            archiveEntry->bIsDir)
622
0
        {
623
0
            return nullptr;
624
0
        }
625
0
        if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
626
0
        {
627
0
            return nullptr;
628
0
        }
629
0
    }
630
0
    return poReader;
631
0
}
632
633
/************************************************************************/
634
/*                                 Stat()                               */
635
/************************************************************************/
636
637
int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
638
                                      VSIStatBufL *pStatBuf, int nFlags)
639
5.90k
{
640
5.90k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
641
642
5.90k
    CPLString osFileInArchive;
643
5.90k
    char *archiveFilename =
644
5.90k
        SplitFilename(pszFilename, osFileInArchive, true,
645
5.90k
                      (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
646
5.90k
    if (archiveFilename == nullptr)
647
4.07k
        return -1;
648
649
1.83k
    int ret = -1;
650
1.83k
    if (!osFileInArchive.empty())
651
1.51k
    {
652
#ifdef DEBUG_VERBOSE
653
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
654
                 osFileInArchive.c_str());
655
#endif
656
657
1.51k
        const VSIArchiveEntry *archiveEntry = nullptr;
658
1.51k
        if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
659
0
        {
660
            // Patching st_size with uncompressed file size.
661
0
            pStatBuf->st_size = archiveEntry->uncompressed_size;
662
0
            pStatBuf->st_mtime =
663
0
                static_cast<time_t>(archiveEntry->nModifiedTime);
664
0
            if (archiveEntry->bIsDir)
665
0
                pStatBuf->st_mode = S_IFDIR;
666
0
            else
667
0
                pStatBuf->st_mode = S_IFREG;
668
0
            ret = 0;
669
0
        }
670
1.51k
    }
671
317
    else
672
317
    {
673
317
        auto poReader = CreateReader(archiveFilename);
674
317
        CPLFree(archiveFilename);
675
317
        archiveFilename = nullptr;
676
677
317
        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
317
    }
707
708
1.83k
    CPLFree(archiveFilename);
709
1.83k
    return ret;
710
1.83k
}
711
712
/************************************************************************/
713
/*                             ReadDirEx()                              */
714
/************************************************************************/
715
716
char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
717
                                              int nMaxFiles)
718
0
{
719
0
    CPLString osInArchiveSubDir;
720
0
    char *archiveFilename =
721
0
        SplitFilename(pszDirname, osInArchiveSubDir, true, true);
722
0
    if (archiveFilename == nullptr)
723
0
        return nullptr;
724
725
0
    const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
726
727
0
    CPLStringList oDir;
728
729
0
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
730
0
    if (!content)
731
0
    {
732
0
        CPLFree(archiveFilename);
733
0
        return nullptr;
734
0
    }
735
736
#ifdef DEBUG_VERBOSE
737
    CPLDebug("VSIArchive", "Read dir %s", pszDirname);
738
#endif
739
740
0
    std::string searchDir;
741
0
    if (lenInArchiveSubDir != 0)
742
0
        searchDir = std::move(osInArchiveSubDir);
743
744
    // Use directory index to find the list of children for this directory
745
0
    auto dirIter = content->dirIndex.find(searchDir);
746
0
    if (dirIter == content->dirIndex.end())
747
0
    {
748
        // Directory not found in index - no children
749
0
        CPLFree(archiveFilename);
750
0
        return oDir.StealList();
751
0
    }
752
0
    const std::vector<int> &childIndices = dirIter->second;
753
754
    // Scan the children of this directory
755
0
    for (int childIdx : childIndices)
756
0
    {
757
0
        const char *fileName = content->entries[childIdx].fileName.c_str();
758
759
0
        const char *baseName = fileName;
760
0
        if (lenInArchiveSubDir != 0)
761
0
        {
762
            // Skip the directory prefix and slash to get just the child name
763
0
            baseName = fileName + lenInArchiveSubDir + 1;
764
0
        }
765
0
        oDir.AddStringDirectly(CPLStrdup(baseName));
766
767
0
        if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
768
0
            break;
769
0
    }
770
0
    CPLFree(archiveFilename);
771
0
    return oDir.StealList();
772
0
}
773
774
/************************************************************************/
775
/*                               IsLocal()                              */
776
/************************************************************************/
777
778
bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
779
0
{
780
0
    if (!STARTS_WITH(pszPath, GetPrefix()))
781
0
        return false;
782
0
    const char *pszBaseFileName = pszPath + strlen(GetPrefix());
783
0
    VSIFilesystemHandler *poFSHandler =
784
0
        VSIFileManager::GetHandler(pszBaseFileName);
785
0
    return poFSHandler->IsLocal(pszPath);
786
0
}
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