LCOV - code coverage report
Current view: top level - port - cpl_vsil_abstract_archive.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 336 379 88.7 %
Date: 2024-11-21 22:18:42 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      581473 : static bool IsEitherSlash(char c)
      36             : {
      37      581473 :     return c == '/' || c == '\\';
      38             : }
      39             : 
      40             : /************************************************************************/
      41             : /*                    ~VSIArchiveEntryFileOffset()                      */
      42             : /************************************************************************/
      43             : 
      44        1503 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
      45             : {
      46        1503 : }
      47             : 
      48             : /************************************************************************/
      49             : /*                        ~VSIArchiveReader()                           */
      50             : /************************************************************************/
      51             : 
      52        5044 : VSIArchiveReader::~VSIArchiveReader()
      53             : {
      54        5044 : }
      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        2608 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
      75             : {
      76        2608 :     hMutex = nullptr;
      77        2608 : }
      78             : 
      79             : /************************************************************************/
      80             : /*                   ~VSIArchiveFilesystemHandler()                     */
      81             : /************************************************************************/
      82             : 
      83        1866 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
      84             : 
      85             : {
      86        1916 :     for (const auto &iter : oFileList)
      87             :     {
      88          50 :         delete iter.second;
      89             :     }
      90             : 
      91        1866 :     if (hMutex != nullptr)
      92          17 :         CPLDestroyMutex(hMutex);
      93        1866 :     hMutex = nullptr;
      94        1866 : }
      95             : 
      96             : /************************************************************************/
      97             : /*                       GetStrippedFilename()                          */
      98             : /************************************************************************/
      99             : 
     100        4959 : static CPLString GetStrippedFilename(const CPLString &osFileName, bool &bIsDir)
     101             : {
     102        4959 :     bIsDir = false;
     103        4959 :     const char *fileName = osFileName.c_str();
     104             : 
     105             :     // Remove ./ pattern at the beginning of a filename.
     106        4959 :     if (fileName[0] == '.' && fileName[1] == '/')
     107             :     {
     108           0 :         fileName += 2;
     109           0 :         if (fileName[0] == '\0')
     110           0 :             return CPLString();
     111             :     }
     112             : 
     113        4959 :     char *pszStrippedFileName = CPLStrdup(fileName);
     114        4959 :     char *pszIter = nullptr;
     115      180199 :     for (pszIter = pszStrippedFileName; *pszIter; pszIter++)
     116             :     {
     117      175240 :         if (*pszIter == '\\')
     118           0 :             *pszIter = '/';
     119             :     }
     120             : 
     121        4959 :     const size_t nLen = strlen(fileName);
     122        4959 :     bIsDir = nLen > 0 && fileName[nLen - 1] == '/';
     123        4959 :     if (bIsDir)
     124             :     {
     125             :         // Remove trailing slash.
     126         198 :         pszStrippedFileName[nLen - 1] = '\0';
     127             :     }
     128        9918 :     CPLString osRet(pszStrippedFileName);
     129        4959 :     CPLFree(pszStrippedFileName);
     130        4959 :     return osRet;
     131             : }
     132             : 
     133             : /************************************************************************/
     134             : /*                       GetContentOfArchive()                          */
     135             : /************************************************************************/
     136             : 
     137             : const VSIArchiveContent *
     138        8212 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
     139             :                                                  VSIArchiveReader *poReader)
     140             : {
     141       16424 :     CPLMutexHolder oHolder(&hMutex);
     142             : 
     143             :     VSIStatBufL sStat;
     144        8212 :     if (VSIStatL(archiveFilename, &sStat) != 0)
     145           0 :         return nullptr;
     146             : 
     147        8212 :     if (oFileList.find(archiveFilename) != oFileList.end())
     148             :     {
     149        8009 :         VSIArchiveContent *content = oFileList[archiveFilename];
     150        8009 :         if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
     151        8008 :             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        8004 :             return content;
     162             :         }
     163             :     }
     164             : 
     165         208 :     bool bMustClose = poReader == nullptr;
     166         208 :     if (poReader == nullptr)
     167             :     {
     168         207 :         poReader = CreateReader(archiveFilename);
     169         207 :         if (!poReader)
     170           7 :             return nullptr;
     171             :     }
     172             : 
     173         201 :     if (poReader->GotoFirstFile() == FALSE)
     174             :     {
     175           0 :         if (bMustClose)
     176           0 :             delete (poReader);
     177           0 :         return nullptr;
     178             :     }
     179             : 
     180         201 :     VSIArchiveContent *content = new VSIArchiveContent;
     181         201 :     content->mTime = sStat.st_mtime;
     182         201 :     content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
     183         201 :     content->nEntries = 0;
     184         201 :     content->entries = nullptr;
     185         201 :     oFileList[archiveFilename] = content;
     186             : 
     187         201 :     std::set<CPLString> oSet;
     188             : 
     189        4618 :     do
     190             :     {
     191        4819 :         const CPLString osFileName = poReader->GetFileName();
     192        4819 :         bool bIsDir = false;
     193             :         const CPLString osStrippedFilename =
     194        4819 :             GetStrippedFilename(osFileName, bIsDir);
     195        9638 :         if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
     196        4819 :             osStrippedFilename.find("//") != std::string::npos)
     197             :         {
     198           0 :             continue;
     199             :         }
     200             : 
     201        4819 :         if (oSet.find(osStrippedFilename) == oSet.end())
     202             :         {
     203        4819 :             oSet.insert(osStrippedFilename);
     204             : 
     205             :             // Add intermediate directory structure.
     206        4819 :             const char *pszBegin = osStrippedFilename.c_str();
     207      178083 :             for (const char *pszIter = pszBegin; *pszIter; pszIter++)
     208             :             {
     209      173264 :                 if (*pszIter == '/')
     210             :                 {
     211        6350 :                     char *pszStrippedFileName2 = CPLStrdup(osStrippedFilename);
     212        6350 :                     pszStrippedFileName2[pszIter - pszBegin] = 0;
     213        6350 :                     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        5103 :                         CPLFree(pszStrippedFileName2);
     242             :                     }
     243             :                 }
     244             :             }
     245             : 
     246        4819 :             content->entries = static_cast<VSIArchiveEntry *>(
     247        9638 :                 CPLRealloc(content->entries,
     248        4819 :                            sizeof(VSIArchiveEntry) * (content->nEntries + 1)));
     249        9638 :             content->entries[content->nEntries].fileName =
     250        4819 :                 CPLStrdup(osStrippedFilename);
     251        9638 :             content->entries[content->nEntries].nModifiedTime =
     252        4819 :                 poReader->GetModifiedTime();
     253        9638 :             content->entries[content->nEntries].uncompressed_size =
     254        4819 :                 poReader->GetFileSize();
     255        4819 :             content->entries[content->nEntries].bIsDir = bIsDir;
     256        9638 :             content->entries[content->nEntries].file_pos =
     257        4819 :                 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        4819 :             content->nEntries++;
     265             :         }
     266             : 
     267        4819 :     } while (poReader->GotoNextFile());
     268             : 
     269         201 :     if (bMustClose)
     270         200 :         delete (poReader);
     271             : 
     272         201 :     return content;
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*                        FindFileInArchive()                           */
     277             : /************************************************************************/
     278             : 
     279        6831 : int VSIArchiveFilesystemHandler::FindFileInArchive(
     280             :     const char *archiveFilename, const char *fileInArchiveName,
     281             :     const VSIArchiveEntry **archiveEntry)
     282             : {
     283        6831 :     if (fileInArchiveName == nullptr)
     284           0 :         return FALSE;
     285             : 
     286        6831 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     287        6831 :     if (content)
     288             :     {
     289     1052560 :         for (int i = 0; i < content->nEntries; i++)
     290             :         {
     291     1052240 :             if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
     292             :             {
     293        6510 :                 if (archiveEntry)
     294        6510 :                     *archiveEntry = &content->entries[i];
     295        6510 :                 return TRUE;
     296             :             }
     297             :         }
     298             :     }
     299         321 :     return FALSE;
     300             : }
     301             : 
     302             : /************************************************************************/
     303             : /*                           CompactFilename()                          */
     304             : /************************************************************************/
     305             : 
     306       12767 : static CPLString CompactFilename(const char *pszArchiveInFileNameIn)
     307             : {
     308       12767 :     char *pszArchiveInFileName = CPLStrdup(pszArchiveInFileNameIn);
     309             : 
     310             :     // Replace a/../b by b and foo/a/../b by foo/b.
     311             :     while (true)
     312             :     {
     313       12767 :         char *pszPrevDir = strstr(pszArchiveInFileName, "/../");
     314       12767 :         if (pszPrevDir == nullptr || pszPrevDir == pszArchiveInFileName)
     315             :             break;
     316             : 
     317           0 :         char *pszPrevSlash = pszPrevDir - 1;
     318           0 :         while (pszPrevSlash != pszArchiveInFileName && *pszPrevSlash != '/')
     319           0 :             pszPrevSlash--;
     320           0 :         if (pszPrevSlash == pszArchiveInFileName)
     321           0 :             memmove(pszArchiveInFileName, pszPrevDir + 4,
     322           0 :                     strlen(pszPrevDir + 4) + 1);
     323             :         else
     324           0 :             memmove(pszPrevSlash + 1, pszPrevDir + 4,
     325           0 :                     strlen(pszPrevDir + 4) + 1);
     326           0 :     }
     327             : 
     328       12767 :     CPLString osFileInArchive = pszArchiveInFileName;
     329       12767 :     CPLFree(pszArchiveInFileName);
     330       12767 :     return osFileInArchive;
     331             : }
     332             : 
     333             : /************************************************************************/
     334             : /*                           SplitFilename()                            */
     335             : /************************************************************************/
     336             : 
     337       14548 : char *VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
     338             :                                                  CPLString &osFileInArchive,
     339             :                                                  int bCheckMainFileExists)
     340             : {
     341             :     // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
     342       14548 :     if (strcmp(pszFilename, GetPrefix()) == 0)
     343           4 :         return nullptr;
     344             : 
     345       14544 :     int i = 0;
     346             : 
     347             :     // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
     348       14544 :     if (pszFilename[strlen(GetPrefix()) + 1] == '{')
     349             :     {
     350        1317 :         pszFilename += strlen(GetPrefix()) + 1;
     351        1317 :         int nCountCurlies = 0;
     352       48753 :         while (pszFilename[i])
     353             :         {
     354       48752 :             if (pszFilename[i] == '{')
     355        1320 :                 nCountCurlies++;
     356       47432 :             else if (pszFilename[i] == '}')
     357             :             {
     358        1319 :                 nCountCurlies--;
     359        1319 :                 if (nCountCurlies == 0)
     360        1316 :                     break;
     361             :             }
     362       47436 :             i++;
     363             :         }
     364        1317 :         if (nCountCurlies > 0)
     365           1 :             return nullptr;
     366        1316 :         char *archiveFilename = CPLStrdup(pszFilename + 1);
     367        1316 :         archiveFilename[i - 1] = 0;
     368             : 
     369        1316 :         bool bArchiveFileExists = false;
     370        1316 :         if (!bCheckMainFileExists)
     371             :         {
     372          35 :             bArchiveFileExists = true;
     373             :         }
     374             :         else
     375             :         {
     376        2562 :             CPLMutexHolder oHolder(&hMutex);
     377             : 
     378        1281 :             if (oFileList.find(archiveFilename) != oFileList.end())
     379             :             {
     380        1146 :                 bArchiveFileExists = true;
     381             :             }
     382             :         }
     383             : 
     384        1316 :         if (!bArchiveFileExists)
     385             :         {
     386             :             VSIStatBufL statBuf;
     387             :             VSIFilesystemHandler *poFSHandler =
     388         135 :                 VSIFileManager::GetHandler(archiveFilename);
     389         405 :             if (poFSHandler->Stat(archiveFilename, &statBuf,
     390             :                                   VSI_STAT_EXISTS_FLAG |
     391         269 :                                       VSI_STAT_NATURE_FLAG) == 0 &&
     392         134 :                 !VSI_ISDIR(statBuf.st_mode))
     393             :             {
     394         134 :                 bArchiveFileExists = true;
     395             :             }
     396             :         }
     397             : 
     398        1316 :         if (bArchiveFileExists)
     399             :         {
     400        1315 :             if (IsEitherSlash(pszFilename[i + 1]))
     401             :             {
     402        1259 :                 osFileInArchive = CompactFilename(pszFilename + i + 2);
     403             :             }
     404          56 :             else if (pszFilename[i + 1] == '\0')
     405             :             {
     406          55 :                 osFileInArchive = "";
     407             :             }
     408             :             else
     409             :             {
     410           1 :                 CPLFree(archiveFilename);
     411           1 :                 return nullptr;
     412             :             }
     413             : 
     414             :             // Remove trailing slash.
     415        1314 :             if (!osFileInArchive.empty())
     416             :             {
     417        1255 :                 const char lastC = osFileInArchive.back();
     418        1255 :                 if (IsEitherSlash(lastC))
     419           2 :                     osFileInArchive.pop_back();
     420             :             }
     421             : 
     422        1314 :             return archiveFilename;
     423             :         }
     424             : 
     425           1 :         CPLFree(archiveFilename);
     426           1 :         return nullptr;
     427             :     }
     428             : 
     429             :     // Allow natural chaining of VSI drivers without requiring double slash.
     430             : 
     431       26454 :     CPLString osDoubleVsi(GetPrefix());
     432       13227 :     osDoubleVsi += "/vsi";
     433             : 
     434       13227 :     if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
     435        4080 :         pszFilename += strlen(GetPrefix());
     436             :     else
     437        9147 :         pszFilename += strlen(GetPrefix()) + 1;
     438             : 
     439             :     // Parsing strings like
     440             :     // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
     441             :     // takes a huge amount of time, so limit the number of nesting of such
     442             :     // file systems.
     443       13227 :     int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
     444       13227 :     if (pnCounter == nullptr)
     445             :     {
     446          24 :         pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
     447          24 :         *pnCounter = 0;
     448          24 :         CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
     449             :     }
     450       13227 :     if (*pnCounter == 3)
     451             :     {
     452          64 :         CPLError(CE_Failure, CPLE_AppDefined,
     453             :                  "Too deep recursion level in "
     454             :                  "VSIArchiveFilesystemHandler::SplitFilename()");
     455          64 :         return nullptr;
     456             :     }
     457             : 
     458       26326 :     const std::vector<CPLString> oExtensions = GetExtensions();
     459       13163 :     int nAttempts = 0;
     460      408042 :     while (pszFilename[i])
     461             :     {
     462      407856 :         int nToSkip = 0;
     463             : 
     464     2766350 :         for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
     465     5124840 :              iter != oExtensions.end(); ++iter)
     466             :         {
     467     2372110 :             const CPLString &osExtension = *iter;
     468     2372110 :             if (EQUALN(pszFilename + i, osExtension.c_str(),
     469             :                        osExtension.size()))
     470             :             {
     471       13618 :                 nToSkip = static_cast<int>(osExtension.size());
     472       13618 :                 break;
     473             :             }
     474             :         }
     475             : 
     476             : #ifdef DEBUG
     477             :         // For AFL, so that .cur_input is detected as the archive filename.
     478      407856 :         if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
     479             :         {
     480          14 :             nToSkip = static_cast<int>(strlen(".cur_input"));
     481             :         }
     482             : #endif
     483             : 
     484      407856 :         if (nToSkip != 0)
     485             :         {
     486       13632 :             nAttempts++;
     487             :             // Arbitrary threshold to avoid DoS with things like
     488             :             // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
     489       13632 :             if (nAttempts == 5)
     490             :             {
     491          21 :                 break;
     492             :             }
     493             :             VSIStatBufL statBuf;
     494       13611 :             char *archiveFilename = CPLStrdup(pszFilename);
     495       13611 :             bool bArchiveFileExists = false;
     496             : 
     497       13611 :             if (IsEitherSlash(archiveFilename[i + nToSkip]))
     498             :             {
     499       11963 :                 archiveFilename[i + nToSkip] = 0;
     500             :             }
     501             : 
     502       13611 :             if (!bCheckMainFileExists)
     503             :             {
     504         954 :                 bArchiveFileExists = true;
     505             :             }
     506             :             else
     507             :             {
     508       25314 :                 CPLMutexHolder oHolder(&hMutex);
     509             : 
     510       12657 :                 if (oFileList.find(archiveFilename) != oFileList.end())
     511             :                 {
     512       11611 :                     bArchiveFileExists = true;
     513             :                 }
     514             :             }
     515             : 
     516       13611 :             if (!bArchiveFileExists)
     517             :             {
     518        1046 :                 (*pnCounter)++;
     519             : 
     520             :                 VSIFilesystemHandler *poFSHandler =
     521        1046 :                     VSIFileManager::GetHandler(archiveFilename);
     522        3138 :                 if (poFSHandler->Stat(archiveFilename, &statBuf,
     523             :                                       VSI_STAT_EXISTS_FLAG |
     524        1857 :                                           VSI_STAT_NATURE_FLAG) == 0 &&
     525         811 :                     !VSI_ISDIR(statBuf.st_mode))
     526             :                 {
     527         391 :                     bArchiveFileExists = true;
     528             :                 }
     529             : 
     530        1046 :                 (*pnCounter)--;
     531             :             }
     532             : 
     533       13611 :             if (bArchiveFileExists)
     534             :             {
     535       12956 :                 if (IsEitherSlash(pszFilename[i + nToSkip]))
     536             :                 {
     537             :                     osFileInArchive =
     538       11508 :                         CompactFilename(pszFilename + i + nToSkip + 1);
     539             :                 }
     540             :                 else
     541             :                 {
     542        1448 :                     osFileInArchive = "";
     543             :                 }
     544             : 
     545             :                 // Remove trailing slash.
     546       12956 :                 if (!osFileInArchive.empty())
     547             :                 {
     548       11502 :                     const char lastC = osFileInArchive.back();
     549       11502 :                     if (IsEitherSlash(lastC))
     550          39 :                         osFileInArchive.pop_back();
     551             :                 }
     552             : 
     553       12956 :                 return archiveFilename;
     554             :             }
     555         655 :             CPLFree(archiveFilename);
     556             :         }
     557      394879 :         i++;
     558             :     }
     559         207 :     return nullptr;
     560             : }
     561             : 
     562             : /************************************************************************/
     563             : /*                           OpenArchiveFile()                          */
     564             : /************************************************************************/
     565             : 
     566             : VSIArchiveReader *
     567        4350 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
     568             :                                              const char *fileInArchiveName)
     569             : {
     570        4350 :     VSIArchiveReader *poReader = CreateReader(archiveFilename);
     571             : 
     572        4350 :     if (poReader == nullptr)
     573             :     {
     574           6 :         return nullptr;
     575             :     }
     576             : 
     577        4344 :     if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
     578             :     {
     579          73 :         if (poReader->GotoFirstFile() == FALSE)
     580             :         {
     581           0 :             delete (poReader);
     582           1 :             return nullptr;
     583             :         }
     584             : 
     585             :         // Skip optional leading subdir.
     586          73 :         const CPLString osFileName = poReader->GetFileName();
     587          73 :         if (osFileName.empty() || IsEitherSlash(osFileName.back()))
     588             :         {
     589           2 :             if (poReader->GotoNextFile() == FALSE)
     590             :             {
     591           0 :                 delete (poReader);
     592           0 :                 return nullptr;
     593             :             }
     594             :         }
     595             : 
     596          73 :         if (poReader->GotoNextFile())
     597             :         {
     598           1 :             CPLString msg;
     599             :             msg.Printf("Support only 1 file in archive file %s when "
     600             :                        "no explicit in-archive filename is specified",
     601           1 :                        archiveFilename);
     602             :             const VSIArchiveContent *content =
     603           1 :                 GetContentOfArchive(archiveFilename, poReader);
     604           1 :             if (content)
     605             :             {
     606           1 :                 msg += "\nYou could try one of the following :\n";
     607           6 :                 for (int i = 0; i < content->nEntries; i++)
     608             :                 {
     609          10 :                     msg += CPLString().Printf("  %s/{%s}/%s\n", GetPrefix(),
     610             :                                               archiveFilename,
     611           5 :                                               content->entries[i].fileName);
     612             :                 }
     613             :             }
     614             : 
     615           1 :             CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
     616             : 
     617           1 :             delete (poReader);
     618           1 :             return nullptr;
     619          72 :         }
     620             :     }
     621             :     else
     622             :     {
     623             :         // Optimization: instead of iterating over all files which can be
     624             :         // slow on .tar.gz files, try reading the first one first.
     625             :         // This can help if it is really huge.
     626             :         {
     627        4271 :             CPLMutexHolder oHolder(&hMutex);
     628             : 
     629        4271 :             if (oFileList.find(archiveFilename) == oFileList.end())
     630             :             {
     631         140 :                 if (poReader->GotoFirstFile() == FALSE)
     632             :                 {
     633           0 :                     delete (poReader);
     634          54 :                     return nullptr;
     635             :                 }
     636             : 
     637         140 :                 const CPLString osFileName = poReader->GetFileName();
     638         140 :                 bool bIsDir = false;
     639             :                 const CPLString osStrippedFilename =
     640         140 :                     GetStrippedFilename(osFileName, bIsDir);
     641         140 :                 if (!osStrippedFilename.empty())
     642             :                 {
     643             :                     const bool bMatch =
     644         140 :                         strcmp(osStrippedFilename, fileInArchiveName) == 0;
     645         140 :                     if (bMatch)
     646             :                     {
     647          54 :                         if (bIsDir)
     648             :                         {
     649           0 :                             delete (poReader);
     650           0 :                             return nullptr;
     651             :                         }
     652          54 :                         return poReader;
     653             :                     }
     654             :                 }
     655             :             }
     656             :         }
     657             : 
     658        4217 :         const VSIArchiveEntry *archiveEntry = nullptr;
     659       12651 :         if (FindFileInArchive(archiveFilename, fileInArchiveName,
     660        8270 :                               &archiveEntry) == FALSE ||
     661        4053 :             archiveEntry->bIsDir)
     662             :         {
     663         168 :             delete (poReader);
     664         168 :             return nullptr;
     665             :         }
     666        4049 :         if (!poReader->GotoFileOffset(archiveEntry->file_pos))
     667             :         {
     668           0 :             delete poReader;
     669           0 :             return nullptr;
     670             :         }
     671             :     }
     672        4121 :     return poReader;
     673             : }
     674             : 
     675             : /************************************************************************/
     676             : /*                                 Stat()                               */
     677             : /************************************************************************/
     678             : 
     679        3186 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
     680             :                                       VSIStatBufL *pStatBuf, int /* nFlags */)
     681             : {
     682        3186 :     memset(pStatBuf, 0, sizeof(VSIStatBufL));
     683             : 
     684        6372 :     CPLString osFileInArchive;
     685        3186 :     char *archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
     686        3186 :     if (archiveFilename == nullptr)
     687          85 :         return -1;
     688             : 
     689        3101 :     int ret = -1;
     690        3101 :     if (!osFileInArchive.empty())
     691             :     {
     692             : #ifdef DEBUG_VERBOSE
     693             :         CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename,
     694             :                  osFileInArchive.c_str());
     695             : #endif
     696             : 
     697        2614 :         const VSIArchiveEntry *archiveEntry = nullptr;
     698        2614 :         if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
     699             :         {
     700             :             // Patching st_size with uncompressed file size.
     701        2457 :             pStatBuf->st_size = archiveEntry->uncompressed_size;
     702        2457 :             pStatBuf->st_mtime =
     703        2457 :                 static_cast<time_t>(archiveEntry->nModifiedTime);
     704        2457 :             if (archiveEntry->bIsDir)
     705        1146 :                 pStatBuf->st_mode = S_IFDIR;
     706             :             else
     707        1311 :                 pStatBuf->st_mode = S_IFREG;
     708        2457 :             ret = 0;
     709             :         }
     710             :     }
     711             :     else
     712             :     {
     713         487 :         VSIArchiveReader *poReader = CreateReader(archiveFilename);
     714         487 :         CPLFree(archiveFilename);
     715         487 :         archiveFilename = nullptr;
     716             : 
     717         487 :         if (poReader != nullptr && poReader->GotoFirstFile())
     718             :         {
     719             :             // Skip optional leading subdir.
     720         482 :             const CPLString osFileName = poReader->GetFileName();
     721         482 :             if (IsEitherSlash(osFileName.back()))
     722             :             {
     723          16 :                 if (poReader->GotoNextFile() == FALSE)
     724             :                 {
     725           0 :                     delete (poReader);
     726           0 :                     return -1;
     727             :                 }
     728             :             }
     729             : 
     730         482 :             if (poReader->GotoNextFile())
     731             :             {
     732             :                 // Several files in archive --> treat as dir.
     733         454 :                 pStatBuf->st_size = 0;
     734         454 :                 pStatBuf->st_mode = S_IFDIR;
     735             :             }
     736             :             else
     737             :             {
     738             :                 // Patching st_size with uncompressed file size.
     739          28 :                 pStatBuf->st_size = poReader->GetFileSize();
     740          28 :                 pStatBuf->st_mtime =
     741          28 :                     static_cast<time_t>(poReader->GetModifiedTime());
     742          28 :                 pStatBuf->st_mode = S_IFREG;
     743             :             }
     744             : 
     745         482 :             ret = 0;
     746             :         }
     747             : 
     748         487 :         delete (poReader);
     749             :     }
     750             : 
     751        3101 :     CPLFree(archiveFilename);
     752        3101 :     return ret;
     753             : }
     754             : 
     755             : /************************************************************************/
     756             : /*                              Unlink()                                */
     757             : /************************************************************************/
     758             : 
     759           2 : int VSIArchiveFilesystemHandler::Unlink(const char * /* pszFilename */)
     760             : {
     761           2 :     return -1;
     762             : }
     763             : 
     764             : /************************************************************************/
     765             : /*                             Rename()                                 */
     766             : /************************************************************************/
     767             : 
     768           0 : int VSIArchiveFilesystemHandler::Rename(const char * /* oldpath */,
     769             :                                         const char * /* newpath */)
     770             : {
     771           0 :     return -1;
     772             : }
     773             : 
     774             : /************************************************************************/
     775             : /*                             Mkdir()                                  */
     776             : /************************************************************************/
     777             : 
     778           0 : int VSIArchiveFilesystemHandler::Mkdir(const char * /* pszDirname */,
     779             :                                        long /* nMode */)
     780             : {
     781           0 :     return -1;
     782             : }
     783             : 
     784             : /************************************************************************/
     785             : /*                             Rmdir()                                  */
     786             : /************************************************************************/
     787             : 
     788           0 : int VSIArchiveFilesystemHandler::Rmdir(const char * /* pszDirname */)
     789             : {
     790           0 :     return -1;
     791             : }
     792             : 
     793             : /************************************************************************/
     794             : /*                             ReadDirEx()                              */
     795             : /************************************************************************/
     796             : 
     797        1380 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
     798             :                                               int nMaxFiles)
     799             : {
     800        2760 :     CPLString osInArchiveSubDir;
     801        1380 :     char *archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
     802        1380 :     if (archiveFilename == nullptr)
     803           0 :         return nullptr;
     804             : 
     805        1380 :     const int lenInArchiveSubDir = static_cast<int>(osInArchiveSubDir.size());
     806             : 
     807        2760 :     CPLStringList oDir;
     808             : 
     809        1380 :     const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
     810        1380 :     if (!content)
     811             :     {
     812           4 :         CPLFree(archiveFilename);
     813           4 :         return nullptr;
     814             :     }
     815             : 
     816             : #ifdef DEBUG_VERBOSE
     817             :     CPLDebug("VSIArchive", "Read dir %s", pszDirname);
     818             : #endif
     819     1079240 :     for (int i = 0; i < content->nEntries; i++)
     820             :     {
     821     1077870 :         const char *fileName = content->entries[i].fileName;
     822             :         /* Only list entries at the same level of inArchiveSubDir */
     823     2119330 :         if (lenInArchiveSubDir != 0 &&
     824     1581740 :             strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
     825     2659610 :             IsEitherSlash(fileName[lenInArchiveSubDir]) &&
     826      539115 :             fileName[lenInArchiveSubDir + 1] != 0)
     827             :         {
     828      539115 :             const char *slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
     829      539115 :             if (slash == nullptr)
     830       39007 :                 slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
     831      539115 :             if (slash == nullptr || slash[1] == 0)
     832             :             {
     833       39007 :                 char *tmpFileName = CPLStrdup(fileName);
     834       39007 :                 if (slash != nullptr)
     835             :                 {
     836           0 :                     tmpFileName[strlen(tmpFileName) - 1] = 0;
     837             :                 }
     838             : #ifdef DEBUG_VERBOSE
     839             :                 CPLDebug("VSIArchive", "Add %s as in directory %s",
     840             :                          tmpFileName + lenInArchiveSubDir + 1, pszDirname);
     841             : #endif
     842       39007 :                 oDir.AddString(tmpFileName + lenInArchiveSubDir + 1);
     843       39007 :                 CPLFree(tmpFileName);
     844             :             }
     845             :         }
     846      538752 :         else if (lenInArchiveSubDir == 0 && strchr(fileName, '/') == nullptr &&
     847         441 :                  strchr(fileName, '\\') == nullptr)
     848             :         {
     849             :             // Only list toplevel files and directories.
     850             : #ifdef DEBUG_VERBOSE
     851             :             CPLDebug("VSIArchive", "Add %s as in directory %s", fileName,
     852             :                      pszDirname);
     853             : #endif
     854         441 :             oDir.AddString(fileName);
     855             :         }
     856             : 
     857     1077870 :         if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
     858           1 :             break;
     859             :     }
     860             : 
     861        1376 :     CPLFree(archiveFilename);
     862        1376 :     return oDir.StealList();
     863             : }
     864             : 
     865             : /************************************************************************/
     866             : /*                               IsLocal()                              */
     867             : /************************************************************************/
     868             : 
     869           0 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath)
     870             : {
     871           0 :     if (!STARTS_WITH(pszPath, GetPrefix()))
     872           0 :         return false;
     873           0 :     const char *pszBaseFileName = pszPath + strlen(GetPrefix());
     874             :     VSIFilesystemHandler *poFSHandler =
     875           0 :         VSIFileManager::GetHandler(pszBaseFileName);
     876           0 :     return poFSHandler->IsLocal(pszPath);
     877             : }
     878             : 
     879             : //! @endcond

Generated by: LCOV version 1.14