Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_abstract_archive.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for archive files.
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "cpl_vsi_virtual.h"
15
16
#include <cstring>
17
#include <ctime>
18
#include <fcntl.h>
19
#include <map>
20
#include <memory>
21
#include <set>
22
#include <string>
23
#include <utility>
24
#include <vector>
25
26
#include "cpl_conv.h"
27
#include "cpl_error.h"
28
#include "cpl_multiproc.h"
29
#include "cpl_string.h"
30
#include "cpl_vsi.h"
31
32
//! @cond Doxygen_Suppress
33
34
static bool IsEitherSlash(char c)
35
17.2k
{
36
17.2k
    return c == '/' || c == '\\';
37
17.2k
}
38
39
/************************************************************************/
40
/*                    ~VSIArchiveEntryFileOffset()                      */
41
/************************************************************************/
42
43
0
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44
45
/************************************************************************/
46
/*                        ~VSIArchiveReader()                           */
47
/************************************************************************/
48
49
1.70k
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
850
{
127
850
    std::unique_lock oLock(oMutex);
128
129
850
    VSIStatBufL sStat;
130
850
    if (VSIStatL(archiveFilename, &sStat) != 0)
131
109
        return nullptr;
132
133
741
    auto oIter = oFileList.find(archiveFilename);
134
741
    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
741
    std::unique_ptr<VSIArchiveReader> temporaryReader;  // keep in that scope
152
741
    if (poReader == nullptr)
153
741
    {
154
741
        temporaryReader = CreateReader(archiveFilename);
155
741
        poReader = temporaryReader.get();
156
741
        if (!poReader)
157
741
            return nullptr;
158
741
    }
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
850
{
244
850
    CPLAssert(fileInArchiveName);
245
246
850
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247
850
    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
850
    return false;
268
850
}
269
270
/************************************************************************/
271
/*                           CompactFilename()                          */
272
/************************************************************************/
273
274
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275
2.19k
{
276
2.19k
    std::string osRet(pszArchiveInFileNameIn);
277
278
    // Replace a/../b by b and foo/a/../b by foo/b.
279
6.76k
    while (true)
280
6.76k
    {
281
6.76k
        size_t nSlashDotDot = osRet.find("/../");
282
6.76k
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283
2.19k
            break;
284
4.57k
        size_t nPos = nSlashDotDot - 1;
285
98.2k
        while (nPos > 0 && osRet[nPos] != '/')
286
93.6k
            --nPos;
287
4.57k
        if (nPos == 0)
288
1.29k
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289
3.27k
        else
290
3.27k
            osRet = osRet.substr(0, nPos + 1) +
291
3.27k
                    osRet.substr(nSlashDotDot + strlen("/../"));
292
4.57k
    }
293
2.19k
    return osRet;
294
2.19k
}
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.56k
{
305
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
306
8.56k
    if (strcmp(pszFilename, GetPrefix()) == 0)
307
27
        return nullptr;
308
309
8.53k
    int i = 0;
310
311
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
312
8.53k
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
313
3.88k
    {
314
3.88k
        pszFilename += strlen(GetPrefix()) + 1;
315
3.88k
        int nCountCurlies = 0;
316
241k
        while (pszFilename[i])
317
241k
        {
318
241k
            if (pszFilename[i] == '{')
319
5.42k
                nCountCurlies++;
320
236k
            else if (pszFilename[i] == '}')
321
5.33k
            {
322
5.33k
                nCountCurlies--;
323
5.33k
                if (nCountCurlies == 0)
324
3.78k
                    break;
325
5.33k
            }
326
237k
            i++;
327
237k
        }
328
3.88k
        if (nCountCurlies > 0)
329
92
            return nullptr;
330
3.78k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
331
3.78k
        archiveFilename[i - 1] = 0;
332
333
3.78k
        bool bArchiveFileExists = false;
334
3.78k
        if (!bCheckMainFileExists)
335
0
        {
336
0
            bArchiveFileExists = true;
337
0
        }
338
3.78k
        else
339
3.78k
        {
340
3.78k
            std::unique_lock oLock(oMutex);
341
342
3.78k
            if (oFileList.find(archiveFilename) != oFileList.end())
343
0
            {
344
0
                bArchiveFileExists = true;
345
0
            }
346
3.78k
        }
347
348
3.78k
        if (!bArchiveFileExists)
349
3.78k
        {
350
3.78k
            VSIStatBufL statBuf;
351
3.78k
            VSIFilesystemHandler *poFSHandler =
352
3.78k
                VSIFileManager::GetHandler(archiveFilename);
353
3.78k
            int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
354
3.78k
            if (bSetError)
355
0
                nFlags |= VSI_STAT_SET_ERROR_FLAG;
356
3.78k
            if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
357
2.58k
                !VSI_ISDIR(statBuf.st_mode))
358
2.40k
            {
359
2.40k
                bArchiveFileExists = true;
360
2.40k
            }
361
3.78k
        }
362
363
3.78k
        if (bArchiveFileExists)
364
2.40k
        {
365
2.40k
            if (IsEitherSlash(pszFilename[i + 1]))
366
1.95k
            {
367
1.95k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
368
1.95k
            }
369
454
            else if (pszFilename[i + 1] == '\0')
370
153
            {
371
153
                osFileInArchive = "";
372
153
            }
373
301
            else
374
301
            {
375
301
                CPLFree(archiveFilename);
376
301
                return nullptr;
377
301
            }
378
379
            // Remove trailing slash.
380
2.10k
            if (!osFileInArchive.empty())
381
1.85k
            {
382
1.85k
                const char lastC = osFileInArchive.back();
383
1.85k
                if (IsEitherSlash(lastC))
384
407
                    osFileInArchive.pop_back();
385
1.85k
            }
386
387
2.10k
            return archiveFilename;
388
2.40k
        }
389
390
1.38k
        CPLFree(archiveFilename);
391
1.38k
        return nullptr;
392
3.78k
    }
393
394
    // Allow natural chaining of VSI drivers without requiring double slash.
395
396
4.65k
    CPLString osDoubleVsi(GetPrefix());
397
4.65k
    osDoubleVsi += "/vsi";
398
399
4.65k
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
400
2.70k
        pszFilename += strlen(GetPrefix());
401
1.94k
    else
402
1.94k
        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
4.65k
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
409
4.65k
    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
4.65k
    if (*pnCounter == 3)
416
278
    {
417
278
        CPLError(CE_Failure, CPLE_AppDefined,
418
278
                 "Too deep recursion level in "
419
278
                 "VSIArchiveFilesystemHandler::SplitFilename()");
420
278
        return nullptr;
421
278
    }
422
423
4.37k
    const std::vector<CPLString> oExtensions = GetExtensions();
424
4.37k
    int nAttempts = 0;
425
963k
    while (pszFilename[i])
426
960k
    {
427
960k
        int nToSkip = 0;
428
429
960k
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
430
4.86M
             iter != oExtensions.end(); ++iter)
431
3.91M
        {
432
3.91M
            const CPLString &osExtension = *iter;
433
3.91M
            if (EQUALN(pszFilename + i, osExtension.c_str(),
434
3.91M
                       osExtension.size()))
435
11.8k
            {
436
11.8k
                nToSkip = static_cast<int>(osExtension.size());
437
11.8k
                break;
438
11.8k
            }
439
3.91M
        }
440
441
960k
#ifdef DEBUG
442
        // For AFL, so that .cur_input is detected as the archive filename.
443
960k
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
444
906
        {
445
906
            nToSkip = static_cast<int>(strlen(".cur_input"));
446
906
        }
447
960k
#endif
448
449
960k
        if (nToSkip != 0)
450
12.7k
        {
451
12.7k
            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
12.7k
            if (nAttempts == 5)
455
473
            {
456
473
                break;
457
473
            }
458
12.2k
            VSIStatBufL statBuf;
459
12.2k
            char *archiveFilename = CPLStrdup(pszFilename);
460
12.2k
            bool bArchiveFileExists = false;
461
462
12.2k
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
463
1.16k
            {
464
1.16k
                archiveFilename[i + nToSkip] = 0;
465
1.16k
            }
466
467
12.2k
            if (!bCheckMainFileExists)
468
0
            {
469
0
                bArchiveFileExists = true;
470
0
            }
471
12.2k
            else
472
12.2k
            {
473
12.2k
                std::unique_lock oLock(oMutex);
474
475
12.2k
                if (oFileList.find(archiveFilename) != oFileList.end())
476
0
                {
477
0
                    bArchiveFileExists = true;
478
0
                }
479
12.2k
            }
480
481
12.2k
            if (!bArchiveFileExists)
482
12.2k
            {
483
12.2k
                (*pnCounter)++;
484
485
12.2k
                VSIFilesystemHandler *poFSHandler =
486
12.2k
                    VSIFileManager::GetHandler(archiveFilename);
487
12.2k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
488
12.2k
                                      VSI_STAT_EXISTS_FLAG |
489
12.2k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
490
569
                    !VSI_ISDIR(statBuf.st_mode))
491
487
                {
492
487
                    bArchiveFileExists = true;
493
487
                }
494
495
12.2k
                (*pnCounter)--;
496
12.2k
            }
497
498
12.2k
            if (bArchiveFileExists)
499
487
            {
500
487
                if (IsEitherSlash(pszFilename[i + nToSkip]))
501
238
                {
502
238
                    osFileInArchive =
503
238
                        CompactFilename(pszFilename + i + nToSkip + 1);
504
238
                }
505
249
                else
506
249
                {
507
249
                    osFileInArchive = "";
508
249
                }
509
510
                // Remove trailing slash.
511
487
                if (!osFileInArchive.empty())
512
230
                {
513
230
                    const char lastC = osFileInArchive.back();
514
230
                    if (IsEitherSlash(lastC))
515
126
                        osFileInArchive.resize(osFileInArchive.size() - 1);
516
230
                }
517
518
487
                return archiveFilename;
519
487
            }
520
11.8k
            CPLFree(archiveFilename);
521
11.8k
        }
