Coverage Report

Created: 2025-06-13 06:18

/src/gdal/port/cpl_path.cpp
Line
Count
Source (jump to first uncovered line)
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
3.15k
{
58
3.15k
    int bMemoryError = FALSE;
59
3.15k
    char *pachBufRingInfo =
60
3.15k
        static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
61
3.15k
    if (bMemoryError)
62
0
        return nullptr;
63
3.15k
    if (pachBufRingInfo == nullptr)
64
1
    {
65
1
        pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
66
1
            1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
67
1
        if (pachBufRingInfo == nullptr)
68
0
            return nullptr;
69
1
        CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
70
1
    }
71
72
    /* -------------------------------------------------------------------- */
73
    /*      Work out which string in the "ring" we want to use this         */
74
    /*      time.                                                           */
75
    /* -------------------------------------------------------------------- */
76
3.15k
    int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
77
3.15k
    const size_t nOffset =
78
3.15k
        sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
79
3.15k
    char *pachBuffer = pachBufRingInfo + nOffset;
80
81
3.15k
    *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
82
83
3.15k
    return pachBuffer;
84
3.15k
}
85
86
/************************************************************************/
87
/*                        CPLPathReturnTLSString()                      */
88
/************************************************************************/
89
90
static const char *CPLPathReturnTLSString(const std::string &osRes,
91
                                          const char *pszFuncName)
92
3.15k
{
93
3.15k
    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
3.15k
    char *pszStaticResult = CPLGetStaticResult();
101
3.15k
    if (pszStaticResult == nullptr)
102
0
        return CPLStaticBufferTooSmall(pszStaticResult);
103
3.15k
    memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
104
3.15k
    return pszStaticResult;
105
3.15k
}
106
107
/************************************************************************/
108
/*                        CPLFindFilenameStart()                        */
109
/************************************************************************/
110
111
static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
112
113
0
{
114
0
    size_t iFileStart = nStart ? nStart : strlen(pszFilename);
115
116
0
    for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
117
0
           pszFilename[iFileStart - 1] != '\\';
118
0
         iFileStart--)
119
0
    {
120
0
    }
121
122
0
    return static_cast<int>(iFileStart);
123
0
}
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
0
{
154
0
    size_t nSuffixPos = 0;
155
0
    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
0
    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
0
    const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
195
0
    if (iFileStart == 0)
196
0
    {
197
0
        return std::string();
198
0
    }
199
200
0
    std::string osRet(pszFilename, iFileStart);
201
202
0
    if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
203
0
        osRet.pop_back();
204
205
0
    if (nSuffixPos)
206
0
    {
207
0
        osRet += (pszFilename + nSuffixPos);
208
0
    }
209
210
0
    return osRet;
211
0
}
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
3.15k
{
712
3.15k
    if (pszBasename[0] == '.' &&
713
3.15k
        (pszBasename[1] == '/' || pszBasename[1] == '\\'))
714
0
        pszBasename += 2;
715
716
3.15k
    const char *pszAddedPathSep = "";
717
3.15k
    const char *pszAddedExtSep = "";
718
719
3.15k
    if (pszPath == nullptr)
720
0
        pszPath = "";
721
3.15k
    size_t nLenPath = strlen(pszPath);
722
723
3.15k
    const char *pszQuestionMark = nullptr;
724
3.15k
    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
3.15k
    if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
735
3.15k
        pszBasename[1] == '.' &&
736
3.15k
        (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
                            pszPath[0] == '/'
764
0
                                ? "/"
765
0
                                : VSIGetDirectorySeparator(pszPath);
766
0
                }
767
0
                break;
768
0
            }
769
0
            else if ((nLenPath > 1 && pszPath[0] == '/') ||
770
0
                     (nLenPath > 2 && pszPath[1] == ':') ||
771
0
                     (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
772
0
            {
773
0
                nLenPath--;
774
0
                pszBasename += 2;
775
0
                if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
776
0
                    pszBasename[1] == '.' && pszBasename[2] == '.')
777
0
                {
778
0
                    pszBasename++;
779
0
                }
780
0
                else
781
0
                {
782
0
                    break;
783
0
                }
784
0
            }
785
0
            else
786
0
            {
787
                // cppcheck-suppress redundantAssignment
788
0
                pszBasename = pszBasenameOri;
789
0
                nLenPath = nLenPathOri;
790
0
                if (pszAddedPathSep[0] == 0)
791
0
                    pszAddedPathSep = pszPath[0] == '/'
792
0
                                          ? "/"
793
0
                                          : VSIGetDirectorySeparator(pszPath);
794
0
                break;
795
0
            }
796
0
        }
