Coverage Report

Created: 2026-02-14 06:52

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
14.8k
{
58
14.8k
    int bMemoryError = FALSE;
59
14.8k
    char *pachBufRingInfo =
60
14.8k
        static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
61
14.8k
    if (bMemoryError)
62
0
        return nullptr;
63
14.8k
    if (pachBufRingInfo == nullptr)
64
2
    {
65
2
        pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
66
2
            1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
67
2
        if (pachBufRingInfo == nullptr)
68
0
            return nullptr;
69
2
        CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
70
2
    }
71
72
    /* -------------------------------------------------------------------- */
73
    /*      Work out which string in the "ring" we want to use this         */
74
    /*      time.                                                           */
75
    /* -------------------------------------------------------------------- */
76
14.8k
    int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
77
14.8k
    const size_t nOffset =
78
14.8k
        sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
79
14.8k
    char *pachBuffer = pachBufRingInfo + nOffset;
80
81
14.8k
    *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
82
83
14.8k
    return pachBuffer;
84
14.8k
}
85
86
/************************************************************************/
87
/*                       CPLPathReturnTLSString()                       */
88
/************************************************************************/
89
90
static const char *CPLPathReturnTLSString(const std::string &osRes,
91
                                          const char *pszFuncName)
92
14.8k
{
93
14.8k
    if (osRes.size() >= CPL_PATH_BUF_SIZE)
94
0
    {
95
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too long result for %s()",
96
0
                 pszFuncName);
97
0
        return "";
98
0
    }
99
100
14.8k
    char *pszStaticResult = CPLGetStaticResult();
101
14.8k
    if (pszStaticResult == nullptr)
102
0
        return CPLStaticBufferTooSmall(pszStaticResult);
103
14.8k
    memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
104
14.8k
    return pszStaticResult;
105
14.8k
}
106
107
/************************************************************************/
108
/*                        CPLFindFilenameStart()                        */
109
/************************************************************************/
110
111
static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
112
113
5.08k
{
114
5.08k
    size_t iFileStart = nStart ? nStart : strlen(pszFilename);
115
116
40.6k
    for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
117
35.5k
           pszFilename[iFileStart - 1] != '\\';
118
35.5k
         iFileStart--)
119
35.5k
    {
120
35.5k
    }
121
122
5.08k
    return static_cast<int>(iFileStart);
123
5.08k
}
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
5.08k
{
154
5.08k
    size_t nSuffixPos = 0;
155
5.08k
    if (STARTS_WITH(pszFilename, "/vsicurl/http"))
156
0
    {
157
0
        const char *pszQuestionMark = strchr(pszFilename, '?');
158
0
        if (pszQuestionMark)
159
0
            nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
160
0
    }
161
5.08k
    else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
162
0
             strstr(pszFilename, "url="))
