LCOV - code coverage report
Current view: top level - port - cpl_vsil_abstract_archive.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 335 371 90.3 %
Date: 2025-07-09 17:50:03 Functions: 14 18 77.8 %

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

Generated by: LCOV version 1.14