797
0
    }
798
3.15k
    else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
799
3.15k
             pszPath[nLenPath - 1] != '\\')
800
3.13k
    {
801
3.13k
        if (pszAddedPathSep[0] == 0)
802
3.13k
            pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
803
3.13k
    }
804
805
3.15k
    if (pszExtension == nullptr)
806
3.15k
        pszExtension = "";
807
0
    else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
808
0
        pszAddedExtSep = ".";
809
810
3.15k
    std::string osRes;
811
3.15k
    osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
812
3.15k
                  strlen(pszAddedExtSep) + strlen(pszExtension) +
813
3.15k
                  (pszQuestionMark ? strlen(pszQuestionMark) : 0));
814
3.15k
    osRes.assign(pszPath, nLenPath);
815
3.15k
    osRes += pszAddedPathSep;
816
3.15k
    osRes += pszBasename;
817
3.15k
    osRes += pszAddedExtSep;
818
3.15k
    osRes += pszExtension;
819
820
3.15k
    if (pszQuestionMark)
821
0
    {
822
0
        osRes += pszQuestionMark;
823
0
    }
824
825
3.15k
    return osRes;
826
3.15k
}
827
828
/************************************************************************/
829
/*                          CPLFormFilename()                           */
830
/************************************************************************/
831
832
/**
833
 * Build a full file path from a passed path, file basename and extension.
834
 *
835
 * The path, and extension are optional.  The basename may in fact contain
836
 * an extension if desired.
837
 *
838
 * \code{.cpp}
839
 * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
840
 * CPLFormFilename(NULL,"def", NULL ) == "def"
841
 * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
842
 * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
843
 * CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
844
 * \endcode
845
 *
846
 * @param pszPath directory path to the directory containing the file.  This
847
 * may be relative or absolute, and may have a trailing path separator or
848
 * not.  May be NULL.
849
 *
850
 * @param pszBasename file basename.  May optionally have path and/or
851
 * extension.  Must *NOT* be NULL.
852
 *
853
 * @param pszExtension file extension, optionally including the period.  May
854
 * be NULL.
855
 *
856
 * @return a fully formed filename in an internal static string.  Do not
857
 * modify or free the returned string.  The string may be destroyed by the
858
 * next CPL call.
859
 *
860
 * @deprecated If using C++, prefer using CPLFormFilenameSafe() instead
861
 */
862
const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
863
                            const char *pszExtension)
864
865
0
{
866
0
    return CPLPathReturnTLSString(
867
0
        CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
868
0
}
869
870
/************************************************************************/
871
/*                       CPLFormCIFilenameSafe()                        */
872
/************************************************************************/
873
874
/**
875
 * Case insensitive file searching, returning full path.
876
 *
877
 * This function tries to return the path to a file regardless of
878
 * whether the file exactly matches the basename, and extension case, or
879
 * is all upper case, or all lower case.  The path is treated as case
880
 * sensitive.  This function is equivalent to CPLFormFilename() on
881
 * case insensitive file systems (like Windows).
882
 *
883
 * @param pszPath directory path to the directory containing the file.  This
884
 * may be relative or absolute, and may have a trailing path separator or
885
 * not.  May be NULL.
886
 *
887
 * @param pszBasename file basename.  May optionally have path and/or
888
 * extension.  May not be NULL.
889
 *
890
 * @param pszExtension file extension, optionally including the period.  May
891
 * be NULL.
892
 *
893
 * @return a fully formed filename.
894
 *
895
 * @since 3.11
896
 */
897
898
std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
899
                                  const char *pszExtension)
