Coverage Report

Created: 2025-06-13 06:29

/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
20.8k
{
37
20.8k
    return c == '/' || c == '\\';
38
20.8k
}
39
40
/************************************************************************/
41
/*                    ~VSIArchiveEntryFileOffset()                      */
42
/************************************************************************/
43
44
VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45
0
{
46
0
}
47
48
/************************************************************************/
49
/*                        ~VSIArchiveReader()                           */
50
/************************************************************************/
51
52
VSIArchiveReader::~VSIArchiveReader()
53
2.06k
{
54
2.06k
}
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
949
{
141
949
    CPLMutexHolder oHolder(&hMutex);
142
143
949
    VSIStatBufL sStat;
144
949
    if (VSIStatL(archiveFilename, &sStat) != 0)
145
175
        return nullptr;
146
147
774
    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
774
    bool bMustClose = poReader == nullptr;
166
774
    if (poReader == nullptr)
167
774
    {
168
774
        poReader = CreateReader(archiveFilename);
169
774
        if (!poReader)
170
774
            return nullptr;
171
774
    }
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
949
{
283
949
    if (fileInArchiveName == nullptr)
284
0
        return FALSE;
285
286
949
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
287
949
    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
949
    return FALSE;
300
949
}
301
302
/************************************************************************/
303
/*                           CompactFilename()                          */
304
/************************************************************************/
305
306
static std::string CompactFilename(const char *pszArchiveInFileNameIn)
307
2.65k
{
308
2.65k
    std::string osRet(pszArchiveInFileNameIn);
309
310
    // Replace a/../b by b and foo/a/../b by foo/b.
311
9.59k
    while (true)
312
9.59k
    {
313
9.59k
        size_t nSlashDotDot = osRet.find("/../");
314
9.59k
        if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
315
2.65k
            break;
316
6.94k
        size_t nPos = nSlashDotDot - 1;
317
136k
        while (nPos > 0 && osRet[nPos] != '/')
318
130k
            --nPos;
319
6.94k
        if (nPos == 0)
320
2.47k
            osRet = osRet.substr(nSlashDotDot + strlen("/../"));
321
4.47k
        else
322
4.47k
            osRet = osRet.substr(0, nPos + 1) +
323
4.47k
                    osRet.substr(nSlashDotDot + strlen("/../"));
324
6.94k
    }
325
2.65k
    return osRet;
326
2.65k
}
327
328
/************************************************************************/
329
/*                           SplitFilename()                            */
330
/************************************************************************/
331
332
char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
333
                                                 CPLString &osFileInArchive,
334
                                                 int bCheckMainFileExists)