163
0
    {
164
0
        std::string osRet;
165
0
        const CPLStringList aosTokens(
166
0
            CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
167
0
        for (int i = 0; i < aosTokens.size(); i++)
168
0
        {
169
0
            if (osRet.empty())
170
0
                osRet = "/vsicurl?";
171
0
            else
172
0
                osRet += '&';
173
0
            if (STARTS_WITH(aosTokens[i], "url=") &&
174
0
                !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
175
0
            {
176
0
                char *pszUnescaped =
177
0
                    CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
178
0
                char *pszPath = CPLEscapeString(
179
0
                    CPLGetPathSafe(pszUnescaped + strlen("url=")).c_str(), -1,
180
0
                    CPLES_URL);
181
0
                osRet += "url=";
182
0
                osRet += pszPath;
183
0
                CPLFree(pszPath);
184
0
                CPLFree(pszUnescaped);
185
0
            }
186
0
            else
187
0
            {
188
0
                osRet += aosTokens[i];
189
0
            }
190
0
        }
191
0
        return osRet;
192
0
    }
193
194
5.08k
    const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
195
5.08k
    if (iFileStart == 0)
196
0
    {
197
0
        return std::string();
198
0
    }
199
200
5.08k
    std::string osRet(pszFilename, iFileStart);
201
202
5.08k
    if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
203
5.08k
        osRet.pop_back();
204
205
5.08k
    if (nSuffixPos)
206
0
    {
207
0
        osRet += (pszFilename + nSuffixPos);
208
0
    }
209
210
5.08k
    return osRet;
211
5.08k
}
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
0
{
244
0
    return CPLPathReturnTLSString(CPLGetPathSafe(pszFilename), __FUNCTION__);
245
0
}
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
0
{
276
0
    size_t nSuffixPos = 0;
277
0
    if (STARTS_WITH(pszFilename, "/vsicurl/http"))
278
0
    {
279
0
        const char *pszQuestionMark = strchr(pszFilename, '?');
280
0
        if (pszQuestionMark)
281
0
            nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
282
0
    }
283
0
    else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
284
0
             strstr(pszFilename, "url="))
285
0
    {
286
0
        std::string osRet;
287
0
        const CPLStringList aosTokens(
288
0
            CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
289
0
        for (int i = 0; i < aosTokens.size(); i++)
290
0
        {
291
0
            if (osRet.empty())
292
0
                osRet = "/vsicurl?";
293
0
            else
294
0
                osRet += '&';
295
0
            if (STARTS_WITH(aosTokens[i], "url=") &&
296
0
                !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
297
0
            {
298
0
                char *pszUnescaped =
299
0
                    CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
300
0
                char *pszPath = CPLEscapeString(
301
0
                    CPLGetDirname(pszUnescaped + strlen("url=")), -1,
302
0
                    CPLES_URL);
303
0
                osRet += "url=";
304
0
                osRet += pszPath;
305
0
                CPLFree(pszPath);
306
0
                CPLFree(pszUnescaped);
307
0
            }
308
0
            else
309
0
            {
310
0
                osRet += aosTokens[i];
311
0
            }
312
0
        }
313
0
        return osRet;
314
0
    }
315
316
0
    const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
317
0
    if (iFileStart == 0)
318
0
    {
319
0
        return std::string(".");
320
0
    }
321
322
0
    std::string osRet(pszFilename, iFileStart);
323
324
0
    if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
325
0
        osRet.pop_back();
326
327
0
    if (nSuffixPos)
328
0
    {
329
0
        osRet += (pszFilename + nSuffixPos);
330
0
    }
331
332
0
    return osRet;
333
0
}
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
0
{
364
0
    return CPLPathReturnTLSString(CPLGetDirnameSafe(pszFilename), __FUNCTION__);
365
0
}
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
0
{
393
0
    const int iFileStart = CPLFindFilenameStart(pszFullFilename);
394
395
0
    return pszFullFilename + iFileStart;
396
0
}
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
0
{
425
0
    const size_t iFileStart =
426
0
        static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
427
428
0
    size_t iExtStart = strlen(pszFullFilename);
429
0
    for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
430
0
         iExtStart--)
431
0
    {
432
0
    }
433
434
0
    if (iExtStart == iFileStart)
435
0
        iExtStart = strlen(pszFullFilename);
436
437
0
    const size_t nLength = iExtStart - iFileStart;
438
0
    return std::string(pszFullFilename + iFileStart, nLength);
439
0
}
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
0
{
470
0
    return CPLPathReturnTLSString(CPLGetBasenameSafe(pszFullFilename),
471
0
                                  __FUNCTION__);
472
0
}
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
0
{
500
0
    if (pszFullFilename[0] == '\0')
501
0
        return std::string();
502
503
0
    size_t iFileStart =
504
0
        static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
505
0
    size_t iExtStart = strlen(pszFullFilename);
506
0
    for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
507
0
         iExtStart--)
508
0
    {
509
0
    }
510
511
0
    if (iExtStart == iFileStart)
512
0
        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
0
    const size_t knMaxExtensionSize = 10;
517
0
    if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
518
0
        return "";
519
520
0
    return std::string(pszFullFilename + iExtStart + 1);
521
0
}
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
0
{
551
0
    return CPLPathReturnTLSString(CPLGetExtensionSafe(pszFullFilename),
552
0
                                  __FUNCTION__);
553
0
}
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
0
{
585
0
#if PATH_MAX
586
0
    const size_t nPathMax = PATH_MAX;
587
#else
588
    const size_t nPathMax = 8192;
589
#endif
590
591
0
    char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
592
0
    if (!pszDirPath)
593
0
        return nullptr;
594
595
0
    return getcwd(pszDirPath, nPathMax);
596
0
}
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
0
{
622
0
    std::string osRet(pszPath);
623
624
    /* -------------------------------------------------------------------- */
625
    /*      First, try and strip off any existing extension.                */
626
    /* -------------------------------------------------------------------- */
627
628
0
    for (size_t i = osRet.size(); i > 0;)
629
0
    {
630
0
        --i;
631
0
        if (osRet[i] == '.')
632
0
        {
633
0
            osRet.resize(i);
634
0
            break;
635
0
        }
636
0
        else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
637
0
        {
638
0
            break;
639
0
        }
640
0
    }
641
642
    /* -------------------------------------------------------------------- */
643
    /*      Append the new extension.                                       */
644
    /* -------------------------------------------------------------------- */
645
0
    osRet += '.';
646
0
    osRet += pszExt;
647
648
0
    return osRet;
649
0
}
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
0
{
671
0
    return CPLPathReturnTLSString(CPLResetExtensionSafe(pszPath, pszExt),
672
0
                                  __FUNCTION__);
673
0
}
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
14.8k
{
712
14.8k
    if (pszBasename[0] == '.' &&
713
0
        (pszBasename[1] == '/' || pszBasename[1] == '\\'))
714
0
        pszBasename += 2;
715
716
14.8k
    const char *pszAddedPathSep = "";
717
14.8k
    const char *pszAddedExtSep = "";
718
719
14.8k
    if (pszPath == nullptr)
720
0
        pszPath = "";
721
14.8k
    size_t nLenPath = strlen(pszPath);
722
723
14.8k
    const char *pszQuestionMark = nullptr;
724
14.8k
    if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
725
0
    {
726
0
        pszQuestionMark = strchr(pszPath, '?');
727
0
        if (pszQuestionMark)
728
0
        {
729
0
            nLenPath = pszQuestionMark - pszPath;
730
0
        }
731
0
        pszAddedPathSep = "/";
732
0
    }
733
734
14.8k
    if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
735
0
        pszBasename[1] == '.' &&
736
0
        (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
737
0
         pszBasename[2] == '/'))
