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_tar.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for tar files (.tar).
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
//! @cond Doxygen_Suppress
14
15
#include "cpl_port.h"
16
#include "cpl_vsi.h"
17
18
#include <cstring>
19
20
#include <fcntl.h>
21
22
#include <memory>
23
#include <string>
24
#include <string_view>
25
#include <vector>
26
27
#include "cpl_conv.h"
28
#include "cpl_error.h"
29
#include "cpl_string.h"
30
#include "cpl_vsi_virtual.h"
31
32
#if (defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)) &&   \
33
    !defined(HAVE_FUZZER_FRIENDLY_ARCHIVE)
34
/* This is a completely custom archive format that is rather inefficient */
35
/* but supports random insertions or deletions, since it doesn't record */
36
/* explicit file size or rely on files starting on a particular boundary */
37
#define HAVE_FUZZER_FRIENDLY_ARCHIVE 1
38
#endif
39
40
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
41
constexpr int HALF_BUFFER_SIZE = 1024;
42
constexpr int BUFFER_SIZE = 2 * HALF_BUFFER_SIZE;
43
#endif
44
45
/************************************************************************/
46
/* ==================================================================== */
47
/*                       VSITarEntryFileOffset                          */
48
/* ==================================================================== */
49
/************************************************************************/
50
51
class VSITarEntryFileOffset final : public VSIArchiveEntryFileOffset
52
{
53
  public:
54
    GUIntBig m_nOffset = 0;
55
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
56
    GUIntBig m_nFileSize = 0;
57
    CPLString m_osFileName{};
58
#endif
59
60
0
    explicit VSITarEntryFileOffset(GUIntBig nOffset) : m_nOffset(nOffset)
61
0
    {
62
0
    }
63
64
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
65
    VSITarEntryFileOffset(GUIntBig nOffset, GUIntBig nFileSize,
66
                          const CPLString &osFileName)
67
0
        : m_nOffset(nOffset), m_nFileSize(nFileSize), m_osFileName(osFileName)
68
0
    {
69
0
    }
70
#endif
71
72
    ~VSITarEntryFileOffset() override;
73
};
74
75
0
VSITarEntryFileOffset::~VSITarEntryFileOffset() = default;
76
77
/************************************************************************/
78
/* ==================================================================== */
79
/*                             VSITarReader                             */
80
/* ==================================================================== */
81
/************************************************************************/
82
83
class VSITarReader final : public VSIArchiveReader
84
{
85
  private:
86
    CPL_DISALLOW_COPY_ASSIGN(VSITarReader)
87
88
    VSILFILE *fp = nullptr;
89
    GUIntBig nCurOffset = 0;
90
    GUIntBig nNextFileSize = 0;
91
    CPLString osNextFileName{};
92
    GIntBig nModifiedTime = 0;
93
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
94
    bool m_bIsFuzzerFriendly = false;
95
    GByte m_abyBuffer[BUFFER_SIZE + 1] = {};
96
    int m_abyBufferIdx = 0;
97
    int m_abyBufferSize = 0;
98
    GUIntBig m_nCurOffsetOld = 0;
99
#endif
100
101
  public:
102
    explicit VSITarReader(const char *pszTarFileName);
103
    ~VSITarReader() override;
104
105
    int IsValid()
106
374
    {
107
374
        return fp != nullptr;
108
374
    }
109
110
    int GotoFirstFile() override;
111
    int GotoNextFile() override;
112
    VSIArchiveEntryFileOffset *GetFileOffset() override;
113
114
    GUIntBig GetFileSize() override
115
0
    {
116
0
        return nNextFileSize;
117
0
    }
118
119
    CPLString GetFileName() override
120
0
    {
121
0
        return osNextFileName;
122
0
    }
123
124
    GIntBig GetModifiedTime() override
125
0
    {
126
0
        return nModifiedTime;
127
0
    }
128
129
    int GotoFileOffset(VSIArchiveEntryFileOffset *pOffset) override;
130
};
131
132
/************************************************************************/
133
/*                               VSIIsTGZ()                             */
134
/************************************************************************/
135
136
static bool VSIIsTGZ(const char *pszFilename)
137
374
{
138
374
    return (
139
374
        !STARTS_WITH_CI(pszFilename, "/vsigzip/") &&
140
332
        ((strlen(pszFilename) > 4 &&
141
332
          STARTS_WITH_CI(pszFilename + strlen(pszFilename) - 4, ".tgz")) ||
142
332
         (strlen(pszFilename) > 7 &&
143
332
          STARTS_WITH_CI(pszFilename + strlen(pszFilename) - 7, ".tar.gz"))));
144
374
}
145
146
/************************************************************************/
147
/*                           VSITarReader()                             */
148
/************************************************************************/
149
150
// TODO(schwehr): What is this ***NEWFILE*** thing?
151
// And make it a symbolic constant.
152
153
VSITarReader::VSITarReader(const char *pszTarFileName)
154
374
    : fp(VSIFOpenL(pszTarFileName, "rb"))