335
10.4k
{
336
    // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
337
10.4k
    if (strcmp(pszFilename, GetPrefix()) == 0)
338
2
        return nullptr;
339
340
10.4k
    int i = 0;
341
342
    // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
343
10.4k
    if (pszFilename[strlen(GetPrefix()) + 1] == '{')
344
4.67k
    {
345
4.67k
        pszFilename += strlen(GetPrefix()) + 1;
346
4.67k
        int nCountCurlies = 0;
347
335k
        while (pszFilename[i])
348
335k
        {
349
335k
            if (pszFilename[i] == '{')
350
5.66k
                nCountCurlies++;
351
329k
            else if (pszFilename[i] == '}')
352
5.41k
            {
353
5.41k
                nCountCurlies--;
354
5.41k
                if (nCountCurlies == 0)
355
4.52k
                    break;
356
5.41k
            }
357
330k
            i++;
358
330k
        }
359
4.67k
        if (nCountCurlies > 0)
360
143
            return nullptr;
361
4.52k
        char *archiveFilename = CPLStrdup(pszFilename + 1);
362
4.52k
        archiveFilename[i - 1] = 0;
363
364
4.52k
        bool bArchiveFileExists = false;
365
4.52k
        if (!bCheckMainFileExists)
366
0
        {
367
0
            bArchiveFileExists = true;
368
0
        }
369
4.52k
        else
370
4.52k
        {
371
4.52k
            CPLMutexHolder oHolder(&hMutex);
372
373
4.52k
            if (oFileList.find(archiveFilename) != oFileList.end())
374
0
            {
375
0
                bArchiveFileExists = true;
376
0
            }
377
4.52k
        }
378
379
4.52k
        if (!bArchiveFileExists)
380
4.52k
        {
381
4.52k
            VSIStatBufL statBuf;
382
4.52k
            VSIFilesystemHandler *poFSHandler =
383
4.52k
                VSIFileManager::GetHandler(archiveFilename);
384
4.52k
            if (poFSHandler->Stat(archiveFilename, &statBuf,
385
4.52k
                                  VSI_STAT_EXISTS_FLAG |
386
4.52k
                                      VSI_STAT_NATURE_FLAG) == 0 &&
387
4.52k
                !VSI_ISDIR(statBuf.st_mode))
388
3.03k
            {
389
3.03k
                bArchiveFileExists = true;
390
3.03k
            }
391
4.52k
        }
392
393
4.52k
        if (bArchiveFileExists)
394
3.03k
        {
395
3.03k
            if (IsEitherSlash(pszFilename[i + 1]))
396
2.27k
            {
397
2.27k
                osFileInArchive = CompactFilename(pszFilename + i + 2);
398
2.27k
            }
399
758
            else if (pszFilename[i + 1] == '\0')
400
1
            {
401
1
                osFileInArchive = "";
402
1
            }
403
757
            else
404
757
            {
405
757
                CPLFree(archiveFilename);
406
757
                return nullptr;
407
757
            }
408
409
            // Remove trailing slash.
410
2.27k
            if (!osFileInArchive.empty())
411
2.23k
            {
412
2.23k
                const char lastC = osFileInArchive.back();
413
2.23k
                if (IsEitherSlash(lastC))
414
450
                    osFileInArchive.pop_back();
415
2.23k
            }
416
417
2.27k
            return archiveFilename;
418
3.03k
        }
419
420
1.49k
        CPLFree(archiveFilename);
421
1.49k
        return nullptr;
422
4.52k
    }
423
424
    // Allow natural chaining of VSI drivers without requiring double slash.
425
426
5.78k
    CPLString osDoubleVsi(GetPrefix());
427
5.78k
    osDoubleVsi += "/vsi";
428
429
5.78k
    if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
430
3.59k
        pszFilename += strlen(GetPrefix());
431
2.19k
    else
432
2.19k
        pszFilename += strlen(GetPrefix()) + 1;
433
434
    // Parsing strings like
435
    // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
436
    // takes a huge amount of time, so limit the number of nesting of such
437
    // file systems.
438
5.78k
    int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
439
5.78k
    if (pnCounter == nullptr)
440
1
    {
441
1
        pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
442
1
        *pnCounter = 0;
443
1
        CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
444
1
    }
445
5.78k
    if (*pnCounter == 3)
446
303
    {
447
303
        CPLError(CE_Failure, CPLE_AppDefined,
448
303
                 "Too deep recursion level in "
449
303
                 "VSIArchiveFilesystemHandler::SplitFilename()");
450
303
        return nullptr;
451
303
    }
452
453
5.48k
    const std::vector<CPLString> oExtensions = GetExtensions();
454
5.48k
    int nAttempts = 0;
455
1.19M
    while (pszFilename[i])
456
1.19M
    {
457
1.19M
        int nToSkip = 0;
458
459
1.19M
        for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
460
5.76M
             iter != oExtensions.end(); ++iter)
461
4.58M
        {
462
4.58M
            const CPLString &osExtension = *iter;
463
4.58M
            if (EQUALN(pszFilename + i, osExtension.c_str(),
464
4.58M
                       osExtension.size()))
465
13.8k
            {
466
13.8k
                nToSkip = static_cast<int>(osExtension.size());
467
13.8k
                break;
468
13.8k
            }
469
4.58M
        }
470
471
1.19M
#ifdef DEBUG
472
        // For AFL, so that .cur_input is detected as the archive filename.
473
1.19M
        if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
474
1.24k
        {
475
1.24k
            nToSkip = static_cast<int>(strlen(".cur_input"));
476
1.24k
        }
477
1.19M
#endif
478
479
1.19M
        if (nToSkip != 0)
480
15.0k
        {
481
15.0k
            nAttempts++;
482
            // Arbitrary threshold to avoid DoS with things like
483
            // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
484
15.0k
            if (nAttempts == 5)
485
527
            {
486
527
                break;
487
527
            }
488
14.5k
            VSIStatBufL statBuf;
489
14.5k
            char *archiveFilename = CPLStrdup(pszFilename);
490
14.5k
            bool bArchiveFileExists = false;
491
492
14.5k
            if (IsEitherSlash(archiveFilename[i + nToSkip]))
493
927
            {
494
927
                archiveFilename[i + nToSkip] = 0;
495
927
            }
496
497
14.5k
            if (!bCheckMainFileExists)
498
0
            {
499
0
                bArchiveFileExists = true;
500
0
            }
501
14.5k
            else
502
14.5k
            {
503
14.5k
                CPLMutexHolder oHolder(&hMutex);
504
505
14.5k
                if (oFileList.find(archiveFilename) != oFileList.end())
506
0
                {
507
0
                    bArchiveFileExists = true;
508
0
                }
509
14.5k
            }
510
511
14.5k
            if (!bArchiveFileExists)
512
14.5k
            {
513
14.5k
                (*pnCounter)++;
514
515
14.5k
                VSIFilesystemHandler *poFSHandler =
516
14.5k
                    VSIFileManager::GetHandler(archiveFilename);
517
14.5k
                if (poFSHandler->Stat(archiveFilename, &statBuf,
518
14.5k
                                      VSI_STAT_EXISTS_FLAG |
519
14.5k
                                          VSI_STAT_NATURE_FLAG) == 0 &&
520
14.5k
                    !VSI_ISDIR(statBuf.st_mode))
521
721
                {
522
721
                    bArchiveFileExists = true;
523
721
                }
524
525
14.5k
                (*pnCounter)--;
526
14.5k
            }
527
528
14.5k
            if (bArchiveFileExists)
529
721
            {
530
721
                if (IsEitherSlash(pszFilename[i + nToSkip]))
531
378
                {
532
378
                    osFileInArchive =
533
378
                        CompactFilename(pszFilename + i + nToSkip + 1);
534
378
                }
535
343
                else
536
343
                {
537
343
                    osFileInArchive = "";
538
343
                }
539
540
                // Remove trailing slash.
541
721
                if (!osFileInArchive.empty())
542
318
                {
543
318
                    const char lastC = osFileInArchive.back();
544
318
                    if (IsEitherSlash(lastC))
545
126
                        osFileInArchive.resize(osFileInArchive.size() - 1);
546
318
                }
547
548
721
                return archiveFilename;
549
721
            }
550
13.8k
            CPLFree(archiveFilename);
551
13.8k
        }
552
1.19M
        i++;
553
1.19M
    }