738
0
    {
739
        // "/a/b/" + "..[/something]" --> "/a[/something]"
740
        // "/a/b" + "..[/something]" --> "/a[/something]"
741
0
        if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
742
0
            nLenPath--;
743
0
        while (true)
744
0
        {
745
0
            const char *pszBasenameOri = pszBasename;
746
0
            const size_t nLenPathOri = nLenPath;
747
0
            while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
748
0
                   pszPath[nLenPath - 1] != '/')
749
0
            {
750
0
                nLenPath--;
751
0
            }
752
0
            if (nLenPath == 1 && pszPath[0] == '/')
753
0
            {
754
0
                pszBasename += 2;
755
0
                if (pszBasename[0] == '/' || pszBasename[0] == '\\')
756
0
                    pszBasename++;
757
0
                if (*pszBasename == '.')
758
0
                {
759
0
                    pszBasename = pszBasenameOri;
760
0
                    nLenPath = nLenPathOri;
761
0
                    if (pszAddedPathSep[0] == 0)
762
0
                        pszAddedPathSep = "/";
763
0
                }
764
0
                break;
765
0
            }
766
0
            else if ((nLenPath > 1 && pszPath[0] == '/') ||
767
0
                     (nLenPath > 2 && pszPath[1] == ':') ||
768
0
                     (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
769
0
            {
770
0
                nLenPath--;
771
0
                pszBasename += 2;
772
0
                if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
773
0
                    pszBasename[1] == '.' && pszBasename[2] == '.')
774
0
                {
775
0
                    pszBasename++;
776
0
                }
777
0
                else
778
0
                {
779
0
                    break;
780
0
                }
781
0
            }
782
0
            else
783
0
            {
784
                // cppcheck-suppress redundantAssignment
785
0
                pszBasename = pszBasenameOri;
786
0
                nLenPath = nLenPathOri;
787
0
                if (pszAddedPathSep[0] == 0)
788
0
                    pszAddedPathSep = pszPath[0] == '/'
789
0
                                          ? "/"
790
0
                                          : VSIGetDirectorySeparator(pszPath);
791
0
                break;
792
0
            }
793
0
        }
794
0
    }