900
901
0
{
902
    // On case insensitive filesystems, just default to CPLFormFilename().
903
0
    if (!VSIIsCaseSensitiveFS(pszPath))
904
0
        return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
905
906
0
    const char *pszAddedExtSep = "";
907
0
    size_t nLen = strlen(pszBasename) + 2;
908
909
0
    if (pszExtension != nullptr)
910
0
        nLen += strlen(pszExtension);
911
912
0
    char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
913
0
    if (pszFilename == nullptr)
914
0
        return "";
915
916
0
    if (pszExtension == nullptr)
917
0
        pszExtension = "";
918
0
    else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
919
0
        pszAddedExtSep = ".";
920
921
0
    snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
922
0
             pszExtension);
923
924
0
    std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
925
0
    VSIStatBufL sStatBuf;
926
0
    int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
927
928
0
    if (nStatRet != 0)
929
0
    {
930
0
        for (size_t i = 0; pszFilename[i] != '\0'; i++)
931
0
        {
932
0
            pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
933
0
        }
934
935
0
        std::string osTmpPath(
936
0
            CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
937
0
        nStatRet =
938
0
            VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
939
0
        if (nStatRet == 0)
940
0
            osRet = std::move(osTmpPath);
941
0
    }
942
943
0
    if (nStatRet != 0)
944
0
    {
945
0
        for (size_t i = 0; pszFilename[i] != '\0'; i++)
946
0
        {
947
0
            pszFilename[i] = static_cast<char>(
948
0
                CPLTolower(static_cast<unsigned char>(pszFilename[i])));
949
0
        }
950
951
0
        std::string osTmpPath(
952
0
            CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
953
0
        nStatRet =
954
0
            VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
955
0
        if (nStatRet == 0)
956
0
            osRet = std::move(osTmpPath);
957
0
    }
958
959
0
    if (nStatRet != 0)
960
0
        osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
961
962
0
    CPLFree(pszFilename);
963
964
0
    return osRet;
965
0
}
966
967
/************************************************************************/
968
/*                          CPLFormCIFilename()                         */
969
/************************************************************************/
970
971
/**
972
 * Case insensitive file searching, returning full path.
973
 *
974
 * This function tries to return the path to a file regardless of
975
 * whether the file exactly matches the basename, and extension case, or
976
 * is all upper case, or all lower case.  The path is treated as case
977
 * sensitive.  This function is equivalent to CPLFormFilename() on
978
 * case insensitive file systems (like Windows).
979
 *
980
 * @param pszPath directory path to the directory containing the file.  This
981
 * may be relative or absolute, and may have a trailing path separator or
982
 * not.  May be NULL.
983
 *
984
 * @param pszBasename file basename.  May optionally have path and/or
985
 * extension.  May not be NULL.
986
 *
987
 * @param pszExtension file extension, optionally including the period.  May
988
 * be NULL.
989
 *
990
 * @return a fully formed filename in an internal static string.  Do not
991
 * modify or free the returned string.  The string may be destroyed by the
992
 * next CPL call.
993
 *
994
 * @deprecated If using C++, prefer using CPLFormCIFilenameSafe() instead
995
*/
996
997
const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
998
                              const char *pszExtension)
999
1000
0
{
1001
0
    return CPLPathReturnTLSString(
1002
0
        CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
1003
0
        __FUNCTION__);
1004
0
}
1005
1006
/************************************************************************/
1007
/*                   CPLProjectRelativeFilenameSafe()                   */
1008
/************************************************************************/
1009
1010
/**
1011
 * Find a file relative to a project file.
1012
 *
1013
 * Given the path to a "project" directory, and a path to a secondary file
1014
 * referenced from that project, build a path to the secondary file
1015
 * that the current application can use.  If the secondary path is already
1016
 * absolute, rather than relative, then it will be returned unaltered.
1017
 *
1018
 * Examples:
1019
 * \code{.cpp}
1020
 * CPLProjectRelativeFilenameSafe("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1021
 * CPLProjectRelativeFilenameSafe("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1022
 * CPLProjectRelativeFilenameSafe("/xy", "abc.gif") == "/xy/abc.gif"
1023
 * CPLProjectRelativeFilenameSafe("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1024
 * CPLProjectRelativeFilenameSafe("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1025
 * \endcode
1026
 *
1027
 * @param pszProjectDir the directory relative to which the secondary files
1028
 * path should be interpreted.
1029
 * @param pszSecondaryFilename the filename (potentially with path) that
1030
 * is to be interpreted relative to the project directory.
1031
 *
1032
 * @return a composed path to the secondary file.
1033
 *
1034
 * @since 3.11
1035
 */
