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 499 : static char *CPLGetStaticResult()
56 :
57 : {
58 499 : int bMemoryError = FALSE;
59 : char *pachBufRingInfo =
60 499 : static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
61 499 : if (bMemoryError)
62 0 : return nullptr;
63 499 : 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 499 : int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
77 499 : const size_t nOffset =
78 499 : sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
79 499 : char *pachBuffer = pachBufRingInfo + nOffset;
80 :
81 499 : *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
82 :
83 499 : return pachBuffer;
84 : }
85 :
86 : /************************************************************************/
87 : /* CPLPathReturnTLSString() */
88 : /************************************************************************/
89 :
90 499 : static const char *CPLPathReturnTLSString(const std::string &osRes,
91 : const char *pszFuncName)
92 : {
93 499 : 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 499 : char *pszStaticResult = CPLGetStaticResult();
101 499 : if (pszStaticResult == nullptr)
102 0 : return CPLStaticBufferTooSmall(pszStaticResult);
103 499 : memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
104 499 : return pszStaticResult;
105 : }
106 :
107 : /************************************************************************/
108 : /* CPLFindFilenameStart() */
109 : /************************************************************************/
110 :
111 2182290 : static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
112 :
113 : {
114 2182290 : size_t iFileStart = nStart ? nStart : strlen(pszFilename);
115 :
116 32572000 : for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
117 30389800 : pszFilename[iFileStart - 1] != '\\';
118 : iFileStart--)
119 : {
120 : }
121 :
122 2182290 : 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 215279 : std::string CPLGetPathSafe(const char *pszFilename)
152 :
153 : {
154 215279 : size_t nSuffixPos = 0;
155 215279 : if (STARTS_WITH(pszFilename, "/vsicurl/http"))
156 : {
157 12 : const char *pszQuestionMark = strchr(pszFilename, '?');
158 12 : if (pszQuestionMark)
159 1 : nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
160 : }
161 215267 : 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 215278 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
195 215281 : if (iFileStart == 0)
196 : {
197 558 : return std::string();
198 : }
199 :
200 429421 : std::string osRet(pszFilename, iFileStart);
201 :
202 214710 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
203 214371 : osRet.pop_back();
204 :
205 214713 : if (nSuffixPos)
206 : {
207 1 : osRet += (pszFilename + nSuffixPos);
208 : }
209 :
210 214713 : 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 143619 : std::string CPLGetDirnameSafe(const char *pszFilename)
274 :
275 : {
276 143619 : size_t nSuffixPos = 0;
277 143619 : if (STARTS_WITH(pszFilename, "/vsicurl/http"))
278 : {
279 149 : const char *pszQuestionMark = strchr(pszFilename, '?');
280 149 : if (pszQuestionMark)
281 1 : nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
282 : }
283 143470 : else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
284 14 : strstr(pszFilename, "url="))
285 : {
286 28 : std::string osRet;
287 : const CPLStringList aosTokens(
288 28 : CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
289 42 : for (int i = 0; i < aosTokens.size(); i++)
290 : {
291 28 : if (osRet.empty())
292 14 : osRet = "/vsicurl?";
293 : else
294 14 : osRet += '&';
295 42 : if (STARTS_WITH(aosTokens[i], "url=") &&
296 14 : !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
297 : {
298 : char *pszUnescaped =
299 14 : CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
300 14 : char *pszPath = CPLEscapeString(
301 14 : CPLGetDirname(pszUnescaped + strlen("url=")), -1,
302 : CPLES_URL);
303 14 : osRet += "url=";
304 14 : osRet += pszPath;
305 14 : CPLFree(pszPath);
306 14 : CPLFree(pszUnescaped);
307 : }
308 : else
309 : {
310 14 : osRet += aosTokens[i];
311 : }
312 : }
313 14 : return osRet;
314 : }
315 :
316 143605 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
317 143597 : if (iFileStart == 0)
318 : {
319 64 : return std::string(".");
320 : }
321 :
322 287012 : std::string osRet(pszFilename, iFileStart);
323 :
324 143463 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
325 143506 : osRet.pop_back();
326 :
327 143408 : if (nSuffixPos)
328 : {
329 1 : osRet += (pszFilename + nSuffixPos);
330 : }
331 :
332 143408 : 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 48 : const char *CPLGetDirname(const char *pszFilename)
362 :
363 : {
364 48 : 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 779615 : const char *CPLGetFilename(const char *pszFullFilename)
391 :
392 : {
393 779615 : const int iFileStart = CPLFindFilenameStart(pszFullFilename);
394 :
395 779613 : 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 436972 : std::string CPLGetBasenameSafe(const char *pszFullFilename)
423 :
424 : {
425 : const size_t iFileStart =
426 436972 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
427 :
428 436974 : size_t iExtStart = strlen(pszFullFilename);
429 2468750 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
430 : iExtStart--)
431 : {
432 : }
433 :
434 436974 : if (iExtStart == iFileStart)
435 26696 : iExtStart = strlen(pszFullFilename);
436 :
437 436974 : const size_t nLength = iExtStart - iFileStart;
438 436974 : 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 98 : const char *CPLGetBasename(const char *pszFullFilename)
468 :
469 : {
470 196 : return CPLPathReturnTLSString(CPLGetBasenameSafe(pszFullFilename),
471 196 : __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 610405 : std::string CPLGetExtensionSafe(const char *pszFullFilename)
498 :
499 : {
500 610405 : if (pszFullFilename[0] == '\0')
501 3583 : return std::string();
502 :
503 : size_t iFileStart =
504 606822 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
505 606819 : size_t iExtStart = strlen(pszFullFilename);
506 4394260 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
507 : iExtStart--)
508 : {
509 : }
510 :
511 606819 : if (iExtStart == iFileStart)
512 104500 : 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 606819 : const size_t knMaxExtensionSize = 10;
517 606819 : if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
518 2069 : return "";
519 :
520 604750 : 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 5137 : char *CPLGetCurrentDir()
584 : {
585 : #if PATH_MAX
586 5137 : const size_t nPathMax = PATH_MAX;
587 : #else
588 : const size_t nPathMax = 8192;
589 : #endif
590 :
591 5137 : char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
592 5137 : if (!pszDirPath)
593 0 : return nullptr;
594 :
595 5137 : 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 164213 : std::string CPLResetExtensionSafe(const char *pszPath, const char *pszExt)
620 :
621 : {
622 164213 : std::string osRet(pszPath);
623 :
624 : /* -------------------------------------------------------------------- */
625 : /* First, try and strip off any existing extension. */
626 : /* -------------------------------------------------------------------- */
627 :
628 887102 : for (size_t i = osRet.size(); i > 0;)
629 : {
630 886823 : --i;
631 886823 : if (osRet[i] == '.')
632 : {
633 147739 : osRet.resize(i);
634 147734 : break;
635 : }
636 739084 : else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
637 : {
638 16198 : break;
639 : }
640 : }
641 :
642 : /* -------------------------------------------------------------------- */
643 : /* Append the new extension. */
644 : /* -------------------------------------------------------------------- */
645 164205 : osRet += '.';
646 164204 : osRet += pszExt;
647 :
648 164208 : 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 133 : const char *CPLResetExtension(const char *pszPath, const char *pszExt)
669 :
670 : {
671 266 : return CPLPathReturnTLSString(CPLResetExtensionSafe(pszPath, pszExt),
672 266 : __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 379793 : std::string CPLFormFilenameSafe(const char *pszPath, const char *pszBasename,
709 : const char *pszExtension)
710 :
711 : {
712 379793 : if (pszBasename[0] == '.' &&
713 9258 : (pszBasename[1] == '/' || pszBasename[1] == '\\'))
714 62 : pszBasename += 2;
715 :
716 379793 : const char *pszAddedPathSep = "";
717 379793 : const char *pszAddedExtSep = "";
718 :
719 379793 : if (pszPath == nullptr)
720 10943 : pszPath = "";
721 379793 : size_t nLenPath = strlen(pszPath);
722 :
723 379793 : const char *pszQuestionMark = nullptr;
724 379793 : if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
725 : {
726 170 : pszQuestionMark = strchr(pszPath, '?');
727 170 : if (pszQuestionMark)
728 : {
729 1 : nLenPath = pszQuestionMark - pszPath;
730 : }
731 170 : pszAddedPathSep = "/";
732 : }
733 :
734 645160 : if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
735 645462 : pszBasename[1] == '.' &&
736 351 : (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
737 118 : pszBasename[2] == '/'))
738 : {
739 : // "/a/b/" + "..[/something]" --> "/a[/something]"
740 : // "/a/b" + "..[/something]" --> "/a[/something]"
741 351 : if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
742 14 : nLenPath--;
743 : while (true)
744 : {
745 402 : const char *pszBasenameOri = pszBasename;
746 402 : const size_t nLenPathOri = nLenPath;
747 4930 : while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
748 4919 : pszPath[nLenPath - 1] != '/')
749 : {
750 4528 : nLenPath--;
751 : }
752 402 : 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 1 : pszPath[0] == '/'
764 1 : ? "/"
765 0 : : VSIGetDirectorySeparator(pszPath);
766 : }
767 18 : break;
768 : }
769 384 : else if ((nLenPath > 1 && pszPath[0] == '/') ||
770 11 : (nLenPath > 2 && pszPath[1] == ':') ||
771 1 : (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
772 : {
773 377 : nLenPath--;
774 377 : pszBasename += 2;
775 377 : if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
776 158 : pszBasename[1] == '.' && pszBasename[2] == '.')
777 : {
778 51 : pszBasename++;
779 : }
780 : else
781 : {
782 : break;
783 : }
784 : }
785 : else
786 : {
787 : // cppcheck-suppress redundantAssignment
788 7 : pszBasename = pszBasenameOri;
789 7 : nLenPath = nLenPathOri;
790 7 : if (pszAddedPathSep[0] == 0)
791 7 : pszAddedPathSep = pszPath[0] == '/'
792 7 : ? "/"
793 2 : : VSIGetDirectorySeparator(pszPath);
794 7 : break;
795 : }
796 51 : }
797 : }
798 379393 : else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
799 366453 : pszPath[nLenPath - 1] != '\\')
800 : {
801 366416 : if (pszAddedPathSep[0] == 0)
802 366234 : pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
803 : }
804 :
805 379810 : if (pszExtension == nullptr)
806 223761 : pszExtension = "";
807 156049 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
808 142710 : pszAddedExtSep = ".";
809 :
810 379810 : std::string osRes;
811 379775 : osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
812 379775 : strlen(pszAddedExtSep) + strlen(pszExtension) +
813 379775 : (pszQuestionMark ? strlen(pszQuestionMark) : 0));
814 379749 : osRes.assign(pszPath, nLenPath);
815 379733 : osRes += pszAddedPathSep;
816 379763 : osRes += pszBasename;
817 379734 : osRes += pszAddedExtSep;
818 379735 : osRes += pszExtension;
819 :
820 379705 : if (pszQuestionMark)
821 : {
822 1 : osRes += pszQuestionMark;
823 : }
824 :
825 379700 : return osRes;
826 : }
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 116 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
863 : const char *pszExtension)
864 :
865 : {
866 116 : return CPLPathReturnTLSString(
867 232 : CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
868 : }
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 4687 : std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
899 : const char *pszExtension)
900 :
901 : {
902 : // On case insensitive filesystems, just default to CPLFormFilename().
903 4687 : if (!VSIIsCaseSensitiveFS(pszPath))
904 0 : return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
905 :
906 4687 : const char *pszAddedExtSep = "";
907 4687 : size_t nLen = strlen(pszBasename) + 2;
908 :
909 4687 : if (pszExtension != nullptr)
910 2214 : nLen += strlen(pszExtension);
911 :
912 4687 : char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
913 4687 : if (pszFilename == nullptr)
914 0 : return "";
915 :
916 4687 : if (pszExtension == nullptr)
917 2473 : pszExtension = "";
918 2214 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
919 2162 : pszAddedExtSep = ".";
920 :
921 4687 : snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
922 : pszExtension);
923 :
924 9374 : std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
925 : VSIStatBufL sStatBuf;
926 4687 : int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
927 :
928 4687 : if (nStatRet != 0)
929 : {
930 55955 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
931 : {
932 51757 : pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
933 : }
934 :
935 : std::string osTmpPath(
936 8396 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
937 : nStatRet =
938 4198 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
939 4198 : if (nStatRet == 0)
940 3 : osRet = std::move(osTmpPath);
941 : }
942 :
943 4687 : if (nStatRet != 0)
944 : {
945 55931 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
946 : {
947 51736 : pszFilename[i] = static_cast<char>(
948 51736 : CPLTolower(static_cast<unsigned char>(pszFilename[i])));
949 : }
950 :
951 : std::string osTmpPath(
952 8390 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
953 : nStatRet =
954 4195 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
955 4195 : if (nStatRet == 0)
956 8 : osRet = std::move(osTmpPath);
957 : }
958 :
959 4687 : if (nStatRet != 0)
960 4187 : osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
961 :
962 4687 : CPLFree(pszFilename);
963 :
964 4687 : return osRet;
965 : }
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 0 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
998 : const char *pszExtension)
999 :
1000 : {
1001 0 : return CPLPathReturnTLSString(
1002 0 : CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
1003 0 : __FUNCTION__);
1004 : }
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 4324 : std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
1038 : const char *pszSecondaryFilename)
1039 :
1040 : {
1041 8403 : if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
1042 4079 : !CPLIsFilenameRelative(pszSecondaryFilename))
1043 : {
1044 687 : return pszSecondaryFilename;
1045 : }
1046 :
1047 7274 : std::string osRes(pszProjectDir);
1048 3637 : if (osRes.back() != '/' && osRes.back() != '\\')
1049 : {
1050 3637 : osRes += VSIGetDirectorySeparator(pszProjectDir);
1051 : }
1052 :
1053 3637 : osRes += pszSecondaryFilename;
1054 3637 : return osRes;
1055 : }
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 0 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
1091 : const char *pszSecondaryFilename)
1092 :
1093 : {
1094 0 : return CPLPathReturnTLSString(
1095 0 : CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
1096 0 : __FUNCTION__);
1097 : }
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 402013 : int CPLIsFilenameRelative(const char *pszFilename)
1116 :
1117 : {
1118 402013 : if ((pszFilename[0] != '\0' &&
1119 390905 : (STARTS_WITH(pszFilename + 1, ":\\") ||
1120 390846 : STARTS_WITH(pszFilename + 1, ":/") ||
1121 390856 : strstr(pszFilename + 1, "://") // http://, ftp:// etc....
1122 401337 : )) ||
1123 401337 : STARTS_WITH(pszFilename, "\\\\?\\") // Windows extended Length Path.
1124 401300 : || pszFilename[0] == '\\' || pszFilename[0] == '/')
1125 276655 : return FALSE;
1126 :
1127 125358 : return TRUE;
1128 : }
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 2712 : const char *CPLExtractRelativePath(const char *pszBaseDir,
1158 : const char *pszTarget, int *pbGotRelative)
1159 :
1160 : {
1161 : /* -------------------------------------------------------------------- */
1162 : /* If we don't have a basedir, then we can't relativize the path. */
1163 : /* -------------------------------------------------------------------- */
1164 2712 : if (pszBaseDir == nullptr)
1165 : {
1166 0 : if (pbGotRelative != nullptr)
1167 0 : *pbGotRelative = FALSE;
1168 :
1169 0 : return pszTarget;
1170 : }
1171 :
1172 2712 : 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 2734 : if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
1179 22 : CPLIsFilenameRelative(pszTarget))
1180 : {
1181 22 : if (pbGotRelative != nullptr)
1182 22 : *pbGotRelative = TRUE;
1183 :
1184 22 : return pszTarget;
1185 : }
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 2690 : if (nBasePathLen == 0)
1192 : {
1193 0 : if (pbGotRelative != nullptr)
1194 0 : *pbGotRelative = FALSE;
1195 :
1196 0 : return pszTarget;
1197 : }
1198 :
1199 : /* -------------------------------------------------------------------- */
1200 : /* If we don't have a common path prefix, then we can't get a */
1201 : /* relative path. */
1202 : /* -------------------------------------------------------------------- */
1203 2690 : if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
1204 2248 : (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
1205 : {
1206 443 : if (pbGotRelative != nullptr)
1207 443 : *pbGotRelative = FALSE;
1208 :
1209 443 : return pszTarget;
1210 : }
1211 :
1212 : /* -------------------------------------------------------------------- */
1213 : /* We have a relative path. Strip it off to get a string to */
1214 : /* return. */
1215 : /* -------------------------------------------------------------------- */
1216 2247 : if (pbGotRelative != nullptr)
1217 2178 : *pbGotRelative = TRUE;
1218 :
1219 2247 : return pszTarget + nBasePathLen + 1;
1220 : }
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 9 : std::string CPLCleanTrailingSlashSafe(const char *pszPath)
1249 :
1250 : {
1251 9 : std::string osRes(pszPath);
1252 9 : if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
1253 0 : osRes.pop_back();
1254 9 : return osRes;
1255 : }
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 0 : const char *CPLCleanTrailingSlash(const char *pszPath)
1285 :
1286 : {
1287 0 : return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
1288 0 : __FUNCTION__);
1289 : }
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 179 : char **CPLCorrespondingPaths(const char *pszOldFilename,
1320 : const char *pszNewFilename, char **papszFileList)
1321 :
1322 : {
1323 179 : 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 347 : if (CSLCount(papszFileList) == 1 &&
1331 168 : strcmp(pszOldFilename, papszFileList[0]) == 0)
1332 : {
1333 168 : return CSLAddString(nullptr, pszNewFilename);
1334 : }
1335 :
1336 22 : const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
1337 22 : const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
1338 22 : 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 11 : if (osOldBasename != osNewBasename)
1345 : {
1346 34 : for (int i = 0; papszFileList[i] != nullptr; i++)
1347 : {
1348 24 : if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
1349 16 : continue;
1350 :
1351 8 : const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
1352 8 : const std::string osFileName = CPLGetFilename(papszFileList[i]);
1353 :
1354 8 : if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
1355 8 : osOldBasename.size()) ||
1356 16 : !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
1357 8 : osFileName[osOldBasename.size()] != '.')
1358 : {
1359 0 : CPLError(CE_Failure, CPLE_AppDefined,
1360 : "Unable to rename fileset due irregular basenames.");
1361 0 : return nullptr;
1362 : }
1363 : }
1364 : }
1365 :
1366 : /* -------------------------------------------------------------------- */
1367 : /* If the filename portions differs, ensure they only differ in */
1368 : /* basename. */
1369 : /* -------------------------------------------------------------------- */
1370 11 : if (osOldBasename != osNewBasename)
1371 : {
1372 : const std::string osOldExtra =
1373 10 : CPLGetFilename(pszOldFilename) + osOldBasename.size();
1374 : const std::string osNewExtra =
1375 10 : CPLGetFilename(pszNewFilename) + osNewBasename.size();
1376 :
1377 10 : if (osOldExtra != osNewExtra)
1378 : {
1379 0 : CPLError(CE_Failure, CPLE_AppDefined,
1380 : "Unable to rename fileset due to irregular filename "
1381 : "correspondence.");
1382 0 : return nullptr;
1383 : }
1384 : }
1385 :
1386 : /* -------------------------------------------------------------------- */
1387 : /* Generate the new filenames. */
1388 : /* -------------------------------------------------------------------- */
1389 11 : char **papszNewList = nullptr;
1390 11 : const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
1391 :
1392 37 : for (int i = 0; papszFileList[i] != nullptr; i++)
1393 : {
1394 52 : const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
1395 :
1396 : const std::string osNewFilename =
1397 26 : osOldBasename == osNewBasename
1398 : ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
1399 : nullptr)
1400 : : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
1401 24 : osOldFilename.c_str() +
1402 50 : osOldBasename.size());
1403 :
1404 26 : papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
1405 : }
1406 :
1407 11 : return papszNewList;
1408 : }
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 2342 : std::string CPLGenerateTempFilenameSafe(const char *pszStem)
1429 :
1430 : {
1431 2342 : const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1432 :
1433 2342 : if (pszDir == nullptr)
1434 2340 : pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1435 :
1436 2342 : if (pszDir == nullptr)
1437 2340 : pszDir = CPLGetConfigOption("TEMP", nullptr);
1438 :
1439 2342 : if (pszDir == nullptr)
1440 2340 : pszDir = ".";
1441 :
1442 2342 : if (pszStem == nullptr)
1443 2173 : pszStem = "";
1444 :
1445 : static int nTempFileCounter = 0;
1446 4684 : CPLString osFilename;
1447 : osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1448 2342 : CPLAtomicInc(&nTempFileCounter));
1449 :
1450 4684 : return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
1451 : }
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 6 : const char *CPLGenerateTempFilename(const char *pszStem)
1472 :
1473 : {
1474 12 : return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
1475 12 : __FUNCTION__);
1476 : }
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 189 : std::string CPLExpandTildeSafe(const char *pszFilename)
1495 :
1496 : {
1497 189 : if (!STARTS_WITH_CI(pszFilename, "~/"))
1498 188 : return pszFilename;
1499 :
1500 1 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1501 1 : if (pszHome == nullptr)
1502 0 : return pszFilename;
1503 :
1504 1 : return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
1505 : }
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 2 : const char *CPLExpandTilde(const char *pszFilename)
1526 :
1527 : {
1528 4 : return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
1529 4 : __FUNCTION__);
1530 : }
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 0 : const char *CPLGetHomeDir()
1548 :
1549 : {
1550 : #ifdef _WIN32
1551 : return CPLGetConfigOption("USERPROFILE", nullptr);
1552 : #else
1553 0 : return CPLGetConfigOption("HOME", nullptr);
1554 : #endif
1555 : }
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 1162 : std::string CPLLaunderForFilenameSafe(const char *pszName,
1573 : CPL_UNUSED const char *pszOutputPath)
1574 : {
1575 1162 : std::string osRet(pszName);
1576 10899 : for (char &ch : osRet)
1577 : {
1578 : // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
1579 9737 : if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
1580 9731 : ch == '\\' || ch == '?' || ch == '*')
1581 : {
1582 9 : ch = '_';
1583 : }
1584 : }
1585 1162 : return osRet;
1586 : }
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 0 : const char *CPLLaunderForFilename(const char *pszName,
1606 : const char *pszOutputPath)
1607 : {
1608 0 : return CPLPathReturnTLSString(
1609 0 : CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
1610 : }
|