554
4.76k
    return nullptr;
555
5.48k
}
556
557
/************************************************************************/
558
/*                           OpenArchiveFile()                          */
559
/************************************************************************/
560
561
VSIArchiveReader *
562
VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
563
                                             const char *fileInArchiveName)
564
1.05k
{
565
1.05k
    VSIArchiveReader *poReader = CreateReader(archiveFilename);
566
567
1.05k
    if (poReader == nullptr)
568
1.05k
    {
569
1.05k
        return nullptr;
570
1.05k
    }
571
572
0
    if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
573
0
    {
574
0
        if (poReader->GotoFirstFile() == FALSE)
575
0
        {
576
0
            delete (poReader);
577
0
            return nullptr;
578
0
        }
579
580
        // Skip optional leading subdir.
581
0
        const CPLString osFileName = poReader->GetFileName();
582
0
        if (osFileName.empty() || IsEitherSlash(osFileName.back()))
583
0
        {
584
0
            if (poReader->GotoNextFile() == FALSE)
585
0
            {
586
0
                delete (poReader);
587
0
                return nullptr;
588
0
            }
589
0
        }
590
591
0
        if (poReader->GotoNextFile())
592
0
        {
593
0
            CPLString msg;
594
0
            msg.Printf("Support only 1 file in archive file %s when "
595
0
                       "no explicit in-archive filename is specified",
596
0
                       archiveFilename);
597
0
            const VSIArchiveContent *content =
598
0
                GetContentOfArchive(archiveFilename, poReader);
599
0
            if (content)
600
0
            {
601
0
                msg += "\nYou could try one of the following :\n";
602
0
                for (int i = 0; i < content->nEntries; i++)
603
0
                {
604
0
                    msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
605
0
                                              archiveFilename,
606
0
                                              content->entries[i].fileName);
607
0
                }
608
0
            }
609
610
0
            CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
611
612
0
            delete (poReader);
613
0
            return nullptr;
614
0
        }
615
0
    }
