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 2183420 : static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
112 :
113 : {
114 2183420 : size_t iFileStart = nStart ? nStart : strlen(pszFilename);
115 :
116 32607800 : for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
117 30424800 : pszFilename[iFileStart - 1] != '\\';
118 : iFileStart--)
119 : {
120 : }
121 :
122 2183420 : 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 211734 : std::string CPLGetPathSafe(const char *pszFilename)
152 :
153 : {
154 211734 : size_t nSuffixPos = 0;
155 211734 : 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 211722 : 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 211733 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
195 211733 : if (iFileStart == 0)
196 : {
197 556 : return std::string();
198 : }
199 :
200 422354 : std::string osRet(pszFilename, iFileStart);
201 :
202 211176 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
203 210837 : osRet.pop_back();
204 :
205 211177 : if (nSuffixPos)
206 : {
207 1 : osRet += (pszFilename + nSuffixPos);
208 : }
209 :
210 211177 : 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 143203 : std::string CPLGetDirnameSafe(const char *pszFilename)
274 :
275 : {
276 143203 : size_t nSuffixPos = 0;
277 143203 : 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 143054 : 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 143189 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
317 143194 : if (iFileStart == 0)
318 : {
319 64 : return std::string(".");
320 : }
321 :
322 286248 : std::string osRet(pszFilename, iFileStart);
323 :
324 143098 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
325 143065 : osRet.pop_back();
326 :
327 143131 : if (nSuffixPos)
328 : {
329 1 : osRet += (pszFilename + nSuffixPos);
330 : }
331 :
332 143131 : 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 774815 : const char *CPLGetFilename(const char *pszFullFilename)
391 :
392 : {
393 774815 : const int iFileStart = CPLFindFilenameStart(pszFullFilename);
394 :
395 774815 : 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 439768 : std::string CPLGetBasenameSafe(const char *pszFullFilename)
423 :
424 : {
425 : const size_t iFileStart =
426 439768 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
427 :
428 439768 : size_t iExtStart = strlen(pszFullFilename);
429 2489810 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
430 : iExtStart--)
431 : {
432 : }
433 :
434 439768 : if (iExtStart == iFileStart)
435 26911 : iExtStart = strlen(pszFullFilename);
436 :
437 439768 : const size_t nLength = iExtStart - iFileStart;
438 439768 : 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 617510 : std::string CPLGetExtensionSafe(const char *pszFullFilename)
498 :
499 : {
500 617510 : if (pszFullFilename[0] == '\0')
501 3556 : return std::string();
502 :
503 : size_t iFileStart =
504 613954 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
505 613955 : size_t iExtStart = strlen(pszFullFilename);
506 4457230 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
507 : iExtStart--)
508 : {
509 : }
510 :
511 613955 : if (iExtStart == iFileStart)
512 105794 : 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 613955 : const size_t knMaxExtensionSize = 10;
517 613955 : if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
518 2069 : return "";
519 :
520 611886 : 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 5107 : char *CPLGetCurrentDir()
584 : {
585 : #if PATH_MAX
586 5107 : const size_t nPathMax = PATH_MAX;
587 : #else
588 : const size_t nPathMax = 8192;
589 : #endif
590 :
591 5107 : char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
592 5107 : if (!pszDirPath)
593 0 : return nullptr;
594 :
595 5107 : 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 162661 : std::string CPLResetExtensionSafe(const char *pszPath, const char *pszExt)
620 :
621 : {
622 162661 : std::string osRet(pszPath);
623 :
624 : /* -------------------------------------------------------------------- */
625 : /* First, try and strip off any existing extension. */
626 : /* -------------------------------------------------------------------- */
627 :
628 878581 : for (size_t i = osRet.size(); i > 0;)
629 : {
630 878306 : --i;
631 878306 : if (osRet[i] == '.')
632 : {
633 146116 : osRet.resize(i);
634 146115 : break;
635 : }
636 732190 : else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
637 : {
638 16270 : break;
639 : }
640 : }
641 :
642 : /* -------------------------------------------------------------------- */
643 : /* Append the new extension. */
644 : /* -------------------------------------------------------------------- */
645 162659 : osRet += '.';
646 162660 : osRet += pszExt;
647 :
648 162660 : 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 380110 : std::string CPLFormFilenameSafe(const char *pszPath, const char *pszBasename,
709 : const char *pszExtension)
710 :
711 : {
712 380110 : if (pszBasename[0] == '.' &&
713 9286 : (pszBasename[1] == '/' || pszBasename[1] == '\\'))
714 62 : pszBasename += 2;
715 :
716 380110 : const char *pszAddedPathSep = "";
717 380110 : const char *pszAddedExtSep = "";
718 :
719 380110 : if (pszPath == nullptr)
720 10901 : pszPath = "";
721 380110 : size_t nLenPath = strlen(pszPath);
722 :
723 380110 : size_t nSuffixPos = 0;
724 380110 : if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
725 : {
726 170 : const char *pszQuestionMark = strchr(pszPath, '?');
727 170 : if (pszQuestionMark)
728 : {
729 1 : nSuffixPos = static_cast<size_t>(pszQuestionMark - pszPath);
730 1 : nLenPath = nSuffixPos;
731 : }
732 170 : pszAddedPathSep = "/";
733 : }
734 :
735 646162 : if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
736 646486 : pszBasename[1] == '.' &&
737 369 : (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
738 118 : pszBasename[2] == '/'))
739 : {
740 : // "/a/b/" + "..[/something]" --> "/a[/something]"
741 : // "/a/b" + "..[/something]" --> "/a[/something]"
742 369 : if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
743 14 : nLenPath--;
744 : while (true)
745 : {
746 420 : const char *pszBasenameOri = pszBasename;
747 420 : const size_t nLenPathOri = nLenPath;
748 5098 : while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
749 5087 : pszPath[nLenPath - 1] != '/')
750 : {
751 4678 : nLenPath--;
752 : }
753 420 : if (nLenPath == 1 && pszPath[0] == '/')
754 : {
755 18 : pszBasename += 2;
756 18 : if (pszBasename[0] == '/' || pszBasename[0] == '\\')
757 10 : pszBasename++;
758 18 : if (*pszBasename == '.')
759 : {
760 1 : pszBasename = pszBasenameOri;
761 1 : nLenPath = nLenPathOri;
762 1 : if (pszAddedPathSep[0] == 0)
763 1 : pszAddedPathSep =
764 1 : pszPath[0] == '/'
765 1 : ? "/"
766 0 : : VSIGetDirectorySeparator(pszPath);
767 : }
768 18 : break;
769 : }
770 402 : else if ((nLenPath > 1 && pszPath[0] == '/') ||
771 11 : (nLenPath > 2 && pszPath[1] == ':') ||
772 1 : (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
773 : {
774 395 : nLenPath--;
775 395 : pszBasename += 2;
776 395 : if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
777 158 : pszBasename[1] == '.' && pszBasename[2] == '.')
778 : {
779 51 : pszBasename++;
780 : }
781 : else
782 : {
783 : break;
784 : }
785 : }
786 : else
787 : {
788 : // cppcheck-suppress redundantAssignment
789 7 : pszBasename = pszBasenameOri;
790 7 : nLenPath = nLenPathOri;
791 7 : if (pszAddedPathSep[0] == 0)
792 7 : pszAddedPathSep = pszPath[0] == '/'
793 7 : ? "/"
794 2 : : VSIGetDirectorySeparator(pszPath);
795 7 : break;
796 : }
797 51 : }
798 : }
799 379696 : else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
800 366824 : pszPath[nLenPath - 1] != '\\')
801 : {
802 366787 : if (pszAddedPathSep[0] == 0)
803 366634 : pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
804 : }
805 :
806 380075 : if (pszExtension == nullptr)
807 225231 : pszExtension = "";
808 154844 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
809 141584 : pszAddedExtSep = ".";
810 :
811 380075 : std::string osRes;
812 380101 : osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
813 380101 : strlen(pszAddedExtSep) + strlen(pszExtension) +
814 380101 : (nSuffixPos ? strlen(pszPath + nSuffixPos) : 0));
815 380084 : osRes.assign(pszPath, nLenPath);
816 380091 : osRes += pszAddedPathSep;
817 380044 : osRes += pszBasename;
818 380028 : osRes += pszAddedExtSep;
819 380032 : osRes += pszExtension;
820 :
821 380038 : if (nSuffixPos)
822 : {
823 1 : osRes += (pszPath + nSuffixPos);
824 : }
825 :
826 380071 : return osRes;
827 : }
828 :
829 : /************************************************************************/
830 : /* CPLFormFilename() */
831 : /************************************************************************/
832 :
833 : /**
834 : * Build a full file path from a passed path, file basename and extension.
835 : *
836 : * The path, and extension are optional. The basename may in fact contain
837 : * an extension if desired.
838 : *
839 : * \code{.cpp}
840 : * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
841 : * CPLFormFilename(NULL,"def", NULL ) == "def"
842 : * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
843 : * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
844 : * CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
845 : * \endcode
846 : *
847 : * @param pszPath directory path to the directory containing the file. This
848 : * may be relative or absolute, and may have a trailing path separator or
849 : * not. May be NULL.
850 : *
851 : * @param pszBasename file basename. May optionally have path and/or
852 : * extension. Must *NOT* be NULL.
853 : *
854 : * @param pszExtension file extension, optionally including the period. May
855 : * be NULL.
856 : *
857 : * @return a fully formed filename in an internal static string. Do not
858 : * modify or free the returned string. The string may be destroyed by the
859 : * next CPL call.
860 : *
861 : * @deprecated If using C++, prefer using CPLFormFilenameSafe() instead
862 : */
863 116 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
864 : const char *pszExtension)
865 :
866 : {
867 116 : return CPLPathReturnTLSString(
868 232 : CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
869 : }
870 :
871 : /************************************************************************/
872 : /* CPLFormCIFilenameSafe() */
873 : /************************************************************************/
874 :
875 : /**
876 : * Case insensitive file searching, returning full path.
877 : *
878 : * This function tries to return the path to a file regardless of
879 : * whether the file exactly matches the basename, and extension case, or
880 : * is all upper case, or all lower case. The path is treated as case
881 : * sensitive. This function is equivalent to CPLFormFilename() on
882 : * case insensitive file systems (like Windows).
883 : *
884 : * @param pszPath directory path to the directory containing the file. This
885 : * may be relative or absolute, and may have a trailing path separator or
886 : * not. May be NULL.
887 : *
888 : * @param pszBasename file basename. May optionally have path and/or
889 : * extension. May not be NULL.
890 : *
891 : * @param pszExtension file extension, optionally including the period. May
892 : * be NULL.
893 : *
894 : * @return a fully formed filename.
895 : *
896 : * @since 3.11
897 : */
898 :
899 4658 : std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
900 : const char *pszExtension)
901 :
902 : {
903 : // On case insensitive filesystems, just default to CPLFormFilename().
904 4658 : if (!VSIIsCaseSensitiveFS(pszPath))
905 0 : return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
906 :
907 4658 : const char *pszAddedExtSep = "";
908 4658 : size_t nLen = strlen(pszBasename) + 2;
909 :
910 4658 : if (pszExtension != nullptr)
911 2200 : nLen += strlen(pszExtension);
912 :
913 4658 : char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
914 4658 : if (pszFilename == nullptr)
915 0 : return "";
916 :
917 4658 : if (pszExtension == nullptr)
918 2458 : pszExtension = "";
919 2200 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
920 2148 : pszAddedExtSep = ".";
921 :
922 4658 : snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
923 : pszExtension);
924 :
925 9316 : std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
926 : VSIStatBufL sStatBuf;
927 4658 : int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
928 :
929 4658 : if (nStatRet != 0)
930 : {
931 55675 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
932 : {
933 51503 : pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
934 : }
935 :
936 : std::string osTmpPath(
937 8344 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
938 : nStatRet =
939 4172 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
940 4172 : if (nStatRet == 0)
941 3 : osRet = std::move(osTmpPath);
942 : }
943 :
944 4658 : if (nStatRet != 0)
945 : {
946 55651 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
947 : {
948 51482 : pszFilename[i] = static_cast<char>(
949 51482 : CPLTolower(static_cast<unsigned char>(pszFilename[i])));
950 : }
951 :
952 : std::string osTmpPath(
953 8338 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
954 : nStatRet =
955 4169 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
956 4169 : if (nStatRet == 0)
957 8 : osRet = std::move(osTmpPath);
958 : }
959 :
960 4658 : if (nStatRet != 0)
961 4161 : osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
962 :
963 4658 : CPLFree(pszFilename);
964 :
965 4658 : return osRet;
966 : }
967 :
968 : /************************************************************************/
969 : /* CPLFormCIFilename() */
970 : /************************************************************************/
971 :
972 : /**
973 : * Case insensitive file searching, returning full path.
974 : *
975 : * This function tries to return the path to a file regardless of
976 : * whether the file exactly matches the basename, and extension case, or
977 : * is all upper case, or all lower case. The path is treated as case
978 : * sensitive. This function is equivalent to CPLFormFilename() on
979 : * case insensitive file systems (like Windows).
980 : *
981 : * @param pszPath directory path to the directory containing the file. This
982 : * may be relative or absolute, and may have a trailing path separator or
983 : * not. May be NULL.
984 : *
985 : * @param pszBasename file basename. May optionally have path and/or
986 : * extension. May not be NULL.
987 : *
988 : * @param pszExtension file extension, optionally including the period. May
989 : * be NULL.
990 : *
991 : * @return a fully formed filename in an internal static string. Do not
992 : * modify or free the returned string. The string may be destroyed by the
993 : * next CPL call.
994 : *
995 : * @deprecated If using C++, prefer using CPLFormCIFilenameSafe() instead
996 : */
997 :
998 0 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
999 : const char *pszExtension)
1000 :
1001 : {
1002 0 : return CPLPathReturnTLSString(
1003 0 : CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
1004 0 : __FUNCTION__);
1005 : }
1006 :
1007 : /************************************************************************/
1008 : /* CPLProjectRelativeFilenameSafe() */
1009 : /************************************************************************/
1010 :
1011 : /**
1012 : * Find a file relative to a project file.
1013 : *
1014 : * Given the path to a "project" directory, and a path to a secondary file
1015 : * referenced from that project, build a path to the secondary file
1016 : * that the current application can use. If the secondary path is already
1017 : * absolute, rather than relative, then it will be returned unaltered.
1018 : *
1019 : * Examples:
1020 : * \code{.cpp}
1021 : * CPLProjectRelativeFilenameSafe("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1022 : * CPLProjectRelativeFilenameSafe("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1023 : * CPLProjectRelativeFilenameSafe("/xy", "abc.gif") == "/xy/abc.gif"
1024 : * CPLProjectRelativeFilenameSafe("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1025 : * CPLProjectRelativeFilenameSafe("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1026 : * \endcode
1027 : *
1028 : * @param pszProjectDir the directory relative to which the secondary files
1029 : * path should be interpreted.
1030 : * @param pszSecondaryFilename the filename (potentially with path) that
1031 : * is to be interpreted relative to the project directory.
1032 : *
1033 : * @return a composed path to the secondary file.
1034 : *
1035 : * @since 3.11
1036 : */
1037 :
1038 4293 : std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
1039 : const char *pszSecondaryFilename)
1040 :
1041 : {
1042 8341 : if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
1043 4048 : !CPLIsFilenameRelative(pszSecondaryFilename))
1044 : {
1045 673 : return pszSecondaryFilename;
1046 : }
1047 :
1048 7240 : std::string osRes(pszProjectDir);
1049 3620 : if (osRes.back() != '/' && osRes.back() != '\\')
1050 : {
1051 3620 : osRes += VSIGetDirectorySeparator(pszProjectDir);
1052 : }
1053 :
1054 3620 : osRes += pszSecondaryFilename;
1055 3620 : return osRes;
1056 : }
1057 :
1058 : /************************************************************************/
1059 : /* CPLProjectRelativeFilename() */
1060 : /************************************************************************/
1061 :
1062 : /**
1063 : * Find a file relative to a project file.
1064 : *
1065 : * Given the path to a "project" directory, and a path to a secondary file
1066 : * referenced from that project, build a path to the secondary file
1067 : * that the current application can use. If the secondary path is already
1068 : * absolute, rather than relative, then it will be returned unaltered.
1069 : *
1070 : * Examples:
1071 : * \code{.cpp}
1072 : * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1073 : * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1074 : * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
1075 : * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1076 : * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1077 : * \endcode
1078 : *
1079 : * @param pszProjectDir the directory relative to which the secondary files
1080 : * path should be interpreted.
1081 : * @param pszSecondaryFilename the filename (potentially with path) that
1082 : * is to be interpreted relative to the project directory.
1083 : *
1084 : * @return a composed path to the secondary file. The returned string is
1085 : * internal and should not be altered, freed, or depending on past the next
1086 : * CPL call.
1087 : *
1088 : * @deprecated If using C++, prefer using CPLProjectRelativeFilenameSafe() instead
1089 : */
1090 :
1091 0 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
1092 : const char *pszSecondaryFilename)
1093 :
1094 : {
1095 0 : return CPLPathReturnTLSString(
1096 0 : CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
1097 0 : __FUNCTION__);
1098 : }
1099 :
1100 : /************************************************************************/
1101 : /* CPLIsFilenameRelative() */
1102 : /************************************************************************/
1103 :
1104 : /**
1105 : * Is filename relative or absolute?
1106 : *
1107 : * The test is filesystem convention agnostic. That is it will test for
1108 : * Unix style and windows style path conventions regardless of the actual
1109 : * system in use.
1110 : *
1111 : * @param pszFilename the filename with path to test.
1112 : *
1113 : * @return TRUE if the filename is relative or FALSE if it is absolute.
1114 : */
1115 :
1116 402304 : int CPLIsFilenameRelative(const char *pszFilename)
1117 :
1118 : {
1119 402304 : if ((pszFilename[0] != '\0' &&
1120 391244 : (STARTS_WITH(pszFilename + 1, ":\\") ||
1121 391193 : STARTS_WITH(pszFilename + 1, ":/") ||
1122 391199 : strstr(pszFilename + 1, "://") // http://, ftp:// etc....
1123 401621 : )) ||
1124 401621 : STARTS_WITH(pszFilename, "\\\\?\\") // Windows extended Length Path.
1125 401615 : || pszFilename[0] == '\\' || pszFilename[0] == '/')
1126 277270 : return FALSE;
1127 :
1128 125034 : return TRUE;
1129 : }
1130 :
1131 : /************************************************************************/
1132 : /* CPLExtractRelativePath() */
1133 : /************************************************************************/
1134 :
1135 : /**
1136 : * Get relative path from directory to target file.
1137 : *
1138 : * Computes a relative path for pszTarget relative to pszBaseDir.
1139 : * Currently this only works if they share a common base path. The returned
1140 : * path is normally into the pszTarget string. It should only be considered
1141 : * valid as long as pszTarget is valid or till the next call to
1142 : * this function, whichever comes first.
1143 : *
1144 : * @param pszBaseDir the name of the directory relative to which the path
1145 : * should be computed. pszBaseDir may be NULL in which case the original
1146 : * target is returned without relativizing.
1147 : *
1148 : * @param pszTarget the filename to be changed to be relative to pszBaseDir.
1149 : *
1150 : * @param pbGotRelative Pointer to location in which a flag is placed
1151 : * indicating that the returned path is relative to the basename (TRUE) or
1152 : * not (FALSE). This pointer may be NULL if flag is not desired.
1153 : *
1154 : * @return an adjusted path or the original if it could not be made relative
1155 : * to the pszBaseFile's path.
1156 : **/
1157 :
1158 2698 : const char *CPLExtractRelativePath(const char *pszBaseDir,
1159 : const char *pszTarget, int *pbGotRelative)
1160 :
1161 : {
1162 : /* -------------------------------------------------------------------- */
1163 : /* If we don't have a basedir, then we can't relativize the path. */
1164 : /* -------------------------------------------------------------------- */
1165 2698 : if (pszBaseDir == nullptr)
1166 : {
1167 0 : if (pbGotRelative != nullptr)
1168 0 : *pbGotRelative = FALSE;
1169 :
1170 0 : return pszTarget;
1171 : }
1172 :
1173 2698 : const size_t nBasePathLen = strlen(pszBaseDir);
1174 :
1175 : /* -------------------------------------------------------------------- */
1176 : /* One simple case is when the base dir is '.' and the target */
1177 : /* filename is relative. */
1178 : /* -------------------------------------------------------------------- */
1179 2720 : if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
1180 22 : CPLIsFilenameRelative(pszTarget))
1181 : {
1182 22 : if (pbGotRelative != nullptr)
1183 22 : *pbGotRelative = TRUE;
1184 :
1185 22 : return pszTarget;
1186 : }
1187 :
1188 : /* -------------------------------------------------------------------- */
1189 : /* By this point, if we don't have a base path, we can't have a */
1190 : /* meaningful common prefix. */
1191 : /* -------------------------------------------------------------------- */
1192 2676 : if (nBasePathLen == 0)
1193 : {
1194 0 : if (pbGotRelative != nullptr)
1195 0 : *pbGotRelative = FALSE;
1196 :
1197 0 : return pszTarget;
1198 : }
1199 :
1200 : /* -------------------------------------------------------------------- */
1201 : /* If we don't have a common path prefix, then we can't get a */
1202 : /* relative path. */
1203 : /* -------------------------------------------------------------------- */
1204 2676 : if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
1205 2234 : (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
1206 : {
1207 443 : if (pbGotRelative != nullptr)
1208 443 : *pbGotRelative = FALSE;
1209 :
1210 443 : return pszTarget;
1211 : }
1212 :
1213 : /* -------------------------------------------------------------------- */
1214 : /* We have a relative path. Strip it off to get a string to */
1215 : /* return. */
1216 : /* -------------------------------------------------------------------- */
1217 2233 : if (pbGotRelative != nullptr)
1218 2164 : *pbGotRelative = TRUE;
1219 :
1220 2233 : return pszTarget + nBasePathLen + 1;
1221 : }
1222 :
1223 : /************************************************************************/
1224 : /* CPLCleanTrailingSlashSafe() */
1225 : /************************************************************************/
1226 :
1227 : /**
1228 : * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1229 : *
1230 : * Returns a string containing the portion of the passed path string with
1231 : * trailing slash removed. If there is no path in the passed filename
1232 : * an empty string will be returned (not NULL).
1233 : *
1234 : * \code{.cpp}
1235 : * CPLCleanTrailingSlashSafe( "abc/def/" ) == "abc/def"
1236 : * CPLCleanTrailingSlashSafe( "abc/def" ) == "abc/def"
1237 : * CPLCleanTrailingSlashSafe( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1238 : * CPLCleanTrailingSlashSafe( "c:\\abc\\def" ) == "c:\\abc\\def"
1239 : * CPLCleanTrailingSlashSafe( "abc" ) == "abc"
1240 : * \endcode
1241 : *
1242 : * @param pszPath the path to be cleaned up
1243 : *
1244 : * @return Path
1245 : *
1246 : * @since 3.11
1247 : */
1248 :
1249 9 : std::string CPLCleanTrailingSlashSafe(const char *pszPath)
1250 :
1251 : {
1252 9 : std::string osRes(pszPath);
1253 9 : if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
1254 0 : osRes.pop_back();
1255 9 : return osRes;
1256 : }
1257 :
1258 : /************************************************************************/
1259 : /* CPLCleanTrailingSlash() */
1260 : /************************************************************************/
1261 :
1262 : /**
1263 : * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1264 : *
1265 : * Returns a string containing the portion of the passed path string with
1266 : * trailing slash removed. If there is no path in the passed filename
1267 : * an empty string will be returned (not NULL).
1268 : *
1269 : * \code{.cpp}
1270 : * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
1271 : * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
1272 : * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1273 : * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
1274 : * CPLCleanTrailingSlash( "abc" ) == "abc"
1275 : * \endcode
1276 : *
1277 : * @param pszPath the path to be cleaned up
1278 : *
1279 : * @return Path in an internal string which must not be freed. The string
1280 : * may be destroyed by the next CPL filename handling call.
1281 : *
1282 : * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1283 : */
1284 :
1285 0 : const char *CPLCleanTrailingSlash(const char *pszPath)
1286 :
1287 : {
1288 0 : return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
1289 0 : __FUNCTION__);
1290 : }
1291 :
1292 : /************************************************************************/
1293 : /* CPLCorrespondingPaths() */
1294 : /************************************************************************/
1295 :
1296 : /**
1297 : * Identify corresponding paths.
1298 : *
1299 : * Given a prototype old and new filename this function will attempt
1300 : * to determine corresponding names for a set of other old filenames that
1301 : * will rename them in a similar manner. This correspondence assumes there
1302 : * are two possibly kinds of renaming going on. A change of path, and a
1303 : * change of filename stem.
1304 : *
1305 : * If a consistent renaming cannot be established for all the files this
1306 : * function will return indicating an error.
1307 : *
1308 : * The returned file list becomes owned by the caller and should be destroyed
1309 : * with CSLDestroy().
1310 : *
1311 : * @param pszOldFilename path to old prototype file.
1312 : * @param pszNewFilename path to new prototype file.
1313 : * @param papszFileList list of other files associated with pszOldFilename to
1314 : * rename similarly.
1315 : *
1316 : * @return a list of files corresponding to papszFileList but renamed to
1317 : * correspond to pszNewFilename.
1318 : */
1319 :
1320 179 : char **CPLCorrespondingPaths(const char *pszOldFilename,
1321 : const char *pszNewFilename, char **papszFileList)
1322 :
1323 : {
1324 179 : if (CSLCount(papszFileList) == 0)
1325 0 : return nullptr;
1326 :
1327 : /* -------------------------------------------------------------------- */
1328 : /* There is a special case for a one item list which exactly */
1329 : /* matches the old name, to rename to the new name. */
1330 : /* -------------------------------------------------------------------- */
1331 347 : if (CSLCount(papszFileList) == 1 &&
1332 168 : strcmp(pszOldFilename, papszFileList[0]) == 0)
1333 : {
1334 168 : return CSLAddString(nullptr, pszNewFilename);
1335 : }
1336 :
1337 22 : const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
1338 22 : const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
1339 22 : const std::string osNewBasename = CPLGetBasenameSafe(pszNewFilename);
1340 :
1341 : /* -------------------------------------------------------------------- */
1342 : /* If the basename is changing, verify that all source files */
1343 : /* have the same starting basename. */
1344 : /* -------------------------------------------------------------------- */
1345 11 : if (osOldBasename != osNewBasename)
1346 : {
1347 34 : for (int i = 0; papszFileList[i] != nullptr; i++)
1348 : {
1349 24 : if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
1350 16 : continue;
1351 :
1352 8 : const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
1353 8 : const std::string osFileName = CPLGetFilename(papszFileList[i]);
1354 :
1355 8 : if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
1356 8 : osOldBasename.size()) ||
1357 16 : !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
1358 8 : osFileName[osOldBasename.size()] != '.')
1359 : {
1360 0 : CPLError(CE_Failure, CPLE_AppDefined,
1361 : "Unable to rename fileset due irregular basenames.");
1362 0 : return nullptr;
1363 : }
1364 : }
1365 : }
1366 :
1367 : /* -------------------------------------------------------------------- */
1368 : /* If the filename portions differs, ensure they only differ in */
1369 : /* basename. */
1370 : /* -------------------------------------------------------------------- */
1371 11 : if (osOldBasename != osNewBasename)
1372 : {
1373 : const std::string osOldExtra =
1374 10 : CPLGetFilename(pszOldFilename) + osOldBasename.size();
1375 : const std::string osNewExtra =
1376 10 : CPLGetFilename(pszNewFilename) + osNewBasename.size();
1377 :
1378 10 : if (osOldExtra != osNewExtra)
1379 : {
1380 0 : CPLError(CE_Failure, CPLE_AppDefined,
1381 : "Unable to rename fileset due to irregular filename "
1382 : "correspondence.");
1383 0 : return nullptr;
1384 : }
1385 : }
1386 :
1387 : /* -------------------------------------------------------------------- */
1388 : /* Generate the new filenames. */
1389 : /* -------------------------------------------------------------------- */
1390 11 : char **papszNewList = nullptr;
1391 11 : const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
1392 :
1393 37 : for (int i = 0; papszFileList[i] != nullptr; i++)
1394 : {
1395 52 : const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
1396 :
1397 : const std::string osNewFilename =
1398 26 : osOldBasename == osNewBasename
1399 : ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
1400 : nullptr)
1401 : : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
1402 24 : osOldFilename.c_str() +
1403 50 : osOldBasename.size());
1404 :
1405 26 : papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
1406 : }
1407 :
1408 11 : return papszNewList;
1409 : }
1410 :
1411 : /************************************************************************/
1412 : /* CPLGenerateTempFilenameSafe() */
1413 : /************************************************************************/
1414 :
1415 : /**
1416 : * Generate temporary file name.
1417 : *
1418 : * Returns a filename that may be used for a temporary file. The location
1419 : * of the file tries to follow operating system semantics but may be
1420 : * forced via the CPL_TMPDIR configuration option.
1421 : *
1422 : * @param pszStem if non-NULL this will be part of the filename.
1423 : *
1424 : * @return a filename
1425 : *
1426 : * @since 3.11
1427 : */
1428 :
1429 2336 : std::string CPLGenerateTempFilenameSafe(const char *pszStem)
1430 :
1431 : {
1432 2336 : const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1433 :
1434 2336 : if (pszDir == nullptr)
1435 2334 : pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1436 :
1437 2336 : if (pszDir == nullptr)
1438 2334 : pszDir = CPLGetConfigOption("TEMP", nullptr);
1439 :
1440 2336 : if (pszDir == nullptr)
1441 2334 : pszDir = ".";
1442 :
1443 2336 : if (pszStem == nullptr)
1444 2167 : pszStem = "";
1445 :
1446 : static int nTempFileCounter = 0;
1447 4672 : CPLString osFilename;
1448 : osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1449 2336 : CPLAtomicInc(&nTempFileCounter));
1450 :
1451 4672 : return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
1452 : }
1453 :
1454 : /************************************************************************/
1455 : /* CPLGenerateTempFilename() */
1456 : /************************************************************************/
1457 :
1458 : /**
1459 : * Generate temporary file name.
1460 : *
1461 : * Returns a filename that may be used for a temporary file. The location
1462 : * of the file tries to follow operating system semantics but may be
1463 : * forced via the CPL_TMPDIR configuration option.
1464 : *
1465 : * @param pszStem if non-NULL this will be part of the filename.
1466 : *
1467 : * @return a filename which is valid till the next CPL call in this thread.
1468 : *
1469 : * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1470 : */
1471 :
1472 6 : const char *CPLGenerateTempFilename(const char *pszStem)
1473 :
1474 : {
1475 12 : return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
1476 12 : __FUNCTION__);
1477 : }
1478 :
1479 : /************************************************************************/
1480 : /* CPLExpandTildeSafe() */
1481 : /************************************************************************/
1482 :
1483 : /**
1484 : * Expands ~/ at start of filename.
1485 : *
1486 : * Assumes that the HOME configuration option is defined.
1487 : *
1488 : * @param pszFilename filename potentially starting with ~/
1489 : *
1490 : * @return an expanded filename.
1491 : *
1492 : * @since GDAL 3.11
1493 : */
1494 :
1495 189 : std::string CPLExpandTildeSafe(const char *pszFilename)
1496 :
1497 : {
1498 189 : if (!STARTS_WITH_CI(pszFilename, "~/"))
1499 188 : return pszFilename;
1500 :
1501 1 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1502 1 : if (pszHome == nullptr)
1503 0 : return pszFilename;
1504 :
1505 1 : return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
1506 : }
1507 :
1508 : /************************************************************************/
1509 : /* CPLExpandTilde() */
1510 : /************************************************************************/
1511 :
1512 : /**
1513 : * Expands ~/ at start of filename.
1514 : *
1515 : * Assumes that the HOME configuration option is defined.
1516 : *
1517 : * @param pszFilename filename potentially starting with ~/
1518 : *
1519 : * @return an expanded filename.
1520 : *
1521 : * @since GDAL 2.2
1522 : *
1523 : * @deprecated If using C++, prefer using CPLExpandTildeSafe() instead
1524 : */
1525 :
1526 2 : const char *CPLExpandTilde(const char *pszFilename)
1527 :
1528 : {
1529 4 : return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
1530 4 : __FUNCTION__);
1531 : }
1532 :
1533 : /************************************************************************/
1534 : /* CPLGetHomeDir() */
1535 : /************************************************************************/
1536 :
1537 : /**
1538 : * Return the path to the home directory
1539 : *
1540 : * That is the value of the USERPROFILE environment variable on Windows,
1541 : * or HOME on other platforms.
1542 : *
1543 : * @return the home directory, or NULL.
1544 : *
1545 : * @since GDAL 2.3
1546 : */
1547 :
1548 0 : const char *CPLGetHomeDir()
1549 :
1550 : {
1551 : #ifdef _WIN32
1552 : return CPLGetConfigOption("USERPROFILE", nullptr);
1553 : #else
1554 0 : return CPLGetConfigOption("HOME", nullptr);
1555 : #endif
1556 : }
1557 :
1558 : /************************************************************************/
1559 : /* CPLLaunderForFilenameSafe() */
1560 : /************************************************************************/
1561 :
1562 : /**
1563 : * Launder a string to be compatible of a filename.
1564 : *
1565 : * @param pszName The input string to launder.
1566 : * @param pszOutputPath The directory where the file would be created.
1567 : * Unused for now. May be NULL.
1568 : * @return the laundered name.
1569 : *
1570 : * @since GDAL 3.11
1571 : */
1572 :
1573 1157 : std::string CPLLaunderForFilenameSafe(const char *pszName,
1574 : CPL_UNUSED const char *pszOutputPath)
1575 : {
1576 1157 : std::string osRet(pszName);
1577 10874 : for (char &ch : osRet)
1578 : {
1579 : // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
1580 9717 : if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
1581 9711 : ch == '\\' || ch == '?' || ch == '*')
1582 : {
1583 9 : ch = '_';
1584 : }
1585 : }
1586 1157 : return osRet;
1587 : }
1588 :
1589 : /************************************************************************/
1590 : /* CPLLaunderForFilename() */
1591 : /************************************************************************/
1592 :
1593 : /**
1594 : * Launder a string to be compatible of a filename.
1595 : *
1596 : * @param pszName The input string to launder.
1597 : * @param pszOutputPath The directory where the file would be created.
1598 : * Unused for now. May be NULL.
1599 : * @return the laundered name.
1600 : *
1601 : * @since GDAL 3.1
1602 : *
1603 : * @deprecated If using C++, prefer using CPLLaunderForFilenameSafe() instead
1604 : */
1605 :
1606 0 : const char *CPLLaunderForFilename(const char *pszName,
1607 : const char *pszOutputPath)
1608 : {
1609 0 : return CPLPathReturnTLSString(
1610 0 : CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
1611 : }
|