LCOV - code coverage report
Current view: top level - port - cpl_path.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 465 512 90.8 %
Date: 2026-03-25 02:32:38 Functions: 33 39 84.6 %

          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             : #include <string_view>
      31             : 
      32             : #include "cpl_atomic_ops.h"
      33             : #include "cpl_config.h"
      34             : #include "cpl_error.h"
      35             : #include "cpl_multiproc.h"
      36             : #include "cpl_string.h"
      37             : #include "cpl_vsi.h"
      38             : 
      39             : // Should be size of larged possible filename.
      40             : constexpr int CPL_PATH_BUF_SIZE = 2048;
      41             : constexpr int CPL_PATH_BUF_COUNT = 10;
      42             : 
      43           0 : static const char *CPLStaticBufferTooSmall(char *pszStaticResult)
      44             : {
      45           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Destination buffer too small");
      46           0 :     if (pszStaticResult == nullptr)
      47           0 :         return "";
      48           0 :     strcpy(pszStaticResult, "");
      49           0 :     return pszStaticResult;
      50             : }
      51             : 
      52             : /************************************************************************/
      53             : /*                         CPLGetStaticResult()                         */
      54             : /************************************************************************/
      55             : 
      56         577 : static char *CPLGetStaticResult()
      57             : 
      58             : {
      59         577 :     int bMemoryError = FALSE;
      60             :     char *pachBufRingInfo =
      61         577 :         static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
      62         577 :     if (bMemoryError)
      63           0 :         return nullptr;
      64         577 :     if (pachBufRingInfo == nullptr)
      65             :     {
      66          14 :         pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
      67             :             1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
      68          14 :         if (pachBufRingInfo == nullptr)
      69           0 :             return nullptr;
      70          14 :         CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
      71             :     }
      72             : 
      73             :     /* -------------------------------------------------------------------- */
      74             :     /*      Work out which string in the "ring" we want to use this         */
      75             :     /*      time.                                                           */
      76             :     /* -------------------------------------------------------------------- */
      77         577 :     int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
      78         577 :     const size_t nOffset =
      79         577 :         sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
      80         577 :     char *pachBuffer = pachBufRingInfo + nOffset;
      81             : 
      82         577 :     *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
      83             : 
      84         577 :     return pachBuffer;
      85             : }
      86             : 
      87             : /************************************************************************/
      88             : /*                       CPLPathReturnTLSString()                       */
      89             : /************************************************************************/
      90             : 
      91         577 : static const char *CPLPathReturnTLSString(const std::string &osRes,
      92             :                                           const char *pszFuncName)
      93             : {
      94         577 :     if (osRes.size() >= CPL_PATH_BUF_SIZE)
      95             :     {
      96           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too long result for %s()",
      97             :                  pszFuncName);
      98           0 :         return "";
      99             :     }
     100             : 
     101         577 :     char *pszStaticResult = CPLGetStaticResult();
     102         577 :     if (pszStaticResult == nullptr)
     103           0 :         return CPLStaticBufferTooSmall(pszStaticResult);
     104         577 :     memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
     105         577 :     return pszStaticResult;
     106             : }
     107             : 
     108             : /************************************************************************/
     109             : /*                        CPLFindFilenameStart()                        */
     110             : /************************************************************************/
     111             : 
     112     2449980 : static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
     113             : 
     114             : {
     115     2449980 :     size_t iFileStart = nStart ? nStart : strlen(pszFilename);
     116             : 
     117    35773500 :     for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
     118    33323700 :            pszFilename[iFileStart - 1] != '\\';
     119             :          iFileStart--)
     120             :     {
     121             :     }
     122             : 
     123     2449980 :     return static_cast<int>(iFileStart);
     124             : }
     125             : 
     126             : /************************************************************************/
     127             : /*                           CPLGetPathSafe()                           */
     128             : /************************************************************************/
     129             : 
     130             : /**
     131             :  * Extract directory path portion of filename.
     132             :  *
     133             :  * Returns a string containing the directory path portion of the passed
     134             :  * filename.  If there is no path in the passed filename an empty string
     135             :  * will be returned (not NULL).
     136             :  *
     137             :  * \code{.cpp}
     138             :  * CPLGetPathSafe( "abc/def.xyz" ) == "abc"
     139             :  * CPLGetPathSafe( "/abc/def/" ) == "/abc/def"
     140             :  * CPLGetPathSafe( "/" ) == "/"
     141             :  * CPLGetPathSafe( "/abc/def" ) == "/abc"
     142             :  * CPLGetPathSafe( "abc" ) == ""
     143             :  * \endcode
     144             :  *
     145             :  * @param pszFilename the filename potentially including a path.
     146             :  *
     147             :  * @return Path.
     148             :  *
     149             :  * @since 3.11
     150             :  */
     151             : 
     152      250086 : std::string CPLGetPathSafe(const char *pszFilename)
     153             : 
     154             : {
     155      250086 :     size_t nSuffixPos = 0;
     156      250086 :     if (STARTS_WITH(pszFilename, "/vsicurl/http"))
     157             :     {
     158           9 :         const char *pszQuestionMark = strchr(pszFilename, '?');
     159           9 :         if (pszQuestionMark)
     160           1 :             nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
     161             :     }
     162      250077 :     else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
     163           1 :              strstr(pszFilename, "url="))
     164             :     {
     165           2 :         std::string osRet;
     166             :         const CPLStringList aosTokens(
     167           2 :             CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
     168           3 :         for (int i = 0; i < aosTokens.size(); i++)
     169             :         {
     170           2 :             if (osRet.empty())
     171           1 :                 osRet = "/vsicurl?";
     172             :             else
     173           1 :                 osRet += '&';
     174           3 :             if (STARTS_WITH(aosTokens[i], "url=") &&
     175           1 :                 !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
     176             :             {
     177             :                 char *pszUnescaped =
     178           1 :                     CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     179           1 :                 char *pszPath = CPLEscapeString(
     180           2 :                     CPLGetPathSafe(pszUnescaped + strlen("url=")).c_str(), -1,
     181             :                     CPLES_URL);
     182           1 :                 osRet += "url=";
     183           1 :                 osRet += pszPath;
     184           1 :                 CPLFree(pszPath);
     185           1 :                 CPLFree(pszUnescaped);
     186             :             }
     187             :             else
     188             :             {
     189           1 :                 osRet += aosTokens[i];
     190             :             }
     191             :         }
     192           1 :         return osRet;
     193             :     }
     194             : 
     195      250085 :     const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
     196      250085 :     if (iFileStart == 0)
     197             :     {
     198        3644 :         return std::string();
     199             :     }
     200             : 
     201      492882 :     std::string osRet(pszFilename, iFileStart);
     202             : 
     203      246441 :     if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
     204      245591 :         osRet.pop_back();
     205             : 
     206      246440 :     if (nSuffixPos)
     207             :     {
     208           1 :         osRet += (pszFilename + nSuffixPos);
     209             :     }
     210             : 
     211      246440 :     return osRet;
     212             : }
     213             : 
     214             : /************************************************************************/
     215             : /*                             CPLGetPath()                             */
     216             : /************************************************************************/
     217             : 
     218             : /**
     219             :  * Extract directory path portion of filename.
     220             :  *
     221             :  * Returns a string containing the directory path portion of the passed
     222             :  * filename.  If there is no path in the passed filename an empty string
     223             :  * will be returned (not NULL).
     224             :  *
     225             :  * \code{.cpp}
     226             :  * CPLGetPath( "abc/def.xyz" ) == "abc"
     227             :  * CPLGetPath( "/abc/def/" ) == "/abc/def"
     228             :  * CPLGetPath( "/" ) == "/"
     229             :  * CPLGetPath( "/abc/def" ) == "/abc"
     230             :  * CPLGetPath( "abc" ) == ""
     231             :  * \endcode
     232             :  *
     233             :  * @param pszFilename the filename potentially including a path.
     234             :  *
     235             :  * @return Path in an internal string which must not be freed.  The string
     236             :  * may be destroyed by the next CPL filename handling call.  The returned
     237             :  * will generally not contain a trailing path separator.
     238             :  *
     239             :  * @deprecated If using C++, prefer using CPLGetPathSafe() instead
     240             :  */
     241             : 
     242          50 : const char *CPLGetPath(const char *pszFilename)
     243             : 
     244             : {
     245          50 :     return CPLPathReturnTLSString(CPLGetPathSafe(pszFilename), __FUNCTION__);
     246             : }
     247             : 
     248             : /************************************************************************/
     249             : /*                           CPLGetDirname()                            */
     250             : /************************************************************************/
     251             : 
     252             : /**
     253             :  * Extract directory path portion of filename.
     254             :  *
     255             :  * Returns a string containing the directory path portion of the passed
     256             :  * filename.  If there is no path in the passed filename the dot will be
     257             :  * returned.  It is the only difference from CPLGetPath().
     258             :  *
     259             :  * \code{.cpp}
     260             :  * CPLGetDirnameSafe( "abc/def.xyz" ) == "abc"
     261             :  * CPLGetDirnameSafe( "/abc/def/" ) == "/abc/def"
     262             :  * CPLGetDirnameSafe( "/" ) == "/"
     263             :  * CPLGetDirnameSafe( "/abc/def" ) == "/abc"
     264             :  * CPLGetDirnameSafe( "abc" ) == "."
     265             :  * \endcode
     266             :  *
     267             :  * @param pszFilename the filename potentially including a path.
     268             :  *
     269             :  * @return Path
     270             :  *
     271             :  * @since 3.11
     272             :  */
     273             : 
     274      183742 : std::string CPLGetDirnameSafe(const char *pszFilename)
     275             : 
     276             : {
     277      183742 :     size_t nSuffixPos = 0;
     278      183742 :     if (STARTS_WITH(pszFilename, "/vsicurl/http"))
     279             :     {
     280         144 :         const char *pszQuestionMark = strchr(pszFilename, '?');
     281         144 :         if (pszQuestionMark)
     282           1 :             nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
     283             :     }
     284      183598 :     else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
     285          16 :              strstr(pszFilename, "url="))
     286             :     {
     287          30 :         std::string osRet;
     288             :         const CPLStringList aosTokens(
     289          30 :             CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
     290          44 :         for (int i = 0; i < aosTokens.size(); i++)
     291             :         {
     292          29 :             if (osRet.empty())
     293          15 :                 osRet = "/vsicurl?";
     294             :             else
     295          14 :                 osRet += '&';
     296          44 :             if (STARTS_WITH(aosTokens[i], "url=") &&
     297          15 :                 !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
     298             :             {
     299             :                 char *pszUnescaped =
     300          15 :                     CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
     301          15 :                 char *pszPath = CPLEscapeString(
     302          15 :                     CPLGetDirname(pszUnescaped + strlen("url=")), -1,
     303             :                     CPLES_URL);
     304          15 :                 osRet += "url=";
     305          15 :                 osRet += pszPath;
     306          15 :                 CPLFree(pszPath);
     307          15 :                 CPLFree(pszUnescaped);
     308             :             }
     309             :             else
     310             :             {
     311          14 :                 osRet += aosTokens[i];
     312             :             }
     313             :         }
     314          15 :         return osRet;
     315             :     }
     316             : 
     317      183727 :     const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
     318      183717 :     if (iFileStart == 0)
     319             :     {
     320          81 :         return std::string(".");
     321             :     }
     322             : 
     323      367259 :     std::string osRet(pszFilename, iFileStart);
     324             : 
     325      183583 :     if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
     326      183543 :         osRet.pop_back();
     327             : 
     328      183645 :     if (nSuffixPos)
     329             :     {
     330           1 :         osRet += (pszFilename + nSuffixPos);
     331             :     }
     332             : 
     333      183645 :     return osRet;
     334             : }
     335             : 
     336             : /************************************************************************/
     337             : /*                           CPLGetDirname()                            */
     338             : /************************************************************************/
     339             : 
     340             : /**
     341             :  * Extract directory path portion of filename.
     342             :  *
     343             :  * Returns a string containing the directory path portion of the passed
     344             :  * filename.  If there is no path in the passed filename the dot will be
     345             :  * returned.  It is the only difference from CPLGetPath().
     346             :  *
     347             :  * \code{.cpp}
     348             :  * CPLGetDirname( "abc/def.xyz" ) == "abc"
     349             :  * CPLGetDirname( "/abc/def/" ) == "/abc/def"
     350             :  * CPLGetDirname( "/" ) == "/"
     351             :  * CPLGetDirname( "/abc/def" ) == "/abc"
     352             :  * CPLGetDirname( "abc" ) == "."
     353             :  * \endcode
     354             :  *
     355             :  * @param pszFilename the filename potentially including a path.
     356             :  *
     357             :  * @return Path in an internal string which must not be freed.  The string
     358             :  * may be destroyed by the next CPL filename handling call.  The returned
     359             :  * will generally not contain a trailing path separator.
     360             :  */
     361             : 
     362          20 : const char *CPLGetDirname(const char *pszFilename)
     363             : 
     364             : {
     365          20 :     return CPLPathReturnTLSString(CPLGetDirnameSafe(pszFilename), __FUNCTION__);
     366             : }
     367             : 
     368             : /************************************************************************/
     369             : /*                           CPLGetFilename()                           */
     370             : /************************************************************************/
     371             : 
     372             : /**
     373             :  * Extract non-directory portion of filename.
     374             :  *
     375             :  * Returns a string containing the bare filename portion of the passed
     376             :  * filename.  If there is no filename (passed value ends in trailing directory
     377             :  * separator) an empty string is returned.
     378             :  *
     379             :  * \code{.cpp}
     380             :  * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
     381             :  * CPLGetFilename( "/abc/def/" ) == ""
     382             :  * CPLGetFilename( "abc/def" ) == "def"
     383             :  * \endcode
     384             :  *
     385             :  * @param pszFullFilename the full filename potentially including a path.
     386             :  *
     387             :  * @return just the non-directory portion of the path (points back into
     388             :  * original string).
     389             :  */
     390             : 
     391      903833 : const char *CPLGetFilename(const char *pszFullFilename)
     392             : 
     393             : {
     394      903833 :     const int iFileStart = CPLFindFilenameStart(pszFullFilename);
     395             : 
     396      903834 :     return pszFullFilename + iFileStart;
     397             : }
     398             : 
     399             : /************************************************************************/
     400             : /*                         CPLGetBasenameSafe()                         */
     401             : /************************************************************************/
     402             : 
     403             : /**
     404             :  * Extract basename (non-directory, non-extension) portion of filename.
     405             :  *
     406             :  * Returns a string containing the file basename portion of the passed
     407             :  * name.  If there is no basename (passed value ends in trailing directory
     408             :  * separator, or filename starts with a dot) an empty string is returned.
     409             :  *
     410             :  * \code{.cpp}
     411             :  * CPLGetBasename( "abc/def.xyz" ) == "def"
     412             :  * CPLGetBasename( "abc/def" ) == "def"
     413             :  * CPLGetBasename( "abc/def/" ) == ""
     414             :  * \endcode
     415             :  *
     416             :  * @param pszFullFilename the full filename potentially including a path.
     417             :  *
     418             :  * @return just the non-directory, non-extension portion of the path
     419             :  *
     420             :  * @since 3.11
     421             :  */
     422             : 
     423      458490 : std::string CPLGetBasenameSafe(const char *pszFullFilename)
     424             : 
     425             : {
     426             :     const size_t iFileStart =
     427      458490 :         static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
     428             : 
     429      458490 :     size_t iExtStart = strlen(pszFullFilename);
     430     2575810 :     for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
     431             :          iExtStart--)
     432             :     {
     433             :     }
     434             : 
     435      458490 :     if (iExtStart == iFileStart)
     436       25973 :         iExtStart = strlen(pszFullFilename);
     437             : 
     438      458490 :     const size_t nLength = iExtStart - iFileStart;
     439      458490 :     return std::string(pszFullFilename + iFileStart, nLength);
     440             : }
     441             : 
     442             : /************************************************************************/
     443             : /*                           CPLGetBasename()                           */
     444             : /************************************************************************/
     445             : 
     446             : /**
     447             :  * Extract basename (non-directory, non-extension) portion of filename.
     448             :  *
     449             :  * Returns a string containing the file basename portion of the passed
     450             :  * name.  If there is no basename (passed value ends in trailing directory
     451             :  * separator, or filename starts with a dot) an empty string is returned.
     452             :  *
     453             :  * \code{.cpp}
     454             :  * CPLGetBasename( "abc/def.xyz" ) == "def"
     455             :  * CPLGetBasename( "abc/def" ) == "def"
     456             :  * CPLGetBasename( "abc/def/" ) == ""
     457             :  * \endcode
     458             :  *
     459             :  * @param pszFullFilename the full filename potentially including a path.
     460             :  *
     461             :  * @return just the non-directory, non-extension portion of the path in
     462             :  * an internal string which must not be freed.  The string
     463             :  * may be destroyed by the next CPL filename handling call.
     464             :  *
     465             :  * @deprecated If using C++, prefer using CPLGetBasenameSafe() instead
     466             :  */
     467             : 
     468         230 : const char *CPLGetBasename(const char *pszFullFilename)
     469             : 
     470             : {
     471         460 :     return CPLPathReturnTLSString(CPLGetBasenameSafe(pszFullFilename),
     472         460 :                                   __FUNCTION__);
     473             : }
     474             : 
     475             : /************************************************************************/
     476             : /*                        CPLGetExtensionSafe()                         */
     477             : /************************************************************************/
     478             : 
     479             : /**
     480             :  * Extract filename extension from full filename.
     481             :  *
     482             :  * Returns a string containing the extension portion of the passed
     483             :  * name.  If there is no extension (the filename has no dot) an empty string
     484             :  * is returned.  The returned extension will not include the period.
     485             :  *
     486             :  * \code{.cpp}
     487             :  * CPLGetExtensionSafe( "abc/def.xyz" ) == "xyz"
     488             :  * CPLGetExtensionSafe( "abc/def" ) == ""
     489             :  * \endcode
     490             :  *
     491             :  * @param pszFullFilename the full filename potentially including a path.
     492             :  *
     493             :  * @return just the extension portion of the path.
     494             :  *
     495             :  * @since 3.11
     496             :  */
     497             : 
     498      655567 : std::string CPLGetExtensionSafe(const char *pszFullFilename)
     499             : 
     500             : {
     501      655567 :     if (pszFullFilename[0] == '\0')
     502        1706 :         return std::string();
     503             : 
     504             :     size_t iFileStart =
     505      653861 :         static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
     506      653861 :     size_t iExtStart = strlen(pszFullFilename);
     507     4472730 :     for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
     508             :          iExtStart--)
     509             :     {
     510             :     }
     511             : 
     512      653861 :     if (iExtStart == iFileStart)
     513       99626 :         iExtStart = strlen(pszFullFilename) - 1;
     514             : 
     515             :     // If the extension is too long, it is very much likely not an extension,
     516             :     // but another component of the path
     517      653861 :     const size_t knMaxExtensionSize = 10;
     518      653861 :     if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
     519        2284 :         return "";
     520             : 
     521      651577 :     return std::string(pszFullFilename + iExtStart + 1);
     522             : }
     523             : 
     524             : /************************************************************************/
     525             : /*                          CPLGetExtension()                           */
     526             : /************************************************************************/
     527             : 
     528             : /**
     529             :  * Extract filename extension from full filename.
     530             :  *
     531             :  * Returns a string containing the extension portion of the passed
     532             :  * name.  If there is no extension (the filename has no dot) an empty string
     533             :  * is returned.  The returned extension will not include the period.
     534             :  *
     535             :  * \code{.cpp}
     536             :  * CPLGetExtension( "abc/def.xyz" ) == "xyz"
     537             :  * CPLGetExtension( "abc/def" ) == ""
     538             :  * \endcode
     539             :  *
     540             :  * @param pszFullFilename the full filename potentially including a path.
     541             :  *
     542             :  * @return just the extension portion of the path in
     543             :  * an internal string which must not be freed.  The string
     544             :  * may be destroyed by the next CPL filename handling call.
     545             :  *
     546             :  * @deprecated If using C++, prefer using CPLGetExtensionSafe() instead
     547             :  */
     548             : 
     549          46 : const char *CPLGetExtension(const char *pszFullFilename)
     550             : 
     551             : {
     552          92 :     return CPLPathReturnTLSString(CPLGetExtensionSafe(pszFullFilename),
     553          92 :                                   __FUNCTION__);
     554             : }
     555             : 
     556             : /************************************************************************/
     557             : /*                          CPLGetCurrentDir()                          */
     558             : /************************************************************************/
     559             : 
     560             : /**
     561             :  * Get the current working directory name.
     562             :  *
     563             :  * @return a pointer to buffer, containing current working directory path
     564             :  * or NULL in case of error.  User is responsible to free that buffer
     565             :  * after usage with CPLFree() function.
     566             :  * If HAVE_GETCWD macro is not defined, the function returns NULL.
     567             :  **/
     568             : 
     569             : #ifdef _WIN32
     570             : char *CPLGetCurrentDir()
     571             : {
     572             :     const size_t nPathMax = _MAX_PATH;
     573             :     wchar_t *pwszDirPath =
     574             :         static_cast<wchar_t *>(VSI_MALLOC_VERBOSE(nPathMax * sizeof(wchar_t)));
     575             :     char *pszRet = nullptr;
     576             :     if (pwszDirPath != nullptr && _wgetcwd(pwszDirPath, nPathMax) != nullptr)
     577             :     {
     578             :         pszRet = CPLRecodeFromWChar(pwszDirPath, CPL_ENC_UCS2, CPL_ENC_UTF8);
     579             :     }
     580             :     CPLFree(pwszDirPath);
     581             :     return pszRet;
     582             : }
     583             : #elif defined(HAVE_GETCWD)
     584        5317 : char *CPLGetCurrentDir()
     585             : {
     586             : #if PATH_MAX
     587        5317 :     const size_t nPathMax = PATH_MAX;
     588             : #else
     589             :     const size_t nPathMax = 8192;
     590             : #endif
     591             : 
     592        5317 :     char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
     593        5317 :     if (!pszDirPath)
     594           0 :         return nullptr;
     595             : 
     596        5317 :     return getcwd(pszDirPath, nPathMax);
     597             : }
     598             : #else   // !HAVE_GETCWD
     599             : char *CPLGetCurrentDir()
     600             : {
     601             :     return nullptr;
     602             : }
     603             : #endif  // HAVE_GETCWD
     604             : 
     605             : /************************************************************************/
     606             : /*                         CPLResetExtension()                          */
     607             : /************************************************************************/
     608             : 
     609             : /**
     610             :  * Replace the extension with the provided one.
     611             :  *
     612             :  * @param pszPath the input path, this string is not altered.
     613             :  * @param pszExt the new extension to apply to the given path.
     614             :  *
     615             :  * @return an altered filename with the new extension.
     616             :  *
     617             :  * @since 3.11
     618             :  */
     619             : 
     620      203926 : std::string CPLResetExtensionSafe(const char *pszPath, const char *pszExt)
     621             : 
     622             : {
     623      203926 :     std::string osRet(pszPath);
     624             : 
     625             :     /* -------------------------------------------------------------------- */
     626             :     /*      First, try and strip off any existing extension.                */
     627             :     /* -------------------------------------------------------------------- */
     628             : 
     629     1043000 :     for (size_t i = osRet.size(); i > 0;)
     630             :     {
     631     1042660 :         --i;
     632     1042660 :         if (osRet[i] == '.')
     633             :         {
     634      188199 :             osRet.resize(i);
     635      188199 :             break;
     636             :         }
     637      854460 :         else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
     638             :         {
     639       15382 :             break;
     640             :         }
     641             :     }
     642             : 
     643             :     /* -------------------------------------------------------------------- */
     644             :     /*      Append the new extension.                                       */
     645             :     /* -------------------------------------------------------------------- */
     646      203925 :     osRet += '.';
     647      203925 :     osRet += pszExt;
     648             : 
     649      203925 :     return osRet;
     650             : }
     651             : 
     652             : /************************************************************************/
     653             : /*                         CPLResetExtension()                          */
     654             : /************************************************************************/
     655             : 
     656             : /**
     657             :  * Replace the extension with the provided one.
     658             :  *
     659             :  * @param pszPath the input path, this string is not altered.
     660             :  * @param pszExt the new extension to apply to the given path.
     661             :  *
     662             :  * @return an altered filename with the new extension.    Do not
     663             :  * modify or free the returned string.  The string may be destroyed by the
     664             :  * next CPL call.
     665             :  *
     666             :  * @deprecated If using C++, prefer using CPLResetExtensionSafe() instead
     667             :  */
     668             : 
     669         135 : const char *CPLResetExtension(const char *pszPath, const char *pszExt)
     670             : 
     671             : {
     672         270 :     return CPLPathReturnTLSString(CPLResetExtensionSafe(pszPath, pszExt),
     673         270 :                                   __FUNCTION__);
     674             : }
     675             : 
     676             : /************************************************************************/
     677             : /*                        CPLFormFilenameSafe()                         */
     678             : /************************************************************************/
     679             : 
     680             : /**
     681             :  * Build a full file path from a passed path, file basename and extension.
     682             :  *
     683             :  * The path, and extension are optional.  The basename may in fact contain
     684             :  * an extension if desired.
     685             :  *
     686             :  * \code{.cpp}
     687             :  * CPLFormFilenameSafe("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
     688             :  * CPLFormFilenameSafe(NULL,"def", NULL ) == "def"
     689             :  * CPLFormFilenameSafe(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
     690             :  * CPLFormFilenameSafe("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
     691             :  * CPLFormFilenameSafe("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
     692             :  * \endcode
     693             :  *
     694             :  * @param pszPath directory path to the directory containing the file.  This
     695             :  * may be relative or absolute, and may have a trailing path separator or
     696             :  * not.  May be NULL.
     697             :  *
     698             :  * @param pszBasename file basename.  May optionally have path and/or
     699             :  * extension.  Must *NOT* be NULL.
     700             :  *
     701             :  * @param pszExtension file extension, optionally including the period.  May
     702             :  * be NULL.
     703             :  *
     704             :  * @return a fully formed filename.
     705             :  *
     706             :  * @since 3.11
     707             :  */
     708             : 
     709      462161 : std::string CPLFormFilenameSafe(const char *pszPath, const char *pszBasename,
     710             :                                 const char *pszExtension)
     711             : 
     712             : {
     713      462161 :     if (pszBasename[0] == '.' &&
     714       13592 :         (pszBasename[1] == '/' || pszBasename[1] == '\\'))
     715          61 :         pszBasename += 2;
     716             : 
     717      462161 :     const char *pszAddedPathSep = "";
     718      462161 :     const char *pszAddedExtSep = "";
     719             : 
     720      462161 :     if (pszPath == nullptr)
     721       11692 :         pszPath = "";
     722      462161 :     size_t nLenPath = strlen(pszPath);
     723             : 
     724      462161 :     const char *pszQuestionMark = nullptr;
     725      462161 :     if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
     726             :     {
     727         157 :         pszQuestionMark = strchr(pszPath, '?');
     728         157 :         if (pszQuestionMark)
     729             :         {
     730           1 :             nLenPath = pszQuestionMark - pszPath;
     731             :         }
     732         157 :         pszAddedPathSep = "/";
     733             :     }
     734             : 
     735      818324 :     if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
     736      818468 :         pszBasename[1] == '.' &&
     737         216 :         (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
     738         183 :          pszBasename[2] == '/'))
     739             :     {
     740             :         // "/a/b/" + "..[/something]" --> "/a[/something]"
     741             :         // "/a/b" + "..[/something]" --> "/a[/something]"
     742         216 :         if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
     743          14 :             nLenPath--;
     744             :         while (true)
     745             :         {
     746         305 :             const char *pszBasenameOri = pszBasename;
     747         305 :             const size_t nLenPathOri = nLenPath;
     748        1553 :             while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
     749        1542 :                    pszPath[nLenPath - 1] != '/')
     750             :             {
     751        1248 :                 nLenPath--;
     752             :             }
     753         305 :             if (nLenPath == 1 && pszPath[0] == '/')
     754             :             {
     755          18 :                 pszBasename += 2;
     756          18 :                 if (pszBasename[0] == '/' || pszBasename[0] == '\\')
     757          10 :                     pszBasename++;
     758          18 :                 if (*pszBasename == '.')
     759             :                 {
     760           1 :                     pszBasename = pszBasenameOri;
     761           1 :                     nLenPath = nLenPathOri;
     762           1 :                     if (pszAddedPathSep[0] == 0)
     763           1 :                         pszAddedPathSep = "/";
     764             :                 }
     765          18 :                 break;
     766             :             }
     767         287 :             else if ((nLenPath > 1 && pszPath[0] == '/') ||
     768          11 :                      (nLenPath > 2 && pszPath[1] == ':') ||
     769           1 :                      (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
     770             :             {
     771         280 :                 nLenPath--;
     772         280 :                 pszBasename += 2;
     773         280 :                 if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
     774         261 :                     pszBasename[1] == '.' && pszBasename[2] == '.')
     775             :                 {
     776          89 :                     pszBasename++;
     777             :                 }
     778             :                 else
     779             :                 {
     780             :                     break;
     781             :                 }
     782             :             }
     783             :             else
     784             :             {
     785             :                 // cppcheck-suppress redundantAssignment
     786           7 :                 pszBasename = pszBasenameOri;
     787           7 :                 nLenPath = nLenPathOri;
     788           7 :                 if (pszAddedPathSep[0] == 0)
     789           7 :                     pszAddedPathSep = pszPath[0] == '/'
     790           7 :                                           ? "/"
     791           2 :                                           : VSIGetDirectorySeparator(pszPath);
     792           7 :                 break;
     793             :             }
     794          89 :         }
     795             :     }
     796      461873 :     else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
     797      447306 :              pszPath[nLenPath - 1] != '\\')
     798             :     {
     799      447295 :         if (pszAddedPathSep[0] == 0)
     800      447136 :             pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
     801             :     }
     802             : 
     803      462103 :     if (pszExtension == nullptr)
     804      285608 :         pszExtension = "";
     805      176495 :     else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
     806      159796 :         pszAddedExtSep = ".";
     807             : 
     808      462103 :     std::string osRes;
     809      462130 :     osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
     810      462130 :                   strlen(pszAddedExtSep) + strlen(pszExtension) +
     811      462130 :                   (pszQuestionMark ? strlen(pszQuestionMark) : 0));
     812      462107 :     osRes.assign(pszPath, nLenPath);
     813      462096 :     osRes += pszAddedPathSep;
     814      462071 :     osRes += pszBasename;
     815      462036 :     osRes += pszAddedExtSep;
     816      462040 :     osRes += pszExtension;
     817             : 
     818      462039 :     if (pszQuestionMark)
     819             :     {
     820           1 :         osRes += pszQuestionMark;
     821             :     }
     822             : 
     823      462010 :     return osRes;
     824             : }
     825             : 
     826             : /************************************************************************/
     827             : /*                          CPLFormFilename()                           */
     828             : /************************************************************************/
     829             : 
     830             : /**
     831             :  * Build a full file path from a passed path, file basename and extension.
     832             :  *
     833             :  * The path, and extension are optional.  The basename may in fact contain
     834             :  * an extension if desired.
     835             :  *
     836             :  * \code{.cpp}
     837             :  * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
     838             :  * CPLFormFilename(NULL,"def", NULL ) == "def"
     839             :  * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
     840             :  * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
     841             :  * CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
     842             :  * \endcode
     843             :  *
     844             :  * @param pszPath directory path to the directory containing the file.  This
     845             :  * may be relative or absolute, and may have a trailing path separator or
     846             :  * not.  May be NULL.
     847             :  *
     848             :  * @param pszBasename file basename.  May optionally have path and/or
     849             :  * extension.  Must *NOT* be NULL.
     850             :  *
     851             :  * @param pszExtension file extension, optionally including the period.  May
     852             :  * be NULL.
     853             :  *
     854             :  * @return a fully formed filename in an internal static string.  Do not
     855             :  * modify or free the returned string.  The string may be destroyed by the
     856             :  * next CPL call.
     857             :  *
     858             :  * @deprecated If using C++, prefer using CPLFormFilenameSafe() instead
     859             :  */
     860          87 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
     861             :                             const char *pszExtension)
     862             : 
     863             : {
     864          87 :     return CPLPathReturnTLSString(
     865         174 :         CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
     866             : }
     867             : 
     868             : /************************************************************************/
     869             : /*                       CPLFormCIFilenameSafe()                        */
     870             : /************************************************************************/
     871             : 
     872             : /**
     873             :  * Case insensitive file searching, returning full path.
     874             :  *
     875             :  * This function tries to return the path to a file regardless of
     876             :  * whether the file exactly matches the basename, and extension case, or
     877             :  * is all upper case, or all lower case.  The path is treated as case
     878             :  * sensitive.  This function is equivalent to CPLFormFilename() on
     879             :  * case insensitive file systems (like Windows).
     880             :  *
     881             :  * @param pszPath directory path to the directory containing the file.  This
     882             :  * may be relative or absolute, and may have a trailing path separator or
     883             :  * not.  May be NULL.
     884             :  *
     885             :  * @param pszBasename file basename.  May optionally have path and/or
     886             :  * extension.  May not be NULL.
     887             :  *
     888             :  * @param pszExtension file extension, optionally including the period.  May
     889             :  * be NULL.
     890             :  *
     891             :  * @return a fully formed filename.
     892             :  *
     893             :  * @since 3.11
     894             :  */
     895             : 
     896        6827 : std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
     897             :                                   const char *pszExtension)
     898             : 
     899             : {
     900             :     // On case insensitive filesystems, just default to CPLFormFilename().
     901        6827 :     if (!VSIIsCaseSensitiveFS(pszPath))
     902           0 :         return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
     903             : 
     904        6827 :     const char *pszAddedExtSep = "";
     905        6827 :     size_t nLen = strlen(pszBasename) + 2;
     906             : 
     907        6827 :     if (pszExtension != nullptr)
     908        2480 :         nLen += strlen(pszExtension);
     909             : 
     910        6827 :     char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
     911        6827 :     if (pszFilename == nullptr)
     912           0 :         return "";
     913             : 
     914        6827 :     if (pszExtension == nullptr)
     915        4347 :         pszExtension = "";
     916        2480 :     else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
     917        2428 :         pszAddedExtSep = ".";
     918             : 
     919        6827 :     snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
     920             :              pszExtension);
     921             : 
     922       13654 :     std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
     923             :     VSIStatBufL sStatBuf;
     924        6827 :     int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     925             : 
     926        6827 :     if (nStatRet != 0)
     927             :     {
     928       90144 :         for (size_t i = 0; pszFilename[i] != '\0'; i++)
     929             :         {
     930       83755 :             pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
     931             :         }
     932             : 
     933             :         std::string osTmpPath(
     934       12778 :             CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
     935             :         nStatRet =
     936        6389 :             VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     937        6389 :         if (nStatRet == 0)
     938           3 :             osRet = std::move(osTmpPath);
     939             :     }
     940             : 
     941        6827 :     if (nStatRet != 0)
     942             :     {
     943       90120 :         for (size_t i = 0; pszFilename[i] != '\0'; i++)
     944             :         {
     945       83734 :             pszFilename[i] = static_cast<char>(
     946       83734 :                 CPLTolower(static_cast<unsigned char>(pszFilename[i])));
     947             :         }
     948             : 
     949             :         std::string osTmpPath(
     950       12772 :             CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
     951             :         nStatRet =
     952        6386 :             VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
     953        6386 :         if (nStatRet == 0)
     954           8 :             osRet = std::move(osTmpPath);
     955             :     }
     956             : 
     957        6827 :     if (nStatRet != 0)
     958        6378 :         osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
     959             : 
     960        6827 :     CPLFree(pszFilename);
     961             : 
     962        6827 :     return osRet;
     963             : }
     964             : 
     965             : /************************************************************************/
     966             : /*                         CPLFormCIFilename()                          */
     967             : /************************************************************************/
     968             : 
     969             : /**
     970             :  * Case insensitive file searching, returning full path.
     971             :  *
     972             :  * This function tries to return the path to a file regardless of
     973             :  * whether the file exactly matches the basename, and extension case, or
     974             :  * is all upper case, or all lower case.  The path is treated as case
     975             :  * sensitive.  This function is equivalent to CPLFormFilename() on
     976             :  * case insensitive file systems (like Windows).
     977             :  *
     978             :  * @param pszPath directory path to the directory containing the file.  This
     979             :  * may be relative or absolute, and may have a trailing path separator or
     980             :  * not.  May be NULL.
     981             :  *
     982             :  * @param pszBasename file basename.  May optionally have path and/or
     983             :  * extension.  May not be NULL.
     984             :  *
     985             :  * @param pszExtension file extension, optionally including the period.  May
     986             :  * be NULL.
     987             :  *
     988             :  * @return a fully formed filename in an internal static string.  Do not
     989             :  * modify or free the returned string.  The string may be destroyed by the
     990             :  * next CPL call.
     991             :  *
     992             :  * @deprecated If using C++, prefer using CPLFormCIFilenameSafe() instead
     993             : */
     994             : 
     995           0 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
     996             :                               const char *pszExtension)
     997             : 
     998             : {
     999           0 :     return CPLPathReturnTLSString(
    1000           0 :         CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
    1001           0 :         __FUNCTION__);
    1002             : }
    1003             : 
    1004             : /************************************************************************/
    1005             : /*                   CPLProjectRelativeFilenameSafe()                   */
    1006             : /************************************************************************/
    1007             : 
    1008             : /**
    1009             :  * Find a file relative to a project file.
    1010             :  *
    1011             :  * Given the path to a "project" directory, and a path to a secondary file
    1012             :  * referenced from that project, build a path to the secondary file
    1013             :  * that the current application can use.  If the secondary path is already
    1014             :  * absolute, rather than relative, then it will be returned unaltered.
    1015             :  *
    1016             :  * Examples:
    1017             :  * \code{.cpp}
    1018             :  * CPLProjectRelativeFilenameSafe("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
    1019             :  * CPLProjectRelativeFilenameSafe("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
    1020             :  * CPLProjectRelativeFilenameSafe("/xy", "abc.gif") == "/xy/abc.gif"
    1021             :  * CPLProjectRelativeFilenameSafe("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
    1022             :  * CPLProjectRelativeFilenameSafe("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
    1023             :  * \endcode
    1024             :  *
    1025             :  * @param pszProjectDir the directory relative to which the secondary files
    1026             :  * path should be interpreted.
    1027             :  * @param pszSecondaryFilename the filename (potentially with path) that
    1028             :  * is to be interpreted relative to the project directory.
    1029             :  *
    1030             :  * @return a composed path to the secondary file.
    1031             :  *
    1032             :  * @since 3.11
    1033             :  */
    1034             : 
    1035        4481 : std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
    1036             :                                            const char *pszSecondaryFilename)
    1037             : 
    1038             : {
    1039        8727 :     if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
    1040        4246 :         !CPLIsFilenameRelative(pszSecondaryFilename))
    1041             :     {
    1042         731 :         return pszSecondaryFilename;
    1043             :     }
    1044             : 
    1045        7500 :     std::string osRes(pszProjectDir);
    1046        3750 :     if (osRes.back() != '/' && osRes.back() != '\\')
    1047             :     {
    1048        3750 :         osRes += VSIGetDirectorySeparator(pszProjectDir);
    1049             :     }
    1050             : 
    1051        3750 :     osRes += pszSecondaryFilename;
    1052        3750 :     return osRes;
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*                     CPLProjectRelativeFilename()                     */
    1057             : /************************************************************************/
    1058             : 
    1059             : /**
    1060             :  * Find a file relative to a project file.
    1061             :  *
    1062             :  * Given the path to a "project" directory, and a path to a secondary file
    1063             :  * referenced from that project, build a path to the secondary file
    1064             :  * that the current application can use.  If the secondary path is already
    1065             :  * absolute, rather than relative, then it will be returned unaltered.
    1066             :  *
    1067             :  * Examples:
    1068             :  * \code{.cpp}
    1069             :  * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
    1070             :  * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
    1071             :  * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
    1072             :  * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
    1073             :  * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
    1074             :  * \endcode
    1075             :  *
    1076             :  * @param pszProjectDir the directory relative to which the secondary files
    1077             :  * path should be interpreted.
    1078             :  * @param pszSecondaryFilename the filename (potentially with path) that
    1079             :  * is to be interpreted relative to the project directory.
    1080             :  *
    1081             :  * @return a composed path to the secondary file.  The returned string is
    1082             :  * internal and should not be altered, freed, or depending on past the next
    1083             :  * CPL call.
    1084             :  *
    1085             :  * @deprecated If using C++, prefer using CPLProjectRelativeFilenameSafe() instead
    1086             :  */
    1087             : 
    1088           0 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
    1089             :                                        const char *pszSecondaryFilename)
    1090             : 
    1091             : {
    1092           0 :     return CPLPathReturnTLSString(
    1093           0 :         CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
    1094           0 :         __FUNCTION__);
    1095             : }
    1096             : 
    1097             : /************************************************************************/
    1098             : /*                       CPLIsFilenameRelative()                        */
    1099             : /************************************************************************/
    1100             : 
    1101             : /**
    1102             :  * Is filename relative or absolute?
    1103             :  *
    1104             :  * The test is filesystem convention agnostic.  That is it will test for
    1105             :  * Unix style and windows style path conventions regardless of the actual
    1106             :  * system in use.
    1107             :  *
    1108             :  * @param pszFilename the filename with path to test.
    1109             :  *
    1110             :  * @return TRUE if the filename is relative or FALSE if it is absolute.
    1111             :  */
    1112             : 
    1113      486352 : int CPLIsFilenameRelative(const char *pszFilename)
    1114             : 
    1115             : {
    1116      486352 :     if ((pszFilename[0] != '\0' &&
    1117      474523 :          (STARTS_WITH(pszFilename + 1, ":\\") ||
    1118      474489 :           STARTS_WITH(pszFilename + 1, ":/") ||
    1119      474502 :           strstr(pszFilename + 1, "://")  // http://, ftp:// etc....
    1120      485167 :           )) ||
    1121      485167 :         STARTS_WITH(pszFilename, "\\\\?\\")  // Windows extended Length Path.
    1122      485172 :         || pszFilename[0] == '\\' || pszFilename[0] == '/')
    1123      368119 :         return FALSE;
    1124             : 
    1125      118233 :     return TRUE;
    1126             : }
    1127             : 
    1128             : /************************************************************************/
    1129             : /*                       CPLExtractRelativePath()                       */
    1130             : /************************************************************************/
    1131             : 
    1132             : /**
    1133             :  * Get relative path from directory to target file.
    1134             :  *
    1135             :  * Computes a relative path for pszTarget relative to pszBaseDir.
    1136             :  * Currently this only works if they share a common base path.  The returned
    1137             :  * path is normally into the pszTarget string.  It should only be considered
    1138             :  * valid as long as pszTarget is valid or till the next call to
    1139             :  * this function, whichever comes first.
    1140             :  *
    1141             :  * @param pszBaseDir the name of the directory relative to which the path
    1142             :  * should be computed.  pszBaseDir may be NULL in which case the original
    1143             :  * target is returned without relativizing.
    1144             :  *
    1145             :  * @param pszTarget the filename to be changed to be relative to pszBaseDir.
    1146             :  *
    1147             :  * @param pbGotRelative Pointer to location in which a flag is placed
    1148             :  * indicating that the returned path is relative to the basename (TRUE) or
    1149             :  * not (FALSE).  This pointer may be NULL if flag is not desired.
    1150             :  *
    1151             :  * @return an adjusted path or the original if it could not be made relative
    1152             :  * to the pszBaseFile's path.
    1153             :  **/
    1154             : 
    1155        2807 : const char *CPLExtractRelativePath(const char *pszBaseDir,
    1156             :                                    const char *pszTarget, int *pbGotRelative)
    1157             : 
    1158             : {
    1159             :     /* -------------------------------------------------------------------- */
    1160             :     /*      If we don't have a basedir, then we can't relativize the path.  */
    1161             :     /* -------------------------------------------------------------------- */
    1162        2807 :     if (pszBaseDir == nullptr)
    1163             :     {
    1164           0 :         if (pbGotRelative != nullptr)
    1165           0 :             *pbGotRelative = FALSE;
    1166             : 
    1167           0 :         return pszTarget;
    1168             :     }
    1169             : 
    1170        2807 :     const size_t nBasePathLen = strlen(pszBaseDir);
    1171             : 
    1172             :     /* -------------------------------------------------------------------- */
    1173             :     /*      One simple case is when the base dir is '.' and the target      */
    1174             :     /*      filename is relative.                                           */
    1175             :     /* -------------------------------------------------------------------- */
    1176        2820 :     if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
    1177          13 :         CPLIsFilenameRelative(pszTarget))
    1178             :     {
    1179          13 :         if (pbGotRelative != nullptr)
    1180          13 :             *pbGotRelative = TRUE;
    1181             : 
    1182          13 :         return pszTarget;
    1183             :     }
    1184             : 
    1185             :     /* -------------------------------------------------------------------- */
    1186             :     /*      By this point, if we don't have a base path, we can't have a    */
    1187             :     /*      meaningful common prefix.                                       */
    1188             :     /* -------------------------------------------------------------------- */
    1189        2794 :     if (nBasePathLen == 0)
    1190             :     {
    1191           0 :         if (pbGotRelative != nullptr)
    1192           0 :             *pbGotRelative = FALSE;
    1193             : 
    1194           0 :         return pszTarget;
    1195             :     }
    1196             : 
    1197             :     /* -------------------------------------------------------------------- */
    1198             :     /*      If we don't have a common path prefix, then we can't get a      */
    1199             :     /*      relative path.                                                  */
    1200             :     /* -------------------------------------------------------------------- */
    1201        2794 :     if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
    1202        2367 :         (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
    1203             :     {
    1204         428 :         if (pbGotRelative != nullptr)
    1205         428 :             *pbGotRelative = FALSE;
    1206             : 
    1207         428 :         return pszTarget;
    1208             :     }
    1209             : 
    1210             :     /* -------------------------------------------------------------------- */
    1211             :     /*      We have a relative path.  Strip it off to get a string to       */
    1212             :     /*      return.                                                         */
    1213             :     /* -------------------------------------------------------------------- */
    1214        2366 :     if (pbGotRelative != nullptr)
    1215        2282 :         *pbGotRelative = TRUE;
    1216             : 
    1217        2366 :     return pszTarget + nBasePathLen + 1;
    1218             : }
    1219             : 
    1220             : /************************************************************************/
    1221             : /*                     CPLCleanTrailingSlashSafe()                      */
    1222             : /************************************************************************/
    1223             : 
    1224             : /**
    1225             :  * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
    1226             :  *
    1227             :  * Returns a string containing the portion of the passed path string with
    1228             :  * trailing slash removed. If there is no path in the passed filename
    1229             :  * an empty string will be returned (not NULL).
    1230             :  *
    1231             :  * \code{.cpp}
    1232             :  * CPLCleanTrailingSlashSafe( "abc/def/" ) == "abc/def"
    1233             :  * CPLCleanTrailingSlashSafe( "abc/def" ) == "abc/def"
    1234             :  * CPLCleanTrailingSlashSafe( "c:\\abc\\def\\" ) == "c:\\abc\\def"
    1235             :  * CPLCleanTrailingSlashSafe( "c:\\abc\\def" ) == "c:\\abc\\def"
    1236             :  * CPLCleanTrailingSlashSafe( "abc" ) == "abc"
    1237             :  * \endcode
    1238             :  *
    1239             :  * @param pszPath the path to be cleaned up
    1240             :  *
    1241             :  * @return Path
    1242             :  *
    1243             :  * @since 3.11
    1244             :  */
    1245             : 
    1246           9 : std::string CPLCleanTrailingSlashSafe(const char *pszPath)
    1247             : 
    1248             : {
    1249           9 :     std::string osRes(pszPath);
    1250           9 :     if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
    1251           0 :         osRes.pop_back();
    1252           9 :     return osRes;
    1253             : }
    1254             : 
    1255             : /************************************************************************/
    1256             : /*                       CPLCleanTrailingSlash()                        */
    1257             : /************************************************************************/
    1258             : 
    1259             : /**
    1260             :  * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
    1261             :  *
    1262             :  * Returns a string containing the portion of the passed path string with
    1263             :  * trailing slash removed. If there is no path in the passed filename
    1264             :  * an empty string will be returned (not NULL).
    1265             :  *
    1266             :  * \code{.cpp}
    1267             :  * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
    1268             :  * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
    1269             :  * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
    1270             :  * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
    1271             :  * CPLCleanTrailingSlash( "abc" ) == "abc"
    1272             :  * \endcode
    1273             :  *
    1274             :  * @param pszPath the path to be cleaned up
    1275             :  *
    1276             :  * @return Path in an internal string which must not be freed.  The string
    1277             :  * may be destroyed by the next CPL filename handling call.
    1278             :  *
    1279             :  * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
    1280             :  */
    1281             : 
    1282           0 : const char *CPLCleanTrailingSlash(const char *pszPath)
    1283             : 
    1284             : {
    1285           0 :     return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
    1286           0 :                                   __FUNCTION__);
    1287             : }
    1288             : 
    1289             : /************************************************************************/
    1290             : /*                       CPLCorrespondingPaths()                        */
    1291             : /************************************************************************/
    1292             : 
    1293             : /**
    1294             :  * Identify corresponding paths.
    1295             :  *
    1296             :  * Given a prototype old and new filename this function will attempt
    1297             :  * to determine corresponding names for a set of other old filenames that
    1298             :  * will rename them in a similar manner.  This correspondence assumes there
    1299             :  * are two possibly kinds of renaming going on.  A change of path, and a
    1300             :  * change of filename stem.
    1301             :  *
    1302             :  * If a consistent renaming cannot be established for all the files this
    1303             :  * function will return indicating an error.
    1304             :  *
    1305             :  * The returned file list becomes owned by the caller and should be destroyed
    1306             :  * with CSLDestroy().
    1307             :  *
    1308             :  * @param pszOldFilename path to old prototype file.
    1309             :  * @param pszNewFilename path to new prototype file.
    1310             :  * @param papszFileList list of other files associated with pszOldFilename to
    1311             :  * rename similarly.
    1312             :  *
    1313             :  * @return a list of files corresponding to papszFileList but renamed to
    1314             :  * correspond to pszNewFilename.
    1315             :  */
    1316             : 
    1317         187 : char **CPLCorrespondingPaths(const char *pszOldFilename,
    1318             :                              const char *pszNewFilename,
    1319             :                              CSLConstList papszFileList)
    1320             : 
    1321             : {
    1322         187 :     if (CSLCount(papszFileList) == 0)
    1323           0 :         return nullptr;
    1324             : 
    1325             :     VSIStatBufL sStatBuf;
    1326         187 :     if (VSIStatL(pszOldFilename, &sStatBuf) == 0 && VSI_ISDIR(sStatBuf.st_mode))
    1327             :     {
    1328           8 :         CPLStringList aosNewList;
    1329           4 :         std::string_view svOldFilename(pszOldFilename);
    1330          20 :         for (int i = 0; papszFileList[i] != nullptr; i++)
    1331             :         {
    1332          16 :             if (cpl::starts_with(std::string_view(papszFileList[i]),
    1333          32 :                                  svOldFilename) &&
    1334          16 :                 (papszFileList[i][svOldFilename.size()] == '/' ||
    1335           0 :                  papszFileList[i][svOldFilename.size()] == '\\'))
    1336             :             {
    1337             :                 // If the old file list contains entries like oldpath/filename,
    1338             :                 // generate newpath/filename
    1339          16 :                 aosNewList.push_back(CPLFormFilenameSafe(
    1340          16 :                     pszNewFilename, papszFileList[i] + svOldFilename.size() + 1,
    1341             :                     nullptr));
    1342             :             }
    1343             :             else
    1344             :             {
    1345           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1346             :                          "Unable to copy/rename fileset due to unexpected "
    1347             :                          "source filename.");
    1348           0 :                 return nullptr;
    1349             :             }
    1350             :         }
    1351           4 :         return aosNewList.StealList();
    1352             :     }
    1353             : 
    1354             :     /* -------------------------------------------------------------------- */
    1355             :     /*      There is a special case for a one item list which exactly       */
    1356             :     /*      matches the old name, to rename to the new name.                */
    1357             :     /* -------------------------------------------------------------------- */
    1358         355 :     if (CSLCount(papszFileList) == 1 &&
    1359         172 :         strcmp(pszOldFilename, papszFileList[0]) == 0)
    1360             :     {
    1361         172 :         return CSLAddString(nullptr, pszNewFilename);
    1362             :     }
    1363             : 
    1364          22 :     const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
    1365          22 :     const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
    1366          22 :     const std::string osNewBasename = CPLGetBasenameSafe(pszNewFilename);
    1367             : 
    1368             :     /* -------------------------------------------------------------------- */
    1369             :     /*      If the basename is changing, verify that all source files       */
    1370             :     /*      have the same starting basename.                                */
    1371             :     /* -------------------------------------------------------------------- */
    1372          11 :     if (osOldBasename != osNewBasename)
    1373             :     {
    1374          34 :         for (int i = 0; papszFileList[i] != nullptr; i++)
    1375             :         {
    1376          24 :             if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
    1377          16 :                 continue;
    1378             : 
    1379           8 :             const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
    1380           8 :             const std::string osFileName = CPLGetFilename(papszFileList[i]);
    1381             : 
    1382           8 :             if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
    1383           8 :                         osOldBasename.size()) ||
    1384          16 :                 !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
    1385           8 :                 osFileName[osOldBasename.size()] != '.')
    1386             :             {
    1387           0 :                 CPLError(
    1388             :                     CE_Failure, CPLE_AppDefined,
    1389             :                     "Unable to copy/rename fileset due irregular basenames.");
    1390           0 :                 return nullptr;
    1391             :             }
    1392             :         }
    1393             :     }
    1394             : 
    1395             :     /* -------------------------------------------------------------------- */
    1396             :     /*      If the filename portions differs, ensure they only differ in    */
    1397             :     /*      basename.                                                       */
    1398             :     /* -------------------------------------------------------------------- */
    1399          11 :     if (osOldBasename != osNewBasename)
    1400             :     {
    1401             :         const std::string osOldExtra =
    1402          10 :             CPLGetFilename(pszOldFilename) + osOldBasename.size();
    1403             :         const std::string osNewExtra =
    1404          10 :             CPLGetFilename(pszNewFilename) + osNewBasename.size();
    1405             : 
    1406          10 :         if (osOldExtra != osNewExtra)
    1407             :         {
    1408           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1409             :                      "Unable to copy/rename fileset due to irregular filename "
    1410             :                      "correspondence.");
    1411           0 :             return nullptr;
    1412             :         }
    1413             :     }
    1414             : 
    1415             :     /* -------------------------------------------------------------------- */
    1416             :     /*      Generate the new filenames.                                     */
    1417             :     /* -------------------------------------------------------------------- */
    1418          11 :     char **papszNewList = nullptr;
    1419          11 :     const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
    1420             : 
    1421          37 :     for (int i = 0; papszFileList[i] != nullptr; i++)
    1422             :     {
    1423          52 :         const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
    1424             : 
    1425             :         const std::string osNewFilename =
    1426          26 :             osOldBasename == osNewBasename
    1427             :                 ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
    1428             :                                       nullptr)
    1429             :                 : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
    1430          24 :                                       osOldFilename.c_str() +
    1431          50 :                                           osOldBasename.size());
    1432             : 
    1433          26 :         papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
    1434             :     }
    1435             : 
    1436          11 :     return papszNewList;
    1437             : }
    1438             : 
    1439             : /************************************************************************/
    1440             : /*                    CPLGenerateTempFilenameSafe()                     */
    1441             : /************************************************************************/
    1442             : 
    1443             : /**
    1444             :  * Generate temporary file name.
    1445             :  *
    1446             :  * Returns a filename that may be used for a temporary file.  The location
    1447             :  * of the file tries to follow operating system semantics but may be
    1448             :  * forced via the CPL_TMPDIR configuration option.
    1449             :  *
    1450             :  * @param pszStem if non-NULL this will be part of the filename.
    1451             :  *
    1452             :  * @return a filename
    1453             :  *
    1454             :  * @since 3.11
    1455             :  */
    1456             : 
    1457        2907 : std::string CPLGenerateTempFilenameSafe(const char *pszStem)
    1458             : 
    1459             : {
    1460        2907 :     const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
    1461             : 
    1462        2907 :     if (pszDir == nullptr)
    1463        2891 :         pszDir = CPLGetConfigOption("TMPDIR", nullptr);
    1464             : 
    1465        2907 :     if (pszDir == nullptr)
    1466        2891 :         pszDir = CPLGetConfigOption("TEMP", nullptr);
    1467             : 
    1468        2907 :     if (pszDir == nullptr)
    1469        2891 :         pszDir = ".";
    1470             : 
    1471        2907 :     if (pszStem == nullptr)
    1472        2667 :         pszStem = "";
    1473             : 
    1474             :     static int nTempFileCounter = 0;
    1475        5814 :     CPLString osFilename;
    1476             :     osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
    1477        2907 :                       CPLAtomicInc(&nTempFileCounter));
    1478             : 
    1479        5814 :     return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
    1480             : }
    1481             : 
    1482             : /************************************************************************/
    1483             : /*                      CPLGenerateTempFilename()                       */
    1484             : /************************************************************************/
    1485             : 
    1486             : /**
    1487             :  * Generate temporary file name.
    1488             :  *
    1489             :  * Returns a filename that may be used for a temporary file.  The location
    1490             :  * of the file tries to follow operating system semantics but may be
    1491             :  * forced via the CPL_TMPDIR configuration option.
    1492             :  *
    1493             :  * @param pszStem if non-NULL this will be part of the filename.
    1494             :  *
    1495             :  * @return a filename which is valid till the next CPL call in this thread.
    1496             :  *
    1497             :  * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
    1498             :  */
    1499             : 
    1500           7 : const char *CPLGenerateTempFilename(const char *pszStem)
    1501             : 
    1502             : {
    1503          14 :     return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
    1504          14 :                                   __FUNCTION__);
    1505             : }
    1506             : 
    1507             : /************************************************************************/
    1508             : /*                         CPLExpandTildeSafe()                         */
    1509             : /************************************************************************/
    1510             : 
    1511             : /**
    1512             :  * Expands ~/ at start of filename.
    1513             :  *
    1514             :  * Assumes that the HOME configuration option is defined.
    1515             :  *
    1516             :  * @param pszFilename filename potentially starting with ~/
    1517             :  *
    1518             :  * @return an expanded filename.
    1519             :  *
    1520             :  * @since GDAL 3.11
    1521             :  */
    1522             : 
    1523         195 : std::string CPLExpandTildeSafe(const char *pszFilename)
    1524             : 
    1525             : {
    1526         195 :     if (!STARTS_WITH_CI(pszFilename, "~/"))
    1527         194 :         return pszFilename;
    1528             : 
    1529           1 :     const char *pszHome = CPLGetConfigOption("HOME", nullptr);
    1530           1 :     if (pszHome == nullptr)
    1531           0 :         return pszFilename;
    1532             : 
    1533           1 :     return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
    1534             : }
    1535             : 
    1536             : /************************************************************************/
    1537             : /*                           CPLExpandTilde()                           */
    1538             : /************************************************************************/
    1539             : 
    1540             : /**
    1541             :  * Expands ~/ at start of filename.
    1542             :  *
    1543             :  * Assumes that the HOME configuration option is defined.
    1544             :  *
    1545             :  * @param pszFilename filename potentially starting with ~/
    1546             :  *
    1547             :  * @return an expanded filename.
    1548             :  *
    1549             :  *
    1550             :  * @deprecated If using C++, prefer using CPLExpandTildeSafe() instead
    1551             :  */
    1552             : 
    1553           2 : const char *CPLExpandTilde(const char *pszFilename)
    1554             : 
    1555             : {
    1556           4 :     return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
    1557           4 :                                   __FUNCTION__);
    1558             : }
    1559             : 
    1560             : /************************************************************************/
    1561             : /*                           CPLGetHomeDir()                            */
    1562             : /************************************************************************/
    1563             : 
    1564             : /**
    1565             :  * Return the path to the home directory
    1566             :  *
    1567             :  * That is the value of the USERPROFILE environment variable on Windows,
    1568             :  * or HOME on other platforms.
    1569             :  *
    1570             :  * @return the home directory, or NULL.
    1571             :  *
    1572             :  */
    1573             : 
    1574           0 : const char *CPLGetHomeDir()
    1575             : 
    1576             : {
    1577             : #ifdef _WIN32
    1578             :     return CPLGetConfigOption("USERPROFILE", nullptr);
    1579             : #else
    1580           0 :     return CPLGetConfigOption("HOME", nullptr);
    1581             : #endif
    1582             : }
    1583             : 
    1584             : /************************************************************************/
    1585             : /*                     CPLLaunderForFilenameSafe()                      */
    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.11
    1597             :  */
    1598             : 
    1599        1531 : std::string CPLLaunderForFilenameSafe(const char *pszName,
    1600             :                                       CPL_UNUSED const char *pszOutputPath)
    1601             : {
    1602        1531 :     return CPLLaunderForFilenameSafe(pszName, '_', nullptr);
    1603             : }
    1604             : 
    1605             : /************************************************************************/
    1606             : /*                     CPLLaunderForFilenameSafe()                      */
    1607             : /************************************************************************/
    1608             : 
    1609             : /** Return a string that is compatible with a filename on Linux, Windows and
    1610             :  * MacOS.
    1611             :  *
    1612             :  * Reserved characters '<', '>', ':', '"', '/', '\\', '|', '?', '*', '^', and
    1613             :  * ASCII control characters are replaced by the replacement character, or
    1614             :  * removed if it is NUL.
    1615             :  *
    1616             :  * Reserved names (".", "..", "CON", "PRN", etc.) are suffixed with the
    1617             :  * replacement character (or underscore).
    1618             :  *
    1619             :  * If the string ends with a final space or dot, the replacement character
    1620             :  * (or underscore) will be appended.
    1621             :  *
    1622             :  * @param osInput Input string.
    1623             :  * @param chReplacementChar Character to substitute to characters that are not
    1624             :  *                          compatible of a file name, or NUL character to
    1625             :  *                          remove them.
    1626             :  * @param pszExtraReservedCharacters String with extra reserved characters that
    1627             :  *                                   are replaced by the replacement character,
    1628             :  *                                   or removed if it is NUL. Or nullptr.
    1629             :  * @since GDAL 3.13
    1630             :  */
    1631        1589 : std::string CPLLaunderForFilenameSafe(const std::string &osInput,
    1632             :                                       char chReplacementChar,
    1633             :                                       const char *pszExtraReservedCharacters)
    1634             : {
    1635             :     // Cf https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
    1636        1589 :     std::string ret;
    1637        1589 :     ret.reserve(osInput.size());
    1638       14474 :     for (char c : osInput)
    1639             :     {
    1640       12885 :         if (static_cast<unsigned>(c) < 32 || c == 127 || c == '<' || c == '>' ||
    1641       12876 :             c == ':' || c == '"' || c == '/' || c == '\\' || c == '|' ||
    1642       12858 :             c == '?' ||
    1643             :             c == '*'
    1644             :             // '^' invalid on FAT
    1645       12856 :             || c == '^')
    1646             :         {
    1647          30 :             if (chReplacementChar)
    1648          29 :                 ret += chReplacementChar;
    1649             :         }
    1650       12855 :         else if (pszExtraReservedCharacters &&
    1651           8 :                  strchr(pszExtraReservedCharacters, c))
    1652             :         {
    1653           2 :             if (chReplacementChar)
    1654           1 :                 ret += chReplacementChar;
    1655             :         }
    1656             :         else
    1657             :         {
    1658       12853 :             ret += c;
    1659             :         }
    1660             :     }
    1661             : 
    1662             :     // Windows reserved filenames (case-insensitive)
    1663        1589 :     const char *const apszReservedNames[] = {
    1664             :         "CON",  "PRN",  "AUX",  "NUL",  "COM1", "COM2", "COM3",   "COM4",
    1665             :         "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",   "LPT3",
    1666             :         "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "CONIN$", "CONOUT$",
    1667             :     };
    1668       39629 :     for (const char *pszReservedName : apszReservedNames)
    1669             :     {
    1670       38044 :         if (EQUAL(ret.c_str(), pszReservedName))
    1671             :         {
    1672           4 :             ret += chReplacementChar ? chReplacementChar : '_';
    1673           4 :             break;
    1674             :         }
    1675             :     }
    1676             : 
    1677             :     // Windows rule: no filename ending with space or dot
    1678             :     // This also prevents "." and ".." which are invalid on POSIX
    1679        1589 :     if (!ret.empty() && (ret.back() == ' ' || ret.back() == '.'))
    1680           5 :         ret += chReplacementChar ? chReplacementChar : '_';
    1681             : 
    1682        3178 :     return ret;
    1683             : }
    1684             : 
    1685             : /************************************************************************/
    1686             : /*                       CPLLaunderForFilename()                        */
    1687             : /************************************************************************/
    1688             : 
    1689             : /**
    1690             :  * Launder a string to be compatible of a filename.
    1691             :  *
    1692             :  * @param pszName The input string to launder.
    1693             :  * @param pszOutputPath The directory where the file would be created.
    1694             :  *                      Unused for now. May be NULL.
    1695             :  * @return the laundered name.
    1696             :  *
    1697             :  * @since GDAL 3.1
    1698             :  *
    1699             :  * @deprecated If using C++, prefer using CPLLaunderForFilenameSafe() instead
    1700             :  */
    1701             : 
    1702           0 : const char *CPLLaunderForFilename(const char *pszName,
    1703             :                                   const char *pszOutputPath)
    1704             : {
    1705           0 :     return CPLPathReturnTLSString(
    1706           0 :         CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
    1707             : }
    1708             : 
    1709             : /************************************************************************/
    1710             : /*                        CPLHasPathTraversal()                         */
    1711             : /************************************************************************/
    1712             : 
    1713             : /**
    1714             :  * Return whether the filename contains a path traversal pattern.
    1715             :  *
    1716             :  * i.e. if it contains "../" or "..\\".
    1717             :  *
    1718             :  * The CPL_ENABLE_PATH_TRAVERSAL_DETECTION configuration option can be set
    1719             :  * to NO to disable this check, although this is not recommended when dealing
    1720             :  * with un-trusted input.
    1721             :  *
    1722             :  * @param pszFilename The input string to check.
    1723             :  * @return true if a path traversal pattern is detected.
    1724             :  *
    1725             :  * @since GDAL 3.12
    1726             :  */
    1727             : 
    1728        2187 : bool CPLHasPathTraversal(const char *pszFilename)
    1729             : {
    1730        2187 :     const char *pszDotDot = strstr(pszFilename, "..");
    1731        2187 :     if (pszDotDot &&
    1732           6 :         (pszDotDot == pszFilename ||
    1733           6 :          pszFilename[pszDotDot - pszFilename - 1] == '/' ||
    1734           2 :          pszFilename[pszDotDot - pszFilename - 1] == '\\') &&
    1735           6 :         (pszDotDot[2] == 0 || pszDotDot[2] == '/' || pszDotDot[2] == '\\'))
    1736             :     {
    1737           6 :         if (CPLTestBool(CPLGetConfigOption(
    1738             :                 "CPL_ENABLE_PATH_TRAVERSAL_DETECTION", "YES")))
    1739             :         {
    1740           4 :             CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
    1741           4 :             return true;
    1742             :         }
    1743             :         else
    1744             :         {
    1745           2 :             CPLDebug("CPL",
    1746             :                      "Path traversal detected for %s but ignored given that "
    1747             :                      "CPL_ENABLE_PATH_TRAVERSAL_DETECTION is disabled",
    1748             :                      pszFilename);
    1749             :         }
    1750             :     }
    1751        2183 :     return false;
    1752             : }
    1753             : 
    1754             : /************************************************************************/
    1755             : /*                   CPLHasUnbalancedPathTraversal()                    */
    1756             : /************************************************************************/
    1757             : 
    1758             : /**
    1759             :  * Return whether the filename contains a unbalanced path traversal pattern.
    1760             :  *
    1761             :  * i.e. if it contains more "../" or "..\\" than preceding nesting.
    1762             :  *
    1763             :  *
    1764             :  * @param pszFilename The input string to check.
    1765             :  * @return true if a path traversal pattern is detected.
    1766             :  *
    1767             :  * @since GDAL 3.12
    1768             :  */
    1769             : 
    1770         549 : bool CPLHasUnbalancedPathTraversal(const char *pszFilename)
    1771             : {
    1772         549 :     size_t nNestLevel = 0;
    1773         549 :     int i = 0;
    1774         549 :     if (pszFilename[0] == '.' &&
    1775           6 :         (pszFilename[1] == '/' || pszFilename[1] == '\\'))
    1776           2 :         i += 2;
    1777         547 :     else if (pszFilename[0] == '/' || pszFilename[0] == '\\')
    1778           2 :         ++i;
    1779       43173 :     for (; pszFilename[i]; ++i)
    1780             :     {
    1781       42634 :         if (pszFilename[i] == '/' || pszFilename[i] == '\\')
    1782             :         {
    1783        1730 :             if (pszFilename[i + 1] == '/' || pszFilename[i + 1] == '\\')
    1784             :             {
    1785           0 :                 continue;
    1786             :             }
    1787        1730 :             if (pszFilename[i + 1] != 0)
    1788        1714 :                 ++nNestLevel;
    1789             :         }
    1790       40904 :         else if (pszFilename[i] == '.' && pszFilename[i + 1] == '.' &&
    1791          24 :                  (pszFilename[i + 2] == '/' || pszFilename[i + 2] == '\\' ||
    1792           4 :                   pszFilename[i + 2] == 0))
    1793             :         {
    1794          24 :             if (nNestLevel == 0)
    1795             :             {
    1796           8 :                 CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
    1797           8 :                 return true;
    1798             :             }
    1799          16 :             if (pszFilename[i + 2] == 0)
    1800           2 :                 break;
    1801          14 :             i += 2;
    1802          14 :             --nNestLevel;
    1803             :         }
    1804             :     }
    1805             : 
    1806         541 :     return false;
    1807             : }
    1808             : 
    1809             : /************************************************************************/
    1810             : /*                       CPLLexicallyNormalize()                        */
    1811             : /************************************************************************/
    1812             : 
    1813             : /**
    1814             :  * Return a path where "/./" or "/../" sequences are removed.
    1815             :  *
    1816             :  * No filesystem access is done.
    1817             :  *
    1818             :  * @param svPath Input path
    1819             :  * @param sep1 Path separator (typically slash or backslash)
    1820             :  * @param sep2 Secondary path separator (typically slash or backslash), or NUL
    1821             :  * @return compacted path
    1822             :  *
    1823             :  * @since GDAL 3.13
    1824             :  */
    1825         576 : std::string CPLLexicallyNormalize(std::string_view svPath, char sep1, char sep2)
    1826             : {
    1827             :     struct Token
    1828             :     {
    1829             :         size_t iStart = 0;  // index of start of token with svPath
    1830             :         size_t nLen = 0;    // length of token (excluding ending separator)
    1831             :         char chSep = 0;     // separator at end of token, or 0 if there is none
    1832             :     };
    1833             : 
    1834        1152 :     std::vector<Token> tokens;
    1835             : 
    1836        1958 :     const auto CompactTokens = [&tokens, &svPath]()
    1837             :     {
    1838        1798 :         Token &t = tokens.back();
    1839        1798 :         if (t.nLen == 1 && svPath[t.iStart] == '.')
    1840             :         {
    1841           3 :             tokens.pop_back();
    1842             :         }
    1843        1820 :         else if (t.nLen == 2 && svPath[t.iStart] == '.' &&
    1844          25 :                  svPath[t.iStart + 1] == '.')
    1845             :         {
    1846          25 :             if (tokens.size() >= 2)
    1847          23 :                 tokens.resize(tokens.size() - 2);
    1848             :         }
    1849        2374 :     };
    1850             : 
    1851         576 :     bool lastCharIsSep = false;
    1852       18026 :     for (size_t i = 0; i < svPath.size(); ++i)
    1853             :     {
    1854       17450 :         const char c = svPath[i];
    1855       17450 :         if (c == sep1 || c == sep2)
    1856             :         {
    1857        1791 :             if (!lastCharIsSep)
    1858             :             {
    1859        1781 :                 if (tokens.empty())
    1860             :                 {
    1861         557 :                     Token t;
    1862         557 :                     t.chSep = c;
    1863         557 :                     tokens.push_back(t);
    1864             :                 }
    1865             :                 else
    1866             :                 {
    1867        1224 :                     Token &t = tokens.back();
    1868        1224 :                     t.chSep = c;
    1869        1224 :                     CompactTokens();
    1870             :                 }
    1871        1781 :                 lastCharIsSep = true;
    1872        1791 :             }
    1873             :         }
    1874             :         else
    1875             :         {
    1876       15659 :             if (tokens.empty() || lastCharIsSep)
    1877             :             {
    1878        1762 :                 Token t;
    1879        1762 :                 t.iStart = i;
    1880        1762 :                 t.nLen = 1;
    1881        1762 :                 tokens.push_back(t);
    1882             :             }
    1883             :             else
    1884             :             {
    1885       13897 :                 Token &t = tokens.back();
    1886       13897 :                 ++t.nLen;
    1887             :             }
    1888       15659 :             lastCharIsSep = false;
    1889             :         }
    1890             :     }
    1891         576 :     if (!tokens.empty())
    1892             :     {
    1893         574 :         CompactTokens();
    1894             :     }
    1895             : 
    1896         576 :     std::string s;
    1897         576 :     s.reserve(svPath.size());
    1898        2846 :     for (const auto &t : tokens)
    1899             :     {
    1900        2270 :         if (t.nLen)
    1901        1713 :             s.append(svPath.substr(t.iStart, t.nLen));
    1902        2270 :         if (t.chSep)
    1903        1734 :             s.push_back(t.chSep);
    1904             :     }
    1905        1152 :     return s;
    1906             : }

Generated by: LCOV version 1.14