1036
1037
std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
1038
                                           const char *pszSecondaryFilename)
1039
1040
0
{
1041
0
    if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
1042
0
        !CPLIsFilenameRelative(pszSecondaryFilename))
1043
0
    {
1044
0
        return pszSecondaryFilename;
1045
0
    }
1046
1047
0
    std::string osRes(pszProjectDir);
1048
0
    if (osRes.back() != '/' && osRes.back() != '\\')
1049
0
    {
1050
0
        osRes += VSIGetDirectorySeparator(pszProjectDir);
1051
0
    }
1052
1053
0
    osRes += pszSecondaryFilename;
1054
0
    return osRes;
1055
0
}
1056
1057
/************************************************************************/
1058
/*                     CPLProjectRelativeFilename()                     */
1059
/************************************************************************/
1060
1061
/**
1062
 * Find a file relative to a project file.
1063
 *
1064
 * Given the path to a "project" directory, and a path to a secondary file
1065
 * referenced from that project, build a path to the secondary file
1066
 * that the current application can use.  If the secondary path is already
1067
 * absolute, rather than relative, then it will be returned unaltered.
1068
 *
1069
 * Examples:
1070
 * \code{.cpp}
1071
 * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1072
 * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1073
 * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
1074
 * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1075
 * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1076
 * \endcode
1077
 *
1078
 * @param pszProjectDir the directory relative to which the secondary files
1079
 * path should be interpreted.
1080
 * @param pszSecondaryFilename the filename (potentially with path) that
1081
 * is to be interpreted relative to the project directory.
1082
 *
1083
 * @return a composed path to the secondary file.  The returned string is
1084
 * internal and should not be altered, freed, or depending on past the next
1085
 * CPL call.
1086
 *
1087
 * @deprecated If using C++, prefer using CPLProjectRelativeFilenameSafe() instead
1088
 */
1089
1090
const char *CPLProjectRelativeFilename(const char *pszProjectDir,
1091
                                       const char *pszSecondaryFilename)
1092
1093
0
{
1094
0
    return CPLPathReturnTLSString(
1095
0
        CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
1096
0
        __FUNCTION__);
1097
0
}
1098
1099
/************************************************************************/
1100
/*                       CPLIsFilenameRelative()                        */
1101
/************************************************************************/
1102
1103
/**
1104
 * Is filename relative or absolute?
1105
 *
1106
 * The test is filesystem convention agnostic.  That is it will test for
1107
 * Unix style and windows style path conventions regardless of the actual
1108
 * system in use.
1109
 *
1110
 * @param pszFilename the filename with path to test.
1111
 *
1112
 * @return TRUE if the filename is relative or FALSE if it is absolute.
1113
 */
1114
1115
int CPLIsFilenameRelative(const char *pszFilename)
1116
1117
3.15k
{
1118
3.15k
    if ((pszFilename[0] != '\0' &&
1119
3.15k
         (STARTS_WITH(pszFilename + 1, ":\\") ||
1120
3.13k
          STARTS_WITH(pszFilename + 1, ":/") ||
1121
3.13k
          strstr(pszFilename + 1, "://")  // http://, ftp:// etc....
1122
3.13k
          )) ||
1123
3.15k
        STARTS_WITH(pszFilename, "\\\\?\\")  // Windows extended Length Path.
1124
3.15k
        || pszFilename[0] == '\\' || pszFilename[0] == '/')
1125
0
        return FALSE;
1126
1127
3.15k
    return TRUE;
1128
3.15k
}
1129
1130
/************************************************************************/
1131
/*                       CPLExtractRelativePath()                       */
1132
/************************************************************************/
1133
1134
/**
1135
 * Get relative path from directory to target file.
1136
 *
1137
 * Computes a relative path for pszTarget relative to pszBaseDir.
1138
 * Currently this only works if they share a common base path.  The returned
1139
 * path is normally into the pszTarget string.  It should only be considered
1140
 * valid as long as pszTarget is valid or till the next call to
1141
 * this function, whichever comes first.
1142
 *
1143
 * @param pszBaseDir the name of the directory relative to which the path
1144
 * should be computed.  pszBaseDir may be NULL in which case the original
1145
 * target is returned without relativizing.
1146
 *
1147
 * @param pszTarget the filename to be changed to be relative to pszBaseDir.
1148
 *
1149
 * @param pbGotRelative Pointer to location in which a flag is placed
1150
 * indicating that the returned path is relative to the basename (TRUE) or
1151
 * not (FALSE).  This pointer may be NULL if flag is not desired.
1152
 *
1153
 * @return an adjusted path or the original if it could not be made relative
1154
 * to the pszBaseFile's path.
1155
 **/
