1 /**********************************************************************
2  *
3  * Project:  CPL - Common Portability Library
4  * Purpose:  Portable filename/path parsing, and forming ala "Glob API".
5  * Author:   Frank Warmerdam, warmerda@home.com
6  *
7  **********************************************************************
8  * Copyright (c) 1999, Frank Warmerdam
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  **********************************************************************
28  *
29  * $Log: cpl_path.cpp,v $
30  * Revision 1.1.1.1  2006/08/21 05:52:20  dsr
31  * Initial import as opencpn, GNU Automake compliant.
32  *
33  * Revision 1.1.1.1  2006/04/19 03:23:28  dsr
34  * Rename/Import to OpenCPN
35  *
36  * Revision 1.14  2003/05/28 19:22:38  warmerda
37  * fixed docs
38  *
39  * Revision 1.13  2003/04/04 09:11:35  dron
40  * strcpy() and strcat() replaced by strncpy() and strncat().
41  * A lot of assertion on string sizes added.
42  *
43  * Revision 1.12  2002/12/13 06:14:17  warmerda
44  * fixed bug with IsRelative function
45  *
46  * Revision 1.11  2002/12/13 06:00:54  warmerda
47  * added CPLProjectRelativeFilename() and CPLIsFilenameRelative()
48  *
49  * Revision 1.10  2002/08/15 09:23:24  dron
50  * Added CPLGetDirname() function
51  *
52  * Revision 1.9  2001/08/30 21:20:49  warmerda
53  * expand tabs
54  *
55  * Revision 1.8  2001/07/18 04:00:49  warmerda
56  *
57  * Revision 1.7  2001/05/12 19:20:55  warmerda
58  * Fixed documentation of CPLGetExtension().
59  *
60  * Revision 1.6  2001/03/16 22:15:08  warmerda
61  * added CPLResetExtension
62  *
63  * Revision 1.5  2001/02/24 01:53:57  warmerda
64  * Added CPLFormCIFilename()
65  *
66  * Revision 1.4  2001/01/19 21:18:25  warmerda
67  * expanded tabs
68  *
69  * Revision 1.3  2000/01/26 17:53:36  warmerda
70  * Fixed CPLGetExtension() for filenames with no extension.
71  *
72  * Revision 1.2  2000/01/24 19:32:59  warmerda
73  * Fixed CPLGetExtension() to not include the dot.
74  *
75  * Revision 1.1  1999/10/14 19:23:39  warmerda
76  * New
77  *
78  **********************************************************************/
79 
80 #include "cpl_conv.h"
81 #include "cpl_string.h"
82 
83 
84 /* should be size of larged possible filename */
85 #define CPL_PATH_BUF_SIZE 2048
86 static char     szStaticResult[CPL_PATH_BUF_SIZE];
87 
88 #ifdef WIN32
89 #define SEP_CHAR '\\'
90 #define SEP_STRING "\\"
91 #else
92 #define SEP_CHAR '/'
93 #define SEP_STRING "/"
94 #endif
95 
96 /************************************************************************/
97 /*                        CPLFindFilenameStart()                        */
98 /************************************************************************/
99 
CPLFindFilenameStart(const char * pszFilename)100 static int CPLFindFilenameStart( const char * pszFilename )
101 
102 {
103     int         iFileStart;
104 
105     for( iFileStart = strlen(pszFilename);
106          iFileStart > 0
107              && pszFilename[iFileStart-1] != '/'
108              && pszFilename[iFileStart-1] != '\\';
109          iFileStart-- ) {}
110 
111     return iFileStart;
112 }
113 
114 /************************************************************************/
115 /*                             CPLGetPath()                             */
116 /************************************************************************/
117 
118 /**
119  * Extract directory path portion of filename.
120  *
121  * Returns a string containing the directory path portion of the passed
122  * filename.  If there is no path in the passed filename an empty string
123  * will be returned (not NULL).
124  *
125  * <pre>
126  * CPLGetPath( "abc/def.xyz" ) == "abc"
127  * CPLGetPath( "/abc/def/" ) == "/abc/def"
128  * CPLGetPath( "/" ) == "/"
129  * CPLGetPath( "/abc/def" ) == "/abc"
130  * CPLGetPath( "abc" ) == ""
131  * </pre>
132  *
133  * @param pszFilename the filename potentially including a path.
134  *
135  *  @return Path in an internal string which must not be freed.  The string
136  * may be destroyed by the next CPL filename handling call.  The returned
137  * will generally not contain a trailing path separator.
138  */
139 
CPLGetPath(const char * pszFilename)140 const char *CPLGetPath( const char *pszFilename )
141 
142 {
143     int         iFileStart = CPLFindFilenameStart(pszFilename);
144 
145     CPLAssert( iFileStart < CPL_PATH_BUF_SIZE );
146 
147     if( iFileStart == 0 )
148     {
149         strcpy( szStaticResult, "" );
150         return szStaticResult;
151     }
152 
153     strncpy( szStaticResult, pszFilename, iFileStart );
154     szStaticResult[iFileStart] = '\0';
155 
156     if( iFileStart > 1
157         && (szStaticResult[iFileStart-1] == '/'
158             || szStaticResult[iFileStart-1] == '\\') )
159         szStaticResult[iFileStart-1] = '\0';
160 
161     return szStaticResult;
162 }
163 
164 /************************************************************************/
165 /*                             CPLGetDirname()                          */
166 /************************************************************************/
167 
168 /**
169  * Extract directory path portion of filename.
170  *
171  * Returns a string containing the directory path portion of the passed
172  * filename.  If there is no path in the passed filename the dot will be
173  * returned.  It is the only difference from CPLGetPath().
174  *
175  * <pre>
176  * CPLGetDirname( "abc/def.xyz" ) == "abc"
177  * CPLGetDirname( "/abc/def/" ) == "/abc/def"
178  * CPLGetDirname( "/" ) == "/"
179  * CPLGetDirname( "/abc/def" ) == "/abc"
180  * CPLGetDirname( "abc" ) == "."
181  * </pre>
182  *
183  * @param pszFilename the filename potentially including a path.
184  *
185  * @return Path in an internal string which must not be freed.  The string
186  * may be destroyed by the next CPL filename handling call.  The returned
187  * will generally not contain a trailing path separator.
188  */
189 
CPLGetDirname(const char * pszFilename)190 const char *CPLGetDirname( const char *pszFilename )
191 
192 {
193     int         iFileStart = CPLFindFilenameStart(pszFilename);
194 
195     CPLAssert( iFileStart < CPL_PATH_BUF_SIZE );
196 
197     if( iFileStart == 0 )
198     {
199         strcpy( szStaticResult, "." );
200         return szStaticResult;
201     }
202 
203     strncpy( szStaticResult, pszFilename, iFileStart );
204     szStaticResult[iFileStart] = '\0';
205 
206     if( iFileStart > 1
207         && (szStaticResult[iFileStart-1] == '/'
208             || szStaticResult[iFileStart-1] == '\\') )
209         szStaticResult[iFileStart-1] = '\0';
210 
211     return szStaticResult;
212 }
213 
214 /************************************************************************/
215 /*                           CPLGetFilename()                           */
216 /************************************************************************/
217 
218 /**
219  * Extract non-directory portion of filename.
220  *
221  * Returns a string containing the bare filename portion of the passed
222  * filename.  If there is no filename (passed value ends in trailing directory
223  * separator) an empty string is returned.
224  *
225  * <pre>
226  * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
227  * CPLGetFilename( "/abc/def/" ) == ""
228  * CPLGetFilename( "abc/def" ) == "def"
229  * </pre>
230  *
231  * @param pszFullFilename the full filename potentially including a path.
232  *
233  *  @return just the non-directory portion of the path in an internal string
234  * which must not be freed.  The string
235  * may be destroyed by the next CPL filename handling call.
236  */
237 
CPLGetFilename(const char * pszFullFilename)238 const char *CPLGetFilename( const char *pszFullFilename )
239 
240 {
241     int iFileStart = CPLFindFilenameStart( pszFullFilename );
242 
243     strncpy( szStaticResult, pszFullFilename + iFileStart, CPL_PATH_BUF_SIZE );
244     szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
245 
246     return szStaticResult;
247 }
248 
249 /************************************************************************/
250 /*                           CPLGetBasename()                           */
251 /************************************************************************/
252 
253 /**
254  * Extract basename (non-directory, non-extension) portion of filename.
255  *
256  * Returns a string containing the file basename portion of the passed
257  * name.  If there is no basename (passed value ends in trailing directory
258  * separator, or filename starts with a dot) an empty string is returned.
259  *
260  * <pre>
261  * CPLGetBasename( "abc/def.xyz" ) == "def"
262  * CPLGetBasename( "abc/def" ) == "def"
263  * CPLGetBasename( "abc/def/" ) == ""
264  * </pre>
265  *
266  * @param pszFullFilename the full filename potentially including a path.
267  *
268  * @return just the non-directory, non-extension portion of the path in
269  * an internal string which must not be freed.  The string
270  * may be destroyed by the next CPL filename handling call.
271  */
272 
CPLGetBasename(const char * pszFullFilename)273 const char *CPLGetBasename( const char *pszFullFilename )
274 
275 {
276     int iFileStart = CPLFindFilenameStart( pszFullFilename );
277     int iExtStart, nLength;
278 
279     for( iExtStart = strlen(pszFullFilename);
280          iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
281          iExtStart-- ) {}
282 
283     if( iExtStart == iFileStart )
284         iExtStart = strlen(pszFullFilename);
285 
286     nLength = iExtStart - iFileStart;
287 
288     CPLAssert( nLength < CPL_PATH_BUF_SIZE );
289 
290     strncpy( szStaticResult, pszFullFilename + iFileStart, nLength );
291     szStaticResult[nLength] = '\0';
292 
293     return szStaticResult;
294 }
295 
296 
297 /************************************************************************/
298 /*                           CPLGetExtension()                          */
299 /************************************************************************/
300 
301 /**
302  * Extract filename extension from full filename.
303  *
304  * Returns a string containing the extention portion of the passed
305  * name.  If there is no extension (the filename has no dot) an empty string
306  * is returned.  The returned extension will not include the period.
307  *
308  * <pre>
309  * CPLGetExtension( "abc/def.xyz" ) == "xyz"
310  * CPLGetExtension( "abc/def" ) == ""
311  * </pre>
312  *
313  * @param pszFullFilename the full filename potentially including a path.
314  *
315  * @return just the extension portion of the path in
316  * an internal string which must not be freed.  The string
317  * may be destroyed by the next CPL filename handling call.
318  */
319 
CPLGetExtension(const char * pszFullFilename)320 const char *CPLGetExtension( const char *pszFullFilename )
321 
322 {
323     int iFileStart = CPLFindFilenameStart( pszFullFilename );
324     int iExtStart;
325 
326     for( iExtStart = strlen(pszFullFilename);
327          iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
328          iExtStart-- ) {}
329 
330     if( iExtStart == iFileStart )
331         iExtStart = strlen(pszFullFilename)-1;
332 
333     strncpy( szStaticResult, pszFullFilename+iExtStart+1, CPL_PATH_BUF_SIZE );
334     szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
335 
336     return szStaticResult;
337 }
338 
339 /************************************************************************/
340 /*                         CPLResetExtension()                          */
341 /************************************************************************/
342 
343 /**
344  * Replace the extension with the provided one.
345  *
346  * @param pszPath the input path, this string is not altered.
347  * @param pszExt the new extension to apply to the given path.
348  *
349  * @return an altered filename with the new extension.    Do not
350  * modify or free the returned string.  The string may be destroyed by the
351  * next CPL call.
352  */
353 
CPLResetExtension(const char * pszPath,const char * pszExt)354 const char *CPLResetExtension( const char *pszPath, const char *pszExt )
355 
356 {
357     int         i;
358 
359 /* -------------------------------------------------------------------- */
360 /*      First, try and strip off any existing extension.                */
361 /* -------------------------------------------------------------------- */
362     strncpy( szStaticResult, pszPath, CPL_PATH_BUF_SIZE );
363     szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
364     for( i = strlen(szStaticResult) - 1; i > 0; i-- )
365     {
366         if( szStaticResult[i] == '.' )
367         {
368             szStaticResult[i] = '\0';
369             break;
370         }
371 
372         if( szStaticResult[i] == '/' || szStaticResult[i] == '\\'
373             || szStaticResult[i] == ':' )
374             break;
375     }
376 
377 /* -------------------------------------------------------------------- */
378 /*      Append the new extension.                                       */
379 /* -------------------------------------------------------------------- */
380     CPLAssert( strlen(pszExt) + 2 < CPL_PATH_BUF_SIZE );
381 
382     strcat( szStaticResult, "." );
383     strcat( szStaticResult, pszExt );
384 
385     return szStaticResult;
386 }
387 
388 /************************************************************************/
389 /*                          CPLFormFilename()                           */
390 /************************************************************************/
391 
392 /**
393  * Build a full file path from a passed path, file basename and extension.
394  *
395  * The path, and extension are optional.  The basename may in fact contain
396  * an extension if desired.
397  *
398  * <pre>
399  * CPLFormFilename("abc/xyz","def", ".dat" ) == "abc/xyz/def.dat"
400  * CPLFormFilename(NULL,"def", NULL ) == "def"
401  * CPLFormFilename(NULL,"abc/def.dat", NULL ) == "abc/def.dat"
402  * CPLFormFilename("/abc/xyz/","def.dat", NULL ) == "/abc/xyz/def.dat"
403  * </pre>
404  *
405  * @param pszPath directory path to the directory containing the file.  This
406  * may be relative or absolute, and may have a trailing path separator or
407  * not.  May be NULL.
408  *
409  * @param pszBasename file basename.  May optionally have path and/or
410  * extension.  May not be NULL.
411  *
412  * @param pszExtension file extension, optionally including the period.  May
413  * be NULL.
414  *
415  * @return a fully formed filename in an internal static string.  Do not
416  * modify or free the returned string.  The string may be destroyed by the
417  * next CPL call.
418  */
419 
CPLFormFilename(const char * pszPath,const char * pszBasename,const char * pszExtension)420 const char *CPLFormFilename( const char * pszPath,
421                              const char * pszBasename,
422                              const char * pszExtension )
423 
424 {
425     const char  *pszAddedPathSep = "";
426     const char  *pszAddedExtSep = "";
427 
428     if( pszPath == NULL )
429         pszPath = "";
430     else if( strlen(pszPath) > 0
431              && pszPath[strlen(pszPath)-1] != '/'
432              && pszPath[strlen(pszPath)-1] != '\\' )
433         pszAddedPathSep = SEP_STRING;
434 
435     if( pszExtension == NULL )
436         pszExtension = "";
437     else if( pszExtension[0] != '.' && strlen(pszExtension) > 0 )
438         pszAddedExtSep = ".";
439 
440     CPLAssert( strlen(pszPath) + strlen(pszAddedPathSep) +
441                strlen(pszBasename) + strlen(pszAddedExtSep) +
442                strlen(pszExtension) + 1 < CPL_PATH_BUF_SIZE );
443 
444     strncpy( szStaticResult, pszPath, CPL_PATH_BUF_SIZE - 1 );
445     strncat( szStaticResult, pszAddedPathSep, sizeof(szStaticResult)-strlen(szStaticResult)-1);
446     strncat( szStaticResult, pszBasename,     sizeof(szStaticResult)-strlen(szStaticResult)-1);
447     strncat( szStaticResult, pszAddedExtSep,  sizeof(szStaticResult)-strlen(szStaticResult)-1);
448     strncat( szStaticResult, pszExtension,    sizeof(szStaticResult)-strlen(szStaticResult)-1);
449     szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
450 
451     return szStaticResult;
452 }
453 
454 /************************************************************************/
455 /*                          CPLFormCIFilename()                         */
456 /************************************************************************/
457 
458 /**
459  * Case insensitive file searching, returing full path.
460  *
461  * This function tries to return the path to a file regardless of
462  * whether the file exactly matches the basename, and extension case, or
463  * is all upper case, or all lower case.  The path is treated as case
464  * sensitive.  This function is equivelent to CPLFormFilename() on
465  * case insensitive file systems (like Windows).
466  *
467  * @param pszPath directory path to the directory containing the file.  This
468  * may be relative or absolute, and may have a trailing path separator or
469  * not.  May be NULL.
470  *
471  * @param pszBasename file basename.  May optionally have path and/or
472  * extension.  May not be NULL.
473  *
474  * @param pszExtension file extension, optionally including the period.  May
475  * be NULL.
476  *
477  * @return a fully formed filename in an internal static string.  Do not
478  * modify or free the returned string.  The string may be destroyed by the
479  * next CPL call.
480  */
481 
CPLFormCIFilename(const char * pszPath,const char * pszBasename,const char * pszExtension)482 const char *CPLFormCIFilename( const char * pszPath,
483                                const char * pszBasename,
484                                const char * pszExtension )
485 
486 {
487 #ifdef WIN32
488     return CPLFormFilename( pszPath, pszBasename, pszExtension );
489 #else
490     const char  *pszAddedExtSep = "";
491     char        *pszFilename;
492     const char  *pszFullPath;
493     int         nLen = strlen(pszBasename)+2, i;
494     FILE        *fp;
495 
496     if( pszExtension != NULL )
497         nLen += strlen(pszExtension);
498 
499     pszFilename = (char *) CPLMalloc(nLen);
500 
501     if( pszExtension == NULL )
502         pszExtension = "";
503     else if( pszExtension[0] != '.' && strlen(pszExtension) > 0 )
504         pszAddedExtSep = ".";
505 
506     sprintf( pszFilename, "%s%s%s",
507              pszBasename, pszAddedExtSep, pszExtension );
508 
509     pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
510     fp = VSIFOpen( pszFullPath, "r" );
511     if( fp == NULL )
512     {
513         for( i = 0; pszFilename[i] != '\0'; i++ )
514         {
515             if( pszFilename[i] >= 'a' && pszFilename[i] <= 'z' )
516                 pszFilename[i] = pszFilename[i] + 'A' - 'a';
517         }
518 
519         pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
520         fp = VSIFOpen( pszFullPath, "r" );
521     }
522 
523     if( fp == NULL )
524     {
525         for( i = 0; pszFilename[i] != '\0'; i++ )
526         {
527             if( pszFilename[i] >= 'A' && pszFilename[i] <= 'Z' )
528                 pszFilename[i] = pszFilename[i] + 'a' - 'A';
529         }
530 
531         pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
532         fp = VSIFOpen( pszFullPath, "r" );
533     }
534 
535     if( fp != NULL )
536         VSIFClose( fp );
537     else
538         pszFullPath = CPLFormFilename( pszPath, pszBasename, pszExtension );
539 
540     CPLFree( pszFilename );
541 
542     return pszFullPath;
543 #endif
544 }
545 
546 /************************************************************************/
547 /*                     CPLProjectRelativeFilename()                     */
548 /************************************************************************/
549 
550 /**
551  * Find a file relative to a project file.
552  *
553  * Given the path to a "project" directory, and a path to a secondary file
554  * referenced from that project, build a path to the secondary file
555  * that the current application can use.  If the secondary path is already
556  * absolute, rather than relative, then it will be returned unaltered.
557  *
558  * Examples:
559  * <pre>
560  * CPLProjectRelativeFilename("abc/def","tmp/abc.gif") == "abc/def/tmp/abc.gif"
561  * CPLProjectRelativeFilename("abc/def","/tmp/abc.gif") == "/tmp/abc.gif"
562  * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
563  * CPLProjectRelativeFilename("/abc/def","../abc.gif") == "/abc/def/../abc.gif"
564  * CPLProjectRelativeFilename("C:\WIN","abc.gif") == "C:\WIN\abc.gif"
565  * </pre>
566  *
567  * @param pszProjectDir the directory relative to which the secondary files
568  * path should be interpreted.
569  * @param pszSecondaryFilename the filename (potentially with path) that
570  * is to be interpreted relative to the project directory.
571  *
572  * @return a composed path to the secondary file.  The returned string is
573  * internal and should not be altered, freed, or depending on past the next
574  * CPL call.
575  */
576 
CPLProjectRelativeFilename(const char * pszProjectDir,const char * pszSecondaryFilename)577 const char *CPLProjectRelativeFilename( const char *pszProjectDir,
578                                         const char *pszSecondaryFilename )
579 
580 {
581     if( !CPLIsFilenameRelative( pszSecondaryFilename ) )
582         return pszSecondaryFilename;
583 
584     if( pszProjectDir == NULL || strlen(pszProjectDir) == 0 )
585         return pszSecondaryFilename;
586 
587     strncpy( szStaticResult, pszProjectDir, CPL_PATH_BUF_SIZE );
588     szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
589 
590     if( pszProjectDir[strlen(pszProjectDir)-1] != '/'
591         && pszProjectDir[strlen(pszProjectDir)-1] != '\\' )
592     {
593         CPLAssert( strlen(SEP_STRING) + 1 < CPL_PATH_BUF_SIZE );
594 
595         strcat( szStaticResult, SEP_STRING );
596     }
597 
598     CPLAssert( strlen(pszSecondaryFilename) + 1 < CPL_PATH_BUF_SIZE );
599 
600     strcat( szStaticResult, pszSecondaryFilename );
601 
602     return szStaticResult;
603 }
604 
605 /************************************************************************/
606 /*                       CPLIsFilenameRelative()                        */
607 /************************************************************************/
608 
609 /**
610  * Is filename relative or absolute?
611  *
612  * The test is filesystem convention agnostic.  That is it will test for
613  * Unix style and windows style path conventions regardless of the actual
614  * system in use.
615  *
616  * @param pszFilename the filename with path to test.
617  *
618  * @return TRUE if the filename is relative or FALSE if it is absolute.
619  */
620 
CPLIsFilenameRelative(const char * pszFilename)621 int CPLIsFilenameRelative( const char *pszFilename )
622 
623 {
624     if( (strlen(pszFilename) > 2 && strncmp(pszFilename+1,":\\",2) == 0)
625         || pszFilename[0] == '\\'
626         || pszFilename[0] == '/' )
627         return FALSE;
628     else
629         return TRUE;
630 }
631