LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gmlas - ogrgmlasxsdcache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 175 186 94.1 %
Date: 2024-11-21 22:18:42 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         359 : void GMLASResourceCache::SetCacheDirectory(const std::string &osCacheDirectory)
      25             : {
      26         359 :     m_osCacheDirectory = osCacheDirectory;
      27         359 : }
      28             : 
      29             : /************************************************************************/
      30             : /*                     RecursivelyCreateDirectoryIfNeeded()             */
      31             : /************************************************************************/
      32             : 
      33          68 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded(
      34             :     const std::string &osDirname)
      35             : {
      36             :     VSIStatBufL sStat;
      37          68 :     if (VSIStatL(osDirname.c_str(), &sStat) == 0)
      38             :     {
      39          60 :         return true;
      40             :     }
      41             : 
      42          16 :     std::string osParent = CPLGetDirname(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         825 : bool GMLASResourceCache::RecursivelyCreateDirectoryIfNeeded()
      52             : {
      53         825 :     if (!m_bHasCheckedCacheDirectory)
      54             :     {
      55          60 :         m_bHasCheckedCacheDirectory = true;
      56          60 :         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         824 :     return true;
      65             : }
      66             : 
      67             : /************************************************************************/
      68             : /*                        GetCachedFilename()                           */
      69             : /************************************************************************/
      70             : 
      71         959 : std::string GMLASResourceCache::GetCachedFilename(const std::string &osResource)
      72             : {
      73         959 :     std::string osLaunderedName(osResource);
      74         959 :     if (STARTS_WITH(osLaunderedName.c_str(), "http://"))
      75         374 :         osLaunderedName = osLaunderedName.substr(strlen("http://"));
      76         585 :     else if (STARTS_WITH(osLaunderedName.c_str(), "https://"))
      77         585 :         osLaunderedName = osLaunderedName.substr(strlen("https://"));
      78       46384 :     for (size_t i = 0; i < osLaunderedName.size(); i++)
      79             :     {
      80       53774 :         if (!isalnum(static_cast<unsigned char>(osLaunderedName[i])) &&
      81        8349 :             osLaunderedName[i] != '.')
      82        5095 :             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         959 :     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         959 :     const size_t nTypicalMaxSizeForDirName = 60;
      95             :     const size_t nSizeForDirName =
      96        1188 :         (m_osCacheDirectory.size() > nTypicalMaxSizeForDirName &&
      97         229 :          m_osCacheDirectory.size() < nWindowsMaxFilenameSize - strlen(".tmp") -
      98             :                                          2 * CPL_SHA256_HASH_SIZE)
      99        1188 :             ? m_osCacheDirectory.size()
     100         959 :             : nTypicalMaxSizeForDirName;
     101         959 :     CPLAssert(nWindowsMaxFilenameSize >= nSizeForDirName);
     102         959 :     const size_t nMaxFilenameSize = nWindowsMaxFilenameSize - nSizeForDirName;
     103             : 
     104         959 :     CPLAssert(nMaxFilenameSize >= strlen(".tmp"));
     105         959 :     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 CPLFormFilename(m_osCacheDirectory.c_str(), osLaunderedName.c_str(),
     119        1918 :                            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             :         static bool bHasWarned = false;
     180           0 :         if (!bHasWarned)
     181             :         {
     182           0 :             bHasWarned = true;
     183           0 :             CPLDebug("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP);
     184             :         }
     185             :     }
     186           4 :     return bSuccess;
     187             : }
     188             : 
     189             : /************************************************************************/
     190             : /*                         CacheAllISO20070417()                        */
     191             : /************************************************************************/
     192             : 
     193           2 : bool GMLASXSDCache::CacheAllISO20070417()
     194             : {
     195             :     // As of today (2024-01-02), the schemas in https://schemas.opengis.net/iso/19139/20070417/
     196             :     // are actually the same as the ones in the iso19139-20070417_5-v20220526.zip archive
     197             :     // in https://schemas.opengis.net/iso/19139/iso19139-20070417.zip archive.
     198             :     // Download the later and unzip it for faster fetching of ISO schemas.
     199             : 
     200           2 :     bool bSuccess = false;
     201           2 :     CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
     202             : 
     203           2 :     const char *pszHTTPZIP =
     204             :         "https://schemas.opengis.net/iso/19139/iso19139-20070417.zip";
     205           2 :     CPLHTTPResult *psResult = CPLHTTPFetch(pszHTTPZIP, nullptr);
     206           2 :     if (psResult && psResult->nDataLen)
     207             :     {
     208             :         const std::string osZIPFilename(
     209           4 :             VSIMemGenerateHiddenFilename("temp.zip"));
     210             :         auto fpZIP =
     211           2 :             VSIFileFromMemBuffer(osZIPFilename.c_str(), psResult->pabyData,
     212           2 :                                  psResult->nDataLen, FALSE);
     213           2 :         if (fpZIP)
     214             :         {
     215           2 :             VSIFCloseL(fpZIP);
     216             : 
     217             :             const std::string osVSIZIPFilename(
     218           2 :                 "/vsizip//vsizip/" + osZIPFilename +
     219           4 :                 "/iso19139-20070417_5-v20220526.zip");
     220             :             const CPLStringList aosFiles(
     221           4 :                 VSIReadDirRecursive(osVSIZIPFilename.c_str()));
     222         142 :             for (int i = 0; i < aosFiles.size(); ++i)
     223             :             {
     224         274 :                 if (STARTS_WITH(aosFiles[i], "iso/19139/20070417/") &&
     225         134 :                     strstr(aosFiles[i], ".xsd"))
     226             :                 {
     227             :                     const std::string osFilename(
     228         144 :                         std::string("https://schemas.opengis.net/") +
     229         216 :                         aosFiles[i]);
     230             :                     const std::string osCachedFileName(
     231         216 :                         GetCachedFilename(osFilename.c_str()));
     232             : 
     233         144 :                     std::string osTmpfilename(osCachedFileName + ".tmp");
     234          72 :                     if (CPLCopyFile(
     235             :                             osTmpfilename.c_str(),
     236         144 :                             (osVSIZIPFilename + "/" + aosFiles[i]).c_str()) ==
     237             :                         0)
     238             :                     {
     239          72 :                         VSIRename(osTmpfilename.c_str(),
     240             :                                   osCachedFileName.c_str());
     241          72 :                         bSuccess = true;
     242             :                     }
     243             :                 }
     244             :             }
     245             :         }
     246           2 :         VSIUnlink(osZIPFilename.c_str());
     247             :     }
     248           2 :     CPLHTTPDestroyResult(psResult);
     249           2 :     if (!bSuccess)
     250             :     {
     251             :         static bool bHasWarned = false;
     252           0 :         if (!bHasWarned)
     253             :         {
     254           0 :             bHasWarned = true;
     255           0 :             CPLDebug("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP);
     256             :         }
     257             :     }
     258           4 :     return bSuccess;
     259             : }
     260             : 
     261             : /************************************************************************/
     262             : /*                               Open()                                 */
     263             : /************************************************************************/
     264             : 
     265        1099 : VSILFILE *GMLASXSDCache::Open(const std::string &osResource,
     266             :                               const std::string &osBasePath,
     267             :                               std::string &osOutFilename)
     268             : {
     269        1099 :     osOutFilename = osResource;
     270        1099 :     if (!STARTS_WITH(osResource.c_str(), "http://") &&
     271        1002 :         !STARTS_WITH(osResource.c_str(), "https://") &&
     272        2101 :         CPLIsFilenameRelative(osResource.c_str()) && !osResource.empty())
     273             :     {
     274             :         /* Transform a/b + ../c --> a/c */
     275         930 :         std::string osResourceModified(osResource);
     276         930 :         std::string osBasePathModified(osBasePath);
     277         465 :         while ((STARTS_WITH(osResourceModified.c_str(), "../") ||
     278         466 :                 STARTS_WITH(osResourceModified.c_str(), "..\\")) &&
     279           1 :                !osBasePathModified.empty())
     280             :         {
     281           0 :             osBasePathModified = CPLGetDirname(osBasePathModified.c_str());
     282           0 :             osResourceModified = osResourceModified.substr(3);
     283             :         }
     284             : 
     285             :         osOutFilename = CPLFormFilename(osBasePathModified.c_str(),
     286         465 :                                         osResourceModified.c_str(), nullptr);
     287             :     }
     288             : 
     289        1099 :     CPLDebug("GMLAS", "Resolving %s (%s) to %s", osResource.c_str(),
     290             :              osBasePath.c_str(), osOutFilename.c_str());
     291             : 
     292        1099 :     VSILFILE *fp = nullptr;
     293        1099 :     bool bHasTriedZIPArchive = false;
     294        1103 : retry:
     295        1103 :     if (!m_osCacheDirectory.empty() &&
     296        1102 :         (STARTS_WITH(osOutFilename.c_str(), "http://") ||
     297        2660 :          STARTS_WITH(osOutFilename.c_str(), "https://")) &&
     298         808 :         RecursivelyCreateDirectoryIfNeeded())
     299             :     {
     300             :         const std::string osCachedFileName(
     301        1614 :             GetCachedFilename(osOutFilename.c_str()));
     302         808 :         if (!m_bRefresh || m_aoSetRefreshedFiles.find(osCachedFileName) !=
     303         808 :                                m_aoSetRefreshedFiles.end())
     304             :         {
     305         806 :             fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
     306             :         }
     307         807 :         if (fp != nullptr)
     308             :         {
     309         791 :             CPLDebug("GMLAS", "Use cached %s", osCachedFileName.c_str());
     310             :         }
     311          16 :         else if (m_bAllowDownload)
     312             :         {
     313          15 :             if (m_bRefresh)
     314           1 :                 m_aoSetRefreshedFiles.insert(osCachedFileName);
     315             : 
     316          42 :             else if (!bHasTriedZIPArchive &&
     317          14 :                      strstr(osOutFilename.c_str(),
     318          28 :                             "://schemas.opengis.net/gml/3.2.1/") &&
     319           2 :                      CPLTestBool(CPLGetConfigOption(
     320             :                          "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
     321             :             {
     322           2 :                 bHasTriedZIPArchive = true;
     323           2 :                 if (CacheAllGML321())
     324           4 :                     goto retry;
     325             :             }
     326             : 
     327          36 :             else if (!bHasTriedZIPArchive &&
     328          12 :                      strstr(osOutFilename.c_str(),
     329          24 :                             "://schemas.opengis.net/iso/19139/20070417/") &&
     330           2 :                      CPLTestBool(CPLGetConfigOption(
     331             :                          "OGR_GMLAS_USE_SCHEMAS_FROM_OGC_ZIP", "YES")))
     332             :             {
     333           2 :                 bHasTriedZIPArchive = true;
     334           2 :                 if (CacheAllISO20070417())
     335           2 :                     goto retry;
     336             :             }
     337             : 
     338             :             CPLHTTPResult *psResult =
     339          11 :                 CPLHTTPFetch(osOutFilename.c_str(), nullptr);
     340          11 :             if (psResult == nullptr || psResult->nDataLen == 0)
     341             :             {
     342           2 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     343             :                          osResource.c_str());
     344           2 :                 CPLHTTPDestroyResult(psResult);
     345           2 :                 return nullptr;
     346             :             }
     347             : 
     348          18 :             std::string osTmpfilename(osCachedFileName + ".tmp");
     349           9 :             VSILFILE *fpTmp = VSIFOpenL(osTmpfilename.c_str(), "wb");
     350           9 :             if (fpTmp)
     351             :             {
     352          18 :                 const auto nRet = VSIFWriteL(psResult->pabyData,
     353           9 :                                              psResult->nDataLen, 1, fpTmp);
     354           9 :                 VSIFCloseL(fpTmp);
     355           9 :                 if (nRet == 1)
     356             :                 {
     357           9 :                     VSIRename(osTmpfilename.c_str(), osCachedFileName.c_str());
     358           9 :                     fp = VSIFOpenL(osCachedFileName.c_str(), "rb");
     359             :                 }
     360             :             }
     361             : 
     362           9 :             CPLHTTPDestroyResult(psResult);
     363             :         }
     364             :     }
     365             :     else
     366             :     {
     367         590 :         if (STARTS_WITH(osOutFilename.c_str(), "http://") ||
     368         294 :             STARTS_WITH(osOutFilename.c_str(), "https://"))
     369             :         {
     370           2 :             if (m_bAllowDownload)
     371             :             {
     372             :                 CPLHTTPResult *psResult =
     373           2 :                     CPLHTTPFetch(osOutFilename.c_str(), nullptr);
     374           2 :                 if (psResult == nullptr || psResult->nDataLen == 0)
     375             :                 {
     376           0 :                     CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     377             :                              osResource.c_str());
     378           0 :                     CPLHTTPDestroyResult(psResult);
     379           0 :                     return nullptr;
     380             :                 }
     381             : 
     382           4 :                 fp = VSIFileFromMemBuffer(nullptr, psResult->pabyData,
     383           2 :                                           psResult->nDataLen, TRUE);
     384           2 :                 if (fp)
     385             :                 {
     386             :                     // Steal the memory buffer from HTTP result
     387           2 :                     psResult->pabyData = nullptr;
     388           2 :                     psResult->nDataLen = 0;
     389           2 :                     psResult->nDataAlloc = 0;
     390             :                 }
     391           2 :                 CPLHTTPDestroyResult(psResult);
     392             :             }
     393             :         }
     394             :         else
     395             :         {
     396         294 :             fp = VSIFOpenL(osOutFilename.c_str(), "rb");
     397             :         }
     398             :     }
     399             : 
     400        1097 :     if (fp == nullptr)
     401             :     {
     402           5 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot resolve %s",
     403             :                  osResource.c_str());
     404             :     }
     405             : 
     406        1097 :     return fp;
     407             : }

Generated by: LCOV version 1.14