1156
1157
const char *CPLExtractRelativePath(const char *pszBaseDir,
1158
                                   const char *pszTarget, int *pbGotRelative)
1159
1160
0
{
1161
    /* -------------------------------------------------------------------- */
1162
    /*      If we don't have a basedir, then we can't relativize the path.  */
1163
    /* -------------------------------------------------------------------- */
1164
0
    if (pszBaseDir == nullptr)
1165
0
    {
1166
0
        if (pbGotRelative != nullptr)
1167
0
            *pbGotRelative = FALSE;
1168
1169
0
        return pszTarget;
1170
0
    }
1171
1172
0
    const size_t nBasePathLen = strlen(pszBaseDir);
1173
1174
    /* -------------------------------------------------------------------- */
1175
    /*      One simple case is when the base dir is '.' and the target      */
1176
    /*      filename is relative.                                           */
1177
    /* -------------------------------------------------------------------- */
1178
0
    if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
1179
0
        CPLIsFilenameRelative(pszTarget))
1180
0
    {
1181
0
        if (pbGotRelative != nullptr)
1182
0
            *pbGotRelative = TRUE;
1183
1184
0
        return pszTarget;
1185
0
    }
1186
1187
    /* -------------------------------------------------------------------- */
1188
    /*      By this point, if we don't have a base path, we can't have a    */
1189
    /*      meaningful common prefix.                                       */
1190
    /* -------------------------------------------------------------------- */
1191
0
    if (nBasePathLen == 0)
1192
0
    {
1193
0
        if (pbGotRelative != nullptr)
1194
0
            *pbGotRelative = FALSE;
1195
1196
0
        return pszTarget;
1197
0
    }
1198
1199
    /* -------------------------------------------------------------------- */
1200
    /*      If we don't have a common path prefix, then we can't get a      */
1201
    /*      relative path.                                                  */
1202
    /* -------------------------------------------------------------------- */
1203
0
    if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
1204
0
        (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
1205
0
    {
1206
0
        if (pbGotRelative != nullptr)
1207
0
            *pbGotRelative = FALSE;
1208
1209
0
        return pszTarget;
1210
0
    }
1211
1212
    /* -------------------------------------------------------------------- */
1213
    /*      We have a relative path.  Strip it off to get a string to       */
1214
    /*      return.                                                         */
1215
    /* -------------------------------------------------------------------- */
1216
0
    if (pbGotRelative != nullptr)
1217
0
        *pbGotRelative = TRUE;
1218
1219
0
    return pszTarget + nBasePathLen + 1;
1220
0
}
1221
1222
/************************************************************************/
1223
/*                      CPLCleanTrailingSlashSafe()                     */
1224
/************************************************************************/
1225
1226
/**
1227
 * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1228
 *
1229
 * Returns a string containing the portion of the passed path string with
1230
 * trailing slash removed. If there is no path in the passed filename
1231
 * an empty string will be returned (not NULL).
1232
 *
1233
 * \code{.cpp}
1234
 * CPLCleanTrailingSlashSafe( "abc/def/" ) == "abc/def"
1235
 * CPLCleanTrailingSlashSafe( "abc/def" ) == "abc/def"
1236
 * CPLCleanTrailingSlashSafe( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1237
 * CPLCleanTrailingSlashSafe( "c:\\abc\\def" ) == "c:\\abc\\def"
1238
 * CPLCleanTrailingSlashSafe( "abc" ) == "abc"
1239
 * \endcode
1240
 *
1241
 * @param pszPath the path to be cleaned up
1242
 *
1243
 * @return Path
1244
 *
1245
 * @since 3.11
1246
 */
