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