616
0
    else
617
0
    {
618
        // Optimization: instead of iterating over all files which can be
619
        // slow on .tar.gz files, try reading the first one first.
620
        // This can help if it is really huge.
621
0
        {
622
0
            CPLMutexHolder oHolder(&hMutex);
623
624
0
            if (oFileList.find(archiveFilename) == oFileList.end())
625
0
            {
626
0
                if (poReader->GotoFirstFile() == FALSE)
627
0
                {
628
0
                    delete (poReader);
629
0
                    return nullptr;
630
0
                }
631
632
0
                const CPLString osFileName = poReader->GetFileName();
633
0
                bool bIsDir = false;
634
0
                const CPLString osStrippedFilename =
635
0
                    GetStrippedFilename(osFileName, bIsDir);
636
0
                if (!osStrippedFilename.empty())
637
0
                {
638
0
                    const bool bMatch =
639
0
                        strcmp(osStrippedFilename, fileInArchiveName) == 0;
640
0
                    if (bMatch)
641
0
                    {
642
0
                        if (bIsDir)
643
0
                        {
644
0
                            delete (poReader);
645
0
                            return nullptr;
646
0
                        }
647
0
                        return poReader;
648
0
                    }
649
0
                }
650
0
            }
651
0
        }
652
653
0
        const VSIArchiveEntry *archiveEntry = nullptr;
654
0
        if (FindFileInArchive(archiveFilename, fileInArchiveName,
655
0
                              &archiveEntry) == FALSE ||
656
0
            archiveEntry->bIsDir)
657
0
        {
658
0
            delete (poReader);
659
0
            return nullptr;
660
0
        }
661
0
        if (!poReader->GotoFileOffset(archiveEntry->file_pos))
662
0
        {
663
0
            delete poReader;
664
0
            return nullptr;
665
0
        }
666
0
    }
667
0
    return poReader;
668
0
}
669
670
/************************************************************************/
671
/*                                 Stat()                               */
672
/************************************************************************/
673
674
int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
675
                                      VSIStatBufL *pStatBuf, int /* nFlags */)
676
5.16k
{
677
5.16k
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
678
679
5.16k
    CPLString osFileInArchive;
680
5.16k
    char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
681
5.16k
    if (archiveFilename == nullptr)
682
3.97k
        return -1;
683
684
1.18k
    int ret = -1;
685
1.18k
    if (!osFileInArchive.empty())
686
949
    {
687
#ifdef DEBUG_VERBOSE
688
        CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
689
                 osFileInArchive.c_str());
690
#endif
691
692
949
        const VSIArchiveEntry *archiveEntry = nullptr;
693
949
        if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
694
0
        {
695
            // Patching st_size with uncompressed file size.
696
0
            pStatBuf->st_size = archiveEntry->uncompressed_size;
697
0
            pStatBuf->st_mtime =
698
0
                static_cast<time_t>(archiveEntry->nModifiedTime);
699
0
            if (archiveEntry->bIsDir)
700
0
                pStatBuf->st_mode = S_IFDIR;
701
0
            else
702
0
                pStatBuf->st_mode = S_IFREG;
703
0
            ret = 0;
704
0
        }
705
949
    }
706
239
    else