1247
1248
std::string CPLCleanTrailingSlashSafe(const char *pszPath)
1249
1250
0
{
1251
0
    std::string osRes(pszPath);
1252
0
    if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
1253
0
        osRes.pop_back();
1254
0
    return osRes;
1255
0
}
1256
1257
/************************************************************************/
1258
/*                            CPLCleanTrailingSlash()                   */
1259
/************************************************************************/
1260
1261
/**
1262
 * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1263
 *
1264
 * Returns a string containing the portion of the passed path string with
1265
 * trailing slash removed. If there is no path in the passed filename
1266
 * an empty string will be returned (not NULL).
1267
 *
1268
 * \code{.cpp}
1269
 * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
1270
 * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
1271
 * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1272
 * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
1273
 * CPLCleanTrailingSlash( "abc" ) == "abc"
1274
 * \endcode
1275
 *
1276
 * @param pszPath the path to be cleaned up
1277
 *
1278
 * @return Path in an internal string which must not be freed.  The string
1279
 * may be destroyed by the next CPL filename handling call.
1280
 *
1281
 * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1282
 */
1283
1284
const char *CPLCleanTrailingSlash(const char *pszPath)
1285
1286
0
{
1287
0
    return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
1288
0
                                  __FUNCTION__);
1289
0
}
1290
1291
/************************************************************************/
1292
/*                       CPLCorrespondingPaths()                        */
1293
/************************************************************************/
1294
1295
/**
1296
 * Identify corresponding paths.
1297
 *
1298
 * Given a prototype old and new filename this function will attempt
1299
 * to determine corresponding names for a set of other old filenames that
1300
 * will rename them in a similar manner.  This correspondence assumes there
1301
 * are two possibly kinds of renaming going on.  A change of path, and a
1302
 * change of filename stem.
1303
 *
1304
 * If a consistent renaming cannot be established for all the files this
1305
 * function will return indicating an error.
1306
 *
1307
 * The returned file list becomes owned by the caller and should be destroyed
1308
 * with CSLDestroy().
1309
 *
1310
 * @param pszOldFilename path to old prototype file.
1311
 * @param pszNewFilename path to new prototype file.
1312
 * @param papszFileList list of other files associated with pszOldFilename to
1313
 * rename similarly.
1314
 *
1315
 * @return a list of files corresponding to papszFileList but renamed to
1316
 * correspond to pszNewFilename.
1317
 */
1318
1319
char **CPLCorrespondingPaths(const char *pszOldFilename,
1320
                             const char *pszNewFilename, char **papszFileList)
1321
1322
0
{
1323
0
    if (CSLCount(papszFileList) == 0)
1324
0
        return nullptr;
1325
1326
    /* -------------------------------------------------------------------- */
1327
    /*      There is a special case for a one item list which exactly       */
1328
    /*      matches the old name, to rename to the new name.                */
1329
    /* -------------------------------------------------------------------- */
1330
0
    if (CSLCount(papszFileList) == 1 &&
1331
0
        strcmp(pszOldFilename, papszFileList[0]) == 0)
1332
0
    {
1333
0
        return CSLAddString(nullptr, pszNewFilename);
1334
0
    }
1335
1336
0
    const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
1337
0
    const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
1338
0
    const std::string osNewBasename = CPLGetBasenameSafe(pszNewFilename);
1339
1340
    /* -------------------------------------------------------------------- */
1341
    /*      If the basename is changing, verify that all source files       */
1342
    /*      have the same starting basename.                                */
1343
    /* -------------------------------------------------------------------- */
1344
0
    if (osOldBasename != osNewBasename)
1345
0
    {
1346
0
        for (int i = 0; papszFileList[i] != nullptr; i++)
1347
0
        {
1348
0
            if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
1349
0
                continue;
1350
1351
0
            const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
1352
0
            const std::string osFileName = CPLGetFilename(papszFileList[i]);
1353
1354
0
            if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
1355
0
                        osOldBasename.size()) ||
1356
0
                !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
1357
0
                osFileName[osOldBasename.size()] != '.')
1358
0
            {
1359
0
                CPLError(CE_Failure, CPLE_AppDefined,
1360
0
                         "Unable to rename fileset due irregular basenames.");
1361
0
                return nullptr;
1362
0
            }
1363
0
        }
