LCOV - code coverage report
Current view: top level - port - cpl_path.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 394 438 90.0 %
Date: 2026-02-12 06:20:29 Functions: 30 36 83.3 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Portable filename/path parsing, and forming ala "Glob API".
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  **********************************************************************
       8             :  * Copyright (c) 1999, Frank Warmerdam
       9             :  * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #define ALLOW_DEPRECATED_CPL_PATH_FUNCTIONS
      15             : 
      16             : #include "cpl_port.h"
      17             : #include "cpl_conv.h"
      18             : 
      19             : #include <cctype>
      20             : #include <climits>
      21             : #include <cstddef>
      22             : #include <cstdio>
      23             : #include <cstring>
      24             : #if HAVE_UNISTD_H
      25             : #include <unistd.h>
      26             : #endif
      27             : 
      28             : #include <algorithm>
      29             : #include <string>
      30             : 
      31             : #include "cpl_atomic_ops.h"
      32             : #include "cpl_config.h"
      33             : #include "cpl_error.h"
      34             : #include "cpl_multiproc.h"
      35             : #include "cpl_string.h"
      36             : #include "cpl_vsi.h"
      37             : 
      38             : // Should be size of larged possible filename.
      39             : constexpr int CPL_PATH_BUF_SIZE = 2048;
      40             : constexpr int CPL_PATH_BUF_COUNT = 10;
      41             : 
      42           0 : static const char *CPLStaticBufferTooSmall(char *pszStaticResult)
      43             : {
      44           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Destination buffer too small");
      45           0 :     if (pszStaticResult == nullptr)
      46           0 :         return "";
      47           0 :     strcpy(pszStaticResult, "");
      48           0 :     return pszStaticResult;
      49             : }
      50             : 
      51             : /************************************************************************/
      52             : /*                         CPLGetStaticResult()                         */
      53             : /************************************************************************/
      54             : 
      55         577 : static char *CPLGetStaticResult()
      56             : 
      57             : {
      58         577 :     int bMemoryError = FALSE;
      59             :     char *pachBufRingInfo =
      60         577 :         static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
      61         577 :     if (bMemoryError)
      62           0 :         return nullptr;
      63         577 :     if (pachBufRingInfo == nullptr)
      64             :     {
      65          14 :         pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
      66             :             1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
      67          14 :         if (pachBufRingInfo == nullptr)
      68           0 :             return nullptr;
      69          14 :         CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
      70             :     }
      71             : 
      72             :     /* -------------------------------------------------------------------- */
      73             :     /*      Work out which string in the "ring" we want to use this         */
      74             :     /*      time.                                                           */
      75             :     /* -------------------------------------------------------------------- */
      76         577 :     int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
      77         577 :     const size_t nOffset =
      78         577 :         sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
      79         577 :     char *pachBuffer = pachBufRingInfo + nOffset;
      80             : 
      81         577 :     *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
      82             : 
      83         577 :     return pachBuffer;
      84             : }
      85             : 
      86             : /************************************************************************/
      87             : /*                       CPLPathReturnTLSString()                       */
      88             : /************************************************************************/
      89             : 
      90         577 : static const char *CPLPathReturnTLSString(const std::string &osRes,
      91             :                                           const char *pszFuncName)
      92             : {
      93         577 :     if (osRes.size() >= CPL_PATH_BUF_SIZE)
      94             :     {
      95           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long result for %s()",
      96             :                  pszFuncName);
      97           0 :         return "";
      98             :     }
      99             : 
     100         577 :     char *pszStaticResult = CPLGetStaticResult();
     101         577 :     if (pszStaticResult == nullptr)
     102           0 :         return CPLStaticBufferTooSmall(pszStaticResult);
     103         577 :     memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
     104         577 :     return pszStaticResult;
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                        CPLFindFilenameStart()                        */
     109             : /************************************************************************/
     110             : 
     111     2727800 : static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
     112             : 
     113             : {
     114     2727800 :     size_t iFileStart = nStart ? nStart : strlen(pszFilename);
     115             : 
     116    42408500 :     for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
     117    39681000 :            pszFilename[iFileStart - 1] != '\\';
     118             :          iFileStart--)
     119             :     {
     120             :     }
     121             : 
     122     2727800 :     return static_cast<int>(iFileStart);
     123             : }
     124             : 
     125             : /************************************************************************/
     126             : /*                           CPLGetPathSafe()                           */
     127             : /************************************************************************/
     128             : 
     129             : /**
     130             :  * Extract directory path portion of filename.
     131             :  *
     132             :  * Returns a string containing the directory path portion of the passed
     133             :  * filename.  If there is no path in the passed filename an empty string
     134             :  * will be returned (not NULL).
     135             :  *
     136             :  * \code{.cpp}
     137             :  * CPLGetPathSafe( "abc/def.xyz" ) == "abc"
     138             :  * CPLGetPathSafe( "/abc/def/" ) == "/abc/def"
     139             :  * CPLGetPathSafe( "/" ) == "/"
     140             :  * CPLGetPathSafe( "/abc/def" ) == "/abc"
     141             :  * CPLGetPathSafe( "abc" ) == ""
     142             :  * \endcode
     143             :  *
     144             :  * @param pszFilename the filename potentially including a path.
     145             :  *
     146             :  * @return Path.
     147             :  *
     148             :  * @since 3.11
     149             :  */
     150             : 
     151      242819 : std::string CPLGetPathSafe(const char *pszFilename)
     152             : 
     153             : {
     154      242819 :     size_t nSuffixPos = 0;
     155      242819 :     if (STARTS_WITH(pszFilename, "/vsicurl/http"))
     156             :     {
     157           9 :         const char *pszQuestionMark = strchr(pszFilename, '?');
     158           9 :         if (pszQuestionMark)
     159           1 :             nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
     160             :     }
     161      242810 :     else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
     162           1 :              strstr(pszFilename, "url="))
     163             :     {
     164           2 :         std::string osRet;
     165             :         const CPLStringList aosTokens(
     166           2 :             CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
     167           3 :         for (int i = 0; i < aosTokens.size(); i++)
     168             :         {
     169           2 :             if (osRet.empty())
     170           1 :                 osRet = "/vsicurl?";
     171             :             else
     172           1 :                 osRet += '&';
     173           3 :             if (STARTS_WITH(aosTokens[i], "url=") &&
     174           1 :                 !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
     175             :             {
     176             :                 char *pszUnescaped =
     177           1 :                     CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     178           1 :                 char *pszPath = CPLEscapeString(
     179           2 :                     CPLGetPathSafe(pszUnescaped + strlen("url=")).c_str(), -1,
     180             :                     CPLES_URL);
     181           1 :                 osRet += "url=";
     182           1 :                 osRet += pszPath;
     183           1 :                 CPLFree(pszPath);
     184           1 :                 CPLFree(pszUnescaped);
     185             :             }
     186             :             else
     187             :             {
     188           1 :                 osRet += aosTokens[i];
     189             :             }
     190             :         }
     191           1 :         return osRet;
     192             :     }
     193             : 
     194      242818 :     const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
     195      242818 :     if (iFileStart == 0)
     196             :     {
     197        3484 :         return std::string();
     198             :     }
     199             : 
     200      478661 :     std::string osRet(pszFilename, iFileStart);
     201             : 
     202      239327 :     if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
     203      238689 :         osRet.pop_back();
     204             : 
     205      239333 :     if (nSuffixPos)
     206             :     {
     207           1 :         osRet += (pszFilename + nSuffixPos);
     208             :     }
     209             : 
     210      239333 :     return osRet;
     211             : }
     212             : 
     213             : /************************************************************************/
     214             : /*                             CPLGetPath()                             */
     215             : /************************************************************************/
     216             : 
     217             : /**
     218             :  * Extract directory path portion of filename.
     219             :  *
     220             :  * Returns a string containing the directory path portion of the passed
     221             :  * filename.  If there is no path in the passed filename an empty string
     222             :  * will be returned (not NULL).
     223             :  *
     224             :  * \code{.cpp}
     225             :  * CPLGetPath( "abc/def.xyz" ) == "abc"
     226             :  * CPLGetPath( "/abc/def/" ) == "/abc/def"
     227             :  * CPLGetPath( "/" ) == "/"
     228             :  * CPLGetPath( "/abc/def" ) == "/abc"
     229             :  * CPLGetPath( "abc" ) == ""
     230             :  * \endcode
     231             :  *
     232             :  * @param pszFilename the filename potentially including a path.
     233             :  *
     234             :  * @return Path in an internal string which must not be freed.  The string
     235             :  * may be destroyed by the next CPL filename handling call.  The returned
     236             :  * will generally not contain a trailing path separator.
     237             :  *
     238             :  * @deprecated If using C++, prefer using CPLGetPathSafe() instead
     239             :  */
     240             : 
     241          50 : const char *CPLGetPath(const char *pszFilename)
     242             : 
     243             : {
     244          50 :     return CPLPathReturnTLSString(CPLGetPathSafe(pszFilename), __FUNCTION__);
     245             : }
     246             : 
     247             : /************************************************************************/
     248             : /*                           CPLGetDirname()                            */
     249             : /************************************************************************/
     250             : 
     251             : /**
     252             :  * Extract directory path portion of filename.
     253             :  *
     254             :  * Returns a string containing the directory path portion of the passed
     255             :  * filename.  If there is no path in the passed filename the dot will be
     256             :  * returned.  It is the only difference from CPLGetPath().
     257             :  *
     258             :  * \code{.cpp}
     259             :  * CPLGetDirnameSafe( "abc/def.xyz" ) == "abc"
     260             :  * CPLGetDirnameSafe( "/abc/def/" ) == "/abc/def"
     261             :  * CPLGetDirnameSafe( "/" ) == "/"
     262             :  * CPLGetDirnameSafe( "/abc/def" ) == "/abc"
     263             :  * CPLGetDirnameSafe( "abc" ) == "."
     264             :  * \endcode
     265             :  *
     266             :  * @param pszFilename the filename potentially including a path.
     267             :  *
     268             :  * @return Path
     269             :  *
     270             :  * @since 3.11
     271             :  */
     272             : 
     273      172412 : std::string CPLGetDirnameSafe(const char *pszFilename)
     274             : 
     275             : {
     276      172412 :     size_t nSuffixPos = 0;
     277      172412 :     if (STARTS_WITH(pszFilename, "/vsicurl/http"))
     278             :     {
     279         141 :         const char *pszQuestionMark = strchr(pszFilename, '?');
     280         141 :         if (pszQuestionMark)
     281           1 :             nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
     282             :     }
     283      172271 :     else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
     284          15 :              strstr(pszFilename, "url="))
     285             :     {
     286          30 :         std::string osRet;
     287             :         const CPLStringList aosTokens(
     288          30 :             CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
     289          44 :         for (int i = 0; i < aosTokens.size(); i++)
     290             :         {
     291          29 :             if (osRet.empty())
     292          15 :                 osRet = "/vsicurl?";
     293             :             else
     294          14 :                 osRet += '&';
     295          44 :             if (STARTS_WITH(aosTokens[i], "url=") &&
     296          15 :                 !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
     297             :             {
     298             :                 char *pszUnescaped =
     299          15 :                     CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     300          15 :                 char *pszPath = CPLEscapeString(
     301          15 :                     CPLGetDirname(pszUnescaped + strlen("url=")), -1,
     302             :                     CPLES_URL);
     303          15 :                 osRet += "url=";
     304          15 :                 osRet += pszPath;
     305          15 :                 CPLFree(pszPath);
     306          15 :                 CPLFree(pszUnescaped);
     307             :             }
     308             :             else
     309             :             {
     310          14 :                 osRet += aosTokens[i];
     311             :             }
     312             :         }
     313          15 :         return osRet;
     314             :     }
     315             : 
     316      172397 :     const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
     317      172397 :     if (iFileStart == 0)
     318             :     {
     319          73 :         return std::string(".");
     320             :     }
     321             : 
     322      344648 :     std::string osRet(pszFilename, iFileStart);
     323             : 
     324      172320 :     if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
     325      172300 :         osRet.pop_back();
     326             : 
     327      172316 :     if (nSuffixPos)
     328             :     {
     329           1 :         osRet += (pszFilename + nSuffixPos);
     330             :     }
     331             : 
     332      172316 :     return osRet;
     333             : }
     334             : 
     335             : /************************************************************************/
     336             : /*                           CPLGetDirname()                            */
     337             : /************************************************************************/
     338             : 
     339             : /**
     340             :  * Extract directory path portion of filename.
     341             :  *
     342             :  * Returns a string containing the directory path portion of the passed
     343             :  * filename.  If there is no path in the passed filename the dot will be
     344             :  * returned.  It is the only difference from CPLGetPath().
     345             :  *
     346             :  * \code{.cpp}
     347             :  * CPLGetDirname( "abc/def.xyz" ) == "abc"
     348             :  * CPLGetDirname( "/abc/def/" ) == "/abc/def"
     349             :  * CPLGetDirname( "/" ) == "/"
     350             :  * CPLGetDirname( "/abc/def" ) == "/abc"
     351             :  * CPLGetDirname( "abc" ) == "."
     352             :  * \endcode
     353             :  *
     354             :  * @param pszFilename the filename potentially including a path.
     355             :  *
     356             :  * @return Path in an internal string which must not be freed.  The string
     357             :  * may be destroyed by the next CPL filename handling call.  The returned
     358             :  * will generally not contain a trailing path separator.
     359             :  */
     360             : 
     361          20 : const char *CPLGetDirname(const char *pszFilename)
     362             : 
     363             : {
     364          20 :     return CPLPathReturnTLSString(CPLGetDirnameSafe(pszFilename), __FUNCTION__);
     365             : }
     366             : 
     367             : /************************************************************************/
     368             : /*                           CPLGetFilename()                           */
     369             : /************************************************************************/
     370             : 
     371             : /**
     372             :  * Extract non-directory portion of filename.
     373             :  *
     374             :  * Returns a string containing the bare filename portion of the passed
     375             :  * filename.  If there is no filename (passed value ends in trailing directory
     376             :  * separator) an empty string is returned.
     377             :  *
     378             :  * \code{.cpp}
     379             :  * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
     380             :  * CPLGetFilename( "/abc/def/" ) == ""
     381             :  * CPLGetFilename( "abc/def" ) == "def"
     382             :  * \endcode
     383             :  *
     384             :  * @param pszFullFilename the full filename potentially including a path.
     385             :  *
     386             :  * @return just the non-directory portion of the path (points back into
     387             :  * original string).
     388             :  */
     389             : 
     390     1207420 : const char *CPLGetFilename(const char *pszFullFilename)
     391             : 
     392             : {
     393     1207420 :     const int iFileStart = CPLFindFilenameStart(pszFullFilename);
     394             : 
     395     1207420 :     return pszFullFilename + iFileStart;
     396             : }
     397             : 
     398             : /************************************************************************/
     399             : /*                         CPLGetBasenameSafe()                         */
     400             : /************************************************************************/
     401             : 
     402             : /**
     403             :  * Extract basename (non-directory, non-extension) portion of filename.
     404             :  *
     405             :  * Returns a string containing the file basename portion of the passed
     406             :  * name.  If there is no basename (passed value ends in trailing directory
     407             :  * separator, or filename starts with a dot) an empty string is returned.
     408             :  *
     409             :  * \code{.cpp}
     410             :  * CPLGetBasename( "abc/def.xyz" ) == "def"
     411             :  * CPLGetBasename( "abc/def" ) == "def"
     412             :  * CPLGetBasename( "abc/def/" ) == ""
     413             :  * \endcode
     414             :  *
     415             :  * @param pszFullFilename the full filename potentially including a path.
     416             :  *
     417             :  * @return just the non-directory, non-extension portion of the path
     418             :  *
     419             :  * @since 3.11
     420             :  */
     421             : 
     422      456324 : std::string CPLGetBasenameSafe(const char *pszFullFilename)
     423             : 
     424             : {
     425             :     const size_t iFileStart =
     426      456324 :         static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
     427             : 
     428      456324 :     size_t iExtStart = strlen(pszFullFilename);
     429     2564460 :     for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
     430             :          iExtStart--)
     431             :     {
     432             :     }
     433             : 
     434      456324 :     if (iExtStart == iFileStart)
     435       25779 :         iExtStart = strlen(pszFullFilename);
     436             : 
     437      456324 :     const size_t nLength = iExtStart - iFileStart;
     438      456324 :     return std::string(pszFullFilename + iFileStart, nLength);
     439             : }
     440             : 
     441             : /************************************************************************/
     442             : /*                           CPLGetBasename()                           */
     443             : /************************************************************************/
     444             : 
     445             : /**
     446             :  * Extract basename (non-directory, non-extension) portion of filename.
     447             :  *
     448             :  * Returns a string containing the file basename portion of the passed
     449             :  * name.  If there is no basename (passed value ends in trailing directory
     450             :  * separator, or filename starts with a dot) an empty string is returned.
     451             :  *
     452             :  * \code{.cpp}
     453             :  * CPLGetBasename( "abc/def.xyz" ) == "def"
     454             :  * CPLGetBasename( "abc/def" ) == "def"
     455             :  * CPLGetBasename( "abc/def/" ) == ""
     456             :  * \endcode
     457             :  *
     458             :  * @param pszFullFilename the full filename potentially including a path.
     459             :  *
     460             :  * @return just the non-directory, non-extension portion of the path in
     461             :  * an internal string which must not be freed.  The string
     462             :  * may be destroyed by the next CPL filename handling call.
     463             :  *
     464             :  * @deprecated If using C++, prefer using CPLGetBasenameSafe() instead
     465             :  */
     466             : 
     467         230 : const char *CPLGetBasename(const char *pszFullFilename)
     468             : 
     469             : {
     470         460 :     return CPLPathReturnTLSString(CPLGetBasenameSafe(pszFullFilename),
     471         460 :                                   __FUNCTION__);
     472             : }
     473             : 
     474             : /************************************************************************/
     475             : /*                        CPLGetExtensionSafe()                         */
     476             : /************************************************************************/
     477             : 
     478             : /**
     479             :  * Extract filename extension from full filename.
     480             :  *
     481             :  * Returns a string containing the extension portion of the passed
     482             :  * name.  If there is no extension (the filename has no dot) an empty string
     483             :  * is returned.  The returned extension will not include the period.
     484             :  *
     485             :  * \code{.cpp}
     486             :  * CPLGetExtensionSafe( "abc/def.xyz" ) == "xyz"
     487             :  * CPLGetExtensionSafe( "abc/def" ) == ""
     488             :  * \endcode
     489             :  *
     490             :  * @param pszFullFilename the full filename potentially including a path.
     491             :  *
     492             :  * @return just the extension portion of the path.
     493             :  *
     494             :  * @since 3.11
     495             :  */
     496             : 
     497      654402 : std::string CPLGetExtensionSafe(const char *pszFullFilename)
     498             : 
     499             : {
     500      654402 :     if (pszFullFilename[0] == '\0')
     501        5544 :         return std::string();
     502             : 
     503             :     size_t iFileStart =
     504      648858 :         static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
     505      648859 :     size_t iExtStart = strlen(pszFullFilename);
     506     4436780 :     for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
     507             :          iExtStart--)
     508             :     {
     509             :     }
     510             : 
     511      648859 :     if (iExtStart == iFileStart)
     512       98408 :         iExtStart = strlen(pszFullFilename) - 1;
     513             : 
     514             :     // If the extension is too long, it is very much likely not an extension,
     515             :     // but another component of the path
     516      648859 :     const size_t knMaxExtensionSize = 10;
     517      648859 :     if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
     518        2277 :         return "";
     519             : 
     520      646582 :     return std::string(pszFullFilename + iExtStart + 1);
     521             : }
     522             : 
     523             : /************************************************************************/
     524             : /*                          CPLGetExtension()                           */
     525             : /************************************************************************/
     526             : 
     527             : /**
     528             :  * Extract filename extension from full filename.
     529             :  *
     530             :  * Returns a string containing the extension portion of the passed
     531             :  * name.  If there is no extension (the filename has no dot) an empty string
     532             :  * is returned.  The returned extension will not include the period.
     533             :  *
     534             :  * \code{.cpp}
     535             :  * CPLGetExtension( "abc/def.xyz" ) == "xyz"
     536             :  * CPLGetExtension( "abc/def" ) == ""
     537             :  * \endcode
     538             :  *
     539             :  * @param pszFullFilename the full filename potentially including a path.
     540             :  *
     541             :  * @return just the extension portion of the path in
     542             :  * an internal string which must not be freed.  The string
     543             :  * may be destroyed by the next CPL filename handling call.
     544             :  *
     545             :  * @deprecated If using C++, prefer using CPLGetExtensionSafe() instead
     546             :  */
     547             : 
     548          46 : const char *CPLGetExtension(const char *pszFullFilename)
     549             : 
     550             : {
     551          92 :     return CPLPathReturnTLSString(CPLGetExtensionSafe(pszFullFilename),
     552          92 :                                   __FUNCTION__);
     553             : }
     554             : 
     555             : /************************************************************************/
     556             : /*                          CPLGetCurrentDir()                          */
     557             : /************************************************************************/
     558             : 
     559             : /**
     560             :  * Get the current working directory name.
     561             :  *
     562             :  * @return a pointer to buffer, containing current working directory path
     563             :  * or NULL in case of error.  User is responsible to free that buffer
     564             :  * after usage with CPLFree() function.
     565             :  * If HAVE_GETCWD macro is not defined, the function returns NULL.
     566             :  **/
     567             : 
     568             : #ifdef _WIN32
     569             : char *CPLGetCurrentDir()
     570             : {
     571             :     const size_t nPathMax = _MAX_PATH;
     572             :     wchar_t *pwszDirPath =
     573             :         static_cast<wchar_t *>(VSI_MALLOC_VERBOSE(nPathMax * sizeof(wchar_t)));
     574             :     char *pszRet = nullptr;
     575             :     if (pwszDirPath != nullptr && _wgetcwd(pwszDirPath, nPathMax) != nullptr)
     576             :     {
     577             :         pszRet = CPLRecodeFromWChar(pwszDirPath, CPL_ENC_UCS2, CPL_ENC_UTF8);
     578             :     }
     579             :     CPLFree(pwszDirPath);
     580             :     return pszRet;
     581             : }
     582             : #elif defined(HAVE_GETCWD)
     583        5370 : char *CPLGetCurrentDir()
     584             : {
     585             : #if PATH_MAX
     586        5370 :     const size_t nPathMax = PATH_MAX;
     587             : #else
     588             :     const size_t nPathMax = 8192;
     589             : #endif
     590             : 
     591        5370 :     char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
     592        5370 :     if (!pszDirPath)
     593           0 :         return nullptr;
     594             : 
     595        5370 :     return getcwd(pszDirPath, nPathMax);
     596             : }
     597             : #else   // !HAVE_GETCWD
     598             : char *CPLGetCurrentDir()
     599             : {
     600             :     return nullptr;
     601             : }
     602             : #endif  // HAVE_GETCWD
     603             : 
     604             : /************************************************************************/
     605             : /*                         CPLResetExtension()                          */
     606             : /************************************************************************/
     607             : 
     608             : /**
     609             :  * Replace the extension with the provided one.
     610             :  *
     611             :  * @param pszPath the input path, this string is not altered.
     612             :  * @param pszExt the new extension to apply to the given path.
     613             :  *
     614             :  * @return an altered filename with the new extension.
     615             :  *
     616             :  * @since 3.11
     617             :  */
     618             : 
     619      201827 : std::string CPLResetExtensionSafe(const char *pszPath, const char *pszExt)
     620             : 
     621             : {
     622      201827 :     std::string osRet(pszPath);
     623             : 
     624             :     /* -------------------------------------------------------------------- */
     625             :     /*      First, try and strip off any existing extension.                */
     626             :     /* -------------------------------------------------------------------- */
     627             : 
     628     1033910 :     for (size_t i = osRet.size(); i > 0;)
     629             :     {
     630     1033680 :         --i;
     631     1033680 :         if (osRet[i] == '.')
     632             :         {
     633      186232 :             osRet.resize(i);
     634      186233 :             break;
     635             :         }
     636      847450 :         else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
     637             :         {
     638       15376 :             break;
     639             :         }
     640             :     }
     641             : 
     642             :     /* -------------------------------------------------------------------- */
     643             :     /*      Append the new extension.                                       */
     644             :     /* -------------------------------------------------------------------- */
     645      201826 :     osRet += '.';
     646      201825 :     osRet += pszExt;
     647             : 
     648      201824 :     return osRet;
     649             : }
     650             : 
     651             : /************************************************************************/
     652             : /*                         CPLResetExtension()                          */
     653             : /************************************************************************/
     654             : 
     655             : /**
     656             :  * Replace the extension with the provided one.
     657             :  *
     658             :  * @param pszPath the input path, this string is not altered.
     659             :  * @param pszExt the new extension to apply to the given path.
     660             :  *
     661             :  * @return an altered filename with the new extension.    Do not
     662             :  * modify or free the returned string.  The string may be destroyed by the
     663             :  * next CPL call.
     664             :  *
     665             :  * @deprecated If using C++, prefer using CPLResetExtensionSafe() instead
     666             :  */
     667             : 
     668         135 : const char *CPLResetExtension(const char *pszPath, const char *pszExt)
     669             : 
     670             : {
     671         270 :     return CPLPathReturnTLSString(CPLResetExtensionSafe(pszPath, pszExt),
     672         270 :                                   __FUNCTION__);
     673             : }
     674             : 
     675             : /************************************************************************/
     676             : /*                        CPLFormFilenameSafe()                         */
     677             : /************************************************************************/
     678             : 
     679             : /**
     680             :  * Build a full file path from a passed path, file basename and extension.
     681             :  *
     682             :  * The path, and extension are optional.  The basename may in fact contain
     683             :  * an extension if desired.
     684             :  *
     685             :  * \code{.cpp}
     686             :  * CPLFormFilenameSafe("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
     687             :  * CPLFormFilenameSafe(NULL,"def", NULL ) == "def"
     688             :  * CPLFormFilenameSafe(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
     689             :  * CPLFormFilenameSafe("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
     690             :  * CPLFormFilenameSafe("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
     691             :  * \endcode
     692             :  *
     693             :  * @param pszPath directory path to the directory containing the file.  This
     694             :  * may be relative or absolute, and may have a trailing path separator or
     695             :  * not.  May be NULL.
     696             :  *
     697             :  * @param pszBasename file basename.  May optionally have path and/or
     698             :  * extension.  Must *NOT* be NULL.
     699             :  *
     700             :  * @param pszExtension file extension, optionally including the period.  May
     701             :  * be NULL.
     702             :  *
     703             :  * @return a fully formed filename.
     704             :  *
     705             :  * @since 3.11
     706             :  */
     707             : 
     708      448748 : std::string CPLFormFilenameSafe(const char *pszPath, const char *pszBasename,
     709             :                                 const char *pszExtension)
     710             : 
     711             : {
     712      448748 :     if (pszBasename[0] == '.' &&
     713       13599 :         (pszBasename[1] == '/' || pszBasename[1] == '\\'))
     714          62 :         pszBasename += 2;
     715             : 
     716      448748 :     const char *pszAddedPathSep = "";
     717      448748 :     const char *pszAddedExtSep = "";
     718             : 
     719      448748 :     if (pszPath == nullptr)
     720       11637 :         pszPath = "";
     721      448748 :     size_t nLenPath = strlen(pszPath);
     722             : 
     723      448748 :     const char *pszQuestionMark = nullptr;
     724      448748 :     if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
     725             :     {
     726         122 :         pszQuestionMark = strchr(pszPath, '?');
     727         122 :         if (pszQuestionMark)
     728             :         {
     729           1 :             nLenPath = pszQuestionMark - pszPath;
     730             :         }
     731         122 :         pszAddedPathSep = "/";
     732             :     }
     733             : 
     734      793243 :     if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
     735      793451 :         pszBasename[1] == '.' &&
     736         194 :         (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
     737         161 :          pszBasename[2] == '/'))
     738             :     {
     739             :         // "/a/b/" + "..[/something]" --> "/a[/something]"
     740             :         // "/a/b" + "..[/something]" --> "/a[/something]"
     741         194 :         if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
     742          14 :             nLenPath--;
     743             :         while (true)
     744             :         {
     745         247 :             const char *pszBasenameOri = pszBasename;
     746         247 :             const size_t nLenPathOri = nLenPath;
     747        1549 :             while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
     748        1538 :                    pszPath[nLenPath - 1] != '/')
     749             :             {
     750        1302 :                 nLenPath--;
     751             :             }
     752         247 :             if (nLenPath == 1 && pszPath[0] == '/')
     753             :             {
     754          18 :                 pszBasename += 2;
     755          18 :                 if (pszBasename[0] == '/' || pszBasename[0] == '\\')
     756          10 :                     pszBasename++;
     757          18 :                 if (*pszBasename == '.')
     758             :                 {
     759           1 :                     pszBasename = pszBasenameOri;
     760           1 :                     nLenPath = nLenPathOri;
     761           1 :                     if (pszAddedPathSep[0] == 0)
     762           1 :                         pszAddedPathSep = "/";
     763             :                 }
     764          18 :                 break;
     765             :             }
     766         229 :             else if ((nLenPath > 1 && pszPath[0] == '/') ||
     767          11 :                      (nLenPath > 2 && pszPath[1] == ':') ||
     768           1 :                      (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
     769             :             {
     770         222 :                 nLenPath--;
     771         222 :                 pszBasename += 2;
     772         222 :                 if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
     773         203 :                     pszBasename[1] == '.' && pszBasename[2] == '.')
     774             :                 {
     775          53 :                     pszBasename++;
     776             :                 }
     777             :                 else
     778             :                 {
     779             :                     break;
     780             :                 }
     781             :             }
     782             :             else
     783             :             {
     784             :                 // cppcheck-suppress redundantAssignment
     785           7 :                 pszBasename = pszBasenameOri;
     786           7 :                 nLenPath = nLenPathOri;
     787           7 :                 if (pszAddedPathSep[0] == 0)
     788           7 :                     pszAddedPathSep = pszPath[0] == '/'
     789           7 :                                           ? "/"
     790           2 :                                           : VSIGetDirectorySeparator(pszPath);
     791           7 :                 break;
     792             :             }
     793          53 :         }
     794             :     }
     795      448568 :     else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
     796      434161 :              pszPath[nLenPath - 1] != '\\')
     797             :     {
     798      434155 :         if (pszAddedPathSep[0] == 0)
     799      434036 :             pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
     800             :     }
     801             : 
     802      448769 :     if (pszExtension == nullptr)
     803      275511 :         pszExtension = "";
     804      173258 :     else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
     805      157510 :         pszAddedExtSep = ".";
     806             : 
     807      448769 :     std::string osRes;
     808      448767 :     osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
     809      448767 :                   strlen(pszAddedExtSep) + strlen(pszExtension) +
     810      448767 :                   (pszQuestionMark ? strlen(pszQuestionMark) : 0));
     811      448771 :     osRes.assign(pszPath, nLenPath);
     812      448763 :     osRes += pszAddedPathSep;
     813      448766 :     osRes += pszBasename;
     814      448761 :     osRes += pszAddedExtSep;
     815      448764 :     osRes += pszExtension;
     816             : 
     817      448764 :     if (pszQuestionMark)
     818             :     {
     819           1 :         osRes += pszQuestionMark;
     820             :     }
     821             : 
     822      448764 :     return osRes;
     823             : }
     824             : 
     825             : /************************************************************************/
     826             : /*                          CPLFormFilename()                           */
     827             : /************************************************************************/
     828             : 
     829             : /**
     830             :  * Build a full file path from a passed path, file basename and extension.
     831             :  *
     832             :  * The path, and extension are optional.  The basename may in fact contain
     833             :  * an extension if desired.
     834             :  *
     835             :  * \code{.cpp}
     836             :  * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
     837             :  * CPLFormFilename(NULL,"def", NULL ) == "def"
     838             :  * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
     839             :  * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
     840             :  * CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
     841             :  * \endcode
     842             :  *
     843             :  * @param pszPath directory path to the directory containing the file.  This
     844             :  * may be relative or absolute, and may have a trailing path separator or
     845             :  * not.  May be NULL.
     846             :  *
     847             :  * @param pszBasename file basename.  May optionally have path and/or
     848             :  * extension.  Must *NOT* be NULL.
     849             :  *
     850             :  * @param pszExtension file extension, optionally including the period.  May
     851             :  * be NULL.
     852             :  *
     853             :  * @return a fully formed filename in an internal static string.  Do not
     854             :  * modify or free the returned string.  The string may be destroyed by the
     855             :  * next CPL call.
     856             :  *
     857             :  * @deprecated If using C++, prefer using CPLFormFilenameSafe() instead
     858             :  */
     859          87 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
     860             :                             const char *pszExtension)
     861             : 
     862             : {
     863          87 :     return CPLPathReturnTLSString(
     864         174 :         CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
     865             : }
     866             : 
     867             : /************************************************************************/
     868             : /*                       CPLFormCIFilenameSafe()                        */
     869             : /************************************************************************/
     870             : 
     871             : /**
     872             :  * Case insensitive file searching, returning full path.
     873             :  *
     874             :  * This function tries to return the path to a file regardless of
     875             :  * whether the file exactly matches the basename, and extension case, or
     876             :  * is all upper case, or all lower case.  The path is treated as case
     877             :  * sensitive.  This function is equivalent to CPLFormFilename() on
     878             :  * case insensitive file systems (like Windows).
     879             :  *
     880             :  * @param pszPath directory path to the directory containing the file.  This
     881             :  * may be relative or absolute, and may have a trailing path separator or
     882             :  * not.  May be NULL.
     883             :  *
     884             :  * @param pszBasename file basename.  May optionally have path and/or
     885             :  * extension.  May not be NULL.
     886             :  *
     887             :  * @param pszExtension file extension, optionally including the period.  May
     888             :  * be NULL.
     889             :  *
     890             :  * @return a fully formed filename.
     891             :  *
     892             :  * @since 3.11
     893             :  */
     894             : 
     895        6272 : std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
     896             :                                   const char *pszExtension)
     897             : 
     898             : {
     899             :     // On case insensitive filesystems, just default to CPLFormFilename().
     900        6272 :     if (!VSIIsCaseSensitiveFS(pszPath))
     901           0 :         return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
     902             : 
     903        6272 :     const char *pszAddedExtSep = "";
     904        6272 :     size_t nLen = strlen(pszBasename) + 2;
     905             : 
     906        6272 :     if (pszExtension != nullptr)
     907        2403 :         nLen += strlen(pszExtension);
     908             : 
     909        6272 :     char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
     910        6272 :     if (pszFilename == nullptr)
     911           0 :         return "";
     912             : 
     913        6272 :     if (pszExtension == nullptr)
     914        3869 :         pszExtension = "";
     915        2403 :     else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
     916        2351 :         pszAddedExtSep = ".";
     917             : 
     918        6272 :     snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
     919             :              pszExtension);
     920             : 
     921       12544 :     std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
     922             :     VSIStatBufL sStatBuf;
     923        6272 :     int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     924             : 
     925        6272 :     if (nStatRet != 0)
     926             :     {
     927       81041 :         for (size_t i = 0; pszFilename[i] != '\0'; i++)
     928             :         {
     929       75207 :             pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
     930             :         }
     931             : 
     932             :         std::string osTmpPath(
     933       11668 :             CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
     934             :         nStatRet =
     935        5834 :             VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     936        5834 :         if (nStatRet == 0)
     937           3 :             osRet = std::move(osTmpPath);
     938             :     }
     939             : 
     940        6272 :     if (nStatRet != 0)
     941             :     {
     942       81017 :         for (size_t i = 0; pszFilename[i] != '\0'; i++)
     943             :         {
     944       75186 :             pszFilename[i] = static_cast<char>(
     945       75186 :                 CPLTolower(static_cast<unsigned char>(pszFilename[i])));
     946             :         }
     947             : 
     948             :         std::string osTmpPath(
     949       11662 :             CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
     950             :         nStatRet =
     951        5831 :             VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     952        5831 :         if (nStatRet == 0)
     953           8 :             osRet = std::move(osTmpPath);
     954             :     }
     955             : 
     956        6272 :     if (nStatRet != 0)
     957        5823 :         osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
     958             : 
     959        6272 :     CPLFree(pszFilename);
     960             : 
     961        6272 :     return osRet;
     962             : }
     963             : 
     964             : /************************************************************************/
     965             : /*                         CPLFormCIFilename()                          */
     966             : /************************************************************************/
     967             : 
     968             : /**
     969             :  * Case insensitive file searching, returning full path.
     970             :  *
     971             :  * This function tries to return the path to a file regardless of
     972             :  * whether the file exactly matches the basename, and extension case, or
     973             :  * is all upper case, or all lower case.  The path is treated as case
     974             :  * sensitive.  This function is equivalent to CPLFormFilename() on
     975             :  * case insensitive file systems (like Windows).
     976             :  *
     977             :  * @param pszPath directory path to the directory containing the file.  This
     978             :  * may be relative or absolute, and may have a trailing path separator or
     979             :  * not.  May be NULL.
     980             :  *
     981             :  * @param pszBasename file basename.  May optionally have path and/or
     982             :  * extension.  May not be NULL.
     983             :  *
     984             :  * @param pszExtension file extension, optionally including the period.  May
     985             :  * be NULL.
     986             :  *
     987             :  * @return a fully formed filename in an internal static string.  Do not
     988             :  * modify or free the returned string.  The string may be destroyed by the
     989             :  * next CPL call.
     990             :  *
     991             :  * @deprecated If using C++, prefer using CPLFormCIFilenameSafe() instead
     992             : */
     993             : 
     994           0 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
     995             :                               const char *pszExtension)
     996             : 
     997             : {
     998           0 :     return CPLPathReturnTLSString(
     999           0 :         CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
    1000           0 :         __FUNCTION__);
    1001             : }
    1002             : 
    1003             : /************************************************************************/
    1004             : /*                   CPLProjectRelativeFilenameSafe()                   */
    1005             : /************************************************************************/
    1006             : 
    1007             : /**
    1008             :  * Find a file relative to a project file.
    1009             :  *
    1010             :  * Given the path to a "project" directory, and a path to a secondary file
    1011             :  * referenced from that project, build a path to the secondary file
    1012             :  * that the current application can use.  If the secondary path is already
    1013             :  * absolute, rather than relative, then it will be returned unaltered.
    1014             :  *
    1015             :  * Examples:
    1016             :  * \code{.cpp}
    1017             :  * CPLProjectRelativeFilenameSafe("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
    1018             :  * CPLProjectRelativeFilenameSafe("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
    1019             :  * CPLProjectRelativeFilenameSafe("/xy", "abc.gif") == "/xy/abc.gif"
    1020             :  * CPLProjectRelativeFilenameSafe("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
    1021             :  * CPLProjectRelativeFilenameSafe("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
    1022             :  * \endcode
    1023             :  *
    1024             :  * @param pszProjectDir the directory relative to which the secondary files
    1025             :  * path should be interpreted.
    1026             :  * @param pszSecondaryFilename the filename (potentially with path) that
    1027             :  * is to be interpreted relative to the project directory.
    1028             :  *
    1029             :  * @return a composed path to the secondary file.
    1030             :  *
    1031             :  * @since 3.11
    1032             :  */
    1033             : 
    1034        4516 : std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
    1035             :                                            const char *pszSecondaryFilename)
    1036             : 
    1037             : {
    1038        8781 :     if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
    1039        4265 :         !CPLIsFilenameRelative(pszSecondaryFilename))
    1040             :     {
    1041         800 :         return pszSecondaryFilename;
    1042             :     }
    1043             : 
    1044        7432 :     std::string osRes(pszProjectDir);
    1045        3716 :     if (osRes.back() != '/' && osRes.back() != '\\')
    1046             :     {
    1047        3716 :         osRes += VSIGetDirectorySeparator(pszProjectDir);
    1048             :     }
    1049             : 
    1050        3716 :     osRes += pszSecondaryFilename;
    1051        3716 :     return osRes;
    1052             : }
    1053             : 
    1054             : /************************************************************************/
    1055             : /*                     CPLProjectRelativeFilename()                     */
    1056             : /************************************************************************/
    1057             : 
    1058             : /**
    1059             :  * Find a file relative to a project file.
    1060             :  *
    1061             :  * Given the path to a "project" directory, and a path to a secondary file
    1062             :  * referenced from that project, build a path to the secondary file
    1063             :  * that the current application can use.  If the secondary path is already
    1064             :  * absolute, rather than relative, then it will be returned unaltered.
    1065             :  *
    1066             :  * Examples:
    1067             :  * \code{.cpp}
    1068             :  * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
    1069             :  * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
    1070             :  * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
    1071             :  * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
    1072             :  * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
    1073             :  * \endcode
    1074             :  *
    1075             :  * @param pszProjectDir the directory relative to which the secondary files
    1076             :  * path should be interpreted.
    1077             :  * @param pszSecondaryFilename the filename (potentially with path) that
    1078             :  * is to be interpreted relative to the project directory.
    1079             :  *
    1080             :  * @return a composed path to the secondary file.  The returned string is
    1081             :  * internal and should not be altered, freed, or depending on past the next
    1082             :  * CPL call.
    1083             :  *
    1084             :  * @deprecated If using C++, prefer using CPLProjectRelativeFilenameSafe() instead
    1085             :  */
    1086             : 
    1087           0 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
    1088             :                                        const char *pszSecondaryFilename)
    1089             : 
    1090             : {
    1091           0 :     return CPLPathReturnTLSString(
    1092           0 :         CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
    1093           0 :         __FUNCTION__);
    1094             : }
    1095             : 
    1096             : /************************************************************************/
    1097             : /*                       CPLIsFilenameRelative()                        */
    1098             : /************************************************************************/
    1099             : 
    1100             : /**
    1101             :  * Is filename relative or absolute?
    1102             :  *
    1103             :  * The test is filesystem convention agnostic.  That is it will test for
    1104             :  * Unix style and windows style path conventions regardless of the actual
    1105             :  * system in use.
    1106             :  *
    1107             :  * @param pszFilename the filename with path to test.
    1108             :  *
    1109             :  * @return TRUE if the filename is relative or FALSE if it is absolute.
    1110             :  */
    1111             : 
    1112      472920 : int CPLIsFilenameRelative(const char *pszFilename)
    1113             : 
    1114             : {
    1115      472920 :     if ((pszFilename[0] != '\0' &&
    1116      461100 :          (STARTS_WITH(pszFilename + 1, ":\\") ||
    1117      461092 :           STARTS_WITH(pszFilename + 1, ":/") ||
    1118      461089 :           strstr(pszFilename + 1, "://")  // http://, ftp:// etc....
    1119      471988 :           )) ||
    1120      471988 :         STARTS_WITH(pszFilename, "\\\\?\\")  // Windows extended Length Path.
    1121      471978 :         || pszFilename[0] == '\\' || pszFilename[0] == '/')
    1122      356346 :         return FALSE;
    1123             : 
    1124      116574 :     return TRUE;
    1125             : }
    1126             : 
    1127             : /************************************************************************/
    1128             : /*                       CPLExtractRelativePath()                       */
    1129             : /************************************************************************/
    1130             : 
    1131             : /**
    1132             :  * Get relative path from directory to target file.
    1133             :  *
    1134             :  * Computes a relative path for pszTarget relative to pszBaseDir.
    1135             :  * Currently this only works if they share a common base path.  The returned
    1136             :  * path is normally into the pszTarget string.  It should only be considered
    1137             :  * valid as long as pszTarget is valid or till the next call to
    1138             :  * this function, whichever comes first.
    1139             :  *
    1140             :  * @param pszBaseDir the name of the directory relative to which the path
    1141             :  * should be computed.  pszBaseDir may be NULL in which case the original
    1142             :  * target is returned without relativizing.
    1143             :  *
    1144             :  * @param pszTarget the filename to be changed to be relative to pszBaseDir.
    1145             :  *
    1146             :  * @param pbGotRelative Pointer to location in which a flag is placed
    1147             :  * indicating that the returned path is relative to the basename (TRUE) or
    1148             :  * not (FALSE).  This pointer may be NULL if flag is not desired.
    1149             :  *
    1150             :  * @return an adjusted path or the original if it could not be made relative
    1151             :  * to the pszBaseFile's path.
    1152             :  **/
    1153             : 
    1154        2811 : const char *CPLExtractRelativePath(const char *pszBaseDir,
    1155             :                                    const char *pszTarget, int *pbGotRelative)
    1156             : 
    1157             : {
    1158             :     /* -------------------------------------------------------------------- */
    1159             :     /*      If we don't have a basedir, then we can't relativize the path.  */
    1160             :     /* -------------------------------------------------------------------- */
    1161        2811 :     if (pszBaseDir == nullptr)
    1162             :     {
    1163           0 :         if (pbGotRelative != nullptr)
    1164           0 :             *pbGotRelative = FALSE;
    1165             : 
    1166           0 :         return pszTarget;
    1167             :     }
    1168             : 
    1169        2811 :     const size_t nBasePathLen = strlen(pszBaseDir);
    1170             : 
    1171             :     /* -------------------------------------------------------------------- */
    1172             :     /*      One simple case is when the base dir is '.' and the target      */
    1173             :     /*      filename is relative.                                           */
    1174             :     /* -------------------------------------------------------------------- */
    1175        2840 :     if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
    1176          29 :         CPLIsFilenameRelative(pszTarget))
    1177             :     {
    1178          29 :         if (pbGotRelative != nullptr)
    1179          29 :             *pbGotRelative = TRUE;
    1180             : 
    1181          29 :         return pszTarget;
    1182             :     }
    1183             : 
    1184             :     /* -------------------------------------------------------------------- */
    1185             :     /*      By this point, if we don't have a base path, we can't have a    */
    1186             :     /*      meaningful common prefix.                                       */
    1187             :     /* -------------------------------------------------------------------- */
    1188        2782 :     if (nBasePathLen == 0)
    1189             :     {
    1190           0 :         if (pbGotRelative != nullptr)
    1191           0 :             *pbGotRelative = FALSE;
    1192             : 
    1193           0 :         return pszTarget;
    1194             :     }
    1195             : 
    1196             :     /* -------------------------------------------------------------------- */
    1197             :     /*      If we don't have a common path prefix, then we can't get a      */
    1198             :     /*      relative path.                                                  */
    1199             :     /* -------------------------------------------------------------------- */
    1200        2782 :     if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
    1201        2324 :         (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
    1202             :     {
    1203         459 :         if (pbGotRelative != nullptr)
    1204         459 :             *pbGotRelative = FALSE;
    1205             : 
    1206         459 :         return pszTarget;
    1207             :     }
    1208             : 
    1209             :     /* -------------------------------------------------------------------- */
    1210             :     /*      We have a relative path.  Strip it off to get a string to       */
    1211             :     /*      return.                                                         */
    1212             :     /* -------------------------------------------------------------------- */
    1213        2323 :     if (pbGotRelative != nullptr)
    1214        2254 :         *pbGotRelative = TRUE;
    1215             : 
    1216        2323 :     return pszTarget + nBasePathLen + 1;
    1217             : }
    1218             : 
    1219             : /************************************************************************/
    1220             : /*                     CPLCleanTrailingSlashSafe()                      */
    1221             : /************************************************************************/
    1222             : 
    1223             : /**
    1224             :  * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
    1225             :  *
    1226             :  * Returns a string containing the portion of the passed path string with
    1227             :  * trailing slash removed. If there is no path in the passed filename
    1228             :  * an empty string will be returned (not NULL).
    1229             :  *
    1230             :  * \code{.cpp}
    1231             :  * CPLCleanTrailingSlashSafe( "abc/def/" ) == "abc/def"
    1232             :  * CPLCleanTrailingSlashSafe( "abc/def" ) == "abc/def"
    1233             :  * CPLCleanTrailingSlashSafe( "c:\\abc\\def\\" ) == "c:\\abc\\def"
    1234             :  * CPLCleanTrailingSlashSafe( "c:\\abc\\def" ) == "c:\\abc\\def"
    1235             :  * CPLCleanTrailingSlashSafe( "abc" ) == "abc"
    1236             :  * \endcode
    1237             :  *
    1238             :  * @param pszPath the path to be cleaned up
    1239             :  *
    1240             :  * @return Path
    1241             :  *
    1242             :  * @since 3.11
    1243             :  */
    1244             : 
    1245           9 : std::string CPLCleanTrailingSlashSafe(const char *pszPath)
    1246             : 
    1247             : {
    1248           9 :     std::string osRes(pszPath);
    1249           9 :     if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
    1250           0 :         osRes.pop_back();
    1251           9 :     return osRes;
    1252             : }
    1253             : 
    1254             : /************************************************************************/
    1255             : /*                       CPLCleanTrailingSlash()                        */
    1256             : /************************************************************************/
    1257             : 
    1258             : /**
    1259             :  * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
    1260             :  *
    1261             :  * Returns a string containing the portion of the passed path string with
    1262             :  * trailing slash removed. If there is no path in the passed filename
    1263             :  * an empty string will be returned (not NULL).
    1264             :  *
    1265             :  * \code{.cpp}
    1266             :  * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
    1267             :  * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
    1268             :  * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
    1269             :  * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
    1270             :  * CPLCleanTrailingSlash( "abc" ) == "abc"
    1271             :  * \endcode
    1272             :  *
    1273             :  * @param pszPath the path to be cleaned up
    1274             :  *
    1275             :  * @return Path in an internal string which must not be freed.  The string
    1276             :  * may be destroyed by the next CPL filename handling call.
    1277             :  *
    1278             :  * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
    1279             :  */
    1280             : 
    1281           0 : const char *CPLCleanTrailingSlash(const char *pszPath)
    1282             : 
    1283             : {
    1284           0 :     return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
    1285           0 :                                   __FUNCTION__);
    1286             : }
    1287             : 
    1288             : /************************************************************************/
    1289             : /*                       CPLCorrespondingPaths()                        */
    1290             : /************************************************************************/
    1291             : 
    1292             : /**
    1293             :  * Identify corresponding paths.
    1294             :  *
    1295             :  * Given a prototype old and new filename this function will attempt
    1296             :  * to determine corresponding names for a set of other old filenames that
    1297             :  * will rename them in a similar manner.  This correspondence assumes there
    1298             :  * are two possibly kinds of renaming going on.  A change of path, and a
    1299             :  * change of filename stem.
    1300             :  *
    1301             :  * If a consistent renaming cannot be established for all the files this
    1302             :  * function will return indicating an error.
    1303             :  *
    1304             :  * The returned file list becomes owned by the caller and should be destroyed
    1305             :  * with CSLDestroy().
    1306             :  *
    1307             :  * @param pszOldFilename path to old prototype file.
    1308             :  * @param pszNewFilename path to new prototype file.
    1309             :  * @param papszFileList list of other files associated with pszOldFilename to
    1310             :  * rename similarly.
    1311             :  *
    1312             :  * @return a list of files corresponding to papszFileList but renamed to
    1313             :  * correspond to pszNewFilename.
    1314             :  */
    1315             : 
    1316         183 : char **CPLCorrespondingPaths(const char *pszOldFilename,
    1317             :                              const char *pszNewFilename, char **papszFileList)
    1318             : 
    1319             : {
    1320         183 :     if (CSLCount(papszFileList) == 0)
    1321           0 :         return nullptr;
    1322             : 
    1323             :     /* -------------------------------------------------------------------- */
    1324             :     /*      There is a special case for a one item list which exactly       */
    1325             :     /*      matches the old name, to rename to the new name.                */
    1326             :     /* -------------------------------------------------------------------- */
    1327         355 :     if (CSLCount(papszFileList) == 1 &&
    1328         172 :         strcmp(pszOldFilename, papszFileList[0]) == 0)
    1329             :     {
    1330         172 :         return CSLAddString(nullptr, pszNewFilename);
    1331             :     }
    1332             : 
    1333          22 :     const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
    1334          22 :     const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
    1335          22 :     const std::string osNewBasename = CPLGetBasenameSafe(pszNewFilename);
    1336             : 
    1337             :     /* -------------------------------------------------------------------- */
    1338             :     /*      If the basename is changing, verify that all source files       */
    1339             :     /*      have the same starting basename.                                */
    1340             :     /* -------------------------------------------------------------------- */
    1341          11 :     if (osOldBasename != osNewBasename)
    1342             :     {
    1343          34 :         for (int i = 0; papszFileList[i] != nullptr; i++)
    1344             :         {
    1345          24 :             if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
    1346          16 :                 continue;
    1347             : 
    1348           8 :             const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
    1349           8 :             const std::string osFileName = CPLGetFilename(papszFileList[i]);
    1350             : 
    1351           8 :             if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
    1352           8 :                         osOldBasename.size()) ||
    1353          16 :                 !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
    1354           8 :                 osFileName[osOldBasename.size()] != '.')
    1355             :             {
    1356           0 :                 CPLError(
    1357             :                     CE_Failure, CPLE_AppDefined,
    1358             :                     "Unable to copy/rename fileset due irregular basenames.");
    1359           0 :                 return nullptr;
    1360             :             }
    1361             :         }
    1362             :     }
    1363             : 
    1364             :     /* -------------------------------------------------------------------- */
    1365             :     /*      If the filename portions differs, ensure they only differ in    */
    1366             :     /*      basename.                                                       */
    1367             :     /* -------------------------------------------------------------------- */
    1368          11 :     if (osOldBasename != osNewBasename)
    1369             :     {
    1370             :         const std::string osOldExtra =
    1371          10 :             CPLGetFilename(pszOldFilename) + osOldBasename.size();
    1372             :         const std::string osNewExtra =
    1373          10 :             CPLGetFilename(pszNewFilename) + osNewBasename.size();
    1374             : 
    1375          10 :         if (osOldExtra != osNewExtra)
    1376             :         {
    1377           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1378             :                      "Unable to copy/rename fileset due to irregular filename "
    1379             :                      "correspondence.");
    1380           0 :             return nullptr;
    1381             :         }
    1382             :     }
    1383             : 
    1384             :     /* -------------------------------------------------------------------- */
    1385             :     /*      Generate the new filenames.                                     */
    1386             :     /* -------------------------------------------------------------------- */
    1387          11 :     char **papszNewList = nullptr;
    1388          11 :     const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
    1389             : 
    1390          37 :     for (int i = 0; papszFileList[i] != nullptr; i++)
    1391             :     {
    1392          52 :         const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
    1393             : 
    1394             :         const std::string osNewFilename =
    1395          26 :             osOldBasename == osNewBasename
    1396             :                 ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
    1397             :                                       nullptr)
    1398             :                 : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
    1399          24 :                                       osOldFilename.c_str() +
    1400          50 :                                           osOldBasename.size());
    1401             : 
    1402          26 :         papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
    1403             :     }
    1404             : 
    1405          11 :     return papszNewList;
    1406             : }
    1407             : 
    1408             : /************************************************************************/
    1409             : /*                    CPLGenerateTempFilenameSafe()                     */
    1410             : /************************************************************************/
    1411             : 
    1412             : /**
    1413             :  * Generate temporary file name.
    1414             :  *
    1415             :  * Returns a filename that may be used for a temporary file.  The location
    1416             :  * of the file tries to follow operating system semantics but may be
    1417             :  * forced via the CPL_TMPDIR configuration option.
    1418             :  *
    1419             :  * @param pszStem if non-NULL this will be part of the filename.
    1420             :  *
    1421             :  * @return a filename
    1422             :  *
    1423             :  * @since 3.11
    1424             :  */
    1425             : 
    1426        2830 : std::string CPLGenerateTempFilenameSafe(const char *pszStem)
    1427             : 
    1428             : {
    1429        2830 :     const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
    1430             : 
    1431        2830 :     if (pszDir == nullptr)
    1432        2814 :         pszDir = CPLGetConfigOption("TMPDIR", nullptr);
    1433             : 
    1434        2830 :     if (pszDir == nullptr)
    1435        2814 :         pszDir = CPLGetConfigOption("TEMP", nullptr);
    1436             : 
    1437        2830 :     if (pszDir == nullptr)
    1438        2814 :         pszDir = ".";
    1439             : 
    1440        2830 :     if (pszStem == nullptr)
    1441        2595 :         pszStem = "";
    1442             : 
    1443             :     static int nTempFileCounter = 0;
    1444        5660 :     CPLString osFilename;
    1445             :     osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
    1446        2830 :                       CPLAtomicInc(&nTempFileCounter));
    1447             : 
    1448        5660 :     return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
    1449             : }
    1450             : 
    1451             : /************************************************************************/
    1452             : /*                      CPLGenerateTempFilename()                       */
    1453             : /************************************************************************/
    1454             : 
    1455             : /**
    1456             :  * Generate temporary file name.
    1457             :  *
    1458             :  * Returns a filename that may be used for a temporary file.  The location
    1459             :  * of the file tries to follow operating system semantics but may be
    1460             :  * forced via the CPL_TMPDIR configuration option.
    1461             :  *
    1462             :  * @param pszStem if non-NULL this will be part of the filename.
    1463             :  *
    1464             :  * @return a filename which is valid till the next CPL call in this thread.
    1465             :  *
    1466             :  * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
    1467             :  */
    1468             : 
    1469           7 : const char *CPLGenerateTempFilename(const char *pszStem)
    1470             : 
    1471             : {
    1472          14 :     return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
    1473          14 :                                   __FUNCTION__);
    1474             : }
    1475             : 
    1476             : /************************************************************************/
    1477             : /*                         CPLExpandTildeSafe()                         */
    1478             : /************************************************************************/
    1479             : 
    1480             : /**
    1481             :  * Expands ~/ at start of filename.
    1482             :  *
    1483             :  * Assumes that the HOME configuration option is defined.
    1484             :  *
    1485             :  * @param pszFilename filename potentially starting with ~/
    1486             :  *
    1487             :  * @return an expanded filename.
    1488             :  *
    1489             :  * @since GDAL 3.11
    1490             :  */
    1491             : 
    1492         195 : std::string CPLExpandTildeSafe(const char *pszFilename)
    1493             : 
    1494             : {
    1495         195 :     if (!STARTS_WITH_CI(pszFilename, "~/"))
    1496         194 :         return pszFilename;
    1497             : 
    1498           1 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
    1499           1 :     if (pszHome == nullptr)
    1500           0 :         return pszFilename;
    1501             : 
    1502           1 :     return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
    1503             : }
    1504             : 
    1505             : /************************************************************************/
    1506             : /*                           CPLExpandTilde()                           */
    1507             : /************************************************************************/
    1508             : 
    1509             : /**
    1510             :  * Expands ~/ at start of filename.
    1511             :  *
    1512             :  * Assumes that the HOME configuration option is defined.
    1513             :  *
    1514             :  * @param pszFilename filename potentially starting with ~/
    1515             :  *
    1516             :  * @return an expanded filename.
    1517             :  *
    1518             :  *
    1519             :  * @deprecated If using C++, prefer using CPLExpandTildeSafe() instead
    1520             :  */
    1521             : 
    1522           2 : const char *CPLExpandTilde(const char *pszFilename)
    1523             : 
    1524             : {
    1525           4 :     return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
    1526           4 :                                   __FUNCTION__);
    1527             : }
    1528             : 
    1529             : /************************************************************************/
    1530             : /*                           CPLGetHomeDir()                            */
    1531             : /************************************************************************/
    1532             : 
    1533             : /**
    1534             :  * Return the path to the home directory
    1535             :  *
    1536             :  * That is the value of the USERPROFILE environment variable on Windows,
    1537             :  * or HOME on other platforms.
    1538             :  *
    1539             :  * @return the home directory, or NULL.
    1540             :  *
    1541             :  */
    1542             : 
    1543           0 : const char *CPLGetHomeDir()
    1544             : 
    1545             : {
    1546             : #ifdef _WIN32
    1547             :     return CPLGetConfigOption("USERPROFILE", nullptr);
    1548             : #else
    1549           0 :     return CPLGetConfigOption("HOME", nullptr);
    1550             : #endif
    1551             : }
    1552             : 
    1553             : /************************************************************************/
    1554             : /*                     CPLLaunderForFilenameSafe()                      */
    1555             : /************************************************************************/
    1556             : 
    1557             : /**
    1558             :  * Launder a string to be compatible of a filename.
    1559             :  *
    1560             :  * @param pszName The input string to launder.
    1561             :  * @param pszOutputPath The directory where the file would be created.
    1562             :  *                      Unused for now. May be NULL.
    1563             :  * @return the laundered name.
    1564             :  *
    1565             :  * @since GDAL 3.11
    1566             :  */
    1567             : 
    1568        1463 : std::string CPLLaunderForFilenameSafe(const char *pszName,
    1569             :                                       CPL_UNUSED const char *pszOutputPath)
    1570             : {
    1571        1463 :     std::string osRet(pszName);
    1572       13684 :     for (char &ch : osRet)
    1573             :     {
    1574             :         // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
    1575       12221 :         if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
    1576       12207 :             ch == '\\' || ch == '?' || ch == '*')
    1577             :         {
    1578          17 :             ch = '_';
    1579             :         }
    1580             :     }
    1581        1463 :     return osRet;
    1582             : }
    1583             : 
    1584             : /************************************************************************/
    1585             : /*                       CPLLaunderForFilename()                        */
    1586             : /************************************************************************/
    1587             : 
    1588             : /**
    1589             :  * Launder a string to be compatible of a filename.
    1590             :  *
    1591             :  * @param pszName The input string to launder.
    1592             :  * @param pszOutputPath The directory where the file would be created.
    1593             :  *                      Unused for now. May be NULL.
    1594             :  * @return the laundered name.
    1595             :  *
    1596             :  * @since GDAL 3.1
    1597             :  *
    1598             :  * @deprecated If using C++, prefer using CPLLaunderForFilenameSafe() instead
    1599             :  */
    1600             : 
    1601           0 : const char *CPLLaunderForFilename(const char *pszName,
    1602             :                                   const char *pszOutputPath)
    1603             : {
    1604           0 :     return CPLPathReturnTLSString(
    1605           0 :         CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
    1606             : }
    1607             : 
    1608             : /************************************************************************/
    1609             : /*                        CPLHasPathTraversal()                         */
    1610             : /************************************************************************/
    1611             : 
    1612             : /**
    1613             :  * Return whether the filename contains a path traversal pattern.
    1614             :  *
    1615             :  * i.e. if it contains "../" or "..\\".
    1616             :  *
    1617             :  * The CPL_ENABLE_PATH_TRAVERSAL_DETECTION configuration option can be set
    1618             :  * to NO to disable this check, although this is not recommended when dealing
    1619             :  * with un-trusted input.
    1620             :  *
    1621             :  * @param pszFilename The input string to check.
    1622             :  * @return true if a path traversal pattern is detected.
    1623             :  *
    1624             :  * @since GDAL 3.12
    1625             :  */
    1626             : 
    1627        2041 : bool CPLHasPathTraversal(const char *pszFilename)
    1628             : {
    1629        2041 :     const char *pszDotDot = strstr(pszFilename, "..");
    1630        2041 :     if (pszDotDot &&
    1631           6 :         (pszDotDot == pszFilename ||
    1632           6 :          pszFilename[pszDotDot - pszFilename - 1] == '/' ||
    1633           2 :          pszFilename[pszDotDot - pszFilename - 1] == '\\') &&
    1634           6 :         (pszDotDot[2] == 0 || pszDotDot[2] == '/' || pszDotDot[2] == '\\'))
    1635             :     {
    1636           6 :         if (CPLTestBool(CPLGetConfigOption(
    1637             :                 "CPL_ENABLE_PATH_TRAVERSAL_DETECTION", "YES")))
    1638             :         {
    1639           4 :             CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
    1640           4 :             return true;
    1641             :         }
    1642             :         else
    1643             :         {
    1644           2 :             CPLDebug("CPL",
    1645             :                      "Path traversal detected for %s but ignored given that "
    1646             :                      "CPL_ENABLE_PATH_TRAVERSAL_DETECTION is disabled",
    1647             :                      pszFilename);
    1648             :         }
    1649             :     }
    1650        2037 :     return false;
    1651             : }
    1652             : 
    1653             : /************************************************************************/
    1654             : /*                   CPLHasUnbalancedPathTraversal()                    */
    1655             : /************************************************************************/
    1656             : 
    1657             : /**
    1658             :  * Return whether the filename contains a unbalanced path traversal pattern.
    1659             :  *
    1660             :  * i.e. if it contains more "../" or "..\\" than preceding nesting.
    1661             :  *
    1662             :  *
    1663             :  * @param pszFilename The input string to check.
    1664             :  * @return true if a path traversal pattern is detected.
    1665             :  *
    1666             :  * @since GDAL 3.12
    1667             :  */
    1668             : 
    1669         547 : bool CPLHasUnbalancedPathTraversal(const char *pszFilename)
    1670             : {
    1671         547 :     size_t nNestLevel = 0;
    1672         547 :     int i = 0;
    1673         547 :     if (pszFilename[0] == '.' &&
    1674           6 :         (pszFilename[1] == '/' || pszFilename[1] == '\\'))
    1675           2 :         i += 2;
    1676         545 :     else if (pszFilename[0] == '/' || pszFilename[0] == '\\')
    1677           2 :         ++i;
    1678       43155 :     for (; pszFilename[i]; ++i)
    1679             :     {
    1680       42618 :         if (pszFilename[i] == '/' || pszFilename[i] == '\\')
    1681             :         {
    1682        1730 :             if (pszFilename[i + 1] == '/' || pszFilename[i + 1] == '\\')
    1683             :             {
    1684           0 :                 continue;
    1685             :             }
    1686        1730 :             if (pszFilename[i + 1] != 0)
    1687        1714 :                 ++nNestLevel;
    1688             :         }
    1689       40888 :         else if (pszFilename[i] == '.' && pszFilename[i + 1] == '.' &&
    1690          24 :                  (pszFilename[i + 2] == '/' || pszFilename[i + 2] == '\\' ||
    1691           4 :                   pszFilename[i + 2] == 0))
    1692             :         {
    1693          24 :             if (nNestLevel == 0)
    1694             :             {
    1695           8 :                 CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
    1696           8 :                 return true;
    1697             :             }
    1698          16 :             if (pszFilename[i + 2] == 0)
    1699           2 :                 break;
    1700          14 :             i += 2;
    1701          14 :             --nNestLevel;
    1702             :         }
    1703             :     }
    1704             : 
    1705         539 :     return false;
    1706             : }

Generated by: LCOV version 1.14