Coverage Report

Created: 2025-06-22 06:59

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