155
374
{
156
374
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
157
374
    if (fp != nullptr)
158
290
    {
159
290
        GByte abySignature[24] = {};
160
290
        m_bIsFuzzerFriendly =
161
290
            (VSIFReadL(abySignature, 1, 24, fp) == 24) &&
162
0
            (memcmp(abySignature, "FUZZER_FRIENDLY_ARCHIVE\n", 24) == 0 ||
163
0
             memcmp(abySignature, "***NEWFILE***:", strlen("***NEWFILE***:")) ==
164
0
                 0);
165
290
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
166
290
    }
167
374
#endif
168
374
}
169
170
/************************************************************************/
171
/*                          ~VSITarReader()                             */
172
/************************************************************************/
173
174
VSITarReader::~VSITarReader()
175
374
{
176
374
    if (fp)
177
290
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
178
374
}
179
180
/************************************************************************/
181
/*                          GetFileOffset()                             */
182
/************************************************************************/
183
184
VSIArchiveEntryFileOffset *VSITarReader::GetFileOffset()
185
0
{
186
0
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
187
0
    if (m_bIsFuzzerFriendly)
188
0
    {
189
0
        return new VSITarEntryFileOffset(nCurOffset, nNextFileSize,
190
0
                                         osNextFileName);
191
0
    }
192
0
#endif
193
0
    return new VSITarEntryFileOffset(nCurOffset);
194
0
}
195
196
/************************************************************************/
197
/*                       IsNumericFieldTerminator()                     */
198
/************************************************************************/
199
200
static bool IsNumericFieldTerminator(GByte byVal)
201
0
{
202
    // See https://github.com/Keruspe/tar-parser.rs/blob/master/tar.specs#L202
203
0
    return byVal == '\0' || byVal == ' ';
204
0
}
205
206
/************************************************************************/
207
/*                           GotoNextFile()                             */
208
/************************************************************************/
209
210
int VSITarReader::GotoNextFile()
211
290
{
212
290
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
213
290
    if (m_bIsFuzzerFriendly)
214
0
    {
215
0
        const int nNewFileMarkerSize =
216
0
            static_cast<int>(strlen("***NEWFILE***:"));
217
0
        while (true)
218
0
        {
219
0
            if (m_abyBufferIdx >= m_abyBufferSize)
220
0
            {
221
0
                if (m_abyBufferSize == 0)
222
0
                {
223
0
                    m_abyBufferSize = static_cast<int>(
224
0
                        VSIFReadL(m_abyBuffer, 1, BUFFER_SIZE, fp));
225
0
                    if (m_abyBufferSize == 0)
226
0
                        return FALSE;
227
0
                    m_abyBuffer[m_abyBufferSize] = '\0';
228
0
                }
229
0
                else
230
0
                {
231
0
                    if (m_abyBufferSize < BUFFER_SIZE)
232
0
                    {
233
0
                        if (nCurOffset > 0 && nCurOffset != m_nCurOffsetOld)
234
0
                        {
235
0
                            nNextFileSize = VSIFTellL(fp);
236
0
                            if (nNextFileSize >= nCurOffset)
237
0
                            {
238
0
                                nNextFileSize -= nCurOffset;
239
0
                                m_nCurOffsetOld = nCurOffset;
240
0
                                return TRUE;
241
0
                            }
242
0
                        }
243
0
                        return FALSE;
244
0
                    }
245
0
                    memcpy(m_abyBuffer, m_abyBuffer + HALF_BUFFER_SIZE,
246
0
                           HALF_BUFFER_SIZE);
247
0
                    m_abyBufferSize = static_cast<int>(
248
0
                        VSIFReadL(m_abyBuffer + HALF_BUFFER_SIZE, 1,
249
0
                                  HALF_BUFFER_SIZE, fp));
250
0
                    if (m_abyBufferSize == 0)
251
0
                        return FALSE;
252
0
                    m_abyBufferIdx = 0;
253
0
                    m_abyBufferSize += HALF_BUFFER_SIZE;
254
0
                    m_abyBuffer[m_abyBufferSize] = '\0';
255
0
                }
256
0
            }
257
258
0
            std::string_view abyBuffer(reinterpret_cast<char *>(m_abyBuffer),
259
0
                                       m_abyBufferSize);
260
0
            const auto posNewFile = abyBuffer.find(
261
0
                std::string_view("***NEWFILE***:", nNewFileMarkerSize),
262
0
                m_abyBufferIdx);
263
0
            if (posNewFile == std::string::npos)
264
0
            {
265
0
                m_abyBufferIdx = m_abyBufferSize;
266
0
            }
267
0
            else
268
0
            {
269
0
                m_abyBufferIdx = static_cast<int>(posNewFile);
270
                // 2: space for at least one-char filename and '\n'
271
0
                if (m_abyBufferIdx < m_abyBufferSize - (nNewFileMarkerSize + 2))
272
0
                {
273
0
                    if (nCurOffset > 0 && nCurOffset != m_nCurOffsetOld)
274
0
                    {
275
0
                        nNextFileSize = VSIFTellL(fp);
276
0
                        nNextFileSize -= m_abyBufferSize;
277
0
                        nNextFileSize += m_abyBufferIdx;
278
0
                        if (nNextFileSize >= nCurOffset)
279
0
                        {
280
0
                            nNextFileSize -= nCurOffset;
281
0
                            m_nCurOffsetOld = nCurOffset;
282
0
                            return TRUE;
283
0
                        }
284
0
                    }
285
0
                    m_abyBufferIdx += nNewFileMarkerSize;
286
0
                    const int nFilenameStartIdx = m_abyBufferIdx;
287
0
                    for (; m_abyBufferIdx < m_abyBufferSize &&
288
0
                           m_abyBuffer[m_abyBufferIdx] != '\n';
289
0
                         ++m_abyBufferIdx)
290
0
                    {
291
                        // Do nothing.
292
0
                    }
293
0
                    if (m_abyBufferIdx < m_abyBufferSize)
294
0
                    {
295
0
                        const char *pszFilename =
296
0
                            reinterpret_cast<const char *>(m_abyBuffer +
297
0
                                                           nFilenameStartIdx);
298
0
                        osNextFileName.assign(
299
0
                            pszFilename,
300
0
                            CPLStrnlen(pszFilename,
301
0
                                       m_abyBufferIdx - nFilenameStartIdx));
302
0
                        if (osNextFileName.empty() || osNextFileName == "." ||
303
0
                            osNextFileName.find("..") != std::string::npos ||
304
0
                            osNextFileName.find("//") != std::string::npos ||
305
0
                            osNextFileName.find("\\\\") != std::string::npos)
306
0
                        {
307
0
                            CPLError(CE_Failure, CPLE_AppDefined,
308
0
                                     "Invalid filename");
309
0
                            return false;
310
0
                        }
311
0
                        nCurOffset = VSIFTellL(fp);
312
0
                        nCurOffset -= m_abyBufferSize;
313
0
                        nCurOffset += m_abyBufferIdx + 1;
314
0
                    }
315
0
                }
316
0
                else
317
0
                {
318
0
                    m_abyBufferIdx = m_abyBufferSize;
319
0
                }
320
0
            }
321
0
        }
322
0
    }
323
290
#endif
324
325
290
    osNextFileName.clear();
326
290
    while (true)
327
290
    {
328
290
        GByte abyHeader[512] = {};
329
290
        if (VSIFReadL(abyHeader, 512, 1, fp) != 1)
330
290
            return FALSE;
331
332
0
        if (!(abyHeader[100] == 0x80 ||
333
0
              IsNumericFieldTerminator(
334
0
                  abyHeader[107])) || /* start/end of filemode */
335
0
            !(abyHeader[108] == 0x80 ||
336
0
              IsNumericFieldTerminator(
337
0
                  abyHeader[115])) || /* start/end of owner ID */
338
0
            !(abyHeader[116] == 0x80 ||
339
0
              IsNumericFieldTerminator(
340
0
                  abyHeader[123])) || /* start/end of group ID */
341
0
            !IsNumericFieldTerminator(abyHeader[135]) || /* end of file size */
342
0
            !IsNumericFieldTerminator(abyHeader[147]))   /* end of mtime */
343
0
        {
344
0
            return FALSE;
345
0
        }
346
0
        if (!(abyHeader[124] == ' ' ||
347
0
              (abyHeader[124] >= '0' && abyHeader[124] <= '7')))
348
0
            return FALSE;
349
350
0
        if (osNextFileName.empty())
351
0
        {
352
0
            osNextFileName.assign(
353
0
                reinterpret_cast<const char *>(abyHeader),
354
0
                CPLStrnlen(reinterpret_cast<const char *>(abyHeader), 100));
355
0
        }
356
357
0
        nNextFileSize = 0;
358
0
        for (int i = 0; i < 11; i++)
359
0
        {
360
0
            if (abyHeader[124 + i] != ' ')
361
0
            {
362
0
                if (nNextFileSize > static_cast<GUIntBig>(GINTBIG_MAX / 8) ||
363
0
                    abyHeader[124 + i] < '0' || abyHeader[124 + i] >= '8')
364
0
                {
365
0
                    CPLError(CE_Failure, CPLE_AppDefined,
366
0
                             "Invalid file size for %s",
367
0
                             osNextFileName.c_str());
368
0
                    return FALSE;
369
0
                }
370
0
                nNextFileSize = nNextFileSize * 8 + (abyHeader[124 + i] - '0');
371
0
            }
372
0
        }
373
0
        if (nNextFileSize > GINTBIG_MAX)
374
0
        {
375
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid file size for %s",
376
0
                     osNextFileName.c_str());
377
0
            return FALSE;
378
0
        }
379
380
0
        nModifiedTime = 0;
381
0
        for (int i = 0; i < 11; i++)
382
0
        {
383
0
            if (abyHeader[136 + i] != ' ')
384
0
            {
385
0
                if (nModifiedTime > GINTBIG_MAX / 8 ||
386
0
                    abyHeader[136 + i] < '0' || abyHeader[136 + i] >= '8' ||
387
0
                    nModifiedTime * 8 >
388
0
                        GINTBIG_MAX - (abyHeader[136 + i] - '0'))
389
0
                {
390
0
                    CPLError(CE_Failure, CPLE_AppDefined,
391
0
                             "Invalid mtime for %s", osNextFileName.c_str());
392
0
                    return FALSE;
393
0
                }
394
0
                nModifiedTime = nModifiedTime * 8 + (abyHeader[136 + i] - '0');
395
0
            }
396
0
        }
397
398
0
        if (abyHeader[156] == 'L' && nNextFileSize > 0 && nNextFileSize < 32768)
399
0
        {
400
            // If this is a large filename record, then read the filename
401
0
            osNextFileName.clear();
402
0
            osNextFileName.resize(
403
0
                static_cast<size_t>(((nNextFileSize + 511) / 512) * 512));
404
0
            if (VSIFReadL(&osNextFileName[0], osNextFileName.size(), 1, fp) !=
405
0
                1)
406
0
                return FALSE;
407
0
            osNextFileName.resize(static_cast<size_t>(nNextFileSize));
408
0
            if (osNextFileName.back() == '\0')
409
0
                osNextFileName.pop_back();
410
0
        }
411
0
        else
412
0
        {
413
            // Is it a ustar extension ?
414
            // Cf https://en.wikipedia.org/wiki/Tar_(computing)#UStar_format
415
0
            if (memcmp(abyHeader + 257, "ustar\0", 6) == 0 &&
416
0
                abyHeader[345] != '\0')
417
0
            {
418
0
                std::string osFilenamePrefix;
419
0
                osFilenamePrefix.assign(
420
0
                    reinterpret_cast<const char *>(abyHeader + 345),
421
0
                    CPLStrnlen(reinterpret_cast<const char *>(abyHeader + 345),
422
0
                               155));
423
0
                osNextFileName = osFilenamePrefix + '/' + osNextFileName;
424
0
            }
425
426
0
            break;
427
0
        }
428
0
    }
