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 : #include <string_view>
31 :
32 : #include "cpl_atomic_ops.h"
33 : #include "cpl_config.h"
34 : #include "cpl_error.h"
35 : #include "cpl_multiproc.h"
36 : #include "cpl_string.h"
37 : #include "cpl_vsi.h"
38 :
39 : // Should be size of larged possible filename.
40 : constexpr int CPL_PATH_BUF_SIZE = 2048;
41 : constexpr int CPL_PATH_BUF_COUNT = 10;
42 :
43 0 : static const char *CPLStaticBufferTooSmall(char *pszStaticResult)
44 : {
45 0 : CPLError(CE_Failure, CPLE_AppDefined, "Destination buffer too small");
46 0 : if (pszStaticResult == nullptr)
47 0 : return "";
48 0 : strcpy(pszStaticResult, "");
49 0 : return pszStaticResult;
50 : }
51 :
52 : /************************************************************************/
53 : /* CPLGetStaticResult() */
54 : /************************************************************************/
55 :
56 577 : static char *CPLGetStaticResult()
57 :
58 : {
59 577 : int bMemoryError = FALSE;
60 : char *pachBufRingInfo =
61 577 : static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
62 577 : if (bMemoryError)
63 0 : return nullptr;
64 577 : if (pachBufRingInfo == nullptr)
65 : {
66 14 : pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
67 : 1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
68 14 : if (pachBufRingInfo == nullptr)
69 0 : return nullptr;
70 14 : CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
71 : }
72 :
73 : /* -------------------------------------------------------------------- */
74 : /* Work out which string in the "ring" we want to use this */
75 : /* time. */
76 : /* -------------------------------------------------------------------- */
77 577 : int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
78 577 : const size_t nOffset =
79 577 : sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
80 577 : char *pachBuffer = pachBufRingInfo + nOffset;
81 :
82 577 : *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
83 :
84 577 : return pachBuffer;
85 : }
86 :
87 : /************************************************************************/
88 : /* CPLPathReturnTLSString() */
89 : /************************************************************************/
90 :
91 577 : static const char *CPLPathReturnTLSString(const std::string &osRes,
92 : const char *pszFuncName)
93 : {
94 577 : if (osRes.size() >= CPL_PATH_BUF_SIZE)
95 : {
96 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too long result for %s()",
97 : pszFuncName);
98 0 : return "";
99 : }
100 :
101 577 : char *pszStaticResult = CPLGetStaticResult();
102 577 : if (pszStaticResult == nullptr)
103 0 : return CPLStaticBufferTooSmall(pszStaticResult);
104 577 : memcpy(pszStaticResult, osRes.c_str(), osRes.size() + 1);
105 577 : return pszStaticResult;
106 : }
107 :
108 : /************************************************************************/
109 : /* CPLFindFilenameStart() */
110 : /************************************************************************/
111 :
112 2449980 : static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0)
113 :
114 : {
115 2449980 : size_t iFileStart = nStart ? nStart : strlen(pszFilename);
116 :
117 35773500 : for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
118 33323700 : pszFilename[iFileStart - 1] != '\\';
119 : iFileStart--)
120 : {
121 : }
122 :
123 2449980 : return static_cast<int>(iFileStart);
124 : }
125 :
126 : /************************************************************************/
127 : /* CPLGetPathSafe() */
128 : /************************************************************************/
129 :
130 : /**
131 : * Extract directory path portion of filename.
132 : *
133 : * Returns a string containing the directory path portion of the passed
134 : * filename. If there is no path in the passed filename an empty string
135 : * will be returned (not NULL).
136 : *
137 : * \code{.cpp}
138 : * CPLGetPathSafe( "abc/def.xyz" ) == "abc"
139 : * CPLGetPathSafe( "/abc/def/" ) == "/abc/def"
140 : * CPLGetPathSafe( "/" ) == "/"
141 : * CPLGetPathSafe( "/abc/def" ) == "/abc"
142 : * CPLGetPathSafe( "abc" ) == ""
143 : * \endcode
144 : *
145 : * @param pszFilename the filename potentially including a path.
146 : *
147 : * @return Path.
148 : *
149 : * @since 3.11
150 : */
151 :
152 250086 : std::string CPLGetPathSafe(const char *pszFilename)
153 :
154 : {
155 250086 : size_t nSuffixPos = 0;
156 250086 : if (STARTS_WITH(pszFilename, "/vsicurl/http"))
157 : {
158 9 : const char *pszQuestionMark = strchr(pszFilename, '?');
159 9 : if (pszQuestionMark)
160 1 : nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
161 : }
162 250077 : else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
163 1 : strstr(pszFilename, "url="))
164 : {
165 2 : std::string osRet;
166 : const CPLStringList aosTokens(
167 2 : CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
168 3 : for (int i = 0; i < aosTokens.size(); i++)
169 : {
170 2 : if (osRet.empty())
171 1 : osRet = "/vsicurl?";
172 : else
173 1 : osRet += '&';
174 3 : if (STARTS_WITH(aosTokens[i], "url=") &&
175 1 : !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
176 : {
177 : char *pszUnescaped =
178 1 : CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
179 1 : char *pszPath = CPLEscapeString(
180 2 : CPLGetPathSafe(pszUnescaped + strlen("url=")).c_str(), -1,
181 : CPLES_URL);
182 1 : osRet += "url=";
183 1 : osRet += pszPath;
184 1 : CPLFree(pszPath);
185 1 : CPLFree(pszUnescaped);
186 : }
187 : else
188 : {
189 1 : osRet += aosTokens[i];
190 : }
191 : }
192 1 : return osRet;
193 : }
194 :
195 250085 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
196 250085 : if (iFileStart == 0)
197 : {
198 3644 : return std::string();
199 : }
200 :
201 492882 : std::string osRet(pszFilename, iFileStart);
202 :
203 246441 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
204 245591 : osRet.pop_back();
205 :
206 246440 : if (nSuffixPos)
207 : {
208 1 : osRet += (pszFilename + nSuffixPos);
209 : }
210 :
211 246440 : return osRet;
212 : }
213 :
214 : /************************************************************************/
215 : /* CPLGetPath() */
216 : /************************************************************************/
217 :
218 : /**
219 : * Extract directory path portion of filename.
220 : *
221 : * Returns a string containing the directory path portion of the passed
222 : * filename. If there is no path in the passed filename an empty string
223 : * will be returned (not NULL).
224 : *
225 : * \code{.cpp}
226 : * CPLGetPath( "abc/def.xyz" ) == "abc"
227 : * CPLGetPath( "/abc/def/" ) == "/abc/def"
228 : * CPLGetPath( "/" ) == "/"
229 : * CPLGetPath( "/abc/def" ) == "/abc"
230 : * CPLGetPath( "abc" ) == ""
231 : * \endcode
232 : *
233 : * @param pszFilename the filename potentially including a path.
234 : *
235 : * @return Path in an internal string which must not be freed. The string
236 : * may be destroyed by the next CPL filename handling call. The returned
237 : * will generally not contain a trailing path separator.
238 : *
239 : * @deprecated If using C++, prefer using CPLGetPathSafe() instead
240 : */
241 :
242 50 : const char *CPLGetPath(const char *pszFilename)
243 :
244 : {
245 50 : return CPLPathReturnTLSString(CPLGetPathSafe(pszFilename), __FUNCTION__);
246 : }
247 :
248 : /************************************************************************/
249 : /* CPLGetDirname() */
250 : /************************************************************************/
251 :
252 : /**
253 : * Extract directory path portion of filename.
254 : *
255 : * Returns a string containing the directory path portion of the passed
256 : * filename. If there is no path in the passed filename the dot will be
257 : * returned. It is the only difference from CPLGetPath().
258 : *
259 : * \code{.cpp}
260 : * CPLGetDirnameSafe( "abc/def.xyz" ) == "abc"
261 : * CPLGetDirnameSafe( "/abc/def/" ) == "/abc/def"
262 : * CPLGetDirnameSafe( "/" ) == "/"
263 : * CPLGetDirnameSafe( "/abc/def" ) == "/abc"
264 : * CPLGetDirnameSafe( "abc" ) == "."
265 : * \endcode
266 : *
267 : * @param pszFilename the filename potentially including a path.
268 : *
269 : * @return Path
270 : *
271 : * @since 3.11
272 : */
273 :
274 183742 : std::string CPLGetDirnameSafe(const char *pszFilename)
275 :
276 : {
277 183742 : size_t nSuffixPos = 0;
278 183742 : if (STARTS_WITH(pszFilename, "/vsicurl/http"))
279 : {
280 144 : const char *pszQuestionMark = strchr(pszFilename, '?');
281 144 : if (pszQuestionMark)
282 1 : nSuffixPos = static_cast<size_t>(pszQuestionMark - pszFilename);
283 : }
284 183598 : else if (STARTS_WITH(pszFilename, "/vsicurl?") &&
285 16 : strstr(pszFilename, "url="))
286 : {
287 30 : std::string osRet;
288 : const CPLStringList aosTokens(
289 30 : CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0));
290 44 : for (int i = 0; i < aosTokens.size(); i++)
291 : {
292 29 : if (osRet.empty())
293 15 : osRet = "/vsicurl?";
294 : else
295 14 : osRet += '&';
296 44 : if (STARTS_WITH(aosTokens[i], "url=") &&
297 15 : !STARTS_WITH(aosTokens[i], "url=/vsicurl"))
298 : {
299 : char *pszUnescaped =
300 15 : CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
301 15 : char *pszPath = CPLEscapeString(
302 15 : CPLGetDirname(pszUnescaped + strlen("url=")), -1,
303 : CPLES_URL);
304 15 : osRet += "url=";
305 15 : osRet += pszPath;
306 15 : CPLFree(pszPath);
307 15 : CPLFree(pszUnescaped);
308 : }
309 : else
310 : {
311 14 : osRet += aosTokens[i];
312 : }
313 : }
314 15 : return osRet;
315 : }
316 :
317 183727 : const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos);
318 183717 : if (iFileStart == 0)
319 : {
320 81 : return std::string(".");
321 : }
322 :
323 367259 : std::string osRet(pszFilename, iFileStart);
324 :
325 183583 : if (iFileStart > 1 && (osRet.back() == '/' || osRet.back() == '\\'))
326 183543 : osRet.pop_back();
327 :
328 183645 : if (nSuffixPos)
329 : {
330 1 : osRet += (pszFilename + nSuffixPos);
331 : }
332 :
333 183645 : return osRet;
334 : }
335 :
336 : /************************************************************************/
337 : /* CPLGetDirname() */
338 : /************************************************************************/
339 :
340 : /**
341 : * Extract directory path portion of filename.
342 : *
343 : * Returns a string containing the directory path portion of the passed
344 : * filename. If there is no path in the passed filename the dot will be
345 : * returned. It is the only difference from CPLGetPath().
346 : *
347 : * \code{.cpp}
348 : * CPLGetDirname( "abc/def.xyz" ) == "abc"
349 : * CPLGetDirname( "/abc/def/" ) == "/abc/def"
350 : * CPLGetDirname( "/" ) == "/"
351 : * CPLGetDirname( "/abc/def" ) == "/abc"
352 : * CPLGetDirname( "abc" ) == "."
353 : * \endcode
354 : *
355 : * @param pszFilename the filename potentially including a path.
356 : *
357 : * @return Path in an internal string which must not be freed. The string
358 : * may be destroyed by the next CPL filename handling call. The returned
359 : * will generally not contain a trailing path separator.
360 : */
361 :
362 20 : const char *CPLGetDirname(const char *pszFilename)
363 :
364 : {
365 20 : return CPLPathReturnTLSString(CPLGetDirnameSafe(pszFilename), __FUNCTION__);
366 : }
367 :
368 : /************************************************************************/
369 : /* CPLGetFilename() */
370 : /************************************************************************/
371 :
372 : /**
373 : * Extract non-directory portion of filename.
374 : *
375 : * Returns a string containing the bare filename portion of the passed
376 : * filename. If there is no filename (passed value ends in trailing directory
377 : * separator) an empty string is returned.
378 : *
379 : * \code{.cpp}
380 : * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
381 : * CPLGetFilename( "/abc/def/" ) == ""
382 : * CPLGetFilename( "abc/def" ) == "def"
383 : * \endcode
384 : *
385 : * @param pszFullFilename the full filename potentially including a path.
386 : *
387 : * @return just the non-directory portion of the path (points back into
388 : * original string).
389 : */
390 :
391 903833 : const char *CPLGetFilename(const char *pszFullFilename)
392 :
393 : {
394 903833 : const int iFileStart = CPLFindFilenameStart(pszFullFilename);
395 :
396 903834 : return pszFullFilename + iFileStart;
397 : }
398 :
399 : /************************************************************************/
400 : /* CPLGetBasenameSafe() */
401 : /************************************************************************/
402 :
403 : /**
404 : * Extract basename (non-directory, non-extension) portion of filename.
405 : *
406 : * Returns a string containing the file basename portion of the passed
407 : * name. If there is no basename (passed value ends in trailing directory
408 : * separator, or filename starts with a dot) an empty string is returned.
409 : *
410 : * \code{.cpp}
411 : * CPLGetBasename( "abc/def.xyz" ) == "def"
412 : * CPLGetBasename( "abc/def" ) == "def"
413 : * CPLGetBasename( "abc/def/" ) == ""
414 : * \endcode
415 : *
416 : * @param pszFullFilename the full filename potentially including a path.
417 : *
418 : * @return just the non-directory, non-extension portion of the path
419 : *
420 : * @since 3.11
421 : */
422 :
423 458490 : std::string CPLGetBasenameSafe(const char *pszFullFilename)
424 :
425 : {
426 : const size_t iFileStart =
427 458490 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
428 :
429 458490 : size_t iExtStart = strlen(pszFullFilename);
430 2575810 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
431 : iExtStart--)
432 : {
433 : }
434 :
435 458490 : if (iExtStart == iFileStart)
436 25973 : iExtStart = strlen(pszFullFilename);
437 :
438 458490 : const size_t nLength = iExtStart - iFileStart;
439 458490 : return std::string(pszFullFilename + iFileStart, nLength);
440 : }
441 :
442 : /************************************************************************/
443 : /* CPLGetBasename() */
444 : /************************************************************************/
445 :
446 : /**
447 : * Extract basename (non-directory, non-extension) portion of filename.
448 : *
449 : * Returns a string containing the file basename portion of the passed
450 : * name. If there is no basename (passed value ends in trailing directory
451 : * separator, or filename starts with a dot) an empty string is returned.
452 : *
453 : * \code{.cpp}
454 : * CPLGetBasename( "abc/def.xyz" ) == "def"
455 : * CPLGetBasename( "abc/def" ) == "def"
456 : * CPLGetBasename( "abc/def/" ) == ""
457 : * \endcode
458 : *
459 : * @param pszFullFilename the full filename potentially including a path.
460 : *
461 : * @return just the non-directory, non-extension portion of the path in
462 : * an internal string which must not be freed. The string
463 : * may be destroyed by the next CPL filename handling call.
464 : *
465 : * @deprecated If using C++, prefer using CPLGetBasenameSafe() instead
466 : */
467 :
468 230 : const char *CPLGetBasename(const char *pszFullFilename)
469 :
470 : {
471 460 : return CPLPathReturnTLSString(CPLGetBasenameSafe(pszFullFilename),
472 460 : __FUNCTION__);
473 : }
474 :
475 : /************************************************************************/
476 : /* CPLGetExtensionSafe() */
477 : /************************************************************************/
478 :
479 : /**
480 : * Extract filename extension from full filename.
481 : *
482 : * Returns a string containing the extension portion of the passed
483 : * name. If there is no extension (the filename has no dot) an empty string
484 : * is returned. The returned extension will not include the period.
485 : *
486 : * \code{.cpp}
487 : * CPLGetExtensionSafe( "abc/def.xyz" ) == "xyz"
488 : * CPLGetExtensionSafe( "abc/def" ) == ""
489 : * \endcode
490 : *
491 : * @param pszFullFilename the full filename potentially including a path.
492 : *
493 : * @return just the extension portion of the path.
494 : *
495 : * @since 3.11
496 : */
497 :
498 655567 : std::string CPLGetExtensionSafe(const char *pszFullFilename)
499 :
500 : {
501 655567 : if (pszFullFilename[0] == '\0')
502 1706 : return std::string();
503 :
504 : size_t iFileStart =
505 653861 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
506 653861 : size_t iExtStart = strlen(pszFullFilename);
507 4472730 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
508 : iExtStart--)
509 : {
510 : }
511 :
512 653861 : if (iExtStart == iFileStart)
513 99626 : iExtStart = strlen(pszFullFilename) - 1;
514 :
515 : // If the extension is too long, it is very much likely not an extension,
516 : // but another component of the path
517 653861 : const size_t knMaxExtensionSize = 10;
518 653861 : if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
519 2284 : return "";
520 :
521 651577 : return std::string(pszFullFilename + iExtStart + 1);
522 : }
523 :
524 : /************************************************************************/
525 : /* CPLGetExtension() */
526 : /************************************************************************/
527 :
528 : /**
529 : * Extract filename extension from full filename.
530 : *
531 : * Returns a string containing the extension portion of the passed
532 : * name. If there is no extension (the filename has no dot) an empty string
533 : * is returned. The returned extension will not include the period.
534 : *
535 : * \code{.cpp}
536 : * CPLGetExtension( "abc/def.xyz" ) == "xyz"
537 : * CPLGetExtension( "abc/def" ) == ""
538 : * \endcode
539 : *
540 : * @param pszFullFilename the full filename potentially including a path.
541 : *
542 : * @return just the extension portion of the path in
543 : * an internal string which must not be freed. The string
544 : * may be destroyed by the next CPL filename handling call.
545 : *
546 : * @deprecated If using C++, prefer using CPLGetExtensionSafe() instead
547 : */
548 :
549 46 : const char *CPLGetExtension(const char *pszFullFilename)
550 :
551 : {
552 92 : return CPLPathReturnTLSString(CPLGetExtensionSafe(pszFullFilename),
553 92 : __FUNCTION__);
554 : }
555 :
556 : /************************************************************************/
557 : /* CPLGetCurrentDir() */
558 : /************************************************************************/
559 :
560 : /**
561 : * Get the current working directory name.
562 : *
563 : * @return a pointer to buffer, containing current working directory path
564 : * or NULL in case of error. User is responsible to free that buffer
565 : * after usage with CPLFree() function.
566 : * If HAVE_GETCWD macro is not defined, the function returns NULL.
567 : **/
568 :
569 : #ifdef _WIN32
570 : char *CPLGetCurrentDir()
571 : {
572 : const size_t nPathMax = _MAX_PATH;
573 : wchar_t *pwszDirPath =
574 : static_cast<wchar_t *>(VSI_MALLOC_VERBOSE(nPathMax * sizeof(wchar_t)));
575 : char *pszRet = nullptr;
576 : if (pwszDirPath != nullptr && _wgetcwd(pwszDirPath, nPathMax) != nullptr)
577 : {
578 : pszRet = CPLRecodeFromWChar(pwszDirPath, CPL_ENC_UCS2, CPL_ENC_UTF8);
579 : }
580 : CPLFree(pwszDirPath);
581 : return pszRet;
582 : }
583 : #elif defined(HAVE_GETCWD)
584 5317 : char *CPLGetCurrentDir()
585 : {
586 : #if PATH_MAX
587 5317 : const size_t nPathMax = PATH_MAX;
588 : #else
589 : const size_t nPathMax = 8192;
590 : #endif
591 :
592 5317 : char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
593 5317 : if (!pszDirPath)
594 0 : return nullptr;
595 :
596 5317 : return getcwd(pszDirPath, nPathMax);
597 : }
598 : #else // !HAVE_GETCWD
599 : char *CPLGetCurrentDir()
600 : {
601 : return nullptr;
602 : }
603 : #endif // HAVE_GETCWD
604 :
605 : /************************************************************************/
606 : /* CPLResetExtension() */
607 : /************************************************************************/
608 :
609 : /**
610 : * Replace the extension with the provided one.
611 : *
612 : * @param pszPath the input path, this string is not altered.
613 : * @param pszExt the new extension to apply to the given path.
614 : *
615 : * @return an altered filename with the new extension.
616 : *
617 : * @since 3.11
618 : */
619 :
620 203926 : std::string CPLResetExtensionSafe(const char *pszPath, const char *pszExt)
621 :
622 : {
623 203926 : std::string osRet(pszPath);
624 :
625 : /* -------------------------------------------------------------------- */
626 : /* First, try and strip off any existing extension. */
627 : /* -------------------------------------------------------------------- */
628 :
629 1043000 : for (size_t i = osRet.size(); i > 0;)
630 : {
631 1042660 : --i;
632 1042660 : if (osRet[i] == '.')
633 : {
634 188199 : osRet.resize(i);
635 188199 : break;
636 : }
637 854460 : else if (osRet[i] == '/' || osRet[i] == '\\' || osRet[i] == ':')
638 : {
639 15382 : break;
640 : }
641 : }
642 :
643 : /* -------------------------------------------------------------------- */
644 : /* Append the new extension. */
645 : /* -------------------------------------------------------------------- */
646 203925 : osRet += '.';
647 203925 : osRet += pszExt;
648 :
649 203925 : return osRet;
650 : }
651 :
652 : /************************************************************************/
653 : /* CPLResetExtension() */
654 : /************************************************************************/
655 :
656 : /**
657 : * Replace the extension with the provided one.
658 : *
659 : * @param pszPath the input path, this string is not altered.
660 : * @param pszExt the new extension to apply to the given path.
661 : *
662 : * @return an altered filename with the new extension. Do not
663 : * modify or free the returned string. The string may be destroyed by the
664 : * next CPL call.
665 : *
666 : * @deprecated If using C++, prefer using CPLResetExtensionSafe() instead
667 : */
668 :
669 135 : const char *CPLResetExtension(const char *pszPath, const char *pszExt)
670 :
671 : {
672 270 : return CPLPathReturnTLSString(CPLResetExtensionSafe(pszPath, pszExt),
673 270 : __FUNCTION__);
674 : }
675 :
676 : /************************************************************************/
677 : /* CPLFormFilenameSafe() */
678 : /************************************************************************/
679 :
680 : /**
681 : * Build a full file path from a passed path, file basename and extension.
682 : *
683 : * The path, and extension are optional. The basename may in fact contain
684 : * an extension if desired.
685 : *
686 : * \code{.cpp}
687 : * CPLFormFilenameSafe("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
688 : * CPLFormFilenameSafe(NULL,"def", NULL ) == "def"
689 : * CPLFormFilenameSafe(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
690 : * CPLFormFilenameSafe("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
691 : * CPLFormFilenameSafe("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
692 : * \endcode
693 : *
694 : * @param pszPath directory path to the directory containing the file. This
695 : * may be relative or absolute, and may have a trailing path separator or
696 : * not. May be NULL.
697 : *
698 : * @param pszBasename file basename. May optionally have path and/or
699 : * extension. Must *NOT* be NULL.
700 : *
701 : * @param pszExtension file extension, optionally including the period. May
702 : * be NULL.
703 : *
704 : * @return a fully formed filename.
705 : *
706 : * @since 3.11
707 : */
708 :
709 462161 : std::string CPLFormFilenameSafe(const char *pszPath, const char *pszBasename,
710 : const char *pszExtension)
711 :
712 : {
713 462161 : if (pszBasename[0] == '.' &&
714 13592 : (pszBasename[1] == '/' || pszBasename[1] == '\\'))
715 61 : pszBasename += 2;
716 :
717 462161 : const char *pszAddedPathSep = "";
718 462161 : const char *pszAddedExtSep = "";
719 :
720 462161 : if (pszPath == nullptr)
721 11692 : pszPath = "";
722 462161 : size_t nLenPath = strlen(pszPath);
723 :
724 462161 : const char *pszQuestionMark = nullptr;
725 462161 : if (STARTS_WITH_CI(pszPath, "/vsicurl/http"))
726 : {
727 157 : pszQuestionMark = strchr(pszPath, '?');
728 157 : if (pszQuestionMark)
729 : {
730 1 : nLenPath = pszQuestionMark - pszPath;
731 : }
732 157 : pszAddedPathSep = "/";
733 : }
734 :
735 818324 : if (!CPLIsFilenameRelative(pszPath) && pszBasename[0] == '.' &&
736 818468 : pszBasename[1] == '.' &&
737 216 : (pszBasename[2] == 0 || pszBasename[2] == '\\' ||
738 183 : pszBasename[2] == '/'))
739 : {
740 : // "/a/b/" + "..[/something]" --> "/a[/something]"
741 : // "/a/b" + "..[/something]" --> "/a[/something]"
742 216 : if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
743 14 : nLenPath--;
744 : while (true)
745 : {
746 305 : const char *pszBasenameOri = pszBasename;
747 305 : const size_t nLenPathOri = nLenPath;
748 1553 : while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
749 1542 : pszPath[nLenPath - 1] != '/')
750 : {
751 1248 : nLenPath--;
752 : }
753 305 : 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 : }
765 18 : break;
766 : }
767 287 : else if ((nLenPath > 1 && pszPath[0] == '/') ||
768 11 : (nLenPath > 2 && pszPath[1] == ':') ||
769 1 : (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
770 : {
771 280 : nLenPath--;
772 280 : pszBasename += 2;
773 280 : if ((pszBasename[0] == '/' || pszBasename[0] == '\\') &&
774 261 : pszBasename[1] == '.' && pszBasename[2] == '.')
775 : {
776 89 : pszBasename++;
777 : }
778 : else
779 : {
780 : break;
781 : }
782 : }
783 : else
784 : {
785 : // cppcheck-suppress redundantAssignment
786 7 : pszBasename = pszBasenameOri;
787 7 : nLenPath = nLenPathOri;
788 7 : if (pszAddedPathSep[0] == 0)
789 7 : pszAddedPathSep = pszPath[0] == '/'
790 7 : ? "/"
791 2 : : VSIGetDirectorySeparator(pszPath);
792 7 : break;
793 : }
794 89 : }
795 : }
796 461873 : else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
797 447306 : pszPath[nLenPath - 1] != '\\')
798 : {
799 447295 : if (pszAddedPathSep[0] == 0)
800 447136 : pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
801 : }
802 :
803 462103 : if (pszExtension == nullptr)
804 285608 : pszExtension = "";
805 176495 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
806 159796 : pszAddedExtSep = ".";
807 :
808 462103 : std::string osRes;
809 462130 : osRes.reserve(nLenPath + strlen(pszAddedPathSep) + strlen(pszBasename) +
810 462130 : strlen(pszAddedExtSep) + strlen(pszExtension) +
811 462130 : (pszQuestionMark ? strlen(pszQuestionMark) : 0));
812 462107 : osRes.assign(pszPath, nLenPath);
813 462096 : osRes += pszAddedPathSep;
814 462071 : osRes += pszBasename;
815 462036 : osRes += pszAddedExtSep;
816 462040 : osRes += pszExtension;
817 :
818 462039 : if (pszQuestionMark)
819 : {
820 1 : osRes += pszQuestionMark;
821 : }
822 :
823 462010 : return osRes;
824 : }
825 :
826 : /************************************************************************/
827 : /* CPLFormFilename() */
828 : /************************************************************************/
829 :
830 : /**
831 : * Build a full file path from a passed path, file basename and extension.
832 : *
833 : * The path, and extension are optional. The basename may in fact contain
834 : * an extension if desired.
835 : *
836 : * \code{.cpp}
837 : * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
838 : * CPLFormFilename(NULL,"def", NULL ) == "def"
839 : * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
840 : * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
841 : * CPLFormFilename("/a/b/c", "../d", NULL ) == "/a/b/d" (since 3.10.1)
842 : * \endcode
843 : *
844 : * @param pszPath directory path to the directory containing the file. This
845 : * may be relative or absolute, and may have a trailing path separator or
846 : * not. May be NULL.
847 : *
848 : * @param pszBasename file basename. May optionally have path and/or
849 : * extension. Must *NOT* be NULL.
850 : *
851 : * @param pszExtension file extension, optionally including the period. May
852 : * be NULL.
853 : *
854 : * @return a fully formed filename in an internal static string. Do not
855 : * modify or free the returned string. The string may be destroyed by the
856 : * next CPL call.
857 : *
858 : * @deprecated If using C++, prefer using CPLFormFilenameSafe() instead
859 : */
860 87 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
861 : const char *pszExtension)
862 :
863 : {
864 87 : return CPLPathReturnTLSString(
865 174 : CPLFormFilenameSafe(pszPath, pszBasename, pszExtension), __FUNCTION__);
866 : }
867 :
868 : /************************************************************************/
869 : /* CPLFormCIFilenameSafe() */
870 : /************************************************************************/
871 :
872 : /**
873 : * Case insensitive file searching, returning full path.
874 : *
875 : * This function tries to return the path to a file regardless of
876 : * whether the file exactly matches the basename, and extension case, or
877 : * is all upper case, or all lower case. The path is treated as case
878 : * sensitive. This function is equivalent to CPLFormFilename() on
879 : * case insensitive file systems (like Windows).
880 : *
881 : * @param pszPath directory path to the directory containing the file. This
882 : * may be relative or absolute, and may have a trailing path separator or
883 : * not. May be NULL.
884 : *
885 : * @param pszBasename file basename. May optionally have path and/or
886 : * extension. May not be NULL.
887 : *
888 : * @param pszExtension file extension, optionally including the period. May
889 : * be NULL.
890 : *
891 : * @return a fully formed filename.
892 : *
893 : * @since 3.11
894 : */
895 :
896 6827 : std::string CPLFormCIFilenameSafe(const char *pszPath, const char *pszBasename,
897 : const char *pszExtension)
898 :
899 : {
900 : // On case insensitive filesystems, just default to CPLFormFilename().
901 6827 : if (!VSIIsCaseSensitiveFS(pszPath))
902 0 : return CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
903 :
904 6827 : const char *pszAddedExtSep = "";
905 6827 : size_t nLen = strlen(pszBasename) + 2;
906 :
907 6827 : if (pszExtension != nullptr)
908 2480 : nLen += strlen(pszExtension);
909 :
910 6827 : char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
911 6827 : if (pszFilename == nullptr)
912 0 : return "";
913 :
914 6827 : if (pszExtension == nullptr)
915 4347 : pszExtension = "";
916 2480 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
917 2428 : pszAddedExtSep = ".";
918 :
919 6827 : snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
920 : pszExtension);
921 :
922 13654 : std::string osRet = CPLFormFilenameSafe(pszPath, pszFilename, nullptr);
923 : VSIStatBufL sStatBuf;
924 6827 : int nStatRet = VSIStatExL(osRet.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
925 :
926 6827 : if (nStatRet != 0)
927 : {
928 90144 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
929 : {
930 83755 : pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
931 : }
932 :
933 : std::string osTmpPath(
934 12778 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
935 : nStatRet =
936 6389 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
937 6389 : if (nStatRet == 0)
938 3 : osRet = std::move(osTmpPath);
939 : }
940 :
941 6827 : if (nStatRet != 0)
942 : {
943 90120 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
944 : {
945 83734 : pszFilename[i] = static_cast<char>(
946 83734 : CPLTolower(static_cast<unsigned char>(pszFilename[i])));
947 : }
948 :
949 : std::string osTmpPath(
950 12772 : CPLFormFilenameSafe(pszPath, pszFilename, nullptr));
951 : nStatRet =
952 6386 : VSIStatExL(osTmpPath.c_str(), &sStatBuf, VSI_STAT_EXISTS_FLAG);
953 6386 : if (nStatRet == 0)
954 8 : osRet = std::move(osTmpPath);
955 : }
956 :
957 6827 : if (nStatRet != 0)
958 6378 : osRet = CPLFormFilenameSafe(pszPath, pszBasename, pszExtension);
959 :
960 6827 : CPLFree(pszFilename);
961 :
962 6827 : return osRet;
963 : }
964 :
965 : /************************************************************************/
966 : /* CPLFormCIFilename() */
967 : /************************************************************************/
968 :
969 : /**
970 : * Case insensitive file searching, returning full path.
971 : *
972 : * This function tries to return the path to a file regardless of
973 : * whether the file exactly matches the basename, and extension case, or
974 : * is all upper case, or all lower case. The path is treated as case
975 : * sensitive. This function is equivalent to CPLFormFilename() on
976 : * case insensitive file systems (like Windows).
977 : *
978 : * @param pszPath directory path to the directory containing the file. This
979 : * may be relative or absolute, and may have a trailing path separator or
980 : * not. May be NULL.
981 : *
982 : * @param pszBasename file basename. May optionally have path and/or
983 : * extension. May not be NULL.
984 : *
985 : * @param pszExtension file extension, optionally including the period. May
986 : * be NULL.
987 : *
988 : * @return a fully formed filename in an internal static string. Do not
989 : * modify or free the returned string. The string may be destroyed by the
990 : * next CPL call.
991 : *
992 : * @deprecated If using C++, prefer using CPLFormCIFilenameSafe() instead
993 : */
994 :
995 0 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
996 : const char *pszExtension)
997 :
998 : {
999 0 : return CPLPathReturnTLSString(
1000 0 : CPLFormCIFilenameSafe(pszPath, pszBasename, pszExtension),
1001 0 : __FUNCTION__);
1002 : }
1003 :
1004 : /************************************************************************/
1005 : /* CPLProjectRelativeFilenameSafe() */
1006 : /************************************************************************/
1007 :
1008 : /**
1009 : * Find a file relative to a project file.
1010 : *
1011 : * Given the path to a "project" directory, and a path to a secondary file
1012 : * referenced from that project, build a path to the secondary file
1013 : * that the current application can use. If the secondary path is already
1014 : * absolute, rather than relative, then it will be returned unaltered.
1015 : *
1016 : * Examples:
1017 : * \code{.cpp}
1018 : * CPLProjectRelativeFilenameSafe("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1019 : * CPLProjectRelativeFilenameSafe("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1020 : * CPLProjectRelativeFilenameSafe("/xy", "abc.gif") == "/xy/abc.gif"
1021 : * CPLProjectRelativeFilenameSafe("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1022 : * CPLProjectRelativeFilenameSafe("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1023 : * \endcode
1024 : *
1025 : * @param pszProjectDir the directory relative to which the secondary files
1026 : * path should be interpreted.
1027 : * @param pszSecondaryFilename the filename (potentially with path) that
1028 : * is to be interpreted relative to the project directory.
1029 : *
1030 : * @return a composed path to the secondary file.
1031 : *
1032 : * @since 3.11
1033 : */
1034 :
1035 4481 : std::string CPLProjectRelativeFilenameSafe(const char *pszProjectDir,
1036 : const char *pszSecondaryFilename)
1037 :
1038 : {
1039 8727 : if (pszProjectDir == nullptr || pszProjectDir[0] == 0 ||
1040 4246 : !CPLIsFilenameRelative(pszSecondaryFilename))
1041 : {
1042 731 : return pszSecondaryFilename;
1043 : }
1044 :
1045 7500 : std::string osRes(pszProjectDir);
1046 3750 : if (osRes.back() != '/' && osRes.back() != '\\')
1047 : {
1048 3750 : osRes += VSIGetDirectorySeparator(pszProjectDir);
1049 : }
1050 :
1051 3750 : osRes += pszSecondaryFilename;
1052 3750 : return osRes;
1053 : }
1054 :
1055 : /************************************************************************/
1056 : /* CPLProjectRelativeFilename() */
1057 : /************************************************************************/
1058 :
1059 : /**
1060 : * Find a file relative to a project file.
1061 : *
1062 : * Given the path to a "project" directory, and a path to a secondary file
1063 : * referenced from that project, build a path to the secondary file
1064 : * that the current application can use. If the secondary path is already
1065 : * absolute, rather than relative, then it will be returned unaltered.
1066 : *
1067 : * Examples:
1068 : * \code{.cpp}
1069 : * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
1070 : * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
1071 : * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
1072 : * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
1073 : * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
1074 : * \endcode
1075 : *
1076 : * @param pszProjectDir the directory relative to which the secondary files
1077 : * path should be interpreted.
1078 : * @param pszSecondaryFilename the filename (potentially with path) that
1079 : * is to be interpreted relative to the project directory.
1080 : *
1081 : * @return a composed path to the secondary file. The returned string is
1082 : * internal and should not be altered, freed, or depending on past the next
1083 : * CPL call.
1084 : *
1085 : * @deprecated If using C++, prefer using CPLProjectRelativeFilenameSafe() instead
1086 : */
1087 :
1088 0 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
1089 : const char *pszSecondaryFilename)
1090 :
1091 : {
1092 0 : return CPLPathReturnTLSString(
1093 0 : CPLProjectRelativeFilenameSafe(pszProjectDir, pszSecondaryFilename),
1094 0 : __FUNCTION__);
1095 : }
1096 :
1097 : /************************************************************************/
1098 : /* CPLIsFilenameRelative() */
1099 : /************************************************************************/
1100 :
1101 : /**
1102 : * Is filename relative or absolute?
1103 : *
1104 : * The test is filesystem convention agnostic. That is it will test for
1105 : * Unix style and windows style path conventions regardless of the actual
1106 : * system in use.
1107 : *
1108 : * @param pszFilename the filename with path to test.
1109 : *
1110 : * @return TRUE if the filename is relative or FALSE if it is absolute.
1111 : */
1112 :
1113 486352 : int CPLIsFilenameRelative(const char *pszFilename)
1114 :
1115 : {
1116 486352 : if ((pszFilename[0] != '\0' &&
1117 474523 : (STARTS_WITH(pszFilename + 1, ":\\") ||
1118 474489 : STARTS_WITH(pszFilename + 1, ":/") ||
1119 474502 : strstr(pszFilename + 1, "://") // http://, ftp:// etc....
1120 485167 : )) ||
1121 485167 : STARTS_WITH(pszFilename, "\\\\?\\") // Windows extended Length Path.
1122 485172 : || pszFilename[0] == '\\' || pszFilename[0] == '/')
1123 368119 : return FALSE;
1124 :
1125 118233 : return TRUE;
1126 : }
1127 :
1128 : /************************************************************************/
1129 : /* CPLExtractRelativePath() */
1130 : /************************************************************************/
1131 :
1132 : /**
1133 : * Get relative path from directory to target file.
1134 : *
1135 : * Computes a relative path for pszTarget relative to pszBaseDir.
1136 : * Currently this only works if they share a common base path. The returned
1137 : * path is normally into the pszTarget string. It should only be considered
1138 : * valid as long as pszTarget is valid or till the next call to
1139 : * this function, whichever comes first.
1140 : *
1141 : * @param pszBaseDir the name of the directory relative to which the path
1142 : * should be computed. pszBaseDir may be NULL in which case the original
1143 : * target is returned without relativizing.
1144 : *
1145 : * @param pszTarget the filename to be changed to be relative to pszBaseDir.
1146 : *
1147 : * @param pbGotRelative Pointer to location in which a flag is placed
1148 : * indicating that the returned path is relative to the basename (TRUE) or
1149 : * not (FALSE). This pointer may be NULL if flag is not desired.
1150 : *
1151 : * @return an adjusted path or the original if it could not be made relative
1152 : * to the pszBaseFile's path.
1153 : **/
1154 :
1155 2807 : const char *CPLExtractRelativePath(const char *pszBaseDir,
1156 : const char *pszTarget, int *pbGotRelative)
1157 :
1158 : {
1159 : /* -------------------------------------------------------------------- */
1160 : /* If we don't have a basedir, then we can't relativize the path. */
1161 : /* -------------------------------------------------------------------- */
1162 2807 : if (pszBaseDir == nullptr)
1163 : {
1164 0 : if (pbGotRelative != nullptr)
1165 0 : *pbGotRelative = FALSE;
1166 :
1167 0 : return pszTarget;
1168 : }
1169 :
1170 2807 : const size_t nBasePathLen = strlen(pszBaseDir);
1171 :
1172 : /* -------------------------------------------------------------------- */
1173 : /* One simple case is when the base dir is '.' and the target */
1174 : /* filename is relative. */
1175 : /* -------------------------------------------------------------------- */
1176 2820 : if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
1177 13 : CPLIsFilenameRelative(pszTarget))
1178 : {
1179 13 : if (pbGotRelative != nullptr)
1180 13 : *pbGotRelative = TRUE;
1181 :
1182 13 : return pszTarget;
1183 : }
1184 :
1185 : /* -------------------------------------------------------------------- */
1186 : /* By this point, if we don't have a base path, we can't have a */
1187 : /* meaningful common prefix. */
1188 : /* -------------------------------------------------------------------- */
1189 2794 : if (nBasePathLen == 0)
1190 : {
1191 0 : if (pbGotRelative != nullptr)
1192 0 : *pbGotRelative = FALSE;
1193 :
1194 0 : return pszTarget;
1195 : }
1196 :
1197 : /* -------------------------------------------------------------------- */
1198 : /* If we don't have a common path prefix, then we can't get a */
1199 : /* relative path. */
1200 : /* -------------------------------------------------------------------- */
1201 2794 : if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
1202 2367 : (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
1203 : {
1204 428 : if (pbGotRelative != nullptr)
1205 428 : *pbGotRelative = FALSE;
1206 :
1207 428 : return pszTarget;
1208 : }
1209 :
1210 : /* -------------------------------------------------------------------- */
1211 : /* We have a relative path. Strip it off to get a string to */
1212 : /* return. */
1213 : /* -------------------------------------------------------------------- */
1214 2366 : if (pbGotRelative != nullptr)
1215 2282 : *pbGotRelative = TRUE;
1216 :
1217 2366 : return pszTarget + nBasePathLen + 1;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* CPLCleanTrailingSlashSafe() */
1222 : /************************************************************************/
1223 :
1224 : /**
1225 : * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1226 : *
1227 : * Returns a string containing the portion of the passed path string with
1228 : * trailing slash removed. If there is no path in the passed filename
1229 : * an empty string will be returned (not NULL).
1230 : *
1231 : * \code{.cpp}
1232 : * CPLCleanTrailingSlashSafe( "abc/def/" ) == "abc/def"
1233 : * CPLCleanTrailingSlashSafe( "abc/def" ) == "abc/def"
1234 : * CPLCleanTrailingSlashSafe( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1235 : * CPLCleanTrailingSlashSafe( "c:\\abc\\def" ) == "c:\\abc\\def"
1236 : * CPLCleanTrailingSlashSafe( "abc" ) == "abc"
1237 : * \endcode
1238 : *
1239 : * @param pszPath the path to be cleaned up
1240 : *
1241 : * @return Path
1242 : *
1243 : * @since 3.11
1244 : */
1245 :
1246 9 : std::string CPLCleanTrailingSlashSafe(const char *pszPath)
1247 :
1248 : {
1249 9 : std::string osRes(pszPath);
1250 9 : if (!osRes.empty() && (osRes.back() == '\\' || osRes.back() == '/'))
1251 0 : osRes.pop_back();
1252 9 : return osRes;
1253 : }
1254 :
1255 : /************************************************************************/
1256 : /* CPLCleanTrailingSlash() */
1257 : /************************************************************************/
1258 :
1259 : /**
1260 : * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
1261 : *
1262 : * Returns a string containing the portion of the passed path string with
1263 : * trailing slash removed. If there is no path in the passed filename
1264 : * an empty string will be returned (not NULL).
1265 : *
1266 : * \code{.cpp}
1267 : * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
1268 : * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
1269 : * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
1270 : * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
1271 : * CPLCleanTrailingSlash( "abc" ) == "abc"
1272 : * \endcode
1273 : *
1274 : * @param pszPath the path to be cleaned up
1275 : *
1276 : * @return Path in an internal string which must not be freed. The string
1277 : * may be destroyed by the next CPL filename handling call.
1278 : *
1279 : * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1280 : */
1281 :
1282 0 : const char *CPLCleanTrailingSlash(const char *pszPath)
1283 :
1284 : {
1285 0 : return CPLPathReturnTLSString(CPLCleanTrailingSlashSafe(pszPath),
1286 0 : __FUNCTION__);
1287 : }
1288 :
1289 : /************************************************************************/
1290 : /* CPLCorrespondingPaths() */
1291 : /************************************************************************/
1292 :
1293 : /**
1294 : * Identify corresponding paths.
1295 : *
1296 : * Given a prototype old and new filename this function will attempt
1297 : * to determine corresponding names for a set of other old filenames that
1298 : * will rename them in a similar manner. This correspondence assumes there
1299 : * are two possibly kinds of renaming going on. A change of path, and a
1300 : * change of filename stem.
1301 : *
1302 : * If a consistent renaming cannot be established for all the files this
1303 : * function will return indicating an error.
1304 : *
1305 : * The returned file list becomes owned by the caller and should be destroyed
1306 : * with CSLDestroy().
1307 : *
1308 : * @param pszOldFilename path to old prototype file.
1309 : * @param pszNewFilename path to new prototype file.
1310 : * @param papszFileList list of other files associated with pszOldFilename to
1311 : * rename similarly.
1312 : *
1313 : * @return a list of files corresponding to papszFileList but renamed to
1314 : * correspond to pszNewFilename.
1315 : */
1316 :
1317 187 : char **CPLCorrespondingPaths(const char *pszOldFilename,
1318 : const char *pszNewFilename,
1319 : CSLConstList papszFileList)
1320 :
1321 : {
1322 187 : if (CSLCount(papszFileList) == 0)
1323 0 : return nullptr;
1324 :
1325 : VSIStatBufL sStatBuf;
1326 187 : if (VSIStatL(pszOldFilename, &sStatBuf) == 0 && VSI_ISDIR(sStatBuf.st_mode))
1327 : {
1328 8 : CPLStringList aosNewList;
1329 4 : std::string_view svOldFilename(pszOldFilename);
1330 20 : for (int i = 0; papszFileList[i] != nullptr; i++)
1331 : {
1332 16 : if (cpl::starts_with(std::string_view(papszFileList[i]),
1333 32 : svOldFilename) &&
1334 16 : (papszFileList[i][svOldFilename.size()] == '/' ||
1335 0 : papszFileList[i][svOldFilename.size()] == '\\'))
1336 : {
1337 : // If the old file list contains entries like oldpath/filename,
1338 : // generate newpath/filename
1339 16 : aosNewList.push_back(CPLFormFilenameSafe(
1340 16 : pszNewFilename, papszFileList[i] + svOldFilename.size() + 1,
1341 : nullptr));
1342 : }
1343 : else
1344 : {
1345 0 : CPLError(CE_Failure, CPLE_AppDefined,
1346 : "Unable to copy/rename fileset due to unexpected "
1347 : "source filename.");
1348 0 : return nullptr;
1349 : }
1350 : }
1351 4 : return aosNewList.StealList();
1352 : }
1353 :
1354 : /* -------------------------------------------------------------------- */
1355 : /* There is a special case for a one item list which exactly */
1356 : /* matches the old name, to rename to the new name. */
1357 : /* -------------------------------------------------------------------- */
1358 355 : if (CSLCount(papszFileList) == 1 &&
1359 172 : strcmp(pszOldFilename, papszFileList[0]) == 0)
1360 : {
1361 172 : return CSLAddString(nullptr, pszNewFilename);
1362 : }
1363 :
1364 22 : const std::string osOldPath = CPLGetPathSafe(pszOldFilename);
1365 22 : const std::string osOldBasename = CPLGetBasenameSafe(pszOldFilename);
1366 22 : const std::string osNewBasename = CPLGetBasenameSafe(pszNewFilename);
1367 :
1368 : /* -------------------------------------------------------------------- */
1369 : /* If the basename is changing, verify that all source files */
1370 : /* have the same starting basename. */
1371 : /* -------------------------------------------------------------------- */
1372 11 : if (osOldBasename != osNewBasename)
1373 : {
1374 34 : for (int i = 0; papszFileList[i] != nullptr; i++)
1375 : {
1376 24 : if (osOldBasename == CPLGetBasenameSafe(papszFileList[i]))
1377 16 : continue;
1378 :
1379 8 : const std::string osFilePath = CPLGetPathSafe(papszFileList[i]);
1380 8 : const std::string osFileName = CPLGetFilename(papszFileList[i]);
1381 :
1382 8 : if (!EQUALN(osFileName.c_str(), osOldBasename.c_str(),
1383 8 : osOldBasename.size()) ||
1384 16 : !EQUAL(osFilePath.c_str(), osOldPath.c_str()) ||
1385 8 : osFileName[osOldBasename.size()] != '.')
1386 : {
1387 0 : CPLError(
1388 : CE_Failure, CPLE_AppDefined,
1389 : "Unable to copy/rename fileset due irregular basenames.");
1390 0 : return nullptr;
1391 : }
1392 : }
1393 : }
1394 :
1395 : /* -------------------------------------------------------------------- */
1396 : /* If the filename portions differs, ensure they only differ in */
1397 : /* basename. */
1398 : /* -------------------------------------------------------------------- */
1399 11 : if (osOldBasename != osNewBasename)
1400 : {
1401 : const std::string osOldExtra =
1402 10 : CPLGetFilename(pszOldFilename) + osOldBasename.size();
1403 : const std::string osNewExtra =
1404 10 : CPLGetFilename(pszNewFilename) + osNewBasename.size();
1405 :
1406 10 : if (osOldExtra != osNewExtra)
1407 : {
1408 0 : CPLError(CE_Failure, CPLE_AppDefined,
1409 : "Unable to copy/rename fileset due to irregular filename "
1410 : "correspondence.");
1411 0 : return nullptr;
1412 : }
1413 : }
1414 :
1415 : /* -------------------------------------------------------------------- */
1416 : /* Generate the new filenames. */
1417 : /* -------------------------------------------------------------------- */
1418 11 : char **papszNewList = nullptr;
1419 11 : const std::string osNewPath = CPLGetPathSafe(pszNewFilename);
1420 :
1421 37 : for (int i = 0; papszFileList[i] != nullptr; i++)
1422 : {
1423 52 : const std::string osOldFilename = CPLGetFilename(papszFileList[i]);
1424 :
1425 : const std::string osNewFilename =
1426 26 : osOldBasename == osNewBasename
1427 : ? CPLFormFilenameSafe(osNewPath.c_str(), osOldFilename.c_str(),
1428 : nullptr)
1429 : : CPLFormFilenameSafe(osNewPath.c_str(), osNewBasename.c_str(),
1430 24 : osOldFilename.c_str() +
1431 50 : osOldBasename.size());
1432 :
1433 26 : papszNewList = CSLAddString(papszNewList, osNewFilename.c_str());
1434 : }
1435 :
1436 11 : return papszNewList;
1437 : }
1438 :
1439 : /************************************************************************/
1440 : /* CPLGenerateTempFilenameSafe() */
1441 : /************************************************************************/
1442 :
1443 : /**
1444 : * Generate temporary file name.
1445 : *
1446 : * Returns a filename that may be used for a temporary file. The location
1447 : * of the file tries to follow operating system semantics but may be
1448 : * forced via the CPL_TMPDIR configuration option.
1449 : *
1450 : * @param pszStem if non-NULL this will be part of the filename.
1451 : *
1452 : * @return a filename
1453 : *
1454 : * @since 3.11
1455 : */
1456 :
1457 2907 : std::string CPLGenerateTempFilenameSafe(const char *pszStem)
1458 :
1459 : {
1460 2907 : const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1461 :
1462 2907 : if (pszDir == nullptr)
1463 2891 : pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1464 :
1465 2907 : if (pszDir == nullptr)
1466 2891 : pszDir = CPLGetConfigOption("TEMP", nullptr);
1467 :
1468 2907 : if (pszDir == nullptr)
1469 2891 : pszDir = ".";
1470 :
1471 2907 : if (pszStem == nullptr)
1472 2667 : pszStem = "";
1473 :
1474 : static int nTempFileCounter = 0;
1475 5814 : CPLString osFilename;
1476 : osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1477 2907 : CPLAtomicInc(&nTempFileCounter));
1478 :
1479 5814 : return CPLFormFilenameSafe(pszDir, osFilename.c_str(), nullptr);
1480 : }
1481 :
1482 : /************************************************************************/
1483 : /* CPLGenerateTempFilename() */
1484 : /************************************************************************/
1485 :
1486 : /**
1487 : * Generate temporary file name.
1488 : *
1489 : * Returns a filename that may be used for a temporary file. The location
1490 : * of the file tries to follow operating system semantics but may be
1491 : * forced via the CPL_TMPDIR configuration option.
1492 : *
1493 : * @param pszStem if non-NULL this will be part of the filename.
1494 : *
1495 : * @return a filename which is valid till the next CPL call in this thread.
1496 : *
1497 : * @deprecated If using C++, prefer using CPLCleanTrailingSlashSafe() instead
1498 : */
1499 :
1500 7 : const char *CPLGenerateTempFilename(const char *pszStem)
1501 :
1502 : {
1503 14 : return CPLPathReturnTLSString(CPLGenerateTempFilenameSafe(pszStem),
1504 14 : __FUNCTION__);
1505 : }
1506 :
1507 : /************************************************************************/
1508 : /* CPLExpandTildeSafe() */
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 3.11
1521 : */
1522 :
1523 195 : std::string CPLExpandTildeSafe(const char *pszFilename)
1524 :
1525 : {
1526 195 : if (!STARTS_WITH_CI(pszFilename, "~/"))
1527 194 : return pszFilename;
1528 :
1529 1 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1530 1 : if (pszHome == nullptr)
1531 0 : return pszFilename;
1532 :
1533 1 : return CPLFormFilenameSafe(pszHome, pszFilename + 2, nullptr);
1534 : }
1535 :
1536 : /************************************************************************/
1537 : /* CPLExpandTilde() */
1538 : /************************************************************************/
1539 :
1540 : /**
1541 : * Expands ~/ at start of filename.
1542 : *
1543 : * Assumes that the HOME configuration option is defined.
1544 : *
1545 : * @param pszFilename filename potentially starting with ~/
1546 : *
1547 : * @return an expanded filename.
1548 : *
1549 : *
1550 : * @deprecated If using C++, prefer using CPLExpandTildeSafe() instead
1551 : */
1552 :
1553 2 : const char *CPLExpandTilde(const char *pszFilename)
1554 :
1555 : {
1556 4 : return CPLPathReturnTLSString(CPLExpandTildeSafe(pszFilename),
1557 4 : __FUNCTION__);
1558 : }
1559 :
1560 : /************************************************************************/
1561 : /* CPLGetHomeDir() */
1562 : /************************************************************************/
1563 :
1564 : /**
1565 : * Return the path to the home directory
1566 : *
1567 : * That is the value of the USERPROFILE environment variable on Windows,
1568 : * or HOME on other platforms.
1569 : *
1570 : * @return the home directory, or NULL.
1571 : *
1572 : */
1573 :
1574 0 : const char *CPLGetHomeDir()
1575 :
1576 : {
1577 : #ifdef _WIN32
1578 : return CPLGetConfigOption("USERPROFILE", nullptr);
1579 : #else
1580 0 : return CPLGetConfigOption("HOME", nullptr);
1581 : #endif
1582 : }
1583 :
1584 : /************************************************************************/
1585 : /* CPLLaunderForFilenameSafe() */
1586 : /************************************************************************/
1587 :
1588 : /**
1589 : * Launder a string to be compatible of a filename.
1590 : *
1591 : * @param pszName The input string to launder.
1592 : * @param pszOutputPath The directory where the file would be created.
1593 : * Unused for now. May be NULL.
1594 : * @return the laundered name.
1595 : *
1596 : * @since GDAL 3.11
1597 : */
1598 :
1599 1531 : std::string CPLLaunderForFilenameSafe(const char *pszName,
1600 : CPL_UNUSED const char *pszOutputPath)
1601 : {
1602 1531 : return CPLLaunderForFilenameSafe(pszName, '_', nullptr);
1603 : }
1604 :
1605 : /************************************************************************/
1606 : /* CPLLaunderForFilenameSafe() */
1607 : /************************************************************************/
1608 :
1609 : /** Return a string that is compatible with a filename on Linux, Windows and
1610 : * MacOS.
1611 : *
1612 : * Reserved characters '<', '>', ':', '"', '/', '\\', '|', '?', '*', '^', and
1613 : * ASCII control characters are replaced by the replacement character, or
1614 : * removed if it is NUL.
1615 : *
1616 : * Reserved names (".", "..", "CON", "PRN", etc.) are suffixed with the
1617 : * replacement character (or underscore).
1618 : *
1619 : * If the string ends with a final space or dot, the replacement character
1620 : * (or underscore) will be appended.
1621 : *
1622 : * @param osInput Input string.
1623 : * @param chReplacementChar Character to substitute to characters that are not
1624 : * compatible of a file name, or NUL character to
1625 : * remove them.
1626 : * @param pszExtraReservedCharacters String with extra reserved characters that
1627 : * are replaced by the replacement character,
1628 : * or removed if it is NUL. Or nullptr.
1629 : * @since GDAL 3.13
1630 : */
1631 1589 : std::string CPLLaunderForFilenameSafe(const std::string &osInput,
1632 : char chReplacementChar,
1633 : const char *pszExtraReservedCharacters)
1634 : {
1635 : // Cf https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
1636 1589 : std::string ret;
1637 1589 : ret.reserve(osInput.size());
1638 14474 : for (char c : osInput)
1639 : {
1640 12885 : if (static_cast<unsigned>(c) < 32 || c == 127 || c == '<' || c == '>' ||
1641 12876 : c == ':' || c == '"' || c == '/' || c == '\\' || c == '|' ||
1642 12858 : c == '?' ||
1643 : c == '*'
1644 : // '^' invalid on FAT
1645 12856 : || c == '^')
1646 : {
1647 30 : if (chReplacementChar)
1648 29 : ret += chReplacementChar;
1649 : }
1650 12855 : else if (pszExtraReservedCharacters &&
1651 8 : strchr(pszExtraReservedCharacters, c))
1652 : {
1653 2 : if (chReplacementChar)
1654 1 : ret += chReplacementChar;
1655 : }
1656 : else
1657 : {
1658 12853 : ret += c;
1659 : }
1660 : }
1661 :
1662 : // Windows reserved filenames (case-insensitive)
1663 1589 : const char *const apszReservedNames[] = {
1664 : "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4",
1665 : "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3",
1666 : "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "CONIN$", "CONOUT$",
1667 : };
1668 39629 : for (const char *pszReservedName : apszReservedNames)
1669 : {
1670 38044 : if (EQUAL(ret.c_str(), pszReservedName))
1671 : {
1672 4 : ret += chReplacementChar ? chReplacementChar : '_';
1673 4 : break;
1674 : }
1675 : }
1676 :
1677 : // Windows rule: no filename ending with space or dot
1678 : // This also prevents "." and ".." which are invalid on POSIX
1679 1589 : if (!ret.empty() && (ret.back() == ' ' || ret.back() == '.'))
1680 5 : ret += chReplacementChar ? chReplacementChar : '_';
1681 :
1682 3178 : return ret;
1683 : }
1684 :
1685 : /************************************************************************/
1686 : /* CPLLaunderForFilename() */
1687 : /************************************************************************/
1688 :
1689 : /**
1690 : * Launder a string to be compatible of a filename.
1691 : *
1692 : * @param pszName The input string to launder.
1693 : * @param pszOutputPath The directory where the file would be created.
1694 : * Unused for now. May be NULL.
1695 : * @return the laundered name.
1696 : *
1697 : * @since GDAL 3.1
1698 : *
1699 : * @deprecated If using C++, prefer using CPLLaunderForFilenameSafe() instead
1700 : */
1701 :
1702 0 : const char *CPLLaunderForFilename(const char *pszName,
1703 : const char *pszOutputPath)
1704 : {
1705 0 : return CPLPathReturnTLSString(
1706 0 : CPLLaunderForFilenameSafe(pszName, pszOutputPath), __FUNCTION__);
1707 : }
1708 :
1709 : /************************************************************************/
1710 : /* CPLHasPathTraversal() */
1711 : /************************************************************************/
1712 :
1713 : /**
1714 : * Return whether the filename contains a path traversal pattern.
1715 : *
1716 : * i.e. if it contains "../" or "..\\".
1717 : *
1718 : * The CPL_ENABLE_PATH_TRAVERSAL_DETECTION configuration option can be set
1719 : * to NO to disable this check, although this is not recommended when dealing
1720 : * with un-trusted input.
1721 : *
1722 : * @param pszFilename The input string to check.
1723 : * @return true if a path traversal pattern is detected.
1724 : *
1725 : * @since GDAL 3.12
1726 : */
1727 :
1728 2187 : bool CPLHasPathTraversal(const char *pszFilename)
1729 : {
1730 2187 : const char *pszDotDot = strstr(pszFilename, "..");
1731 2187 : if (pszDotDot &&
1732 6 : (pszDotDot == pszFilename ||
1733 6 : pszFilename[pszDotDot - pszFilename - 1] == '/' ||
1734 2 : pszFilename[pszDotDot - pszFilename - 1] == '\\') &&
1735 6 : (pszDotDot[2] == 0 || pszDotDot[2] == '/' || pszDotDot[2] == '\\'))
1736 : {
1737 6 : if (CPLTestBool(CPLGetConfigOption(
1738 : "CPL_ENABLE_PATH_TRAVERSAL_DETECTION", "YES")))
1739 : {
1740 4 : CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
1741 4 : return true;
1742 : }
1743 : else
1744 : {
1745 2 : CPLDebug("CPL",
1746 : "Path traversal detected for %s but ignored given that "
1747 : "CPL_ENABLE_PATH_TRAVERSAL_DETECTION is disabled",
1748 : pszFilename);
1749 : }
1750 : }
1751 2183 : return false;
1752 : }
1753 :
1754 : /************************************************************************/
1755 : /* CPLHasUnbalancedPathTraversal() */
1756 : /************************************************************************/
1757 :
1758 : /**
1759 : * Return whether the filename contains a unbalanced path traversal pattern.
1760 : *
1761 : * i.e. if it contains more "../" or "..\\" than preceding nesting.
1762 : *
1763 : *
1764 : * @param pszFilename The input string to check.
1765 : * @return true if a path traversal pattern is detected.
1766 : *
1767 : * @since GDAL 3.12
1768 : */
1769 :
1770 549 : bool CPLHasUnbalancedPathTraversal(const char *pszFilename)
1771 : {
1772 549 : size_t nNestLevel = 0;
1773 549 : int i = 0;
1774 549 : if (pszFilename[0] == '.' &&
1775 6 : (pszFilename[1] == '/' || pszFilename[1] == '\\'))
1776 2 : i += 2;
1777 547 : else if (pszFilename[0] == '/' || pszFilename[0] == '\\')
1778 2 : ++i;
1779 43173 : for (; pszFilename[i]; ++i)
1780 : {
1781 42634 : if (pszFilename[i] == '/' || pszFilename[i] == '\\')
1782 : {
1783 1730 : if (pszFilename[i + 1] == '/' || pszFilename[i + 1] == '\\')
1784 : {
1785 0 : continue;
1786 : }
1787 1730 : if (pszFilename[i + 1] != 0)
1788 1714 : ++nNestLevel;
1789 : }
1790 40904 : else if (pszFilename[i] == '.' && pszFilename[i + 1] == '.' &&
1791 24 : (pszFilename[i + 2] == '/' || pszFilename[i + 2] == '\\' ||
1792 4 : pszFilename[i + 2] == 0))
1793 : {
1794 24 : if (nNestLevel == 0)
1795 : {
1796 8 : CPLDebug("CPL", "Path traversal detected for %s", pszFilename);
1797 8 : return true;
1798 : }
1799 16 : if (pszFilename[i + 2] == 0)
1800 2 : break;
1801 14 : i += 2;
1802 14 : --nNestLevel;
1803 : }
1804 : }
1805 :
1806 541 : return false;
1807 : }
1808 :
1809 : /************************************************************************/
1810 : /* CPLLexicallyNormalize() */
1811 : /************************************************************************/
1812 :
1813 : /**
1814 : * Return a path where "/./" or "/../" sequences are removed.
1815 : *
1816 : * No filesystem access is done.
1817 : *
1818 : * @param svPath Input path
1819 : * @param sep1 Path separator (typically slash or backslash)
1820 : * @param sep2 Secondary path separator (typically slash or backslash), or NUL
1821 : * @return compacted path
1822 : *
1823 : * @since GDAL 3.13
1824 : */
1825 576 : std::string CPLLexicallyNormalize(std::string_view svPath, char sep1, char sep2)
1826 : {
1827 : struct Token
1828 : {
1829 : size_t iStart = 0; // index of start of token with svPath
1830 : size_t nLen = 0; // length of token (excluding ending separator)
1831 : char chSep = 0; // separator at end of token, or 0 if there is none
1832 : };
1833 :
1834 1152 : std::vector<Token> tokens;
1835 :
1836 1958 : const auto CompactTokens = [&tokens, &svPath]()
1837 : {
1838 1798 : Token &t = tokens.back();
1839 1798 : if (t.nLen == 1 && svPath[t.iStart] == '.')
1840 : {
1841 3 : tokens.pop_back();
1842 : }
1843 1820 : else if (t.nLen == 2 && svPath[t.iStart] == '.' &&
1844 25 : svPath[t.iStart + 1] == '.')
1845 : {
1846 25 : if (tokens.size() >= 2)
1847 23 : tokens.resize(tokens.size() - 2);
1848 : }
1849 2374 : };
1850 :
1851 576 : bool lastCharIsSep = false;
1852 18026 : for (size_t i = 0; i < svPath.size(); ++i)
1853 : {
1854 17450 : const char c = svPath[i];
1855 17450 : if (c == sep1 || c == sep2)
1856 : {
1857 1791 : if (!lastCharIsSep)
1858 : {
1859 1781 : if (tokens.empty())
1860 : {
1861 557 : Token t;
1862 557 : t.chSep = c;
1863 557 : tokens.push_back(t);
1864 : }
1865 : else
1866 : {
1867 1224 : Token &t = tokens.back();
1868 1224 : t.chSep = c;
1869 1224 : CompactTokens();
1870 : }
1871 1781 : lastCharIsSep = true;
1872 1791 : }
1873 : }
1874 : else
1875 : {
1876 15659 : if (tokens.empty() || lastCharIsSep)
1877 : {
1878 1762 : Token t;
1879 1762 : t.iStart = i;
1880 1762 : t.nLen = 1;
1881 1762 : tokens.push_back(t);
1882 : }
1883 : else
1884 : {
1885 13897 : Token &t = tokens.back();
1886 13897 : ++t.nLen;
1887 : }
1888 15659 : lastCharIsSep = false;
1889 : }
1890 : }
1891 576 : if (!tokens.empty())
1892 : {
1893 574 : CompactTokens();
1894 : }
1895 :
1896 576 : std::string s;
1897 576 : s.reserve(svPath.size());
1898 2846 : for (const auto &t : tokens)
1899 : {
1900 2270 : if (t.nLen)
1901 1713 : s.append(svPath.substr(t.iStart, t.nLen));
1902 2270 : if (t.chSep)
1903 1734 : s.push_back(t.chSep);
1904 : }
1905 1152 : return s;
1906 : }
|