1364
0
    }
1365
1366
    /* -------------------------------------------------------------------- */
1367
    /*      If the filename portions differs, ensure they only differ in    */
1368
    /*      basename.                                                       */
1369
    /* -------------------------------------------------------------------- */
1370
0
    if (osOldBasename != osNewBasename)
1371
0
    {
1372
0
        const std::string osOldExtra =
1373
0
            CPLGetFilename(pszOldFilename) + osOldBasename.size();
1374
0
        const std::string osNewExtra =
1375
0
            CPLGetFilename(pszNewFilename) + osNewBasename.size();
1376
1377
0
        if (osOldExtra != osNewExtra)
1378
0
        {
1379
0
            CPLError(CE_Failure, CPLE_AppDefined,
1380
0
                     "Unable to rename fileset due to irregular filename "
1381
0
                     "correspondence.");
1382
0
            return nullptr;
1383
0
        }
1384
0
    }
1385
1386
    /* -------------------------------------------------------------------- */
1387
    /*      Generate the new filenames.                                     */
1388
    /* -------------------------------------------------------------------- */
1389
0
    char **papszNewList = nullptr;
1390
0
    const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
1391
1392
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
1393
0
    {
1394
0
        const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
1395
1396
0
        const std::string osNewFilename =
1397
0
            osOldBasename == osNewBasename
1398
0
                ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
1399
0
                                      nullptr)
1400
0
                : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
1401
0
                                      osOldFilename.c_str() +
1402
0
                                          osOldBasename.size());
1403
1404
0
        papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
1405
0
    }
1406
1407
0
    return papszNewList;
1408
0
}
1409
1410
/************************************************************************/
1411
/*                   CPLGenerateTempFilenameSafe()                      */
1412
/************************************************************************/
1413
1414
/**
1415
 * Generate temporary file name.
1416
 *
1417
 * Returns a filename that may be used for a temporary file.  The location
1418
 * of the file tries to follow operating system semantics but may be
1419
 * forced via the CPL_TMPDIR configuration option.
1420
 *
1421
 * @param pszStem if non-NULL this will be part of the filename.
1422
 *
1423
 * @return a filename
1424
 *
1425
 * @since 3.11
1426
 */
1427
1428
std::string CPLGenerateTempFilenameSafe(const char *pszStem)
1429
1430
3.15k
{
1431
3.15k
    const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1432
1433
3.15k
    if (pszDir == nullptr)
1434
3.15k
        pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1435
1436
3.15k
    if (pszDir == nullptr)
1437
3.15k
        pszDir = CPLGetConfigOption("TEMP", nullptr);
1438
1439
3.15k
    if (pszDir == nullptr)
1440
3.13k
        pszDir = ".";
1441
1442
3.15k
    if (pszStem == nullptr)
1443
3.15k
        pszStem = "";
1444
1445
3.15k
    static int nTempFileCounter = 0;
1446
3.15k
    CPLString osFilename;
1447
3.15k
    osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1448
3.15k
                      CPLAtomicInc(&nTempFileCounter));
1449
1450
3.15k
    return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
1451
3.15k
}
1452
1453
/************************************************************************/
1454
/*                      CPLGenerateTempFilename()                       */
1455
/************************************************************************/
1456
1457
/**
1458
 * Generate temporary file name.
1459
 *
1460
 * Returns a filename that may be used for a temporary file.  The location
1461
 * of the file tries to follow operating system semantics but may be
1462
 * forced via the CPL_TMPDIR configuration option.
1463
 *
1464
 * @param pszStem if non-NULL this will be part of the filename.
1465
 *
1466
 * @return a filename which is valid till the next CPL call in this thread.
1467
 *
1468
 * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1469
 */
1470
1471
const char *CPLGenerateTempFilename(const char *pszStem)
1472
1473
3.15k
{
1474
3.15k
    return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
1475
3.15k
                                  __FUNCTION__);