795
14.8k
    else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
796
14.8k
             pszPath[nLenPath - 1] != '\\')
797
14.8k
    {
798
14.8k
        if (pszAddedPathSep[0] == 0)
799
14.8k
            pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
800
14.8k
    }
801
802
14.8k
    if (pszExtension == nullptr)
803
14.8k
        pszExtension = "";
804
0
    else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
805
0
        pszAddedExtSep = ".";
806
807
14.8k
    std::string osRes;
808
14.8k
    osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
809
14.8k
                  strlen(pszAddedExtSep) + strlen(pszExtension) +
810
14.8k
                  (pszQuestionMark ? strlen(pszQuestionMark) : 0));
811
14.8k
    osRes.assign(pszPath, nLenPath);
812
14.8k
    osRes += pszAddedPathSep;
813
14.8k
    osRes += pszBasename;
814
14.8k
    osRes += pszAddedExtSep;
815
14.8k
    osRes += pszExtension;
816
817
14.8k
    if (pszQuestionMark)
818
0
    {
819
0
        osRes += pszQuestionMark;
820
0
    }
821
822
14.8k
    return osRes;
823
14.8k
}
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
0
{
863
0
    return CPLPathReturnTLSString(
864
0
        CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
865
0
}
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
0
{
899
    // On case insensitive filesystems, just default to CPLFormFilename().
900
0
    if (!VSIIsCaseSensitiveFS(pszPath))
901
0
        return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
902
903
0
    const char *pszAddedExtSep = "";
904
0
    size_t nLen = strlen(pszBasename) + 2;
905
906
0
    if (pszExtension != nullptr)
907
0
        nLen += strlen(pszExtension);
908
909
0
    char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
910
0
    if (pszFilename == nullptr)
911
0
        return "";
912
913
0
    if (pszExtension == nullptr)
914
0
        pszExtension = "";
915
0
    else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
916
0
        pszAddedExtSep = ".";
917
918
0
    snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
919
0
             pszExtension);
920
921
0
    std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
922
0
    VSIStatBufL sStatBuf;
923
0
    int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
924
925
0
    if (nStatRet != 0)
926
0
    {
927
0
        for (size_t i = 0; pszFilename[i] != '\0'; i++)
928
0
        {
929
0
            pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
930
0
        }
931
932
0
        std::string osTmpPath(
933
0
            CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
934
0
        nStatRet =
935
0
            VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
936
0
        if (nStatRet == 0)
937
0
            osRet = std::move(osTmpPath);
938
0
    }
939
940
0
    if (nStatRet != 0)
941
0
    {
942
0
        for (size_t i = 0; pszFilename[i] != '\0'; i++)
943
0
        {
944
0
            pszFilename[i] = static_cast<char>(
945
0
                CPLTolower(static_cast<unsigned char>(pszFilename[i])));
946
0
        }
947
948
0
        std::string osTmpPath(
949
0
            CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
950
0
        nStatRet =
951
0
            VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
952
0
        if (nStatRet == 0)
953
0
            osRet = std::move(osTmpPath);
954
0
    }
955
956
0
    if (nStatRet != 0)
957
0
        osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
958
959
0
    CPLFree(pszFilename);
960
961
0
    return osRet;
962
0
}
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
0
{
1038
0
    if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
1039
0
        !CPLIsFilenameRelative(pszSecondaryFilename))
1040
0
    {
1041
0
        return pszSecondaryFilename;
1042
0
    }
1043
1044
0
    std::string osRes(pszProjectDir);
1045
0
    if (osRes.back() != '/' && osRes.back() != '\\')
1046
0
    {
1047
0
        osRes += VSIGetDirectorySeparator(pszProjectDir);
1048
0
    }
1049
1050
0
    osRes += pszSecondaryFilename;
1051
0
    return osRes;
1052
0
}
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
14.8k
{
1115
14.8k
    if ((pszFilename[0] != '\0' &&
1116
14.8k
         (STARTS_WITH(pszFilename + 1, ":\\") ||
1117
14.8k
          STARTS_WITH(pszFilename + 1, ":/") ||
1118
14.8k
          strstr(pszFilename + 1, "://")  // http://, ftp:// etc....
1119
14.8k
          )) ||
1120
14.8k
        STARTS_WITH(pszFilename, "\\\\?\\")  // Windows extended Length Path.
1121
14.8k
        || pszFilename[0] == '\\' || pszFilename[0] == '/')
1122
0
        return FALSE;
1123
1124
14.8k
    return TRUE;
1125
14.8k
}
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
0
{
1158
    /* -------------------------------------------------------------------- */
1159
    /*      If we don't have a basedir, then we can't relativize the path.  */
1160
    /* -------------------------------------------------------------------- */
1161
0
    if (pszBaseDir == nullptr)
1162
0
    {
1163
0
        if (pbGotRelative != nullptr)
1164
0
            *pbGotRelative = FALSE;
1165
1166
0
        return pszTarget;
1167
0
    }
1168
1169
0
    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
0
    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
0
    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
0
    if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
1201
0
        (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
1202
0
    {
1203
0
        if (pbGotRelative != nullptr)
1204
0
            *pbGotRelative = FALSE;
1205
1206
0
        return pszTarget;
1207
0
    }
1208
1209
    /* -------------------------------------------------------------------- */
1210
    /*      We have a relative path.  Strip it off to get a string to       */
1211
    /*      return.                                                         */
1212
    /* -------------------------------------------------------------------- */
1213
0
    if (pbGotRelative != nullptr)
1214
0
        *pbGotRelative = TRUE;
1215
1216
0
    return pszTarget + nBasePathLen + 1;
1217
0
}
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
0
{
1248
0
    std::string osRes(pszPath);
1249
0
    if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
1250
0
        osRes.pop_back();
1251
0
    return osRes;
1252
0
}
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
14.8k
{
1429
14.8k
    const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1430
1431
14.8k
    if (pszDir == nullptr)
1432
14.8k
        pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1433
1434
14.8k
    if (pszDir == nullptr)
1435
14.8k
        pszDir = CPLGetConfigOption("TEMP", nullptr);
1436
1437
14.8k
    if (pszDir == nullptr)
1438
14.8k
        pszDir = ".";
1439
1440
14.8k
    if (pszStem == nullptr)
1441
14.8k
        pszStem = "";
1442
1443
14.8k
    static int nTempFileCounter = 0;
1444
14.8k
    CPLString osFilename;
1445
14.8k
    osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1446
14.8k
                      CPLAtomicInc(&nTempFileCounter));
1447
1448
14.8k
    return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
1449
14.8k
}
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
14.8k
{
1472
14.8k
    return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
1473
14.8k
                                  __FUNCTION__);
1474
14.8k
}
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
0
{
1495
0
    if (!STARTS_WITH_CI(pszFilename, "~/"))
1496
0
        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
0
{
1571
0
    std::string osRet(pszName);
1572
0
    for (char &ch : osRet)
1573
0
    {
1574
        // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
1575
0
        if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
1576
0
            ch == '\\' || ch == '?' || ch == '*')
1577
0
        {
1578
0
            ch = '_';
1579
0
        }
1580
0
    }
1581
0
    return osRet;
1582
0
}
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
0
{
1629
0
    const char *pszDotDot = strstr(pszFilename, "..");
1630
0
    if (pszDotDot &&
1631
0
        (pszDotDot == pszFilename ||
1632
0
         pszFilename[pszDotDot - pszFilename - 1] == '/' ||
1633
0
         pszFilename[pszDotDot - pszFilename - 1] == '\\') &&
1634
0
        (pszDotDot[2] == 0 || pszDotDot[2] == '/' || pszDotDot[2] == '\\'))
1635
0
    {
1636
0
        if (CPLTestBool(CPLGetConfigOption(
1637
0
                "CPL_ENABLE_PATH_TRAVERSAL_DETECTION", "YES")))
1638
0
        {
1639
0
            CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
1640
0
            return true;
1641
0
        }
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
0
    }
1650
0
    return false;
1651
0
}
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
}