429
430
0
    nCurOffset = VSIFTellL(fp);
431
432
0
    const GUIntBig nBytesToSkip = ((nNextFileSize + 511) / 512) * 512;
433
0
    if (nBytesToSkip > (~(static_cast<GUIntBig>(0))) - nCurOffset)
434
0
    {
435
0
        CPLError(CE_Failure, CPLE_AppDefined, "Bad .tar structure");
436
0
        return FALSE;
437
0
    }
438
439
0
    if (VSIFSeekL(fp, nBytesToSkip, SEEK_CUR) < 0)
440
0
        return FALSE;
441
442
0
    return TRUE;
443
0
}
444
445
/************************************************************************/
446
/*                          GotoFirstFile()                             */
447
/************************************************************************/
448
449
int VSITarReader::GotoFirstFile()
450
290
{
451
290
    if (VSIFSeekL(fp, 0, SEEK_SET) < 0)
452
0
        return FALSE;
453
290
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
454
290
    m_abyBufferIdx = 0;
455
290
    m_abyBufferSize = 0;
456
290
    nCurOffset = 0;
457
290
    m_nCurOffsetOld = 0;
458
290
    osNextFileName = "";
459
290
    nNextFileSize = 0;
460
290
#endif
461
290
    return GotoNextFile();
462
290
}
463
464
/************************************************************************/
465
/*                         GotoFileOffset()                             */
466
/************************************************************************/
467
468
int VSITarReader::GotoFileOffset(VSIArchiveEntryFileOffset *pOffset)
469
0
{
470
0
    VSITarEntryFileOffset *pTarEntryOffset =
471
0
        static_cast<VSITarEntryFileOffset *>(pOffset);
472
0
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
473
0
    if (m_bIsFuzzerFriendly)
474
0
    {
475
0
        if (VSIFSeekL(fp,
476
0
                      pTarEntryOffset->m_nOffset + pTarEntryOffset->m_nFileSize,
477
0
                      SEEK_SET) < 0)
478
0
            return FALSE;
479
0
        m_abyBufferIdx = 0;
480
0
        m_abyBufferSize = 0;
481
0
        nCurOffset = pTarEntryOffset->m_nOffset;
482
0
        m_nCurOffsetOld = pTarEntryOffset->m_nOffset;
483
0
        osNextFileName = pTarEntryOffset->m_osFileName;
484
0
        nNextFileSize = pTarEntryOffset->m_nFileSize;
485
0
        return TRUE;
486
0
    }
487
0
#endif
488
0
    if (pTarEntryOffset->m_nOffset < 512 ||
489
0
        VSIFSeekL(fp, pTarEntryOffset->m_nOffset - 512, SEEK_SET) < 0)
490
0
        return FALSE;
491
0
    return GotoNextFile();
492
0
}
493
494
/************************************************************************/
495
/* ==================================================================== */
496
/*                        VSITarFilesystemHandler                      */
497
/* ==================================================================== */
498
/************************************************************************/
499
500
class VSITarFilesystemHandler final : public VSIArchiveFilesystemHandler
501
{
502
  public:
503
    const char *GetPrefix() const override
504
15.0k
    {
505
15.0k
        return "/vsitar";
506
15.0k
    }
507
508
    std::vector<CPLString> GetExtensions() const override;
509
    std::unique_ptr<VSIArchiveReader>
510
    CreateReader(const char *pszTarFileName) override;
511
512
    VSIVirtualHandleUniquePtr Open(const char *pszFilename,
513
                                   const char *pszAccess, bool bSetError,
514
                                   CSLConstList /* papszOptions */) override;
515
};
516
517
/************************************************************************/
518
/*                          GetExtensions()                             */
519
/************************************************************************/
520
521
std::vector<CPLString> VSITarFilesystemHandler::GetExtensions() const
522
3.25k
{
523
3.25k
    std::vector<CPLString> oList;
524
3.25k
    oList.push_back(".tar.gz");
525
3.25k
    oList.push_back(".tar");
526
3.25k
    oList.push_back(".tgz");
527
3.25k
    return oList;
528
3.25k
}
529
530
/************************************************************************/
531
/*                           CreateReader()                             */
532
/************************************************************************/
533
534
std::unique_ptr<VSIArchiveReader>
535
VSITarFilesystemHandler::CreateReader(const char *pszTarFileName)
536
374
{
537
374
    CPLString osTarInFileName;
538
539
374
    if (VSIIsTGZ(pszTarFileName))
540
0
    {
541
0
        osTarInFileName = "/vsigzip/";
542
0
        osTarInFileName += pszTarFileName;
543
0
    }
544
374
    else
545
374
        osTarInFileName = pszTarFileName;
546
547
374
    auto poReader = std::make_unique<VSITarReader>(osTarInFileName);
548
374
    if (!poReader->IsValid() || !poReader->GotoFirstFile())
549
374
    {
550
374
        return nullptr;
551
374
    }
552
553
0
    return poReader;
554
374
}
555
556
/************************************************************************/
557
/*                                 Open()                               */
558
/************************************************************************/
559
560
VSIVirtualHandleUniquePtr
561
VSITarFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
562
                              bool bSetError, CSLConstList /* papszOptions */)
