LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gmlas - ogrgmlasxsdcache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 176 183 96.2 %
Date: 2025-01-18 12:42:00 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  * Project:  OGR
       3             :  * Purpose:  OGRGMLASDriver implementation
       4             :  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
       5             :  *
       6             :  * Initial development funded by the European Earth observation programme
       7             :  * Copernicus
       8             :  *
       9             :  ******************************************************************************
      10             :  * Copyright (c) 2016, Even Rouault, <even dot rouault at spatialys dot com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "ogr_gmlas.h"
      16             : 
      17             : #include "cpl_http.h"
      18             : #include "cpl_sha256.h"
      19             : 
      20             : /************************************************************************/
      21             : /*                         SetCacheDirectory()                          */
      22             : /************************************************************************/
      23             : 
      24         361 : void GMLASResourceCache::SetCacheDirectory(const std::string &osCacheDirectory)
      25             : {
      26         361 :     m_osCacheDirectory = osCacheDirectory;
      27         361 : }
      28             : 
      29             : /************************************************************************/
      30             : /*                     RecursivelyCreateDirectoryIfNeeded()             */
      31             : /************************************************************************/
      32             : 
      33          69 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded(
      34             :     const std::string &osDirname)
      35             : {
      36             :     VSIStatBufL sStat;
      37          69 :     if (VSIStatL(osDirname.c_str(), &sStat) == 0)
      38             :     {
      39          61 :         return true;
      40             :     }
      41             : 
      42          16 :     std::string osParent = CPLGetDirnameSafe(osDirname.c_str());
      43           8 :     if (!osParent.empty() && osParent != ".")
      44             :     {
      45           8 :         if (!RecursivelyCreateDirectoryIfNeeded(osParent.c_str()))
      46           2 :             return false;
      47             :     }
      48           6 :     return VSIMkdir(osDirname.c_str(), 0755) == 0;
      49             : }
      50             : 
      51         914 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded()
      52             : {
      53         914 :     if (!m_bHasCheckedCacheDirectory)
      54             :     {
      55          61 :         m_bHasCheckedCacheDirectory = true;
      56          61 :         if (!RecursivelyCreateDirectoryIfNeeded(m_osCacheDirectory))
      57             :         {
      58           1 :             CPLError(CE_Warning, CPLE_AppDefined, "Cannot create %s",
      59             :                      m_osCacheDirectory.c_str());
      60           1 :             m_osCacheDirectory.clear();
      61           1 :             return false;
      62             :         }
      63             :     }
      64         913 :     return true;
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*                        GetCachedFilename()                           */
      69             : /************************************************************************/
      70             : 
      71        1048 : std::string GMLASResourceCache::GetCachedFilename(const std::string &osResource)
      72             : {
      73        2096 :     std::string osLaunderedName(osResource);
      74        1048 :     if (STARTS_WITH(osLaunderedName.c_str(), "http://"))
      75         463 :         osLaunderedName = osLaunderedName.substr(strlen("http://"));
      76         585 :     else if (STARTS_WITH(osLaunderedName.c_str(), "https://"))
      77         585 :         osLaunderedName = osLaunderedName.substr(strlen("https://"));
      78       50689 :     for (size_t i = 0; i < osLaunderedName.size(); i++)
      79             :     {
      80       58780 :         if (!isalnum(static_cast<unsigned char>(osLaunderedName[i])) &&
      81        9139 :             osLaunderedName[i] != '.')
      82        5453 :             osLaunderedName[i] = '_';
      83             :     }
      84             : 
      85             :     // If filename is too long, then truncate it and put a hash at the end
      86             :     // We try to make sure that the whole filename (including the cache path)
      87             :     // fits into 255 characters, for windows compat
      88             : 
      89        1048 :     const size_t nWindowsMaxFilenameSize = 255;
      90             :     // 60 is arbitrary but should be sufficient for most people. We could
      91             :     // always take into account m_osCacheDirectory.size(), but if we want to
      92             :     // to be able to share caches between computers, then this would be
      93             :     // impractical.
      94        1048 :     const size_t nTypicalMaxSizeForDirName = 60;
      95             :     const size_t nSizeForDirName =
      96        1277 :         (m_osCacheDirectory.size() > nTypicalMaxSizeForDirName &&
      97         229 :          m_osCacheDirectory.size() < nWindowsMaxFilenameSize - strlen(".tmp") -
      98             :                                          2 * CPL_SHA256_HASH_SIZE)
      99        1277 :             ? m_osCacheDirectory.size()
     100        1048 :             : nTypicalMaxSizeForDirName;
     101        1048 :     CPLAssert(nWindowsMaxFilenameSize >= nSizeForDirName);
     102        1048 :     const size_t nMaxFilenameSize = nWindowsMaxFilenameSize - nSizeForDirName;
     103             : 
     104        1048 :     CPLAssert(nMaxFilenameSize >= strlen(".tmp"));
     105        1048 :     if (osLaunderedName.size() >= nMaxFilenameSize - strlen(".tmp"))
     106             :     {
     107             :         GByte abyHash[CPL_SHA256_HASH_SIZE];
     108           3 :         CPL_SHA256(osResource.c_str(), osResource.size(), abyHash);
     109           3 :         char *pszHash = CPLBinaryToHex(CPL_SHA256_HASH_SIZE, abyHash);
     110           3 :         osLaunderedName.resize(nMaxFilenameSize - strlen(".tmp") -
     111             :                                2 * CPL_SHA256_HASH_SIZE);
     112           3 :         osLaunderedName += pszHash;
     113           3 :         CPLFree(pszHash);
     114           3 :         CPLDebug("GMLAS", "Cached filename truncated to %s",
     115             :                  osLaunderedName.c_str());
     116             :     }
     117             : 
     118             :     return CPLFormFilenameSafe(m_osCacheDirectory.c_str(),
     119        2096 :                                osLaunderedName.c_str(), nullptr);
     120             : }
     121             : 
     122             : /************************************************************************/
     123             : /*                          CacheAllGML321()                            */
     124             : /************************************************************************/
     125             : 
     126           2 : bool GMLASXSDCache::CacheAllGML321()
     127             : {
     128             :     // As of today (2024-01-02), the schemas in https://schemas.opengis.net/gml/3.2.1
     129             :     // are actually the same as the ones in the https://schemas.opengis.net/gml/gml-3_2_2.zip archive.
     130             :     // Download the later and unzip it for faster fetching of GML schemas.
     131             : 
     132           2 :     bool bSuccess = false;
     133           2 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     134             : 
     135           2 :     const char *pszHTTPZIP = "https://schemas.opengis.net/gml/gml-3_2_2.zip";
     136           2 :     CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
     137           2 :     if (psResult && psResult->nDataLen)
     138             :     {
     139             :         const std::string osZIPFilename(
     140           4 :             VSIMemGenerateHiddenFilename("temp.zip"));
     141             :         auto fpZIP =
     142           2 :             VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
     143           2 :                                  psResult->nDataLen, FALSE);
     144           2 :         if (fpZIP)
     145             :         {
     146           2 :             VSIFCloseL(fpZIP);
     147             : 
     148           4 :             const std::string osVSIZIPFilename("/vsizip/" + osZIPFilename);
     149             :             const CPLStringList aosFiles(
     150           4 :                 VSIReadDirRecursive(osVSIZIPFilename.c_str()));
     151          70 :             for (int i = 0; i < aosFiles.size(); ++i)
     152             :             {
     153          68 :                 if (strstr(aosFiles[i], ".xsd"))
     154             :                 {
     155             :                     const std::string osFilename(
     156         116 :                         std::string("https://schemas.opengis.net/gml/3.2.1/") +
     157         174 :                         CPLGetFilename(aosFiles[i]));
     158             :                     const std::string osCachedFileName(
     159         174 :                         GetCachedFilename(osFilename.c_str()));
     160             : 
     161         116 :                     std::string osTmpfilename(osCachedFileName + ".tmp");
     162          58 :                     if (CPLCopyFile(
     163             :                             osTmpfilename.c_str(),
     164         116 :                             (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
     165             :                         0)
     166             :                     {
     167          58 :                         VSIRename(osTmpfilename.c_str(),
     168             :                                   osCachedFileName.c_str());
     169          58 :                         bSuccess = true;
     170             :                     }
     171             :                 }
     172             :             }
     173             :         }
     174           2 :         VSIUnlink(osZIPFilename.c_str());
     175             :     }
     176           2 :     CPLHTTPDestroyResult(psResult);
     177           2 :     if (!bSuccess)
     178             :     {
     179           0 :         CPLDebugOnce("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP);
     180             :     }
     181           4 :     return bSuccess;
     182             : }
     183             : 
     184             : /************************************************************************/
     185             : /*                         CacheAllISO20070417()                        */
     186             : /************************************************************************/
     187             : 
     188           2 : bool GMLASXSDCache::CacheAllISO20070417()
     189             : {
     190             :     // As of today (2024-01-02), the schemas in https://schemas.opengis.net/iso/19139/20070417/
     191             :     // are actually the same as the ones in the iso19139-20070417_5-v20220526.zip archive
     192             :     // in https://schemas.opengis.net/iso/19139/iso19139-20070417.zip archive.
     193             :     // Download the later and unzip it for faster fetching of ISO schemas.
     194             : 
     195           2 :     bool bSuccess = false;
     196           2 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     197             : 
     198           2 :     const char *pszHTTPZIP =
     199             :         "https://schemas.opengis.net/iso/19139/iso19139-20070417.zip";
     200           2 :     CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
     201           2 :     if (psResult && psResult->nDataLen)
     202             :     {
     203             :         const std::string osZIPFilename(
     204           4 :             VSIMemGenerateHiddenFilename("temp.zip"));
     205             :         auto fpZIP =
     206           2 :             VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
     207           2 :                                  psResult->nDataLen, FALSE);
     208           2 :         if (fpZIP)
     209             :         {
     210           2 :             VSIFCloseL(fpZIP);
     211             : 
     212             :             const std::string osVSIZIPFilename(
     213           2 :                 "/vsizip//vsizip/" + osZIPFilename +
     214           4 :                 "/iso19139-20070417_5-v20220526.zip");
     215             :             const CPLStringList aosFiles(
     216           4 :                 VSIReadDirRecursive(osVSIZIPFilename.c_str()));
     217         142 :             for (int i = 0; i < aosFiles.size(); ++i)
     218             :             {
     219         274 :                 if (STARTS_WITH(aosFiles[i], "iso/19139/20070417/") &&
     220         134 :                     strstr(aosFiles[i], ".xsd"))
     221             :                 {
     222             :                     const std::string osFilename(
     223         144 :                         std::string("https://schemas.opengis.net/") +
     224         216 :                         aosFiles[i]);
     225             :                     const std::string osCachedFileName(
     226         216 :                         GetCachedFilename(osFilename.c_str()));
     227             : 
     228         144 :                     std::string osTmpfilename(osCachedFileName + ".tmp");
     229          72 :                     if (CPLCopyFile(
     230             :                             osTmpfilename.c_str(),
     231         144 :                             (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
     232             :                         0)
     233             :                     {
     234          72 :                         VSIRename(osTmpfilename.c_str(),
     235             :                                   osCachedFileName.c_str());
     236          72 :                         bSuccess = true;
     237             :                     }
     238             :                 }
     239             :             }
     240             :         }
     241           2 :         VSIUnlink(osZIPFilename.c_str());
     242             :     }
     243           2 :     CPLHTTPDestroyResult(psResult);
     244           2 :     if (!bSuccess)
     245             :     {
     246           0 :         CPLDebugOnce("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP);
     247             :     }
     248           4 :     return bSuccess;
     249             : }
     250             : 
     251             : /************************************************************************/
     252             : /*                               Open()                                 */
     253             : /************************************************************************/
     254             : 
     255        1188 : VSILFILE *GMLASXSDCache::Open(const std::string &osResource,
     256             :                               const std::string &osBasePath,
     257             :                               std::string &osOutFilename)
     258             : {
     259        1188 :     osOutFilename = osResource;
     260        1188 :     if (!STARTS_WITH(osResource.c_str(), "http://") &&
     261        1079 :         !STARTS_WITH(osResource.c_str(), "https://") &&
     262        2267 :         CPLIsFilenameRelative(osResource.c_str()) && !osResource.empty())
     263             :     {
     264             :         /* Transform a/b + ../c --> a/c */
     265        1084 :         std::string osResourceModified(osResource);
     266         542 :         std::string osBasePathModified(osBasePath);
     267         542 :         while ((STARTS_WITH(osResourceModified.c_str(), "../") ||
     268         543 :                 STARTS_WITH(osResourceModified.c_str(), "..\\")) &&
     269           1 :                !osBasePathModified.empty())
     270             :         {
     271           0 :             osBasePathModified = CPLGetDirnameSafe(osBasePathModified.c_str());
     272           0 :             osResourceModified = osResourceModified.substr(3);
     273             :         }
     274             : 
     275        1084 :         osOutFilename = CPLFormFilenameSafe(
     276         542 :             osBasePathModified.c_str(), osResourceModified.c_str(), nullptr);
     277             :     }
     278             : 
     279        1188 :     CPLDebug("GMLAS", "Resolving %s (%s) to %s", osResource.c_str(),
     280             :              osBasePath.c_str(), osOutFilename.c_str());
     281             : 
     282        1188 :     VSILFILE *fp = nullptr;
     283        1188 :     bool bHasTriedZIPArchive = false;
     284        1192 : retry:
     285        1192 :     if (!m_osCacheDirectory.empty() &&
     286        1191 :         (STARTS_WITH(osOutFilename.c_str(), "http://") ||
     287        2838 :          STARTS_WITH(osOutFilename.c_str(), "https://")) &&
     288         897 :         RecursivelyCreateDirectoryIfNeeded())
     289             :     {
     290             :         const std::string osCachedFileName(
     291        1792 :             GetCachedFilename(osOutFilename.c_str()));
     292         897 :         if (!m_bRefresh || m_aoSetRefreshedFiles.find(osCachedFileName) !=
     293         897 :                                m_aoSetRefreshedFiles.end())
     294             :         {
     295         895 :             fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
     296             :         }
     297         896 :         if (fp != nullptr)
     298             :         {
     299         846 :             CPLDebug("GMLAS", "Use cached %s", osCachedFileName.c_str());
     300             :         }
     301          50 :         else if (m_bAllowDownload)
     302             :         {
     303          49 :             if (m_bRefresh)
     304           1 :                 m_aoSetRefreshedFiles.insert(osCachedFileName);
     305             : 
     306         144 :             else if (!bHasTriedZIPArchive &&
     307          48 :                      strstr(osOutFilename.c_str(),
     308          96 :                             "://schemas.opengis.net/gml/3.2.1/") &&
     309           2 :                      CPLTestBool(CPLGetConfigOption(
     310             :                          "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
     311             :             {
     312           2 :                 bHasTriedZIPArchive = true;
     313           2 :                 if (CacheAllGML321())
     314           4 :                     goto retry;
     315             :             }
     316             : 
     317         138 :             else if (!bHasTriedZIPArchive &&
     318          46 :                      strstr(osOutFilename.c_str(),
     319          92 :                             "://schemas.opengis.net/iso/19139/20070417/") &&
     320           2 :                      CPLTestBool(CPLGetConfigOption(
     321             :                          "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
     322             :             {
     323           2 :                 bHasTriedZIPArchive = true;
     324           2 :                 if (CacheAllISO20070417())
     325           2 :                     goto retry;
     326             :             }
     327             : 
     328             :             CPLHTTPResult *psResult =
     329          45 :                 CPLHTTPFetch(osOutFilename.c_str(), nullptr);
     330          45 :             if (psResult == nullptr || psResult->nDataLen == 0)
     331             :             {
     332           2 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     333             :                          osResource.c_str());
     334           2 :                 CPLHTTPDestroyResult(psResult);
     335           2 :                 return nullptr;
     336             :             }
     337             : 
     338          86 :             std::string osTmpfilename(osCachedFileName + ".tmp");
     339          43 :             VSILFILE *fpTmp = VSIFOpenL(osTmpfilename.c_str(), "wb");
     340          43 :             if (fpTmp)
     341             :             {
     342          86 :                 const auto nRet = VSIFWriteL(psResult->pabyData,
     343          43 :                                              psResult->nDataLen, 1, fpTmp);
     344          43 :                 VSIFCloseL(fpTmp);
     345          43 :                 if (nRet == 1)
     346             :                 {
     347          43 :                     VSIRename(osTmpfilename.c_str(), osCachedFileName.c_str());
     348          43 :                     fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
     349             :                 }
     350             :             }
     351             : 
     352          43 :             CPLHTTPDestroyResult(psResult);
     353             :         }
     354             :     }
     355             :     else
     356             :     {
     357         590 :         if (STARTS_WITH(osOutFilename.c_str(), "http://") ||
     358         294 :             STARTS_WITH(osOutFilename.c_str(), "https://"))
     359             :         {
     360           2 :             if (m_bAllowDownload)
     361             :             {
     362             :                 CPLHTTPResult *psResult =
     363           2 :                     CPLHTTPFetch(osOutFilename.c_str(), nullptr);
     364           2 :                 if (psResult == nullptr || psResult->nDataLen == 0)
     365             :                 {
     366           0 :                     CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     367             :                              osResource.c_str());
     368           0 :                     CPLHTTPDestroyResult(psResult);
     369           0 :                     return nullptr;
     370             :                 }
     371             : 
     372           4 :                 fp = VSIFileFromMemBuffer(nullptr, psResult->pabyData,
     373           2 :                                           psResult->nDataLen, TRUE);
     374           2 :                 if (fp)
     375             :                 {
     376             :                     // Steal the memory buffer from HTTP result
     377           2 :                     psResult->pabyData = nullptr;
     378           2 :                     psResult->nDataLen = 0;
     379           2 :                     psResult->nDataAlloc = 0;
     380             :                 }
     381           2 :                 CPLHTTPDestroyResult(psResult);
     382             :             }
     383             :         }
     384             :         else
     385             :         {
     386         294 :             fp = VSIFOpenL(osOutFilename.c_str(), "rb");
     387             :         }
     388             :     }
     389             : 
     390        1186 :     if (fp == nullptr)
     391             :     {
     392           5 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     393             :                  osResource.c_str());
     394             :     }
     395             : 
     396        1186 :     return fp;
     397             : }

Generated by: LCOV version 1.14