707
239
    {
708
239
        VSIArchiveReader *poReader = CreateReader(archiveFilename);
709
239
        CPLFree(archiveFilename);
710
239
        archiveFilename = nullptr;
711
712
239
        if (poReader != nullptr && poReader->GotoFirstFile())
713
0
        {
714
            // Skip optional leading subdir.
715
0
            const CPLString osFileName = poReader->GetFileName();
716
0
            if (IsEitherSlash(osFileName.back()))
717
0
            {
718
0
                if (poReader->GotoNextFile() == FALSE)
719
0
                {
720
0
                    delete (poReader);
721
0
                    return -1;
722
0
                }
723
0
            }
724
725
0
            if (poReader->GotoNextFile())
726
0
            {
727
                // Several files in archive --> treat as dir.
728
0
                pStatBuf->st_size = 0;
729
0
                pStatBuf->st_mode = S_IFDIR;
730
0
            }
731
0
            else
732
0
            {
733
                // Patching st_size with uncompressed file size.
734
0
                pStatBuf->st_size = poReader->GetFileSize();
735
0
                pStatBuf->st_mtime =
736
0
                    static_cast<time_t>(poReader->GetModifiedTime());
737
0
                pStatBuf->st_mode = S_IFREG;
738
0
            }
739
740
0
            ret = 0;
741
0
        }
742
743
239
        delete (poReader);
744
239
    }
745
746
1.18k
    CPLFree(archiveFilename);
747
1.18k
    return ret;
748
1.18k
}
749
750
/************************************************************************/
751
/*                             ReadDirEx()                              */
752
/************************************************************************/
753
754
char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
755
                                              int nMaxFiles)
756
0
{
757
0
    CPLString osInArchiveSubDir;
758
0
    char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
759
0
    if (archiveFilename == nullptr)
760
0
        return nullptr;
761
762
0
    const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
763
764
0
    CPLStringList oDir;
765
766
0
    const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
767
0
    if (!content)
768
0
    {
769
0
        CPLFree(archiveFilename);
770
0
        return nullptr;
771
0
    }
772
773
#ifdef DEBUG_VERBOSE
774
    CPLDebug("VSIArchive", "Read dir %s", pszDirname);
775
#endif
776
0
    for (int i = 0; i < content->nEntries; i++)
777
0
    {
778
0
        const char *fileName = content->entries[i].fileName;
779
        /* Only list entries at the same level of inArchiveSubDir */
780
0
        if (lenInArchiveSubDir != 0 &&
781
0
            strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
782
0
            IsEitherSlash(fileName[lenInArchiveSubDir]) &&
783
0
            fileName[lenInArchiveSubDir + 1] != 0)
784
0
        {
785
0
            const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
786
0
            if (slash == nullptr)
787
0
                slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
788
0
            if (slash == nullptr || slash[1] == 0)
789
0
            {
790
0
                char *tmpFileName = CPLStrdup(fileName);
791
0
                if (slash != nullptr)
792
0
                {
793
0
                    tmpFileName[strlen(tmpFileName) - 1] = 0;
794
0
                }
795
#ifdef DEBUG_VERBOSE
796
                CPLDebug("VSIArchive", "Add %s as in directory %s",
797
                         tmpFileName + lenInArchiveSubDir + 1, pszDirname);
798
#endif
799
0
                oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
800
0
                CPLFree(tmpFileName);
801
0
            }
802
0
        }
803
0
        else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
804
0
                 strchr(fileName, '\\') == nullptr)
805
0
        {
806
            // Only list toplevel files and directories.
807
#ifdef DEBUG_VERBOSE
808
            CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
809
                     pszDirname);
810
#endif
811
0
            oDir.AddString(fileName);
812
0
        }
813
814
0
        if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
815
0
            break;
816
0
    }
817
818
0
    CPLFree(archiveFilename);
819
0
    return oDir.StealList();
820
0
}
821
822
/************************************************************************/
823
/*                               IsLocal()                              */
824
/************************************************************************/
825
826
bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
827
0
{
828
0
    if (!STARTS_WITH(pszPath, GetPrefix()))
829
0
        return false;
830
0
    const char *pszBaseFileName = pszPath + strlen(GetPrefix());
831
0
    VSIFilesystemHandler *poFSHandler =
832
0
        VSIFileManager::GetHandler(pszBaseFileName);
833
0
    return poFSHandler->IsLocal(pszPath);
834
0
}
835
836
//! @endcond