563
853
{
564
565
853
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
566
0
    {
567
0
        CPLError(CE_Failure, CPLE_AppDefined,
568
0
                 "Only read-only mode is supported for /vsitar");
569
0
        return nullptr;
570
0
    }
571
572
853
    CPLString osTarInFileName;
573
853
    std::unique_ptr<char, VSIFreeReleaser> tarFilename(
574
853
        SplitFilename(pszFilename, osTarInFileName, true, bSetError));
575
853
    if (tarFilename == nullptr)
576
775
        return nullptr;
577
578
78
    auto poReader = OpenArchiveFile(tarFilename.get(), osTarInFileName);
579
78
    if (poReader == nullptr)
580
78
    {
581
78
        return nullptr;
582
78
    }
583
584
0
    CPLString osSubFileName("/vsisubfile/");
585
0
    VSITarEntryFileOffset *pOffset =
586
0
        reinterpret_cast<VSITarEntryFileOffset *>(poReader->GetFileOffset());
587
0
    osSubFileName += CPLString().Printf(CPL_FRMT_GUIB, pOffset->m_nOffset);
588
0
    osSubFileName += "_";
589
0
    osSubFileName += CPLString().Printf(CPL_FRMT_GUIB, poReader->GetFileSize());
590
0
    osSubFileName += ",";
591
0
    delete pOffset;
592
593
0
    if (VSIIsTGZ(tarFilename.get()))
594
0
    {
595
0
        osSubFileName += "/vsigzip/";
596
0
        osSubFileName += tarFilename.get();
597
0
    }
598
0
    else
599
0
        osSubFileName += tarFilename.get();
600
601
0
    return VSIFilesystemHandler::OpenStatic(osSubFileName, "rb");
602
78
}
603
604
//! @endcond
605
606
/************************************************************************/
607
/*                    VSIInstallTarFileHandler()                        */
608
/************************************************************************/
609
610
/*!
611
 \brief Install /vsitar/ file system handler.
612
613
 A special file handler is installed that allows reading on-the-fly in TAR
614
 (regular .tar, or compressed .tar.gz/.tgz) archives.
615
616
 All portions of the file system underneath the base path "/vsitar/" will be
617
 handled by this driver.
618
619
 \verbatim embed:rst
620
 See :ref:`/vsitar/ documentation <vsitar>`
621
 \endverbatim
622
623
 */
624
625
void VSIInstallTarFileHandler(void)
626
3
{
627
3
    VSIFileManager::InstallHandler("/vsitar/", new VSITarFilesystemHandler());
628
3
}