LCOV - code coverage report
Current view: top level - port - cpl_vsil_abstract_archive.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 334 376 88.8 %
Date: 2025-01-18 12:42:00 Functions: 15 22 68.2 %

          Line data    Source code
       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      581507 : static bool IsEitherSlash(char c)
      36             : {
      37      581507 :     return c == '/' || c == '\\';
      38             : }
      39             : 
      40             : /************************************************************************/
      41             : /*                    ~VSIArchiveEntryFileOffset()                      */
      42             : /************************************************************************/
      43             : 
      44        1510 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
      45             : {
      46        1510 : }
      47             : 
      48             : /************************************************************************/
      49             : /*                        ~VSIArchiveReader()                           */
      50             : /************************************************************************/
      51             : 
      52        5054 : VSIArchiveReader::~VSIArchiveReader()
      53             : {
      54        5054 : }
      55             : 
      56             : /************************************************************************/
      57             : /*                        ~VSIArchiveContent()                          */
      58             : /************************************************************************/
      59             : 
      60         130 : VSIArchiveContent::~VSIArchiveContent()
      61             : {
      62        1650 :     for (int i = 0; i < nEntries; i++)
      63             :     {
      64        1585 :         delete entries[i].file_pos;
      65        1585 :         CPLFree(entries[i].fileName);
      66             :     }
      67          65 :     CPLFree(entries);
      68          65 : }
      69             : 
      70             : /************************************************************************/
      71             : /*                   VSIArchiveFilesystemHandler()                      */
      72             : /************************************************************************/
      73             : 
      74        2784 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
      75             : {
      76        2784 :     hMutex = nullptr;
      77        2784 : }
      78             : 
      79             : /************************************************************************/
      80             : /*                   ~VSIArchiveFilesystemHandler()                     */
      81             : /************************************************************************/
      82             : 
      83        1882 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
      84             : 
      85             : {
      86        1932 :     for (const auto &iter : oFileList)
      87             :     {
      88          50 :         delete iter.second;
      89             :     }
      90             : 
      91        1882 :     if (hMutex != nullptr)
      92          17 :         CPLDestroyMutex(hMutex);
      93        1882 :     hMutex = nullptr;
      94        1882 : }
      95             : 
      96             : /************************************************************************/
      97             : /*                       GetStrippedFilename()                          */
      98             : /************************************************************************/
      99             : 
     100        4967 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
     101             : {
     102        4967 :     bIsDir = false;
     103        4967 :     const char *fileName = osFileName.c_str();
     104             : 
     105             :     // Remove ./ pattern at the beginning of a filename.
     106        4967 :     if (fileName[0] == '.' && fileName[1] == '/')
     107             :     {
     108           0 :         fileName += 2;
     109           0 :         if (fileName[0] == '\0')
     110           0 :             return CPLString();
     111             :     }
     112             : 
     113        4967 :     char *pszStrippedFileName = CPLStrdup(fileName);
     114        4967 :     char *pszIter = nullptr;
     115      180338 :     for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
     116             :     {
     117      175371 :         if (*pszIter == '\\')
     118           0 :             *pszIter = '/';
     119             :     }
     120             : 
     121        4967 :     const size_t nLen = strlen(fileName);
     122        4967 :     bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
     123        4967 :     if (bIsDir)
     124             :     {
     125             :         // Remove trailing slash.
     126         202 :         pszStrippedFileName[nLen - 1] = '\0';
     127             :     }
     128        9934 :     CPLString osRet(pszStrippedFileName);
     129        4967 :     CPLFree(pszStrippedFileName);
     130        4967 :     return osRet;
     131             : }
     132             : 
     133             : /************************************************************************/
     134             : /*                       GetContentOfArchive()                          */
     135             : /************************************************************************/
     136             : 
     137             : const VSIArchiveContent *
     138        8222 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
     139             :                                                  VSIArchiveReader *poReader)
     140             : {
     141       16444 :     CPLMutexHolder oHolder(&hMutex);
     142             : 
     143             :     VSIStatBufL sStat;
     144        8222 :     if (VSIStatL(archiveFilename, &sStat) != 0)
     145           0 :         return nullptr;
     146             : 
     147        8222 :     if (oFileList.find(archiveFilename) != oFileList.end())
     148             :     {
     149        8017 :         VSIArchiveContent *content = oFileList[archiveFilename];
     150        8017 :         if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
     151        8016 :             static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
     152             :         {
     153           5 :             CPLDebug("VSIArchive",
     154             :                      "The content of %s has changed since it was cached",
     155             :                      archiveFilename);
     156           5 :             delete content;
     157           5 :             oFileList.erase(archiveFilename);
     158             :         }
     159             :         else
     160             :         {
     161        8012 :             return content;
     162             :         }
     163             :     }
     164             : 
     165         210 :     bool bMustClose = poReader == nullptr;
     166         210 :     if (poReader == nullptr)
     167             :     {
     168         209 :         poReader = CreateReader(archiveFilename);
     169         209 :         if (!poReader)
     170           7 :             return nullptr;
     171             :     }
     172             : 
     173         203 :     if (poReader->GotoFirstFile() == FALSE)
     174             :     {
     175           0 :         if (bMustClose)
     176           0 :             delete (poReader);
     177           0 :         return nullptr;
     178             :     }
     179             : 
     180         203 :     VSIArchiveContent *content = new VSIArchiveContent;
     181         203 :     content->mTime = sStat.st_mtime;
     182         203 :     content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
     183         203 :     content->nEntries = 0;
     184         203 :     content->entries = nullptr;
     185         203 :     oFileList[archiveFilename] = content;
     186             : 
     187         203 :     std::set<CPLString> oSet;
     188             : 
     189        4623 :     do
     190             :     {
     191        4826 :         const CPLString osFileName = poReader->GetFileName();
     192        4826 :         bool bIsDir = false;
     193             :         const CPLString osStrippedFilename =
     194        4826 :             GetStrippedFilename(osFileName, bIsDir);
     195        9652 :         if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
     196        4826 :             osStrippedFilename.find("//") != std::string::npos)
     197             :         {
     198           0 :             continue;
     199             :         }
     200             : 
     201        4826 :         if (oSet.find(osStrippedFilename) == oSet.end())
     202             :         {
     203        4826 :             oSet.insert(osStrippedFilename);
     204             : 
     205             :             // Add intermediate directory structure.
     206        4826 :             const char *pszBegin = osStrippedFilename.c_str();
     207      178211 :             for (const char *pszIter = pszBegin; *pszIter; pszIter++)
     208             :             {
     209      173385 :                 if (*pszIter == '/')
     210             :                 {
     211        6358 :                     char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename);
     212        6358 :                     pszStrippedFileName2[pszIter - pszBegin] = 0;
     213        6358 :                     if (oSet.find(pszStrippedFileName2) == oSet.end())
     214             :                     {
     215        1247 :                         oSet.insert(pszStrippedFileName2);
     216             : 
     217        1247 :                         content->entries =
     218        2494 :                             static_cast<VSIArchiveEntry *>(CPLRealloc(
     219        1247 :                                 content->entries, sizeof(VSIArchiveEntry) *
     220        1247 :                                                       (content->nEntries + 1)));
     221        1247 :                         content->entries[content->nEntries].fileName =
     222             :                             pszStrippedFileName2;
     223        2494 :                         content->entries[content->nEntries].nModifiedTime =
     224        1247 :                             poReader->GetModifiedTime();
     225        1247 :                         content->entries[content->nEntries].uncompressed_size =
     226             :                             0;
     227        1247 :                         content->entries[content->nEntries].bIsDir = TRUE;
     228        1247 :                         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        1247 :                         content->nEntries++;
     238             :                     }
     239             :                     else
     240             :                     {
     241        5111 :                         CPLFree(pszStrippedFileName2);
     242             :                     }
     243             :                 }
     244             :             }
     245             : 
     246        4826 :             content->entries = static_cast<VSIArchiveEntry *>(
     247        9652 :                 CPLRealloc(content->entries,
     248        4826 :                            sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
     249        9652 :             content->entries[content->nEntries].fileName =
     250        4826 :                 CPLStrdup(osStrippedFilename);
     251        9652 :             content->entries[content->nEntries].nModifiedTime =
     252        4826 :                 poReader->GetModifiedTime();
     253        9652 :             content->entries[content->nEntries].uncompressed_size =
     254        4826 :                 poReader->GetFileSize();
     255        4826 :             content->entries[content->nEntries].bIsDir = bIsDir;
     256        9652 :             content->entries[content->nEntries].file_pos =
     257        4826 :                 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        4826 :             content->nEntries++;
     265             :         }
     266             : 
     267        4826 :     } while (poReader->GotoNextFile());
     268             : 
     269         203 :     if (bMustClose)
     270         202 :         delete (poReader);
     271             : 
     272         203 :     return content;
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*                        FindFileInArchive()                           */
     277             : /************************************************************************/
     278             : 
     279        6840 : int VSIArchiveFilesystemHandler::FindFileInArchive(
     280             :     const char *archiveFilename, const char *fileInArchiveName,
     281             :     const VSIArchiveEntry **archiveEntry)
     282             : {
     283        6840 :     if (fileInArchiveName == nullptr)
     284           0 :         return FALSE;
     285             : 
     286        6840 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     287        6840 :     if (content)
     288             :     {
     289     1052580 :         for (int i = 0; i < content->nEntries; i++)
     290             :         {
     291     1052260 :             if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
     292             :             {
     293        6518 :                 if (archiveEntry)
     294        6518 :                     *archiveEntry = &content->entries[i];
     295        6518 :                 return TRUE;
     296             :             }
     297             :         }
     298             :     }
     299         322 :     return FALSE;
     300             : }
     301             : 
     302             : /************************************************************************/
     303             : /*                           CompactFilename()                          */
     304             : /************************************************************************/
     305             : 
     306       12777 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
     307             : {
     308       25554 :     std::string osRet(pszArchiveInFileNameIn);
     309             : 
     310             :     // Replace a/../b by b and foo/a/../b by foo/b.
     311             :     while (true)
     312             :     {
     313       12777 :         size_t nSlashDotDot = osRet.find("/../");
     314       12777 :         if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
     315             :             break;
     316           0 :         size_t nPos = nSlashDotDot - 1;
     317           0 :         while (nPos > 0 && osRet[nPos] != '/')
     318           0 :             --nPos;
     319           0 :         if (nPos == 0)
     320           0 :             osRet = osRet.substr(nSlashDotDot + strlen("/../"));
     321             :         else
     322           0 :             osRet = osRet.substr(0, nPos + 1) +
     323           0 :                     osRet.substr(nSlashDotDot + strlen("/../"));
     324           0 :     }
     325       12777 :     return osRet;
     326             : }
     327             : 
     328             : /************************************************************************/
     329             : /*                           SplitFilename()                            */
     330             : /************************************************************************/
     331             : 
     332       14560 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
     333             :                                                  CPLString &osFileInArchive,
     334             :                                                  int bCheckMainFileExists)
     335             : {
     336             :     // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
     337       14560 :     if (strcmp(pszFilename, GetPrefix()) == 0)
     338           4 :         return nullptr;
     339             : 
     340       14556 :     int i = 0;
     341             : 
     342             :     // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
     343       14556 :     if (pszFilename[strlen(GetPrefix()) + 1] == '{')
     344             :     {
     345        1317 :         pszFilename += strlen(GetPrefix()) + 1;
     346        1317 :         int nCountCurlies = 0;
     347       48753 :         while (pszFilename[i])
     348             :         {
     349       48752 :             if (pszFilename[i] == '{')
     350        1320 :                 nCountCurlies++;
     351       47432 :             else if (pszFilename[i] == '}')
     352             :             {
     353        1319 :                 nCountCurlies--;
     354        1319 :                 if (nCountCurlies == 0)
     355        1316 :                     break;
     356             :             }
     357       47436 :             i++;
     358             :         }
     359        1317 :         if (nCountCurlies > 0)
     360           1 :             return nullptr;
     361        1316 :         char *archiveFilename = CPLStrdup(pszFilename + 1);
     362        1316 :         archiveFilename[i - 1] = 0;
     363             : 
     364        1316 :         bool bArchiveFileExists = false;
     365        1316 :         if (!bCheckMainFileExists)
     366             :         {
     367          35 :             bArchiveFileExists = true;
     368             :         }
     369             :         else
     370             :         {
     371        2562 :             CPLMutexHolder oHolder(&hMutex);
     372             : 
     373        1281 :             if (oFileList.find(archiveFilename) != oFileList.end())
     374             :             {
     375        1146 :                 bArchiveFileExists = true;
     376             :             }
     377             :         }
     378             : 
     379        1316 :         if (!bArchiveFileExists)
     380             :         {
     381             :             VSIStatBufL statBuf;
     382             :             VSIFilesystemHandler *poFSHandler =
     383         135 :                 VSIFileManager::GetHandler(archiveFilename);
     384         405 :             if (poFSHandler->Stat(archiveFilename, &statBuf,
     385             :                                   VSI_STAT_EXISTS_FLAG |
     386         269 :                                       VSI_STAT_NATURE_FLAG) == 0 &&
     387         134 :                 !VSI_ISDIR(statBuf.st_mode))
     388             :             {
     389         134 :                 bArchiveFileExists = true;
     390             :             }
     391             :         }
     392             : 
     393        1316 :         if (bArchiveFileExists)
     394             :         {
     395        1315 :             if (IsEitherSlash(pszFilename[i + 1]))
     396             :             {
     397        1259 :                 osFileInArchive = CompactFilename(pszFilename + i + 2);
     398             :             }
     399          56 :             else if (pszFilename[i + 1] == '\0')
     400             :             {
     401          55 :                 osFileInArchive = "";
     402             :             }
     403             :             else
     404             :             {
     405           1 :                 CPLFree(archiveFilename);
     406           1 :                 return nullptr;
     407             :             }
     408             : 
     409             :             // Remove trailing slash.
     410        1314 :             if (!osFileInArchive.empty())
     411             :             {
     412        1255 :                 const char lastC = osFileInArchive.back();
     413        1255 :                 if (IsEitherSlash(lastC))
     414           2 :                     osFileInArchive.pop_back();
     415             :             }
     416             : 
     417        1314 :             return archiveFilename;
     418             :         }
     419             : 
     420           1 :         CPLFree(archiveFilename);
     421           1 :         return nullptr;
     422             :     }
     423             : 
     424             :     // Allow natural chaining of VSI drivers without requiring double slash.
     425             : 
     426       26478 :     CPLString osDoubleVsi(GetPrefix());
     427       13239 :     osDoubleVsi += "/vsi";
     428             : 
     429       13239 :     if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
     430        4080 :         pszFilename += strlen(GetPrefix());
     431             :     else
     432        9159 :         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       13239 :     int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
     439       13239 :     if (pnCounter == nullptr)
     440             :     {
     441          25 :         pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
     442          25 :         *pnCounter = 0;
     443          25 :         CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
     444             :     }
     445       13239 :     if (*pnCounter == 3)
     446             :     {
     447          64 :         CPLError(CE_Failure, CPLE_AppDefined,
     448             :                  "Too deep recursion level in "
     449             :                  "VSIArchiveFilesystemHandler::SplitFilename()");
     450          64 :         return nullptr;
     451             :     }
     452             : 
     453       26350 :     const std::vector<CPLString> oExtensions = GetExtensions();
     454       13175 :     int nAttempts = 0;
     455      408278 :     while (pszFilename[i])
     456             :     {
     457      408092 :         int nToSkip = 0;
     458             : 
     459     2767530 :         for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
     460     5126960 :              iter != oExtensions.end(); ++iter)
     461             :         {
     462     2373060 :             const CPLString &osExtension = *iter;
     463     2373060 :             if (EQUALN(pszFilename + i, osExtension.c_str(),
     464             :                        osExtension.size()))
     465             :             {
     466       13630 :                 nToSkip = static_cast<int>(osExtension.size());
     467       13630 :                 break;
     468             :             }
     469             :         }
     470             : 
     471             : #ifdef DEBUG
     472             :         // For AFL, so that .cur_input is detected as the archive filename.
     473      408092 :         if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
     474             :         {
     475          14 :             nToSkip = static_cast<int>(strlen(".cur_input"));
     476             :         }
     477             : #endif
     478             : 
     479      408092 :         if (nToSkip != 0)
     480             :         {
     481       13644 :             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       13644 :             if (nAttempts == 5)
     485             :             {
     486          21 :                 break;
     487             :             }
     488             :             VSIStatBufL statBuf;
     489       13623 :             char *archiveFilename = CPLStrdup(pszFilename);
     490       13623 :             bool bArchiveFileExists = false;
     491             : 
     492       13623 :             if (IsEitherSlash(archiveFilename[i + nToSkip]))
     493             :             {
     494       11973 :                 archiveFilename[i + nToSkip] = 0;
     495             :             }
     496             : 
     497       13623 :             if (!bCheckMainFileExists)
     498             :             {
     499         954 :                 bArchiveFileExists = true;
     500             :             }
     501             :             else
     502             :             {
     503       25338 :                 CPLMutexHolder oHolder(&hMutex);
     504             : 
     505       12669 :                 if (oFileList.find(archiveFilename) != oFileList.end())
     506             :                 {
     507       11620 :                     bArchiveFileExists = true;
     508             :                 }
     509             :             }
     510             : 
     511       13623 :             if (!bArchiveFileExists)
     512             :             {
     513        1049 :                 (*pnCounter)++;
     514             : 
     515             :                 VSIFilesystemHandler *poFSHandler =
     516        1049 :                     VSIFileManager::GetHandler(archiveFilename);
     517        3147 :                 if (poFSHandler->Stat(archiveFilename, &statBuf,
     518             :                                       VSI_STAT_EXISTS_FLAG |
     519        1863 :                                           VSI_STAT_NATURE_FLAG) == 0 &&
     520         814 :                     !VSI_ISDIR(statBuf.st_mode))
     521             :                 {
     522         394 :                     bArchiveFileExists = true;
     523             :                 }
     524             : 
     525        1049 :                 (*pnCounter)--;
     526             :             }
     527             : 
     528       13623 :             if (bArchiveFileExists)
     529             :             {
     530       12968 :                 if (IsEitherSlash(pszFilename[i + nToSkip]))
     531             :                 {
     532             :                     osFileInArchive =
     533       11518 :                         CompactFilename(pszFilename + i + nToSkip + 1);
     534             :                 }
     535             :                 else
     536             :                 {
     537        1450 :                     osFileInArchive = "";
     538             :                 }
     539             : 
     540             :                 // Remove trailing slash.
     541       12968 :                 if (!osFileInArchive.empty())
     542             :                 {
     543       11512 :                     const char lastC = osFileInArchive.back();
     544       11512 :                     if (IsEitherSlash(lastC))
     545          39 :                         osFileInArchive.resize(osFileInArchive.size() - 1);
     546             :                 }
     547             : 
     548       12968 :                 return archiveFilename;
     549             :             }
     550         655 :             CPLFree(archiveFilename);
     551             :         }
     552      395103 :         i++;
     553             :     }
     554         207 :     return nullptr;
     555             : }
     556             : 
     557             : /************************************************************************/
     558             : /*                           OpenArchiveFile()                          */
     559             : /************************************************************************/
     560             : 
     561             : VSIArchiveReader *
     562        4358 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
     563             :                                              const char *fileInArchiveName)
     564             : {
     565        4358 :     VSIArchiveReader *poReader = CreateReader(archiveFilename);
     566             : 
     567        4358 :     if (poReader == nullptr)
     568             :     {
     569           6 :         return nullptr;
     570             :     }
     571             : 
     572        4352 :     if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
     573             :     {
     574          73 :         if (poReader->GotoFirstFile() == FALSE)
     575             :         {
     576           0 :             delete (poReader);
     577           1 :             return nullptr;
     578             :         }
     579             : 
     580             :         // Skip optional leading subdir.
     581          73 :         const CPLString osFileName = poReader->GetFileName();
     582          73 :         if (osFileName.empty() || IsEitherSlash(osFileName.back()))
     583             :         {
     584           2 :             if (poReader->GotoNextFile() == FALSE)
     585             :             {
     586           0 :                 delete (poReader);
     587           0 :                 return nullptr;
     588             :             }
     589             :         }
     590             : 
     591          73 :         if (poReader->GotoNextFile())
     592             :         {
     593           1 :             CPLString msg;
     594             :             msg.Printf("Support only 1 file in archive file %s when "
     595             :                        "no explicit in-archive filename is specified",
     596           1 :                        archiveFilename);
     597             :             const VSIArchiveContent *content =
     598           1 :                 GetContentOfArchive(archiveFilename, poReader);
     599           1 :             if (content)
     600             :             {
     601           1 :                 msg += "\nYou could try one of the following :\n";
     602           6 :                 for (int i = 0; i < content->nEntries; i++)
     603             :                 {
     604          10 :                     msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
     605             :                                               archiveFilename,
     606           5 :                                               content->entries[i].fileName);
     607             :                 }
     608             :             }
     609             : 
     610           1 :             CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
     611             : 
     612           1 :             delete (poReader);
     613           1 :             return nullptr;
     614          72 :         }
     615             :     }
     616             :     else
     617             :     {
     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             :         {
     622        4279 :             CPLMutexHolder oHolder(&hMutex);
     623             : 
     624        4279 :             if (oFileList.find(archiveFilename) == oFileList.end())
     625             :             {
     626         141 :                 if (poReader->GotoFirstFile() == FALSE)
     627             :                 {
     628           0 :                     delete (poReader);
     629          54 :                     return nullptr;
     630             :                 }
     631             : 
     632         141 :                 const CPLString osFileName = poReader->GetFileName();
     633         141 :                 bool bIsDir = false;
     634             :                 const CPLString osStrippedFilename =
     635         141 :                     GetStrippedFilename(osFileName, bIsDir);
     636         141 :                 if (!osStrippedFilename.empty())
     637             :                 {
     638             :                     const bool bMatch =
     639         141 :                         strcmp(osStrippedFilename, fileInArchiveName) == 0;
     640         141 :                     if (bMatch)
     641             :                     {
     642          54 :                         if (bIsDir)
     643             :                         {
     644           0 :                             delete (poReader);
     645           0 :                             return nullptr;
     646             :                         }
     647          54 :                         return poReader;
     648             :                     }
     649             :                 }
     650             :             }
     651             :         }
     652             : 
     653        4225 :         const VSIArchiveEntry *archiveEntry = nullptr;
     654       12675 :         if (FindFileInArchive(archiveFilename, fileInArchiveName,
     655        8285 :                               &archiveEntry) == FALSE ||
     656        4060 :             archiveEntry->bIsDir)
     657             :         {
     658         169 :             delete (poReader);
     659         169 :             return nullptr;
     660             :         }
     661        4056 :         if (!poReader->GotoFileOffset(archiveEntry->file_pos))
     662             :         {
     663           0 :             delete poReader;
     664           0 :             return nullptr;
     665             :         }
     666             :     }
     667        4128 :     return poReader;
     668             : }
     669             : 
     670             : /************************************************************************/
     671             : /*                                 Stat()                               */
     672             : /************************************************************************/
     673             : 
     674        3187 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
     675             :                                       VSIStatBufL *pStatBuf, int /* nFlags */)
     676             : {
     677        3187 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     678             : 
     679        6374 :     CPLString osFileInArchive;
     680        3187 :     char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
     681        3187 :     if (archiveFilename == nullptr)
     682          85 :         return -1;
     683             : 
     684        3102 :     int ret = -1;
     685        3102 :     if (!osFileInArchive.empty())
     686             :     {
     687             : #ifdef DEBUG_VERBOSE
     688             :         CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
     689             :                  osFileInArchive.c_str());
     690             : #endif
     691             : 
     692        2615 :         const VSIArchiveEntry *archiveEntry = nullptr;
     693        2615 :         if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
     694             :         {
     695             :             // Patching st_size with uncompressed file size.
     696        2458 :             pStatBuf->st_size = archiveEntry->uncompressed_size;
     697        2458 :             pStatBuf->st_mtime =
     698        2458 :                 static_cast<time_t>(archiveEntry->nModifiedTime);
     699        2458 :             if (archiveEntry->bIsDir)
     700        1146 :                 pStatBuf->st_mode = S_IFDIR;
     701             :             else
     702        1312 :                 pStatBuf->st_mode = S_IFREG;
     703        2458 :             ret = 0;
     704             :         }
     705             :     }
     706             :     else
     707             :     {
     708         487 :         VSIArchiveReader *poReader = CreateReader(archiveFilename);
     709         487 :         CPLFree(archiveFilename);
     710         487 :         archiveFilename = nullptr;
     711             : 
     712         487 :         if (poReader != nullptr && poReader->GotoFirstFile())
     713             :         {
     714             :             // Skip optional leading subdir.
     715         482 :             const CPLString osFileName = poReader->GetFileName();
     716         482 :             if (IsEitherSlash(osFileName.back()))
     717             :             {
     718          16 :                 if (poReader->GotoNextFile() == FALSE)
     719             :                 {
     720           0 :                     delete (poReader);
     721           0 :                     return -1;
     722             :                 }
     723             :             }
     724             : 
     725         482 :             if (poReader->GotoNextFile())
     726             :             {
     727             :                 // Several files in archive --> treat as dir.
     728         454 :                 pStatBuf->st_size = 0;
     729         454 :                 pStatBuf->st_mode = S_IFDIR;
     730             :             }
     731             :             else
     732             :             {
     733             :                 // Patching st_size with uncompressed file size.
     734          28 :                 pStatBuf->st_size = poReader->GetFileSize();
     735          28 :                 pStatBuf->st_mtime =
     736          28 :                     static_cast<time_t>(poReader->GetModifiedTime());
     737          28 :                 pStatBuf->st_mode = S_IFREG;
     738             :             }
     739             : 
     740         482 :             ret = 0;
     741             :         }
     742             : 
     743         487 :         delete (poReader);
     744             :     }
     745             : 
     746        3102 :     CPLFree(archiveFilename);
     747        3102 :     return ret;
     748             : }
     749             : 
     750             : /************************************************************************/
     751             : /*                              Unlink()                                */
     752             : /************************************************************************/
     753             : 
     754           2 : int VSIArchiveFilesystemHandler::Unlink(const char * /* pszFilename */)
     755             : {
     756           2 :     return -1;
     757             : }
     758             : 
     759             : /************************************************************************/
     760             : /*                             Rename()                                 */
     761             : /************************************************************************/
     762             : 
     763           0 : int VSIArchiveFilesystemHandler::Rename(const char * /* oldpath */,
     764             :                                         const char * /* newpath */)
     765             : {
     766           0 :     return -1;
     767             : }
     768             : 
     769             : /************************************************************************/
     770             : /*                             Mkdir()                                  */
     771             : /************************************************************************/
     772             : 
     773           0 : int VSIArchiveFilesystemHandler::Mkdir(const char * /* pszDirname */,
     774             :                                        long /* nMode */)
     775             : {
     776           0 :     return -1;
     777             : }
     778             : 
     779             : /************************************************************************/
     780             : /*                             Rmdir()                                  */
     781             : /************************************************************************/
     782             : 
     783           0 : int VSIArchiveFilesystemHandler::Rmdir(const char * /* pszDirname */)
     784             : {
     785           0 :     return -1;
     786             : }
     787             : 
     788             : /************************************************************************/
     789             : /*                             ReadDirEx()                              */
     790             : /************************************************************************/
     791             : 
     792        1381 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
     793             :                                               int nMaxFiles)
     794             : {
     795        2762 :     CPLString osInArchiveSubDir;
     796        1381 :     char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
     797        1381 :     if (archiveFilename == nullptr)
     798           0 :         return nullptr;
     799             : 
     800        1381 :     const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
     801             : 
     802        2762 :     CPLStringList oDir;
     803             : 
     804        1381 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     805        1381 :     if (!content)
     806             :     {
     807           4 :         CPLFree(archiveFilename);
     808           4 :         return nullptr;
     809             :     }
     810             : 
     811             : #ifdef DEBUG_VERBOSE
     812             :     CPLDebug("VSIArchive", "Read dir %s", pszDirname);
     813             : #endif
     814     1079240 :     for (int i = 0; i < content->nEntries; i++)
     815             :     {
     816     1077870 :         const char *fileName = content->entries[i].fileName;
     817             :         /* Only list entries at the same level of inArchiveSubDir */
     818     2119330 :         if (lenInArchiveSubDir != 0 &&
     819     1581740 :             strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
     820     2659610 :             IsEitherSlash(fileName[lenInArchiveSubDir]) &&
     821      539115 :             fileName[lenInArchiveSubDir + 1] != 0)
     822             :         {
     823      539115 :             const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
     824      539115 :             if (slash == nullptr)
     825       39007 :                 slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
     826      539115 :             if (slash == nullptr || slash[1] == 0)
     827             :             {
     828       39007 :                 char *tmpFileName = CPLStrdup(fileName);
     829       39007 :                 if (slash != nullptr)
     830             :                 {
     831           0 :                     tmpFileName[strlen(tmpFileName) - 1] = 0;
     832             :                 }
     833             : #ifdef DEBUG_VERBOSE
     834             :                 CPLDebug("VSIArchive", "Add %s as in directory %s",
     835             :                          tmpFileName + lenInArchiveSubDir + 1, pszDirname);
     836             : #endif
     837       39007 :                 oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
     838       39007 :                 CPLFree(tmpFileName);
     839             :             }
     840             :         }
     841      538753 :         else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
     842         442 :                  strchr(fileName, '\\') == nullptr)
     843             :         {
     844             :             // Only list toplevel files and directories.
     845             : #ifdef DEBUG_VERBOSE
     846             :             CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
     847             :                      pszDirname);
     848             : #endif
     849         442 :             oDir.AddString(fileName);
     850             :         }
     851             : 
     852     1077870 :         if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
     853           1 :             break;
     854             :     }
     855             : 
     856        1377 :     CPLFree(archiveFilename);
     857        1377 :     return oDir.StealList();
     858             : }
     859             : 
     860             : /************************************************************************/
     861             : /*                               IsLocal()                              */
     862             : /************************************************************************/
     863             : 
     864           0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
     865             : {
     866           0 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     867           0 :         return false;
     868           0 :     const char *pszBaseFileName = pszPath + strlen(GetPrefix());
     869             :     VSIFilesystemHandler *poFSHandler =
     870           0 :         VSIFileManager::GetHandler(pszBaseFileName);
     871           0 :     return poFSHandler->IsLocal(pszPath);
     872             : }
     873             : 
     874             : //! @endcond

Generated by: LCOV version 1.14