522
959k
        i++;
523
959k
    }
524
3.88k
    return nullptr;
525
4.37k
}
526
527
/************************************************************************/
528
/*                           OpenArchiveFile()                          */
529
/************************************************************************/
530
531
std::unique_ptr<VSIArchiveReader>
532
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
533
                                             const char *fileInArchiveName)
534
703
{
535
703
    auto poReader = CreateReader(archiveFilename);
536
537
703
    if (poReader == nullptr)
538
703
    {
539
703
        return nullptr;
540
703
    }
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
3.84k
{
640
3.84k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
641
642
3.84k
    CPLString osFileInArchive;
643
3.84k
    char *archiveFilename =
644
3.84k
        SplitFilename(pszFilename, osFileInArchive, true,
645
3.84k
                      (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
646
3.84k
    if (archiveFilename == nullptr)
647
2.73k
        return -1;
648
649
1.11k
    int ret = -1;
650
1.11k
    if (!osFileInArchive.empty())
651
850
    {
652
#ifdef DEBUG_VERBOSE
653
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
654
                 osFileInArchive.c_str());
655
#endif
656
657
850
        const VSIArchiveEntry *archiveEntry = nullptr;
658
850
        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
850
    }
671
260
    else
672
260
    {
673
260
        auto poReader = CreateReader(archiveFilename);
674
260
        CPLFree(archiveFilename);
675
260
        archiveFilename = nullptr;
676
677
260
        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
260
    }
707
708
1.11k
    CPLFree(archiveFilename);
709
1.11k
    return ret;
710
1.11k
}
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