Coverage Report

Created: 2026-02-14 09:00

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