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 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "cpl_port.h"
31 : #include "cpl_conv.h"
32 :
33 : #include <cctype>
34 : #include <climits>
35 : #include <cstddef>
36 : #include <cstdio>
37 : #include <cstring>
38 : #if HAVE_UNISTD_H
39 : #include <unistd.h>
40 : #endif
41 :
42 : #include <algorithm>
43 : #include <string>
44 :
45 : #include "cpl_atomic_ops.h"
46 : #include "cpl_config.h"
47 : #include "cpl_error.h"
48 : #include "cpl_multiproc.h"
49 : #include "cpl_string.h"
50 : #include "cpl_vsi.h"
51 :
52 : // Should be size of larged possible filename.
53 : constexpr int CPL_PATH_BUF_SIZE = 2048;
54 : constexpr int CPL_PATH_BUF_COUNT = 10;
55 :
56 0 : static const char *CPLStaticBufferTooSmall(char *pszStaticResult)
57 : {
58 0 : CPLError(CE_Failure, CPLE_AppDefined, "Destination buffer too small");
59 0 : if (pszStaticResult == nullptr)
60 0 : return "";
61 0 : strcpy(pszStaticResult, "");
62 0 : return pszStaticResult;
63 : }
64 :
65 : /************************************************************************/
66 : /* CPLGetStaticResult() */
67 : /************************************************************************/
68 :
69 2750660 : static char *CPLGetStaticResult()
70 :
71 : {
72 2750660 : int bMemoryError = FALSE;
73 : char *pachBufRingInfo =
74 2750660 : static_cast<char *>(CPLGetTLSEx(CTLS_PATHBUF, &bMemoryError));
75 2750620 : if (bMemoryError)
76 0 : return nullptr;
77 2750620 : if (pachBufRingInfo == nullptr)
78 : {
79 1282 : pachBufRingInfo = static_cast<char *>(VSI_CALLOC_VERBOSE(
80 : 1, sizeof(int) + CPL_PATH_BUF_SIZE * CPL_PATH_BUF_COUNT));
81 1282 : if (pachBufRingInfo == nullptr)
82 0 : return nullptr;
83 1282 : CPLSetTLS(CTLS_PATHBUF, pachBufRingInfo, TRUE);
84 : }
85 :
86 : /* -------------------------------------------------------------------- */
87 : /* Work out which string in the "ring" we want to use this */
88 : /* time. */
89 : /* -------------------------------------------------------------------- */
90 2750670 : int *pnBufIndex = reinterpret_cast<int *>(pachBufRingInfo);
91 2750670 : const size_t nOffset =
92 2750670 : sizeof(int) + static_cast<size_t>(*pnBufIndex * CPL_PATH_BUF_SIZE);
93 2750670 : char *pachBuffer = pachBufRingInfo + nOffset;
94 :
95 2750670 : *pnBufIndex = (*pnBufIndex + 1) % CPL_PATH_BUF_COUNT;
96 :
97 2750670 : return pachBuffer;
98 : }
99 :
100 : /************************************************************************/
101 : /* CPLFindFilenameStart() */
102 : /************************************************************************/
103 :
104 2939700 : static int CPLFindFilenameStart(const char *pszFilename)
105 :
106 : {
107 2939700 : size_t iFileStart = strlen(pszFilename);
108 :
109 48022300 : for (; iFileStart > 0 && pszFilename[iFileStart - 1] != '/' &&
110 45082800 : pszFilename[iFileStart - 1] != '\\';
111 : iFileStart--)
112 : {
113 : }
114 :
115 2939700 : return static_cast<int>(iFileStart);
116 : }
117 :
118 : /************************************************************************/
119 : /* CPLGetPath() */
120 : /************************************************************************/
121 :
122 : /**
123 : * Extract directory path portion of filename.
124 : *
125 : * Returns a string containing the directory path portion of the passed
126 : * filename. If there is no path in the passed filename an empty string
127 : * will be returned (not NULL).
128 : *
129 : * <pre>
130 : * CPLGetPath( "abc/def.xyz" ) == "abc"
131 : * CPLGetPath( "/abc/def/" ) == "/abc/def"
132 : * CPLGetPath( "/" ) == "/"
133 : * CPLGetPath( "/abc/def" ) == "/abc"
134 : * CPLGetPath( "abc" ) == ""
135 : * </pre>
136 : *
137 : * @param pszFilename the filename potentially including a path.
138 : *
139 : * @return Path in an internal string which must not be freed. The string
140 : * may be destroyed by the next CPL filename handling call. The returned
141 : * will generally not contain a trailing path separator.
142 : */
143 :
144 115851 : const char *CPLGetPath(const char *pszFilename)
145 :
146 : {
147 115851 : const int iFileStart = CPLFindFilenameStart(pszFilename);
148 115851 : char *pszStaticResult = CPLGetStaticResult();
149 :
150 115851 : if (pszStaticResult == nullptr || iFileStart >= CPL_PATH_BUF_SIZE)
151 0 : return CPLStaticBufferTooSmall(pszStaticResult);
152 :
153 115851 : CPLAssert(!(pszFilename >= pszStaticResult &&
154 : pszFilename < pszStaticResult + CPL_PATH_BUF_SIZE));
155 :
156 115851 : if (iFileStart == 0)
157 : {
158 549 : strcpy(pszStaticResult, "");
159 549 : return pszStaticResult;
160 : }
161 :
162 115302 : CPLStrlcpy(pszStaticResult, pszFilename,
163 115302 : static_cast<size_t>(iFileStart) + 1);
164 :
165 115302 : if (iFileStart > 1 && (pszStaticResult[iFileStart - 1] == '/' ||
166 0 : pszStaticResult[iFileStart - 1] == '\\'))
167 114134 : pszStaticResult[iFileStart - 1] = '\0';
168 :
169 115302 : return pszStaticResult;
170 : }
171 :
172 : /************************************************************************/
173 : /* CPLGetDirname() */
174 : /************************************************************************/
175 :
176 : /**
177 : * Extract directory path portion of filename.
178 : *
179 : * Returns a string containing the directory path portion of the passed
180 : * filename. If there is no path in the passed filename the dot will be
181 : * returned. It is the only difference from CPLGetPath().
182 : *
183 : * <pre>
184 : * CPLGetDirname( "abc/def.xyz" ) == "abc"
185 : * CPLGetDirname( "/abc/def/" ) == "/abc/def"
186 : * CPLGetDirname( "/" ) == "/"
187 : * CPLGetDirname( "/abc/def" ) == "/abc"
188 : * CPLGetDirname( "abc" ) == "."
189 : * </pre>
190 : *
191 : * @param pszFilename the filename potentially including a path.
192 : *
193 : * @return Path in an internal string which must not be freed. The string
194 : * may be destroyed by the next CPL filename handling call. The returned
195 : * will generally not contain a trailing path separator.
196 : */
197 :
198 135738 : const char *CPLGetDirname(const char *pszFilename)
199 :
200 : {
201 135738 : const int iFileStart = CPLFindFilenameStart(pszFilename);
202 135732 : char *pszStaticResult = CPLGetStaticResult();
203 :
204 135709 : if (pszStaticResult == nullptr || iFileStart >= CPL_PATH_BUF_SIZE)
205 21 : return CPLStaticBufferTooSmall(pszStaticResult);
206 :
207 135688 : CPLAssert(!(pszFilename >= pszStaticResult &&
208 : pszFilename < pszStaticResult + CPL_PATH_BUF_SIZE));
209 :
210 135688 : if (iFileStart == 0)
211 : {
212 62 : strcpy(pszStaticResult, ".");
213 62 : return pszStaticResult;
214 : }
215 :
216 135626 : CPLStrlcpy(pszStaticResult, pszFilename,
217 135626 : static_cast<size_t>(iFileStart) + 1);
218 :
219 135678 : if (iFileStart > 1 && (pszStaticResult[iFileStart - 1] == '/' ||
220 22 : pszStaticResult[iFileStart - 1] == '\\'))
221 135609 : pszStaticResult[iFileStart - 1] = '\0';
222 :
223 135678 : return pszStaticResult;
224 : }
225 :
226 : /************************************************************************/
227 : /* CPLGetFilename() */
228 : /************************************************************************/
229 :
230 : /**
231 : * Extract non-directory portion of filename.
232 : *
233 : * Returns a string containing the bare filename portion of the passed
234 : * filename. If there is no filename (passed value ends in trailing directory
235 : * separator) an empty string is returned.
236 : *
237 : * <pre>
238 : * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
239 : * CPLGetFilename( "/abc/def/" ) == ""
240 : * CPLGetFilename( "abc/def" ) == "def"
241 : * </pre>
242 : *
243 : * @param pszFullFilename the full filename potentially including a path.
244 : *
245 : * @return just the non-directory portion of the path (points back into
246 : * original string).
247 : */
248 :
249 697087 : const char *CPLGetFilename(const char *pszFullFilename)
250 :
251 : {
252 697087 : const int iFileStart = CPLFindFilenameStart(pszFullFilename);
253 :
254 697083 : return pszFullFilename + iFileStart;
255 : }
256 :
257 : /************************************************************************/
258 : /* CPLGetBasename() */
259 : /************************************************************************/
260 :
261 : /**
262 : * Extract basename (non-directory, non-extension) portion of filename.
263 : *
264 : * Returns a string containing the file basename portion of the passed
265 : * name. If there is no basename (passed value ends in trailing directory
266 : * separator, or filename starts with a dot) an empty string is returned.
267 : *
268 : * <pre>
269 : * CPLGetBasename( "abc/def.xyz" ) == "def"
270 : * CPLGetBasename( "abc/def" ) == "def"
271 : * CPLGetBasename( "abc/def/" ) == ""
272 : * </pre>
273 : *
274 : * @param pszFullFilename the full filename potentially including a path.
275 : *
276 : * @return just the non-directory, non-extension portion of the path in
277 : * an internal string which must not be freed. The string
278 : * may be destroyed by the next CPL filename handling call.
279 : */
280 :
281 428746 : const char *CPLGetBasename(const char *pszFullFilename)
282 :
283 : {
284 : const size_t iFileStart =
285 428746 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
286 428746 : char *pszStaticResult = CPLGetStaticResult();
287 428746 : if (pszStaticResult == nullptr)
288 0 : return CPLStaticBufferTooSmall(pszStaticResult);
289 :
290 428746 : CPLAssert(!(pszFullFilename >= pszStaticResult &&
291 : pszFullFilename < pszStaticResult + CPL_PATH_BUF_SIZE));
292 :
293 428746 : size_t iExtStart = strlen(pszFullFilename);
294 2417900 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
295 : iExtStart--)
296 : {
297 : }
298 :
299 428746 : if (iExtStart == iFileStart)
300 23628 : iExtStart = strlen(pszFullFilename);
301 :
302 428746 : const size_t nLength = iExtStart - iFileStart;
303 :
304 428746 : if (nLength >= static_cast<size_t>(CPL_PATH_BUF_SIZE))
305 0 : return CPLStaticBufferTooSmall(pszStaticResult);
306 :
307 428746 : CPLStrlcpy(pszStaticResult, pszFullFilename + iFileStart, nLength + 1);
308 :
309 428746 : return pszStaticResult;
310 : }
311 :
312 : /************************************************************************/
313 : /* CPLGetExtension() */
314 : /************************************************************************/
315 :
316 : /**
317 : * Extract filename extension from full filename.
318 : *
319 : * Returns a string containing the extension portion of the passed
320 : * name. If there is no extension (the filename has no dot) an empty string
321 : * is returned. The returned extension will not include the period.
322 : *
323 : * <pre>
324 : * CPLGetExtension( "abc/def.xyz" ) == "xyz"
325 : * CPLGetExtension( "abc/def" ) == ""
326 : * </pre>
327 : *
328 : * @param pszFullFilename the full filename potentially including a path.
329 : *
330 : * @return just the extension portion of the path in
331 : * an internal string which must not be freed. The string
332 : * may be destroyed by the next CPL filename handling call.
333 : */
334 :
335 1580820 : const char *CPLGetExtension(const char *pszFullFilename)
336 :
337 : {
338 1580820 : if (pszFullFilename[0] == '\0')
339 18514 : return "";
340 :
341 : size_t iFileStart =
342 1562300 : static_cast<size_t>(CPLFindFilenameStart(pszFullFilename));
343 1562320 : char *pszStaticResult = CPLGetStaticResult();
344 1562280 : if (pszStaticResult == nullptr)
345 0 : return CPLStaticBufferTooSmall(pszStaticResult);
346 :
347 1562280 : CPLAssert(!(pszFullFilename >= pszStaticResult &&
348 : pszFullFilename < pszStaticResult + CPL_PATH_BUF_SIZE));
349 :
350 1562280 : size_t iExtStart = strlen(pszFullFilename);
351 14498900 : for (; iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
352 : iExtStart--)
353 : {
354 : }
355 :
356 1562280 : if (iExtStart == iFileStart)
357 544161 : iExtStart = strlen(pszFullFilename) - 1;
358 :
359 : // If the extension is too long, it is very much likely not an extension,
360 : // but another component of the path
361 1562280 : const size_t knMaxExtensionSize = 10;
362 1562280 : if (strlen(pszFullFilename + iExtStart + 1) > knMaxExtensionSize)
363 7668 : return "";
364 :
365 1554610 : if (CPLStrlcpy(pszStaticResult, pszFullFilename + iExtStart + 1,
366 1554640 : CPL_PATH_BUF_SIZE) >= static_cast<size_t>(CPL_PATH_BUF_SIZE))
367 12 : return CPLStaticBufferTooSmall(pszStaticResult);
368 :
369 1554620 : return pszStaticResult;
370 : }
371 :
372 : /************************************************************************/
373 : /* CPLGetCurrentDir() */
374 : /************************************************************************/
375 :
376 : /**
377 : * Get the current working directory name.
378 : *
379 : * @return a pointer to buffer, containing current working directory path
380 : * or NULL in case of error. User is responsible to free that buffer
381 : * after usage with CPLFree() function.
382 : * If HAVE_GETCWD macro is not defined, the function returns NULL.
383 : **/
384 :
385 : #ifdef _WIN32
386 : char *CPLGetCurrentDir()
387 : {
388 : const size_t nPathMax = _MAX_PATH;
389 : wchar_t *pwszDirPath =
390 : static_cast<wchar_t *>(VSI_MALLOC_VERBOSE(nPathMax * sizeof(wchar_t)));
391 : char *pszRet = nullptr;
392 : if (pwszDirPath != nullptr && _wgetcwd(pwszDirPath, nPathMax) != nullptr)
393 : {
394 : pszRet = CPLRecodeFromWChar(pwszDirPath, CPL_ENC_UCS2, CPL_ENC_UTF8);
395 : }
396 : CPLFree(pwszDirPath);
397 : return pszRet;
398 : }
399 : #elif defined(HAVE_GETCWD)
400 3122 : char *CPLGetCurrentDir()
401 : {
402 : #if PATH_MAX
403 3122 : const size_t nPathMax = PATH_MAX;
404 : #else
405 : const size_t nPathMax = 8192;
406 : #endif
407 :
408 3122 : char *pszDirPath = static_cast<char *>(VSI_MALLOC_VERBOSE(nPathMax));
409 3122 : if (!pszDirPath)
410 0 : return nullptr;
411 :
412 3122 : return getcwd(pszDirPath, nPathMax);
413 : }
414 : #else // !HAVE_GETCWD
415 : char *CPLGetCurrentDir()
416 : {
417 : return nullptr;
418 : }
419 : #endif // HAVE_GETCWD
420 :
421 : /************************************************************************/
422 : /* CPLResetExtension() */
423 : /************************************************************************/
424 :
425 : /**
426 : * Replace the extension with the provided one.
427 : *
428 : * @param pszPath the input path, this string is not altered.
429 : * @param pszExt the new extension to apply to the given path.
430 : *
431 : * @return an altered filename with the new extension. Do not
432 : * modify or free the returned string. The string may be destroyed by the
433 : * next CPL call.
434 : */
435 :
436 152489 : const char *CPLResetExtension(const char *pszPath, const char *pszExt)
437 :
438 : {
439 152489 : char *pszStaticResult = CPLGetStaticResult();
440 152489 : if (pszStaticResult == nullptr)
441 0 : return CPLStaticBufferTooSmall(pszStaticResult);
442 :
443 152489 : CPLAssert(!(pszPath >= pszStaticResult &&
444 : pszPath < pszStaticResult + CPL_PATH_BUF_SIZE));
445 :
446 : /* -------------------------------------------------------------------- */
447 : /* First, try and strip off any existing extension. */
448 : /* -------------------------------------------------------------------- */
449 152489 : if (CPLStrlcpy(pszStaticResult, pszPath, CPL_PATH_BUF_SIZE) >=
450 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
451 0 : return CPLStaticBufferTooSmall(pszStaticResult);
452 :
453 152489 : if (*pszStaticResult)
454 : {
455 966472 : for (size_t i = strlen(pszStaticResult) - 1; i > 0; i--)
456 : {
457 966220 : if (pszStaticResult[i] == '.')
458 : {
459 135973 : pszStaticResult[i] = '\0';
460 135973 : break;
461 : }
462 :
463 830247 : if (pszStaticResult[i] == '/' || pszStaticResult[i] == '\\' ||
464 814033 : pszStaticResult[i] == ':')
465 : break;
466 : }
467 : }
468 :
469 : /* -------------------------------------------------------------------- */
470 : /* Append the new extension. */
471 : /* -------------------------------------------------------------------- */
472 152489 : if (CPLStrlcat(pszStaticResult, ".", CPL_PATH_BUF_SIZE) >=
473 304978 : static_cast<size_t>(CPL_PATH_BUF_SIZE) ||
474 152489 : CPLStrlcat(pszStaticResult, pszExt, CPL_PATH_BUF_SIZE) >=
475 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
476 : {
477 0 : return CPLStaticBufferTooSmall(pszStaticResult);
478 : }
479 :
480 152489 : return pszStaticResult;
481 : }
482 :
483 : /************************************************************************/
484 : /* CPLFormFilename() */
485 : /************************************************************************/
486 :
487 : /**
488 : * Build a full file path from a passed path, file basename and extension.
489 : *
490 : * The path, and extension are optional. The basename may in fact contain
491 : * an extension if desired.
492 : *
493 : * <pre>
494 : * CPLFormFilename("abc/xyz", "def", ".dat" ) == "abc/xyz/def.dat"
495 : * CPLFormFilename(NULL,"def", NULL ) == "def"
496 : * CPLFormFilename(NULL, "abc/def.dat", NULL ) == "abc/def.dat"
497 : * CPLFormFilename("/abc/xyz/", "def.dat", NULL ) == "/abc/xyz/def.dat"
498 : * </pre>
499 : *
500 : * @param pszPath directory path to the directory containing the file. This
501 : * may be relative or absolute, and may have a trailing path separator or
502 : * not. May be NULL.
503 : *
504 : * @param pszBasename file basename. May optionally have path and/or
505 : * extension. Must *NOT* be NULL.
506 : *
507 : * @param pszExtension file extension, optionally including the period. May
508 : * be NULL.
509 : *
510 : * @return a fully formed filename in an internal static string. Do not
511 : * modify or free the returned string. The string may be destroyed by the
512 : * next CPL call.
513 : */
514 :
515 352944 : const char *CPLFormFilename(const char *pszPath, const char *pszBasename,
516 : const char *pszExtension)
517 :
518 : {
519 352944 : char *pszStaticResult = CPLGetStaticResult();
520 352943 : if (pszStaticResult == nullptr)
521 0 : return CPLStaticBufferTooSmall(pszStaticResult);
522 :
523 352943 : CPLAssert(!(pszPath >= pszStaticResult &&
524 : pszPath < pszStaticResult + CPL_PATH_BUF_SIZE));
525 352943 : CPLAssert(!(pszBasename >= pszStaticResult &&
526 : pszBasename < pszStaticResult + CPL_PATH_BUF_SIZE));
527 :
528 352943 : if (pszBasename[0] == '.' &&
529 8834 : (pszBasename[1] == '/' || pszBasename[1] == '\\'))
530 56 : pszBasename += 2;
531 :
532 352943 : const char *pszAddedPathSep = "";
533 352943 : const char *pszAddedExtSep = "";
534 :
535 352943 : if (pszPath == nullptr)
536 10533 : pszPath = "";
537 352943 : size_t nLenPath = strlen(pszPath);
538 352943 : if (!CPLIsFilenameRelative(pszPath) && strcmp(pszBasename, "..") == 0)
539 : {
540 : // /a/b + .. --> /a
541 246 : if (pszPath[nLenPath - 1] == '\\' || pszPath[nLenPath - 1] == '/')
542 9 : nLenPath--;
543 246 : size_t nLenPathOri = nLenPath;
544 4208 : while (nLenPath > 0 && pszPath[nLenPath - 1] != '\\' &&
545 4197 : pszPath[nLenPath - 1] != '/')
546 : {
547 3962 : nLenPath--;
548 : }
549 246 : if (nLenPath == 1 && pszPath[0] == '/')
550 : {
551 7 : pszBasename = "";
552 : }
553 239 : else if ((nLenPath > 1 && pszPath[0] == '/') ||
554 11 : (nLenPath > 2 && pszPath[1] == ':') ||
555 1 : (nLenPath > 6 && strncmp(pszPath, "\\\\$\\", 4) == 0))
556 : {
557 232 : nLenPath--;
558 232 : pszBasename = "";
559 : }
560 : else
561 : {
562 7 : nLenPath = nLenPathOri;
563 7 : pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
564 : }
565 : }
566 352706 : else if (nLenPath > 0 && pszPath[nLenPath - 1] != '/' &&
567 340335 : pszPath[nLenPath - 1] != '\\')
568 : {
569 340315 : pszAddedPathSep = VSIGetDirectorySeparator(pszPath);
570 : }
571 :
572 352969 : if (pszExtension == nullptr)
573 205468 : pszExtension = "";
574 147501 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
575 135362 : pszAddedExtSep = ".";
576 :
577 352954 : if (CPLStrlcpy(
578 : pszStaticResult, pszPath,
579 352969 : std::min(nLenPath + 1, static_cast<size_t>(CPL_PATH_BUF_SIZE))) >=
580 352958 : static_cast<size_t>(CPL_PATH_BUF_SIZE) ||
581 352964 : CPLStrlcat(pszStaticResult, pszAddedPathSep, CPL_PATH_BUF_SIZE) >=
582 352965 : static_cast<size_t>(CPL_PATH_BUF_SIZE) ||
583 352957 : CPLStrlcat(pszStaticResult, pszBasename, CPL_PATH_BUF_SIZE) >=
584 352964 : static_cast<size_t>(CPL_PATH_BUF_SIZE) ||
585 352962 : CPLStrlcat(pszStaticResult, pszAddedExtSep, CPL_PATH_BUF_SIZE) >=
586 705925 : static_cast<size_t>(CPL_PATH_BUF_SIZE) ||
587 352963 : CPLStrlcat(pszStaticResult, pszExtension, CPL_PATH_BUF_SIZE) >=
588 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
589 : {
590 0 : return CPLStaticBufferTooSmall(pszStaticResult);
591 : }
592 :
593 352961 : return pszStaticResult;
594 : }
595 :
596 : /************************************************************************/
597 : /* CPLFormCIFilename() */
598 : /************************************************************************/
599 :
600 : /**
601 : * Case insensitive file searching, returning full path.
602 : *
603 : * This function tries to return the path to a file regardless of
604 : * whether the file exactly matches the basename, and extension case, or
605 : * is all upper case, or all lower case. The path is treated as case
606 : * sensitive. This function is equivalent to CPLFormFilename() on
607 : * case insensitive file systems (like Windows).
608 : *
609 : * @param pszPath directory path to the directory containing the file. This
610 : * may be relative or absolute, and may have a trailing path separator or
611 : * not. May be NULL.
612 : *
613 : * @param pszBasename file basename. May optionally have path and/or
614 : * extension. May not be NULL.
615 : *
616 : * @param pszExtension file extension, optionally including the period. May
617 : * be NULL.
618 : *
619 : * @return a fully formed filename in an internal static string. Do not
620 : * modify or free the returned string. The string may be destroyed by the
621 : * next CPL call.
622 : */
623 :
624 4103 : const char *CPLFormCIFilename(const char *pszPath, const char *pszBasename,
625 : const char *pszExtension)
626 :
627 : {
628 : // On case insensitive filesystems, just default to CPLFormFilename().
629 4103 : if (!VSIIsCaseSensitiveFS(pszPath))
630 0 : return CPLFormFilename(pszPath, pszBasename, pszExtension);
631 :
632 4103 : const char *pszAddedExtSep = "";
633 4103 : size_t nLen = strlen(pszBasename) + 2;
634 :
635 4103 : if (pszExtension != nullptr)
636 2139 : nLen += strlen(pszExtension);
637 :
638 4103 : char *pszFilename = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen));
639 4103 : if (pszFilename == nullptr)
640 0 : return "";
641 :
642 4103 : if (pszExtension == nullptr)
643 1964 : pszExtension = "";
644 2139 : else if (pszExtension[0] != '.' && strlen(pszExtension) > 0)
645 2087 : pszAddedExtSep = ".";
646 :
647 4103 : snprintf(pszFilename, nLen, "%s%s%s", pszBasename, pszAddedExtSep,
648 : pszExtension);
649 :
650 4103 : const char *pszFullPath = CPLFormFilename(pszPath, pszFilename, nullptr);
651 : VSIStatBufL sStatBuf;
652 4103 : int nStatRet = VSIStatExL(pszFullPath, &sStatBuf, VSI_STAT_EXISTS_FLAG);
653 4103 : if (nStatRet != 0)
654 : {
655 47099 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
656 : {
657 43474 : pszFilename[i] = static_cast<char>(CPLToupper(pszFilename[i]));
658 : }
659 :
660 3625 : pszFullPath = CPLFormFilename(pszPath, pszFilename, nullptr);
661 3625 : nStatRet = VSIStatExL(pszFullPath, &sStatBuf, VSI_STAT_EXISTS_FLAG);
662 : }
663 :
664 4103 : if (nStatRet != 0)
665 : {
666 47075 : for (size_t i = 0; pszFilename[i] != '\0'; i++)
667 : {
668 43453 : pszFilename[i] = static_cast<char>(
669 43453 : CPLTolower(static_cast<unsigned char>(pszFilename[i])));
670 : }
671 :
672 3622 : pszFullPath = CPLFormFilename(pszPath, pszFilename, nullptr);
673 3622 : nStatRet = VSIStatExL(pszFullPath, &sStatBuf, VSI_STAT_EXISTS_FLAG);
674 : }
675 :
676 4103 : if (nStatRet != 0)
677 3614 : pszFullPath = CPLFormFilename(pszPath, pszBasename, pszExtension);
678 :
679 4103 : CPLFree(pszFilename);
680 :
681 4103 : return pszFullPath;
682 : }
683 :
684 : /************************************************************************/
685 : /* CPLProjectRelativeFilename() */
686 : /************************************************************************/
687 :
688 : /**
689 : * Find a file relative to a project file.
690 : *
691 : * Given the path to a "project" directory, and a path to a secondary file
692 : * referenced from that project, build a path to the secondary file
693 : * that the current application can use. If the secondary path is already
694 : * absolute, rather than relative, then it will be returned unaltered.
695 : *
696 : * Examples:
697 : * <pre>
698 : * CPLProjectRelativeFilename("abc/def", "tmp/abc.gif") == "abc/def/tmp/abc.gif"
699 : * CPLProjectRelativeFilename("abc/def", "/tmp/abc.gif") == "/tmp/abc.gif"
700 : * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
701 : * CPLProjectRelativeFilename("/abc/def", "../abc.gif") == "/abc/def/../abc.gif"
702 : * CPLProjectRelativeFilename("C:\WIN", "abc.gif") == "C:\WIN\abc.gif"
703 : * </pre>
704 : *
705 : * @param pszProjectDir the directory relative to which the secondary files
706 : * path should be interpreted.
707 : * @param pszSecondaryFilename the filename (potentially with path) that
708 : * is to be interpreted relative to the project directory.
709 : *
710 : * @return a composed path to the secondary file. The returned string is
711 : * internal and should not be altered, freed, or depending on past the next
712 : * CPL call.
713 : */
714 :
715 2599 : const char *CPLProjectRelativeFilename(const char *pszProjectDir,
716 : const char *pszSecondaryFilename)
717 :
718 : {
719 2599 : char *pszStaticResult = CPLGetStaticResult();
720 2599 : if (pszStaticResult == nullptr)
721 0 : return CPLStaticBufferTooSmall(pszStaticResult);
722 :
723 2599 : CPLAssert(!(pszProjectDir >= pszStaticResult &&
724 : pszProjectDir < pszStaticResult + CPL_PATH_BUF_SIZE));
725 2599 : CPLAssert(!(pszSecondaryFilename >= pszStaticResult &&
726 : pszSecondaryFilename < pszStaticResult + CPL_PATH_BUF_SIZE));
727 :
728 2599 : if (!CPLIsFilenameRelative(pszSecondaryFilename))
729 416 : return pszSecondaryFilename;
730 :
731 2183 : if (pszProjectDir == nullptr || strlen(pszProjectDir) == 0)
732 217 : return pszSecondaryFilename;
733 :
734 1966 : if (CPLStrlcpy(pszStaticResult, pszProjectDir, CPL_PATH_BUF_SIZE) >=
735 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
736 0 : return CPLStaticBufferTooSmall(pszStaticResult);
737 :
738 1966 : if (pszProjectDir[strlen(pszProjectDir) - 1] != '/' &&
739 1966 : pszProjectDir[strlen(pszProjectDir) - 1] != '\\')
740 : {
741 1966 : const char *pszAddedPathSep = VSIGetDirectorySeparator(pszProjectDir);
742 1966 : if (CPLStrlcat(pszStaticResult, pszAddedPathSep, CPL_PATH_BUF_SIZE) >=
743 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
744 0 : return CPLStaticBufferTooSmall(pszStaticResult);
745 : }
746 :
747 1966 : if (CPLStrlcat(pszStaticResult, pszSecondaryFilename, CPL_PATH_BUF_SIZE) >=
748 : static_cast<size_t>(CPL_PATH_BUF_SIZE))
749 0 : return CPLStaticBufferTooSmall(pszStaticResult);
750 :
751 1966 : return pszStaticResult;
752 : }
753 :
754 : /************************************************************************/
755 : /* CPLIsFilenameRelative() */
756 : /************************************************************************/
757 :
758 : /**
759 : * Is filename relative or absolute?
760 : *
761 : * The test is filesystem convention agnostic. That is it will test for
762 : * Unix style and windows style path conventions regardless of the actual
763 : * system in use.
764 : *
765 : * @param pszFilename the filename with path to test.
766 : *
767 : * @return TRUE if the filename is relative or FALSE if it is absolute.
768 : */
769 :
770 364688 : int CPLIsFilenameRelative(const char *pszFilename)
771 :
772 : {
773 364688 : if ((pszFilename[0] != '\0' &&
774 354002 : (STARTS_WITH(pszFilename + 1, ":\\") ||
775 353988 : STARTS_WITH(pszFilename + 1, ":/") ||
776 353988 : strstr(pszFilename + 1, "://") // http://, ftp:// etc....
777 364283 : )) ||
778 364283 : STARTS_WITH(pszFilename, "\\\\?\\") // Windows extended Length Path.
779 364263 : || pszFilename[0] == '\\' || pszFilename[0] == '/')
780 248120 : return FALSE;
781 :
782 116568 : return TRUE;
783 : }
784 :
785 : /************************************************************************/
786 : /* CPLExtractRelativePath() */
787 : /************************************************************************/
788 :
789 : /**
790 : * Get relative path from directory to target file.
791 : *
792 : * Computes a relative path for pszTarget relative to pszBaseDir.
793 : * Currently this only works if they share a common base path. The returned
794 : * path is normally into the pszTarget string. It should only be considered
795 : * valid as long as pszTarget is valid or till the next call to
796 : * this function, whichever comes first.
797 : *
798 : * @param pszBaseDir the name of the directory relative to which the path
799 : * should be computed. pszBaseDir may be NULL in which case the original
800 : * target is returned without relativizing.
801 : *
802 : * @param pszTarget the filename to be changed to be relative to pszBaseDir.
803 : *
804 : * @param pbGotRelative Pointer to location in which a flag is placed
805 : * indicating that the returned path is relative to the basename (TRUE) or
806 : * not (FALSE). This pointer may be NULL if flag is not desired.
807 : *
808 : * @return an adjusted path or the original if it could not be made relative
809 : * to the pszBaseFile's path.
810 : **/
811 :
812 1086 : const char *CPLExtractRelativePath(const char *pszBaseDir,
813 : const char *pszTarget, int *pbGotRelative)
814 :
815 : {
816 : /* -------------------------------------------------------------------- */
817 : /* If we don't have a basedir, then we can't relativize the path. */
818 : /* -------------------------------------------------------------------- */
819 1086 : if (pszBaseDir == nullptr)
820 : {
821 0 : if (pbGotRelative != nullptr)
822 0 : *pbGotRelative = FALSE;
823 :
824 0 : return pszTarget;
825 : }
826 :
827 1086 : const size_t nBasePathLen = strlen(pszBaseDir);
828 :
829 : /* -------------------------------------------------------------------- */
830 : /* One simple case is when the base dir is '.' and the target */
831 : /* filename is relative. */
832 : /* -------------------------------------------------------------------- */
833 1109 : if ((nBasePathLen == 0 || EQUAL(pszBaseDir, ".")) &&
834 23 : CPLIsFilenameRelative(pszTarget))
835 : {
836 23 : if (pbGotRelative != nullptr)
837 23 : *pbGotRelative = TRUE;
838 :
839 23 : return pszTarget;
840 : }
841 :
842 : /* -------------------------------------------------------------------- */
843 : /* By this point, if we don't have a base path, we can't have a */
844 : /* meaningful common prefix. */
845 : /* -------------------------------------------------------------------- */
846 1063 : if (nBasePathLen == 0)
847 : {
848 0 : if (pbGotRelative != nullptr)
849 0 : *pbGotRelative = FALSE;
850 :
851 0 : return pszTarget;
852 : }
853 :
854 : /* -------------------------------------------------------------------- */
855 : /* If we don't have a common path prefix, then we can't get a */
856 : /* relative path. */
857 : /* -------------------------------------------------------------------- */
858 1063 : if (!EQUALN(pszBaseDir, pszTarget, nBasePathLen) ||
859 649 : (pszTarget[nBasePathLen] != '\\' && pszTarget[nBasePathLen] != '/'))
860 : {
861 415 : if (pbGotRelative != nullptr)
862 415 : *pbGotRelative = FALSE;
863 :
864 415 : return pszTarget;
865 : }
866 :
867 : /* -------------------------------------------------------------------- */
868 : /* We have a relative path. Strip it off to get a string to */
869 : /* return. */
870 : /* -------------------------------------------------------------------- */
871 648 : if (pbGotRelative != nullptr)
872 579 : *pbGotRelative = TRUE;
873 :
874 648 : return pszTarget + nBasePathLen + 1;
875 : }
876 :
877 : /************************************************************************/
878 : /* CPLCleanTrailingSlash() */
879 : /************************************************************************/
880 :
881 : /**
882 : * Remove trailing forward/backward slash from the path for UNIX/Windows resp.
883 : *
884 : * Returns a string containing the portion of the passed path string with
885 : * trailing slash removed. If there is no path in the passed filename
886 : * an empty string will be returned (not NULL).
887 : *
888 : * <pre>
889 : * CPLCleanTrailingSlash( "abc/def/" ) == "abc/def"
890 : * CPLCleanTrailingSlash( "abc/def" ) == "abc/def"
891 : * CPLCleanTrailingSlash( "c:\\abc\\def\\" ) == "c:\\abc\\def"
892 : * CPLCleanTrailingSlash( "c:\\abc\\def" ) == "c:\\abc\\def"
893 : * CPLCleanTrailingSlash( "abc" ) == "abc"
894 : * </pre>
895 : *
896 : * @param pszPath the path to be cleaned up
897 : *
898 : * @return Path in an internal string which must not be freed. The string
899 : * may be destroyed by the next CPL filename handling call.
900 : */
901 :
902 9 : const char *CPLCleanTrailingSlash(const char *pszPath)
903 :
904 : {
905 9 : char *pszStaticResult = CPLGetStaticResult();
906 9 : if (pszStaticResult == nullptr)
907 0 : return CPLStaticBufferTooSmall(pszStaticResult);
908 9 : CPLAssert(!(pszPath >= pszStaticResult &&
909 : pszPath < pszStaticResult + CPL_PATH_BUF_SIZE));
910 :
911 9 : const size_t iPathLength = strlen(pszPath);
912 9 : if (iPathLength >= static_cast<size_t>(CPL_PATH_BUF_SIZE))
913 0 : return CPLStaticBufferTooSmall(pszStaticResult);
914 :
915 9 : CPLStrlcpy(pszStaticResult, pszPath, iPathLength + 1);
916 :
917 9 : if (iPathLength > 0 && (pszStaticResult[iPathLength - 1] == '\\' ||
918 9 : pszStaticResult[iPathLength - 1] == '/'))
919 0 : pszStaticResult[iPathLength - 1] = '\0';
920 :
921 9 : return pszStaticResult;
922 : }
923 :
924 : /************************************************************************/
925 : /* CPLCorrespondingPaths() */
926 : /************************************************************************/
927 :
928 : /**
929 : * Identify corresponding paths.
930 : *
931 : * Given a prototype old and new filename this function will attempt
932 : * to determine corresponding names for a set of other old filenames that
933 : * will rename them in a similar manner. This correspondence assumes there
934 : * are two possibly kinds of renaming going on. A change of path, and a
935 : * change of filename stem.
936 : *
937 : * If a consistent renaming cannot be established for all the files this
938 : * function will return indicating an error.
939 : *
940 : * The returned file list becomes owned by the caller and should be destroyed
941 : * with CSLDestroy().
942 : *
943 : * @param pszOldFilename path to old prototype file.
944 : * @param pszNewFilename path to new prototype file.
945 : * @param papszFileList list of other files associated with pszOldFilename to
946 : * rename similarly.
947 : *
948 : * @return a list of files corresponding to papszFileList but renamed to
949 : * correspond to pszNewFilename.
950 : */
951 :
952 172 : char **CPLCorrespondingPaths(const char *pszOldFilename,
953 : const char *pszNewFilename, char **papszFileList)
954 :
955 : {
956 172 : if (CSLCount(papszFileList) == 0)
957 0 : return nullptr;
958 :
959 : /* -------------------------------------------------------------------- */
960 : /* There is a special case for a one item list which exactly */
961 : /* matches the old name, to rename to the new name. */
962 : /* -------------------------------------------------------------------- */
963 339 : if (CSLCount(papszFileList) == 1 &&
964 167 : strcmp(pszOldFilename, papszFileList[0]) == 0)
965 : {
966 167 : return CSLAddString(nullptr, pszNewFilename);
967 : }
968 :
969 10 : const CPLString osOldPath = CPLGetPath(pszOldFilename);
970 10 : const CPLString osOldBasename = CPLGetBasename(pszOldFilename);
971 10 : const CPLString osNewBasename = CPLGetBasename(pszNewFilename);
972 :
973 : /* -------------------------------------------------------------------- */
974 : /* If the basename is changing, verify that all source files */
975 : /* have the same starting basename. */
976 : /* -------------------------------------------------------------------- */
977 5 : if (osOldBasename != osNewBasename)
978 : {
979 19 : for (int i = 0; papszFileList[i] != nullptr; i++)
980 : {
981 14 : if (osOldBasename == CPLGetBasename(papszFileList[i]))
982 11 : continue;
983 :
984 3 : const CPLString osFilePath = CPLGetPath(papszFileList[i]);
985 3 : const CPLString osFileName = CPLGetFilename(papszFileList[i]);
986 :
987 3 : if (!EQUALN(osFileName, osOldBasename, osOldBasename.size()) ||
988 6 : !EQUAL(osFilePath, osOldPath) ||
989 3 : osFileName[osOldBasename.size()] != '.')
990 : {
991 0 : CPLError(CE_Failure, CPLE_AppDefined,
992 : "Unable to rename fileset due irregular basenames.");
993 0 : return nullptr;
994 : }
995 : }
996 : }
997 :
998 : /* -------------------------------------------------------------------- */
999 : /* If the filename portions differs, ensure they only differ in */
1000 : /* basename. */
1001 : /* -------------------------------------------------------------------- */
1002 5 : if (osOldBasename != osNewBasename)
1003 : {
1004 : const CPLString osOldExtra =
1005 5 : CPLGetFilename(pszOldFilename) + osOldBasename.size();
1006 : const CPLString osNewExtra =
1007 5 : CPLGetFilename(pszNewFilename) + osNewBasename.size();
1008 :
1009 5 : if (osOldExtra != osNewExtra)
1010 : {
1011 0 : CPLError(CE_Failure, CPLE_AppDefined,
1012 : "Unable to rename fileset due to irregular filename "
1013 : "correspondence.");
1014 0 : return nullptr;
1015 : }
1016 : }
1017 :
1018 : /* -------------------------------------------------------------------- */
1019 : /* Generate the new filenames. */
1020 : /* -------------------------------------------------------------------- */
1021 5 : char **papszNewList = nullptr;
1022 5 : const CPLString osNewPath = CPLGetPath(pszNewFilename);
1023 :
1024 19 : for (int i = 0; papszFileList[i] != nullptr; i++)
1025 : {
1026 28 : const CPLString osOldFilename = CPLGetFilename(papszFileList[i]);
1027 :
1028 : const CPLString osNewFilename =
1029 14 : osOldBasename == osNewBasename
1030 0 : ? CPLFormFilename(osNewPath, osOldFilename, nullptr)
1031 14 : : CPLFormFilename(osNewPath, osNewBasename,
1032 28 : osOldFilename.c_str() + osOldBasename.size());
1033 :
1034 14 : papszNewList = CSLAddString(papszNewList, osNewFilename);
1035 : }
1036 :
1037 5 : return papszNewList;
1038 : }
1039 :
1040 : /************************************************************************/
1041 : /* CPLGenerateTempFilename() */
1042 : /************************************************************************/
1043 :
1044 : /**
1045 : * Generate temporary file name.
1046 : *
1047 : * Returns a filename that may be used for a temporary file. The location
1048 : * of the file tries to follow operating system semantics but may be
1049 : * forced via the CPL_TMPDIR configuration option.
1050 : *
1051 : * @param pszStem if non-NULL this will be part of the filename.
1052 : *
1053 : * @return a filename which is valid till the next CPL call in this thread.
1054 : */
1055 :
1056 2313 : const char *CPLGenerateTempFilename(const char *pszStem)
1057 :
1058 : {
1059 2313 : const char *pszDir = CPLGetConfigOption("CPL_TMPDIR", nullptr);
1060 :
1061 2313 : if (pszDir == nullptr)
1062 2311 : pszDir = CPLGetConfigOption("TMPDIR", nullptr);
1063 :
1064 2313 : if (pszDir == nullptr)
1065 2311 : pszDir = CPLGetConfigOption("TEMP", nullptr);
1066 :
1067 2313 : if (pszDir == nullptr)
1068 2311 : pszDir = ".";
1069 :
1070 2313 : if (pszStem == nullptr)
1071 2146 : pszStem = "";
1072 :
1073 : static int nTempFileCounter = 0;
1074 4626 : CPLString osFilename;
1075 : osFilename.Printf("%s_%d_%d", pszStem, CPLGetCurrentProcessID(),
1076 2313 : CPLAtomicInc(&nTempFileCounter));
1077 :
1078 4626 : return CPLFormFilename(pszDir, osFilename, nullptr);
1079 : }
1080 :
1081 : /************************************************************************/
1082 : /* CPLExpandTilde() */
1083 : /************************************************************************/
1084 :
1085 : /**
1086 : * Expands ~/ at start of filename.
1087 : *
1088 : * Assumes that the HOME configuration option is defined.
1089 : *
1090 : * @param pszFilename filename potentially starting with ~/
1091 : *
1092 : * @return an expanded filename.
1093 : *
1094 : * @since GDAL 2.2
1095 : */
1096 :
1097 187 : const char *CPLExpandTilde(const char *pszFilename)
1098 :
1099 : {
1100 187 : if (!STARTS_WITH_CI(pszFilename, "~/"))
1101 186 : return pszFilename;
1102 :
1103 1 : const char *pszHome = CPLGetConfigOption("HOME", nullptr);
1104 1 : if (pszHome == nullptr)
1105 0 : return pszFilename;
1106 :
1107 1 : return CPLFormFilename(pszHome, pszFilename + 2, nullptr);
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* CPLGetHomeDir() */
1112 : /************************************************************************/
1113 :
1114 : /**
1115 : * Return the path to the home directory
1116 : *
1117 : * That is the value of the USERPROFILE environment variable on Windows,
1118 : * or HOME on other platforms.
1119 : *
1120 : * @return the home directory, or NULL.
1121 : *
1122 : * @since GDAL 2.3
1123 : */
1124 :
1125 0 : const char *CPLGetHomeDir()
1126 :
1127 : {
1128 : #ifdef _WIN32
1129 : return CPLGetConfigOption("USERPROFILE", nullptr);
1130 : #else
1131 0 : return CPLGetConfigOption("HOME", nullptr);
1132 : #endif
1133 : }
1134 :
1135 : /************************************************************************/
1136 : /* CPLLaunderForFilename() */
1137 : /************************************************************************/
1138 :
1139 : /**
1140 : * Launder a string to be compatible of a filename.
1141 : *
1142 : * @param pszName The input string to launder.
1143 : * @param pszOutputPath The directory where the file would be created.
1144 : * Unused for now. May be NULL.
1145 : * @return the laundered name.
1146 : *
1147 : * @since GDAL 3.1
1148 : */
1149 :
1150 1173 : const char *CPLLaunderForFilename(const char *pszName,
1151 : CPL_UNUSED const char *pszOutputPath)
1152 : {
1153 2346 : std::string osRet(pszName);
1154 10983 : for (char &ch : osRet)
1155 : {
1156 : // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
1157 9810 : if (ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '/' ||
1158 9804 : ch == '\\' || ch == '?' || ch == '*')
1159 : {
1160 9 : ch = '_';
1161 : }
1162 : }
1163 2346 : return CPLSPrintf("%s", osRet.c_str());
1164 : }
|