1476
3.15k
}
1477
1478
/************************************************************************/
1479
/*                        CPLExpandTildeSafe()                          */
1480
/************************************************************************/
1481
1482
/**
1483
 * Expands ~/ at start of filename.
1484
 *
1485
 * Assumes that the HOME configuration option is defined.
1486
 *
1487
 * @param pszFilename filename potentially starting with ~/
1488
 *
1489
 * @return an expanded filename.
1490
 *
1491
 * @since GDAL 3.11
1492
 */
1493
1494
std::string CPLExpandTildeSafe(const char *pszFilename)
1495
1496
0
{
1497
0
    if (!STARTS_WITH_CI(pszFilename, "~/"))
1498
0
        return pszFilename;
1499
1500
0
    const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1501
0
    if (pszHome == nullptr)
1502
0
        return pszFilename;
1503
1504
0
    return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
1505
0
}
1506
1507
/************************************************************************/
1508
/*                         CPLExpandTilde()                             */
1509
/************************************************************************/
1510
1511
/**
1512
 * Expands ~/ at start of filename.
1513
 *
1514
 * Assumes that the HOME configuration option is defined.
1515
 *
1516
 * @param pszFilename filename potentially starting with ~/
1517
 *
1518
 * @return an expanded filename.
1519
 *
1520
 * @since GDAL 2.2
1521
 *
1522
 * @deprecated If using C++, prefer using CPLExpandTildeSafe() instead
1523
 */
1524
1525
const char *CPLExpandTilde(const char *pszFilename)
1526
1527
0
{
1528
0
    return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
1529
0
                                  __FUNCTION__);
1530
0
}
1531
1532
/************************************************************************/
1533
/*                         CPLGetHomeDir()                              */
1534
/************************************************************************/
1535
1536
/**
1537
 * Return the path to the home directory
1538
 *
1539
 * That is the value of the USERPROFILE environment variable on Windows,
1540
 * or HOME on other platforms.
1541
 *
1542
 * @return the home directory, or NULL.
1543
 *
1544
 * @since GDAL 2.3
1545
 */
1546
1547
const char *CPLGetHomeDir()
1548
1549
0
{
1550
#ifdef _WIN32
1551
    return CPLGetConfigOption("USERPROFILE", nullptr);
1552
#else
1553
0
    return CPLGetConfigOption("HOME", nullptr);
1554
0
#endif
1555
0
}
1556
1557
/************************************************************************/
1558
/*                      CPLLaunderForFilenameSafe()                     */
1559
/************************************************************************/
1560
1561
/**
1562
 * Launder a string to be compatible of a filename.
1563
 *
1564
 * @param pszName The input string to launder.
1565
 * @param pszOutputPath The directory where the file would be created.
1566
 *                      Unused for now. May be NULL.
1567
 * @return the laundered name.
1568
 *
1569
 * @since GDAL 3.11
1570
 */
1571
1572
std::string CPLLaunderForFilenameSafe(const char *pszName,
1573
                                      CPL_UNUSED const char *pszOutputPath)
1574
0
{
1575
0
    std::string osRet(pszName);
1576
0
    for (char &ch : osRet)
1577
0
    {
1578
        // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
1579
0
        if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
1580
0
            ch == '\\' || ch == '?' || ch == '*')
1581
0
        {
1582
0
            ch = '_';
1583
0
        }
1584
0
    }
1585
0
    return osRet;
1586
0
}
1587
1588
/************************************************************************/
1589
/*                        CPLLaunderForFilename()                       */
1590
/************************************************************************/
1591
1592
/**
1593
 * Launder a string to be compatible of a filename.
1594
 *
1595
 * @param pszName The input string to launder.
1596
 * @param pszOutputPath The directory where the file would be created.
1597
 *                      Unused for now. May be NULL.
1598
 * @return the laundered name.
1599
 *
1600
 * @since GDAL 3.1
1601
 *
1602
 * @deprecated If using C++, prefer using CPLLaunderForFilenameSafe() instead
1603
 */
1604
1605
const char *CPLLaunderForFilename(const char *pszName,
1606
                                  const char *pszOutputPath)
1607
0
{
1608
0
    return CPLPathReturnTLSString(
1609
0
        CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
1610
0
}