1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
17  * All rights reserved.
18  * various string, file, list operations.
19  */
20 
21 /** \file
22  * \ingroup bli
23  */
24 
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "DNA_listBase.h"
30 
31 #include "BLI_fileops.h"
32 #include "BLI_fnmatch.h"
33 #include "BLI_path_util.h"
34 #include "BLI_string.h"
35 #include "BLI_utildefines.h"
36 
37 #ifdef WIN32
38 #  include "utf_winfunc.h"
39 #  include "utfconv.h"
40 #  include <io.h>
41 #  ifdef _WIN32_IE
42 #    undef _WIN32_IE
43 #  endif
44 #  define _WIN32_IE 0x0501
45 #  include "BLI_alloca.h"
46 #  include "BLI_winstuff.h"
47 #  include <shlobj.h>
48 #  include <windows.h>
49 #else
50 #  include "unistd.h"
51 #endif /* WIN32 */
52 
53 #include "MEM_guardedalloc.h"
54 
55 /* Declarations */
56 
57 #ifdef WIN32
58 
59 /**
60  * Return true if the path is absolute ie starts with a drive specifier
61  * (eg A:\) or is a UNC path.
62  */
63 static bool BLI_path_is_abs(const char *name);
64 
65 #endif /* WIN32 */
66 
67 // #define DEBUG_STRSIZE
68 
69 /* implementation */
70 
71 /**
72  * Looks for a sequence of decimal digits in string, preceding any filename extension,
73  * returning the integer value if found, or 0 if not.
74  *
75  * \param string: String to scan.
76  * \param head: Optional area to return copy of part of string prior to digits,
77  * or before dot if no digits.
78  * \param tail: Optional area to return copy of part of string following digits,
79  * or from dot if no digits.
80  * \param r_num_len: Optional to return number of digits found.
81  */
BLI_path_sequence_decode(const char * string,char * head,char * tail,ushort * r_num_len)82 int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort *r_num_len)
83 {
84   uint nums = 0, nume = 0;
85   int i;
86   bool found_digit = false;
87   const char *const lslash = BLI_path_slash_rfind(string);
88   const uint string_len = strlen(string);
89   const uint lslash_len = lslash != NULL ? (int)(lslash - string) : 0;
90   uint name_end = string_len;
91 
92   while (name_end > lslash_len && string[--name_end] != '.') {
93     /* name ends at dot if present */
94   }
95   if (name_end == lslash_len && string[name_end] != '.') {
96     name_end = string_len;
97   }
98 
99   for (i = name_end - 1; i >= (int)lslash_len; i--) {
100     if (isdigit(string[i])) {
101       if (found_digit) {
102         nums = i;
103       }
104       else {
105         nume = i;
106         nums = i;
107         found_digit = true;
108       }
109     }
110     else {
111       if (found_digit) {
112         break;
113       }
114     }
115   }
116 
117   if (found_digit) {
118     const long long int ret = strtoll(&(string[nums]), NULL, 10);
119     if (ret >= INT_MIN && ret <= INT_MAX) {
120       if (tail) {
121         strcpy(tail, &string[nume + 1]);
122       }
123       if (head) {
124         strcpy(head, string);
125         head[nums] = 0;
126       }
127       if (r_num_len) {
128         *r_num_len = nume - nums + 1;
129       }
130       return (int)ret;
131     }
132   }
133 
134   if (tail) {
135     strcpy(tail, string + name_end);
136   }
137   if (head) {
138     /* name_end points to last character of head,
139      * make it +1 so null-terminator is nicely placed
140      */
141     BLI_strncpy(head, string, name_end + 1);
142   }
143   if (r_num_len) {
144     *r_num_len = 0;
145   }
146   return 0;
147 }
148 
149 /**
150  * Returns in area pointed to by string a string of the form "<head><pic><tail>", where pic
151  * is formatted as numlen digits with leading zeroes.
152  */
BLI_path_sequence_encode(char * string,const char * head,const char * tail,unsigned short numlen,int pic)153 void BLI_path_sequence_encode(
154     char *string, const char *head, const char *tail, unsigned short numlen, int pic)
155 {
156   sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail);
157 }
158 
159 static int BLI_path_unc_prefix_len(const char *path); /* defined below in same file */
160 
161 /* ******************** string encoding ***************** */
162 
163 /**
164  * Remove redundant characters from \a path and optionally make absolute.
165  *
166  * \param relabase: The path this is relative to, or ignored when NULL.
167  * \param path: Can be any input, and this function converts it to a regular full path.
168  * Also removes garbage from directory paths, like `/../` or double slashes etc.
169  *
170  * \note \a path isn't protected for max string names...
171  */
BLI_path_normalize(const char * relabase,char * path)172 void BLI_path_normalize(const char *relabase, char *path)
173 {
174   ptrdiff_t a;
175   char *start, *eind;
176   if (relabase) {
177     BLI_path_abs(path, relabase);
178   }
179   else {
180     if (path[0] == '/' && path[1] == '/') {
181       if (path[2] == '\0') {
182         return; /* path is "//" - cant clean it */
183       }
184       path = path + 2; /* leave the initial "//" untouched */
185     }
186   }
187 
188   /* Note
189    *   memmove(start, eind, strlen(eind) + 1);
190    * is the same as
191    *   strcpy(start, eind);
192    * except strcpy should not be used because there is overlap,
193    * so use memmove's slightly more obscure syntax - Campbell
194    */
195 
196 #ifdef WIN32
197   while ((start = strstr(path, "\\..\\"))) {
198     eind = start + strlen("\\..\\") - 1;
199     a = start - path - 1;
200     while (a > 0) {
201       if (path[a] == '\\') {
202         break;
203       }
204       a--;
205     }
206     if (a < 0) {
207       break;
208     }
209     else {
210       memmove(path + a, eind, strlen(eind) + 1);
211     }
212   }
213 
214   while ((start = strstr(path, "\\.\\"))) {
215     eind = start + strlen("\\.\\") - 1;
216     memmove(start, eind, strlen(eind) + 1);
217   }
218 
219   /* remove two consecutive backslashes, but skip the UNC prefix,
220    * which needs to be preserved */
221   while ((start = strstr(path + BLI_path_unc_prefix_len(path), "\\\\"))) {
222     eind = start + strlen("\\\\") - 1;
223     memmove(start, eind, strlen(eind) + 1);
224   }
225 #else
226   while ((start = strstr(path, "/../"))) {
227     a = start - path - 1;
228     if (a > 0) {
229       /* <prefix>/<parent>/../<postfix> => <prefix>/<postfix> */
230       eind = start + (4 - 1) /* strlen("/../") - 1 */; /* strip "/.." and keep last "/" */
231       while (a > 0 && path[a] != '/') {                /* find start of <parent> */
232         a--;
233       }
234       memmove(path + a, eind, strlen(eind) + 1);
235     }
236     else {
237       /* support for odd paths: eg /../home/me --> /home/me
238        * this is a valid path in blender but we cant handle this the usual way below
239        * simply strip this prefix then evaluate the path as usual.
240        * pythons os.path.normpath() does this */
241 
242       /* Note: previous version of following call used an offset of 3 instead of 4,
243        * which meant that the "/../home/me" example actually became "home/me".
244        * Using offset of 3 gives behavior consistent with the aforementioned
245        * Python routine. */
246       memmove(path, path + 3, strlen(path + 3) + 1);
247     }
248   }
249 
250   while ((start = strstr(path, "/./"))) {
251     eind = start + (3 - 1) /* strlen("/./") - 1 */;
252     memmove(start, eind, strlen(eind) + 1);
253   }
254 
255   while ((start = strstr(path, "//"))) {
256     eind = start + (2 - 1) /* strlen("//") - 1 */;
257     memmove(start, eind, strlen(eind) + 1);
258   }
259 #endif
260 }
261 
262 /**
263  * Cleanup filepath ensuring a trailing slash.
264  */
BLI_path_normalize_dir(const char * relabase,char * dir)265 void BLI_path_normalize_dir(const char *relabase, char *dir)
266 {
267   BLI_path_normalize(relabase, dir);
268   BLI_path_slash_ensure(dir);
269 }
270 
271 /**
272  * Make given name safe to be used in paths.
273  *
274  * \return true if \a fname was changed, false otherwise.
275  *
276  * For now, simply replaces reserved chars (as listed in
277  * https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words )
278  * by underscores ('_').
279  *
280  * \note Space case ' ' is a bit of an edge case here - in theory it is allowed,
281  * but again can be an issue in some cases, so we simply replace it by an underscore too
282  * (good practice anyway).
283  * REMOVED based on popular demand (see T45900).
284  * Percent '%' char is a bit same case - not recommended to use it,
285  * but supported by all decent FS/OS around.
286  *
287  * \note On Windows, it also ensures there is no '.' (dot char) at the end of the file,
288  * this can lead to issues.
289  *
290  * \note On Windows, it also checks for forbidden names
291  * (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx ).
292  */
BLI_filename_make_safe(char * fname)293 bool BLI_filename_make_safe(char *fname)
294 {
295   const char *invalid =
296       "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
297       "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
298       "/\\?*:|\"<>";
299   char *fn;
300   bool changed = false;
301 
302   if (*fname == '\0') {
303     return changed;
304   }
305 
306   for (fn = fname; *fn && (fn = strpbrk(fn, invalid)); fn++) {
307     *fn = '_';
308     changed = true;
309   }
310 
311   /* Forbid only dots. */
312   for (fn = fname; *fn == '.'; fn++) {
313     /* pass */
314   }
315   if (*fn == '\0') {
316     *fname = '_';
317     changed = true;
318   }
319 
320 #ifdef WIN32
321   {
322     const size_t len = strlen(fname);
323     const char *invalid_names[] = {
324         "con",  "prn",  "aux",  "null", "com1", "com2", "com3", "com4",
325         "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3",
326         "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", NULL,
327     };
328     char *lower_fname = BLI_strdup(fname);
329     const char **iname;
330 
331     /* Forbid trailing dot (trailing space has already been replaced above). */
332     if (fname[len - 1] == '.') {
333       fname[len - 1] = '_';
334       changed = true;
335     }
336 
337     /* Check for forbidden names - not we have to check all combination
338      * of upper and lower cases, hence the usage of lower_fname
339      * (more efficient than using BLI_strcasestr repeatedly). */
340     BLI_str_tolower_ascii(lower_fname, len);
341     for (iname = invalid_names; *iname; iname++) {
342       if (strstr(lower_fname, *iname) == lower_fname) {
343         const size_t iname_len = strlen(*iname);
344         /* Only invalid if the whole name is made of the invalid chunk, or it has an
345          * (assumed extension) dot just after. This means it will also catch 'valid'
346          * names like 'aux.foo.bar', but should be
347          * good enough for us! */
348         if ((iname_len == len) || (lower_fname[iname_len] == '.')) {
349           *fname = '_';
350           changed = true;
351           break;
352         }
353       }
354     }
355 
356     MEM_freeN(lower_fname);
357   }
358 #endif
359 
360   return changed;
361 }
362 
363 /**
364  * Make given path OS-safe.
365  *
366  * \return true if \a path was changed, false otherwise.
367  */
BLI_path_make_safe(char * path)368 bool BLI_path_make_safe(char *path)
369 {
370   /* Simply apply BLI_filename_make_safe() over each component of the path.
371    * Luckily enough, same 'safe' rules applies to filenames and dirnames. */
372   char *curr_slash, *curr_path = path;
373   bool changed = false;
374   bool skip_first = false;
375 
376 #ifdef WIN32
377   if (BLI_path_is_abs(path)) {
378     /* Do not make safe 'C:' in 'C:\foo\bar'... */
379     skip_first = true;
380   }
381 #endif
382 
383   for (curr_slash = (char *)BLI_path_slash_find(curr_path); curr_slash;
384        curr_slash = (char *)BLI_path_slash_find(curr_path)) {
385     const char backup = *curr_slash;
386     *curr_slash = '\0';
387     if (!skip_first && (*curr_path != '\0') && BLI_filename_make_safe(curr_path)) {
388       changed = true;
389     }
390     skip_first = false;
391     curr_path = curr_slash + 1;
392     *curr_slash = backup;
393   }
394   if (BLI_filename_make_safe(curr_path)) {
395     changed = true;
396   }
397 
398   return changed;
399 }
400 
401 /**
402  * Does path begin with the special "//" prefix that Blender uses to indicate
403  * a path relative to the .blend file.
404  */
BLI_path_is_rel(const char * path)405 bool BLI_path_is_rel(const char *path)
406 {
407   return path[0] == '/' && path[1] == '/';
408 }
409 
410 /* return true if the path is a UNC share */
BLI_path_is_unc(const char * name)411 bool BLI_path_is_unc(const char *name)
412 {
413   return name[0] == '\\' && name[1] == '\\';
414 }
415 
416 /**
417  * Returns the length of the identifying prefix
418  * of a UNC path which can start with '\\' (short version)
419  * or '\\?\' (long version)
420  * If the path is not a UNC path, return 0
421  */
BLI_path_unc_prefix_len(const char * path)422 static int BLI_path_unc_prefix_len(const char *path)
423 {
424   if (BLI_path_is_unc(path)) {
425     if ((path[2] == '?') && (path[3] == '\\')) {
426       /* we assume long UNC path like \\?\server\share\folder etc... */
427       return 4;
428     }
429 
430     return 2;
431   }
432 
433   return 0;
434 }
435 
436 #if defined(WIN32)
437 
438 /**
439  * Return true if the path is absolute ie starts with a drive specifier
440  * (eg A:\) or is a UNC path.
441  */
BLI_path_is_abs(const char * name)442 static bool BLI_path_is_abs(const char *name)
443 {
444   return (name[1] == ':' && ELEM(name[2], '\\', '/')) || BLI_path_is_unc(name);
445 }
446 
next_slash(wchar_t * path)447 static wchar_t *next_slash(wchar_t *path)
448 {
449   wchar_t *slash = path;
450   while (*slash && *slash != L'\\') {
451     slash++;
452   }
453   return slash;
454 }
455 
456 /* adds a slash if the unc path points sto a share */
BLI_path_add_slash_to_share(wchar_t * uncpath)457 static void BLI_path_add_slash_to_share(wchar_t *uncpath)
458 {
459   wchar_t *slash_after_server = next_slash(uncpath + 2);
460   if (*slash_after_server) {
461     wchar_t *slash_after_share = next_slash(slash_after_server + 1);
462     if (!(*slash_after_share)) {
463       slash_after_share[0] = L'\\';
464       slash_after_share[1] = L'\0';
465     }
466   }
467 }
468 
BLI_path_unc_to_short(wchar_t * unc)469 static void BLI_path_unc_to_short(wchar_t *unc)
470 {
471   wchar_t tmp[PATH_MAX];
472 
473   int len = wcslen(unc);
474   /* convert:
475    *    \\?\UNC\server\share\folder\... to \\server\share\folder\...
476    *    \\?\C:\ to C:\ and \\?\C:\folder\... to C:\folder\...
477    */
478   if ((len > 3) && (unc[0] == L'\\') && (unc[1] == L'\\') && (unc[2] == L'?') &&
479       ELEM(unc[3], L'\\', L'/')) {
480     if ((len > 5) && (unc[5] == L':')) {
481       wcsncpy(tmp, unc + 4, len - 4);
482       tmp[len - 4] = L'\0';
483       wcscpy(unc, tmp);
484     }
485     else if ((len > 7) && (wcsncmp(&unc[4], L"UNC", 3) == 0) && ELEM(unc[7], L'\\', L'/')) {
486       tmp[0] = L'\\';
487       tmp[1] = L'\\';
488       wcsncpy(tmp + 2, unc + 8, len - 8);
489       tmp[len - 6] = L'\0';
490       wcscpy(unc, tmp);
491     }
492   }
493 }
494 
BLI_path_normalize_unc(char * path,int maxlen)495 void BLI_path_normalize_unc(char *path, int maxlen)
496 {
497   wchar_t *tmp_16 = alloc_utf16_from_8(path, 1);
498   BLI_path_normalize_unc_16(tmp_16);
499   conv_utf_16_to_8(tmp_16, path, maxlen);
500 }
501 
BLI_path_normalize_unc_16(wchar_t * path_16)502 void BLI_path_normalize_unc_16(wchar_t *path_16)
503 {
504   BLI_path_unc_to_short(path_16);
505   BLI_path_add_slash_to_share(path_16);
506 }
507 #endif
508 
509 /**
510  * Replaces *file with a relative version (prefixed by "//") such that BLI_path_abs, given
511  * the same *relfile, will convert it back to its original value.
512  */
BLI_path_rel(char * file,const char * relfile)513 void BLI_path_rel(char *file, const char *relfile)
514 {
515   const char *lslash;
516   char temp[FILE_MAX];
517   char res[FILE_MAX];
518 
519   /* if file is already relative, bail out */
520   if (BLI_path_is_rel(file)) {
521     return;
522   }
523 
524   /* also bail out if relative path is not set */
525   if (relfile[0] == '\0') {
526     return;
527   }
528 
529 #ifdef WIN32
530   if (BLI_strnlen(relfile, 3) > 2 && !BLI_path_is_abs(relfile)) {
531     char *ptemp;
532     /* fix missing volume name in relative base,
533      * can happen with old recent-files.txt files */
534     BLI_windows_get_default_root_dir(temp);
535     ptemp = &temp[2];
536     if (relfile[0] != '\\' && relfile[0] != '/') {
537       ptemp++;
538     }
539     BLI_strncpy(ptemp, relfile, FILE_MAX - 3);
540   }
541   else {
542     BLI_strncpy(temp, relfile, FILE_MAX);
543   }
544 
545   if (BLI_strnlen(file, 3) > 2) {
546     bool is_unc = BLI_path_is_unc(file);
547 
548     /* Ensure paths are both UNC paths or are both drives */
549     if (BLI_path_is_unc(temp) != is_unc) {
550       return;
551     }
552 
553     /* Ensure both UNC paths are on the same share */
554     if (is_unc) {
555       int off;
556       int slash = 0;
557       for (off = 0; temp[off] && slash < 4; off++) {
558         if (temp[off] != file[off]) {
559           return;
560         }
561 
562         if (temp[off] == '\\') {
563           slash++;
564         }
565       }
566     }
567     else if ((temp[1] == ':' && file[1] == ':') && (tolower(temp[0]) != tolower(file[0]))) {
568       return;
569     }
570   }
571 #else
572   BLI_strncpy(temp, relfile, FILE_MAX);
573 #endif
574 
575   BLI_str_replace_char(temp + BLI_path_unc_prefix_len(temp), '\\', '/');
576   BLI_str_replace_char(file + BLI_path_unc_prefix_len(file), '\\', '/');
577 
578   /* remove /./ which confuse the following slash counting... */
579   BLI_path_normalize(NULL, file);
580   BLI_path_normalize(NULL, temp);
581 
582   /* the last slash in the file indicates where the path part ends */
583   lslash = BLI_path_slash_rfind(temp);
584 
585   if (lslash) {
586     /* find the prefix of the filename that is equal for both filenames.
587      * This is replaced by the two slashes at the beginning */
588     const char *p = temp;
589     const char *q = file;
590     char *r = res;
591 
592 #ifdef WIN32
593     while (tolower(*p) == tolower(*q))
594 #else
595     while (*p == *q)
596 #endif
597     {
598       p++;
599       q++;
600 
601       /* don't search beyond the end of the string
602        * in the rare case they match */
603       if ((*p == '\0') || (*q == '\0')) {
604         break;
605       }
606     }
607 
608     /* we might have passed the slash when the beginning of a dir matches
609      * so we rewind. Only check on the actual filename
610      */
611     if (*q != '/') {
612       while ((q >= file) && (*q != '/')) {
613         q--;
614         p--;
615       }
616     }
617     else if (*p != '/') {
618       while ((p >= temp) && (*p != '/')) {
619         p--;
620         q--;
621       }
622     }
623 
624     r += BLI_strcpy_rlen(r, "//");
625 
626     /* p now points to the slash that is at the beginning of the part
627      * where the path is different from the relative path.
628      * We count the number of directories we need to go up in the
629      * hierarchy to arrive at the common 'prefix' of the path
630      */
631     if (p < temp) {
632       p = temp;
633     }
634     while (p && p < lslash) {
635       if (*p == '/') {
636         r += BLI_strcpy_rlen(r, "../");
637       }
638       p++;
639     }
640 
641     /* don't copy the slash at the beginning */
642     r += BLI_strncpy_rlen(r, q + 1, FILE_MAX - (r - res));
643 
644 #ifdef WIN32
645     BLI_str_replace_char(res + 2, '/', '\\');
646 #endif
647     strcpy(file, res);
648   }
649 }
650 
651 /**
652  * Appends a suffix to the string, fitting it before the extension
653  *
654  * string = Foo.png, suffix = 123, separator = _
655  * Foo.png -> Foo_123.png
656  *
657  * \param string: original (and final) string
658  * \param maxlen: Maximum length of string
659  * \param suffix: String to append to the original string
660  * \param sep: Optional separator character
661  * \return  true if succeeded
662  */
BLI_path_suffix(char * string,size_t maxlen,const char * suffix,const char * sep)663 bool BLI_path_suffix(char *string, size_t maxlen, const char *suffix, const char *sep)
664 {
665 #ifdef DEBUG_STRSIZE
666   memset(string, 0xff, sizeof(*string) * maxlen);
667 #endif
668   const size_t string_len = strlen(string);
669   const size_t suffix_len = strlen(suffix);
670   const size_t sep_len = strlen(sep);
671   ssize_t a;
672   char extension[FILE_MAX];
673   bool has_extension = false;
674 
675   if (string_len + sep_len + suffix_len >= maxlen) {
676     return false;
677   }
678 
679   for (a = string_len - 1; a >= 0; a--) {
680     if (string[a] == '.') {
681       has_extension = true;
682       break;
683     }
684     if (ELEM(string[a], '/', '\\')) {
685       break;
686     }
687   }
688 
689   if (!has_extension) {
690     a = string_len;
691   }
692 
693   BLI_strncpy(extension, string + a, sizeof(extension));
694   sprintf(string + a, "%s%s%s", sep, suffix, extension);
695   return true;
696 }
697 
698 /**
699  * Replaces path with the path of its parent directory, returning true if
700  * it was able to find a parent directory within the pathname.
701  */
BLI_path_parent_dir(char * path)702 bool BLI_path_parent_dir(char *path)
703 {
704   const char parent_dir[] = {'.', '.', SEP, '\0'}; /* "../" or "..\\" */
705   char tmp[FILE_MAX + 4];
706 
707   BLI_join_dirfile(tmp, sizeof(tmp), path, parent_dir);
708   BLI_path_normalize(NULL, tmp); /* does all the work of normalizing the path for us */
709 
710   if (!BLI_path_extension_check(tmp, parent_dir)) {
711     strcpy(path, tmp); /* We assume pardir is always shorter... */
712     return true;
713   }
714 
715   return false;
716 }
717 
718 /**
719  * Strips off nonexistent (or non-accessible) subdirectories from the end of *dir,
720  * leaving the path of the lowest-level directory that does exist and we can read.
721  */
BLI_path_parent_dir_until_exists(char * dir)722 bool BLI_path_parent_dir_until_exists(char *dir)
723 {
724   bool valid_path = true;
725 
726   /* Loop as long as cur path is not a dir, and we can get a parent path. */
727   while ((BLI_access(dir, R_OK) != 0) && (valid_path = BLI_path_parent_dir(dir))) {
728     /* pass */
729   }
730   return (valid_path && dir[0]);
731 }
732 
733 /**
734  * Looks for a sequence of "#" characters in the last slash-separated component of *path,
735  * returning the indexes of the first and one past the last character in the sequence in
736  * *char_start and *char_end respectively. Returns true if such a sequence was found.
737  */
stringframe_chars(const char * path,int * char_start,int * char_end)738 static bool stringframe_chars(const char *path, int *char_start, int *char_end)
739 {
740   uint ch_sta, ch_end, i;
741   /* Insert current frame: file### -> file001 */
742   ch_sta = ch_end = 0;
743   for (i = 0; path[i] != '\0'; i++) {
744     if (ELEM(path[i], '\\', '/')) {
745       ch_end = 0; /* this is a directory name, don't use any hashes we found */
746     }
747     else if (path[i] == '#') {
748       ch_sta = i;
749       ch_end = ch_sta + 1;
750       while (path[ch_end] == '#') {
751         ch_end++;
752       }
753       i = ch_end - 1; /* keep searching */
754 
755       /* don't break, there may be a slash after this that invalidates the previous #'s */
756     }
757   }
758 
759   if (ch_end) {
760     *char_start = ch_sta;
761     *char_end = ch_end;
762     return true;
763   }
764 
765   *char_start = -1;
766   *char_end = -1;
767   return false;
768 }
769 
770 /**
771  * Ensure *path contains at least one "#" character in its last slash-separated
772  * component, appending one digits long if not.
773  */
ensure_digits(char * path,int digits)774 static void ensure_digits(char *path, int digits)
775 {
776   char *file = (char *)BLI_path_slash_rfind(path);
777 
778   if (file == NULL) {
779     file = path;
780   }
781 
782   if (strrchr(file, '#') == NULL) {
783     int len = strlen(file);
784 
785     while (digits--) {
786       file[len++] = '#';
787     }
788     file[len] = '\0';
789   }
790 }
791 
792 /**
793  * Replaces "#" character sequence in last slash-separated component of *path
794  * with frame as decimal integer, with leading zeroes as necessary, to make digits digits.
795  */
BLI_path_frame(char * path,int frame,int digits)796 bool BLI_path_frame(char *path, int frame, int digits)
797 {
798   int ch_sta, ch_end;
799 
800   if (digits) {
801     ensure_digits(path, digits);
802   }
803 
804   if (stringframe_chars(path, &ch_sta, &ch_end)) { /* warning, ch_end is the last # +1 */
805     char tmp[FILE_MAX];
806     BLI_snprintf(
807         tmp, sizeof(tmp), "%.*s%.*d%s", ch_sta, path, ch_end - ch_sta, frame, path + ch_end);
808     BLI_strncpy(path, tmp, FILE_MAX);
809     return true;
810   }
811   return false;
812 }
813 
814 /**
815  * Replaces "#" character sequence in last slash-separated component of *path
816  * with sta and end as decimal integers, with leading zeroes as necessary, to make digits
817  * digits each, with a hyphen in-between.
818  */
BLI_path_frame_range(char * path,int sta,int end,int digits)819 bool BLI_path_frame_range(char *path, int sta, int end, int digits)
820 {
821   int ch_sta, ch_end;
822 
823   if (digits) {
824     ensure_digits(path, digits);
825   }
826 
827   if (stringframe_chars(path, &ch_sta, &ch_end)) { /* warning, ch_end is the last # +1 */
828     char tmp[FILE_MAX];
829     BLI_snprintf(tmp,
830                  sizeof(tmp),
831                  "%.*s%.*d-%.*d%s",
832                  ch_sta,
833                  path,
834                  ch_end - ch_sta,
835                  sta,
836                  ch_end - ch_sta,
837                  end,
838                  path + ch_end);
839     BLI_strncpy(path, tmp, FILE_MAX);
840     return true;
841   }
842   return false;
843 }
844 
845 /**
846  * Get the frame from a filename formatted by blender's frame scheme
847  */
BLI_path_frame_get(char * path,int * r_frame,int * r_numdigits)848 bool BLI_path_frame_get(char *path, int *r_frame, int *r_numdigits)
849 {
850   if (*path) {
851     char *file = (char *)BLI_path_slash_rfind(path);
852     char *c;
853     int len, numdigits;
854 
855     numdigits = *r_numdigits = 0;
856 
857     if (file == NULL) {
858       file = path;
859     }
860 
861     /* first get the extension part */
862     len = strlen(file);
863 
864     c = file + len;
865 
866     /* isolate extension */
867     while (--c != file) {
868       if (*c == '.') {
869         c--;
870         break;
871       }
872     }
873 
874     /* find start of number */
875     while (c != (file - 1) && isdigit(*c)) {
876       c--;
877       numdigits++;
878     }
879 
880     if (numdigits) {
881       char prevchar;
882 
883       c++;
884       prevchar = c[numdigits];
885       c[numdigits] = 0;
886 
887       /* was the number really an extension? */
888       *r_frame = atoi(c);
889       c[numdigits] = prevchar;
890 
891       *r_numdigits = numdigits;
892 
893       return true;
894     }
895   }
896 
897   return false;
898 }
899 
BLI_path_frame_strip(char * path,char * r_ext)900 void BLI_path_frame_strip(char *path, char *r_ext)
901 {
902   *r_ext = '\0';
903   if (*path == '\0') {
904     return;
905   }
906 
907   char *file = (char *)BLI_path_slash_rfind(path);
908   char *c, *suffix;
909   int len;
910   int numdigits = 0;
911 
912   if (file == NULL) {
913     file = path;
914   }
915 
916   /* first get the extension part */
917   len = strlen(file);
918 
919   c = file + len;
920 
921   /* isolate extension */
922   while (--c != file) {
923     if (*c == '.') {
924       c--;
925       break;
926     }
927   }
928 
929   suffix = c + 1;
930 
931   /* find start of number */
932   while (c != (file - 1) && isdigit(*c)) {
933     c--;
934     numdigits++;
935   }
936 
937   c++;
938 
939   int suffix_length = len - (suffix - file);
940   BLI_strncpy(r_ext, suffix, suffix_length + 1);
941 
942   /* replace the number with the suffix and terminate the string */
943   while (numdigits--) {
944     *c++ = '#';
945   }
946   *c = '\0';
947 }
948 
949 /**
950  * Check if we have '#' chars, usable for #BLI_path_frame, #BLI_path_frame_range
951  */
BLI_path_frame_check_chars(const char * path)952 bool BLI_path_frame_check_chars(const char *path)
953 {
954   int ch_sta, ch_end; /* dummy args */
955   return stringframe_chars(path, &ch_sta, &ch_end);
956 }
957 
958 /**
959  * Creates a display string from path to be used menus and the user interface.
960  * Like bpy.path.display_name().
961  */
BLI_path_to_display_name(char * display_name,int maxlen,const char * name)962 void BLI_path_to_display_name(char *display_name, int maxlen, const char *name)
963 {
964   /* Strip leading underscores and spaces. */
965   int strip_offset = 0;
966   while (ELEM(name[strip_offset], '_', ' ')) {
967     strip_offset++;
968   }
969 
970   BLI_strncpy(display_name, name + strip_offset, maxlen);
971 
972   /* Replace underscores with spaces. */
973   BLI_str_replace_char(display_name, '_', ' ');
974 
975   /* Strip extension. */
976   BLI_path_extension_replace(display_name, maxlen, "");
977 
978   /* Test if string has any upper case characters. */
979   bool all_lower = true;
980   for (int i = 0; display_name[i]; i++) {
981     if (isupper(display_name[i])) {
982       all_lower = false;
983       break;
984     }
985   }
986 
987   if (all_lower) {
988     /* For full lowercase string, use title case. */
989     bool prevspace = true;
990     for (int i = 0; display_name[i]; i++) {
991       if (prevspace) {
992         display_name[i] = toupper(display_name[i]);
993       }
994 
995       prevspace = isspace(display_name[i]);
996     }
997   }
998 }
999 
1000 /**
1001  * If path begins with "//", strips that and replaces it with basepath directory.
1002  *
1003  * \note Also converts drive-letter prefix to something more sensible
1004  * if this is a non-drive-letter-based system.
1005  *
1006  * \param path: The path to convert.
1007  * \param basepath: The directory to base relative paths with.
1008  * \return true if the path was relative (started with "//").
1009  */
BLI_path_abs(char * path,const char * basepath)1010 bool BLI_path_abs(char *path, const char *basepath)
1011 {
1012   const bool wasrelative = BLI_path_is_rel(path);
1013   char tmp[FILE_MAX];
1014   char base[FILE_MAX];
1015 #ifdef WIN32
1016 
1017   /* without this: "" --> "C:\" */
1018   if (*path == '\0') {
1019     return wasrelative;
1020   }
1021 
1022   /* we are checking here if we have an absolute path that is not in the current
1023    * blend file as a lib main - we are basically checking for the case that a
1024    * UNIX root '/' is passed.
1025    */
1026   if (!wasrelative && !BLI_path_is_abs(path)) {
1027     char *p = path;
1028     BLI_windows_get_default_root_dir(tmp);
1029     /* Get rid of the slashes at the beginning of the path. */
1030     while (ELEM(*p, '\\', '/')) {
1031       p++;
1032     }
1033     strcat(tmp, p);
1034   }
1035   else {
1036     BLI_strncpy(tmp, path, FILE_MAX);
1037   }
1038 #else
1039   BLI_strncpy(tmp, path, sizeof(tmp));
1040 
1041   /* Check for loading a windows path on a posix system
1042    * in this case, there is no use in trying C:/ since it
1043    * will never exist on a unix os.
1044    *
1045    * Add a '/' prefix and lowercase the drive-letter, remove the ':'.
1046    * C:\foo.JPG -> /c/foo.JPG */
1047 
1048   if (isalpha(tmp[0]) && (tmp[1] == ':') && ELEM(tmp[2], '\\', '/')) {
1049     tmp[1] = tolower(tmp[0]); /* Replace ':' with drive-letter. */
1050     tmp[0] = '/';
1051     /* '\' the slash will be converted later */
1052   }
1053 
1054 #endif
1055 
1056   /* push slashes into unix mode - strings entering this part are
1057    * potentially messed up: having both back- and forward slashes.
1058    * Here we push into one conform direction, and at the end we
1059    * push them into the system specific dir. This ensures uniformity
1060    * of paths and solving some problems (and prevent potential future
1061    * ones) -jesterKing.
1062    * For UNC paths the first characters containing the UNC prefix
1063    * shouldn't be switched as we need to distinguish them from
1064    * paths relative to the .blend file -elubie */
1065   BLI_str_replace_char(tmp + BLI_path_unc_prefix_len(tmp), '\\', '/');
1066 
1067   /* Paths starting with // will get the blend file as their base,
1068    * this isn't standard in any os but is used in blender all over the place */
1069   if (wasrelative) {
1070     const char *lslash;
1071     BLI_strncpy(base, basepath, sizeof(base));
1072 
1073     /* file component is ignored, so don't bother with the trailing slash */
1074     BLI_path_normalize(NULL, base);
1075     lslash = BLI_path_slash_rfind(base);
1076     BLI_str_replace_char(base + BLI_path_unc_prefix_len(base), '\\', '/');
1077 
1078     if (lslash) {
1079       /* length up to and including last "/" */
1080       const int baselen = (int)(lslash - base) + 1;
1081       /* use path for temp storage here, we copy back over it right away */
1082       BLI_strncpy(path, tmp + 2, FILE_MAX); /* strip "//" */
1083 
1084       memcpy(tmp, base, baselen); /* prefix with base up to last "/" */
1085       BLI_strncpy(tmp + baselen, path, sizeof(tmp) - baselen); /* append path after "//" */
1086       BLI_strncpy(path, tmp, FILE_MAX);                        /* return as result */
1087     }
1088     else {
1089       /* base doesn't seem to be a directory--ignore it and just strip "//" prefix on path */
1090       BLI_strncpy(path, tmp + 2, FILE_MAX);
1091     }
1092   }
1093   else {
1094     /* base ignored */
1095     BLI_strncpy(path, tmp, FILE_MAX);
1096   }
1097 
1098 #ifdef WIN32
1099   /* skip first two chars, which in case of
1100    * absolute path will be drive:/blabla and
1101    * in case of relpath //blabla/. So relpath
1102    * // will be retained, rest will be nice and
1103    * shiny win32 backward slashes :) -jesterKing
1104    */
1105   BLI_str_replace_char(path + 2, '/', '\\');
1106 #endif
1107 
1108   /* ensure this is after correcting for path switch */
1109   BLI_path_normalize(NULL, path);
1110 
1111   return wasrelative;
1112 }
1113 
1114 /**
1115  * Checks for relative path, expanding them relative to the current working directory.
1116  * Returns true if the expansion was performed.
1117  *
1118  * \note Should only be called with command line paths.
1119  * This is _not_ something Blender's internal paths support, instead they use the "//" prefix.
1120  * In most cases #BLI_path_abs should be used instead.
1121  */
BLI_path_abs_from_cwd(char * path,const size_t maxlen)1122 bool BLI_path_abs_from_cwd(char *path, const size_t maxlen)
1123 {
1124 #ifdef DEBUG_STRSIZE
1125   memset(path, 0xff, sizeof(*path) * maxlen);
1126 #endif
1127   bool wasrelative = true;
1128   const int filelen = strlen(path);
1129 
1130 #ifdef WIN32
1131   if ((filelen >= 3 && BLI_path_is_abs(path)) || BLI_path_is_unc(path)) {
1132     wasrelative = false;
1133   }
1134 #else
1135   if (filelen >= 2 && path[0] == '/') {
1136     wasrelative = false;
1137   }
1138 #endif
1139 
1140   if (wasrelative) {
1141     char cwd[FILE_MAX];
1142     /* in case the full path to the blend isn't used */
1143     if (BLI_current_working_dir(cwd, sizeof(cwd))) {
1144       char origpath[FILE_MAX];
1145       BLI_strncpy(origpath, path, FILE_MAX);
1146       BLI_join_dirfile(path, maxlen, cwd, origpath);
1147     }
1148     else {
1149       printf("Could not get the current working directory - $PWD for an unknown reason.\n");
1150     }
1151   }
1152 
1153   return wasrelative;
1154 }
1155 
1156 #ifdef _WIN32
1157 /**
1158  * Tries appending each of the semicolon-separated extensions in the PATHEXT
1159  * environment variable (Windows-only) onto *name in turn until such a file is found.
1160  * Returns success/failure.
1161  */
BLI_path_program_extensions_add_win32(char * name,const size_t maxlen)1162 bool BLI_path_program_extensions_add_win32(char *name, const size_t maxlen)
1163 {
1164   bool retval = false;
1165   int type;
1166 
1167   type = BLI_exists(name);
1168   if ((type == 0) || S_ISDIR(type)) {
1169     /* typically 3-5, ".EXE", ".BAT"... etc */
1170     const int ext_max = 12;
1171     const char *ext = BLI_getenv("PATHEXT");
1172     if (ext) {
1173       const int name_len = strlen(name);
1174       char *filename = alloca(name_len + ext_max);
1175       char *filename_ext;
1176       const char *ext_next;
1177 
1178       /* null terminated in the loop */
1179       memcpy(filename, name, name_len);
1180       filename_ext = filename + name_len;
1181 
1182       do {
1183         int ext_len;
1184         ext_next = strchr(ext, ';');
1185         ext_len = ext_next ? ((ext_next++) - ext) : strlen(ext);
1186 
1187         if (LIKELY(ext_len < ext_max)) {
1188           memcpy(filename_ext, ext, ext_len);
1189           filename_ext[ext_len] = '\0';
1190 
1191           type = BLI_exists(filename);
1192           if (type && (!S_ISDIR(type))) {
1193             retval = true;
1194             BLI_strncpy(name, filename, maxlen);
1195             break;
1196           }
1197         }
1198       } while ((ext = ext_next));
1199     }
1200   }
1201   else {
1202     retval = true;
1203   }
1204 
1205   return retval;
1206 }
1207 #endif /* WIN32 */
1208 
1209 /**
1210  * Search for a binary (executable)
1211  */
BLI_path_program_search(char * fullname,const size_t maxlen,const char * name)1212 bool BLI_path_program_search(char *fullname, const size_t maxlen, const char *name)
1213 {
1214 #ifdef DEBUG_STRSIZE
1215   memset(fullname, 0xff, sizeof(*fullname) * maxlen);
1216 #endif
1217   const char *path;
1218   bool retval = false;
1219 
1220 #ifdef _WIN32
1221   const char separator = ';';
1222 #else
1223   const char separator = ':';
1224 #endif
1225 
1226   path = BLI_getenv("PATH");
1227   if (path) {
1228     char filename[FILE_MAX];
1229     const char *temp;
1230 
1231     do {
1232       temp = strchr(path, separator);
1233       if (temp) {
1234         memcpy(filename, path, temp - path);
1235         filename[temp - path] = 0;
1236         path = temp + 1;
1237       }
1238       else {
1239         BLI_strncpy(filename, path, sizeof(filename));
1240       }
1241 
1242       BLI_path_append(filename, maxlen, name);
1243       if (
1244 #ifdef _WIN32
1245           BLI_path_program_extensions_add_win32(filename, maxlen)
1246 #else
1247           BLI_exists(filename)
1248 #endif
1249       ) {
1250         BLI_strncpy(fullname, filename, maxlen);
1251         retval = true;
1252         break;
1253       }
1254     } while (temp);
1255   }
1256 
1257   if (retval == false) {
1258     *fullname = '\0';
1259   }
1260 
1261   return retval;
1262 }
1263 
1264 /**
1265  * Sets the specified environment variable to the specified value,
1266  * and clears it if val == NULL.
1267  */
BLI_setenv(const char * env,const char * val)1268 void BLI_setenv(const char *env, const char *val)
1269 {
1270   /* free windows */
1271 
1272 #if (defined(WIN32) || defined(WIN64))
1273   uputenv(env, val);
1274 
1275 #else
1276   /* linux/osx/bsd */
1277   if (val) {
1278     setenv(env, val, 1);
1279   }
1280   else {
1281     unsetenv(env);
1282   }
1283 #endif
1284 }
1285 
1286 /**
1287  * Only set an env var if already not there.
1288  * Like Unix setenv(env, val, 0);
1289  *
1290  * (not used anywhere).
1291  */
BLI_setenv_if_new(const char * env,const char * val)1292 void BLI_setenv_if_new(const char *env, const char *val)
1293 {
1294   if (BLI_getenv(env) == NULL) {
1295     BLI_setenv(env, val);
1296   }
1297 }
1298 
1299 /**
1300  * Get an env var, result has to be used immediately.
1301  *
1302  * On windows getenv gets its variables from a static copy of the environment variables taken at
1303  * process start-up, causing it to not pick up on environment variables created during runtime.
1304  * This function uses an alternative method to get environment variables that does pick up on
1305  * runtime environment variables.
1306  */
BLI_getenv(const char * env)1307 const char *BLI_getenv(const char *env)
1308 {
1309 #ifdef _MSC_VER
1310   static char buffer[32767]; /* 32767 is the total size of the environment block on windows*/
1311   if (GetEnvironmentVariableA(env, buffer, sizeof(buffer))) {
1312     return buffer;
1313   }
1314   else {
1315     return NULL;
1316   }
1317 #else
1318   return getenv(env);
1319 #endif
1320 }
1321 
1322 /**
1323  * Ensures that the parent directory of *name exists.
1324  *
1325  * \return true on success (i.e. given path now exists on FS), false otherwise.
1326  */
BLI_make_existing_file(const char * name)1327 bool BLI_make_existing_file(const char *name)
1328 {
1329   char di[FILE_MAX];
1330   BLI_split_dir_part(name, di, sizeof(di));
1331 
1332   /* make if the dir doesn't exist */
1333   return BLI_dir_create_recursive(di);
1334 }
1335 
1336 /**
1337  * Returns in *string the concatenation of *dir and *file (also with *relabase on the
1338  * front if specified and *dir begins with "//"). Normalizes all occurrences of path
1339  * separators, including ensuring there is exactly one between the copies of *dir and *file,
1340  * and between the copies of *relabase and *dir.
1341  *
1342  * \param relabase: Optional prefix to substitute for "//" on front of *dir
1343  * \param string: Area to return result
1344  */
BLI_make_file_string(const char * relabase,char * string,const char * dir,const char * file)1345 void BLI_make_file_string(const char *relabase, char *string, const char *dir, const char *file)
1346 {
1347   int sl;
1348 
1349   if (string) {
1350     /* ensure this is always set even if dir/file are NULL */
1351     string[0] = '\0';
1352 
1353     if (ELEM(NULL, dir, file)) {
1354       return; /* We don't want any NULLs */
1355     }
1356   }
1357   else {
1358     return; /* string is NULL, probably shouldnt happen but return anyway */
1359   }
1360 
1361   /* Resolve relative references */
1362   if (relabase && dir[0] == '/' && dir[1] == '/') {
1363     char *lslash;
1364 
1365     /* Get the file name, chop everything past the last slash (ie. the filename) */
1366     strcpy(string, relabase);
1367 
1368     lslash = (char *)BLI_path_slash_rfind(string);
1369     if (lslash) {
1370       *(lslash + 1) = 0;
1371     }
1372 
1373     dir += 2; /* Skip over the relative reference */
1374   }
1375 #ifdef WIN32
1376   else {
1377     if (BLI_strnlen(dir, 3) >= 2 && dir[1] == ':') {
1378       BLI_strncpy(string, dir, 3);
1379       dir += 2;
1380     }
1381     else if (BLI_strnlen(dir, 3) >= 2 && BLI_path_is_unc(dir)) {
1382       string[0] = 0;
1383     }
1384     else { /* no drive specified */
1385            /* first option: get the drive from the relabase if it has one */
1386       if (relabase && BLI_strnlen(relabase, 3) >= 2 && relabase[1] == ':') {
1387         BLI_strncpy(string, relabase, 3);
1388         string[2] = '\\';
1389         string[3] = '\0';
1390       }
1391       else { /* we're out of luck here, guessing the first valid drive, usually c:\ */
1392         BLI_windows_get_default_root_dir(string);
1393       }
1394 
1395       /* ignore leading slashes */
1396       while (ELEM(*dir, '/', '\\')) {
1397         dir++;
1398       }
1399     }
1400   }
1401 #endif
1402 
1403   strcat(string, dir);
1404 
1405   /* Make sure string ends in one (and only one) slash */
1406   /* first trim all slashes from the end of the string */
1407   sl = strlen(string);
1408   while ((sl > 0) && ELEM(string[sl - 1], '/', '\\')) {
1409     string[sl - 1] = '\0';
1410     sl--;
1411   }
1412   /* since we've now removed all slashes, put back one slash at the end. */
1413   strcat(string, "/");
1414 
1415   while (ELEM(*file, '/', '\\')) {
1416     /* Trim slashes from the front of file */
1417     file++;
1418   }
1419 
1420   strcat(string, file);
1421 
1422   /* Push all slashes to the system preferred direction */
1423   BLI_path_slash_native(string);
1424 }
1425 
path_extension_check_ex(const char * str,const size_t str_len,const char * ext,const size_t ext_len)1426 static bool path_extension_check_ex(const char *str,
1427                                     const size_t str_len,
1428                                     const char *ext,
1429                                     const size_t ext_len)
1430 {
1431   BLI_assert(strlen(str) == str_len);
1432   BLI_assert(strlen(ext) == ext_len);
1433 
1434   return (((str_len == 0 || ext_len == 0 || ext_len >= str_len) == 0) &&
1435           (BLI_strcasecmp(ext, str + str_len - ext_len) == 0));
1436 }
1437 
1438 /* does str end with ext. */
BLI_path_extension_check(const char * str,const char * ext)1439 bool BLI_path_extension_check(const char *str, const char *ext)
1440 {
1441   return path_extension_check_ex(str, strlen(str), ext, strlen(ext));
1442 }
1443 
BLI_path_extension_check_n(const char * str,...)1444 bool BLI_path_extension_check_n(const char *str, ...)
1445 {
1446   const size_t str_len = strlen(str);
1447 
1448   va_list args;
1449   const char *ext;
1450   bool ret = false;
1451 
1452   va_start(args, str);
1453 
1454   while ((ext = (const char *)va_arg(args, void *))) {
1455     if (path_extension_check_ex(str, str_len, ext, strlen(ext))) {
1456       ret = true;
1457       break;
1458     }
1459   }
1460 
1461   va_end(args);
1462 
1463   return ret;
1464 }
1465 
1466 /* does str end with any of the suffixes in *ext_array. */
BLI_path_extension_check_array(const char * str,const char ** ext_array)1467 bool BLI_path_extension_check_array(const char *str, const char **ext_array)
1468 {
1469   const size_t str_len = strlen(str);
1470   int i = 0;
1471 
1472   while (ext_array[i]) {
1473     if (path_extension_check_ex(str, str_len, ext_array[i], strlen(ext_array[i]))) {
1474       return true;
1475     }
1476 
1477     i++;
1478   }
1479   return false;
1480 }
1481 
1482 /**
1483  * Semicolon separated wildcards, eg:
1484  *  '*.zip;*.py;*.exe'
1485  * does str match any of the semicolon-separated glob patterns in fnmatch.
1486  */
BLI_path_extension_check_glob(const char * str,const char * ext_fnmatch)1487 bool BLI_path_extension_check_glob(const char *str, const char *ext_fnmatch)
1488 {
1489   const char *ext_step = ext_fnmatch;
1490   char pattern[16];
1491 
1492   while (ext_step[0]) {
1493     const char *ext_next;
1494     size_t len_ext;
1495 
1496     if ((ext_next = strchr(ext_step, ';'))) {
1497       len_ext = ext_next - ext_step + 1;
1498       BLI_strncpy(pattern, ext_step, (len_ext > sizeof(pattern)) ? sizeof(pattern) : len_ext);
1499     }
1500     else {
1501       len_ext = BLI_strncpy_rlen(pattern, ext_step, sizeof(pattern));
1502     }
1503 
1504     if (fnmatch(pattern, str, FNM_CASEFOLD) == 0) {
1505       return true;
1506     }
1507     ext_step += len_ext;
1508   }
1509 
1510   return false;
1511 }
1512 
1513 /**
1514  * Does basic validation of the given glob string, to prevent common issues from string truncation.
1515  *
1516  * For now, only forbids last group to be a wildcard-only one, if there are more than one group
1517  * (i.e. things like "*.txt;*.cpp;*" are changed to "*.txt;*.cpp;")
1518  *
1519  * \returns true if it had to modify given \a ext_fnmatch pattern.
1520  */
BLI_path_extension_glob_validate(char * ext_fnmatch)1521 bool BLI_path_extension_glob_validate(char *ext_fnmatch)
1522 {
1523   bool only_wildcards = false;
1524 
1525   for (size_t i = strlen(ext_fnmatch); i-- > 0;) {
1526     if (ext_fnmatch[i] == ';') {
1527       /* Group separator, we truncate here if we only had wildcards so far.
1528        * Otherwise, all is sound and fine. */
1529       if (only_wildcards) {
1530         ext_fnmatch[i] = '\0';
1531         return true;
1532       }
1533       return false;
1534     }
1535     if (!ELEM(ext_fnmatch[i], '?', '*')) {
1536       /* Non-wildcard char, we can break here and consider the pattern valid. */
1537       return false;
1538     }
1539     /* So far, only wildcards in last group of the pattern... */
1540     only_wildcards = true;
1541   }
1542   /* Only one group in the pattern, so even if its only made of wildcard(s),
1543    * it is assumed valid. */
1544   return false;
1545 }
1546 
1547 /**
1548  * Removes any existing extension on the end of \a path and appends \a ext.
1549  * \return false if there was no room.
1550  */
BLI_path_extension_replace(char * path,size_t maxlen,const char * ext)1551 bool BLI_path_extension_replace(char *path, size_t maxlen, const char *ext)
1552 {
1553 #ifdef DEBUG_STRSIZE
1554   memset(path, 0xff, sizeof(*path) * maxlen);
1555 #endif
1556   const size_t path_len = strlen(path);
1557   const size_t ext_len = strlen(ext);
1558   ssize_t a;
1559 
1560   for (a = path_len - 1; a >= 0; a--) {
1561     if (ELEM(path[a], '.', '/', '\\')) {
1562       break;
1563     }
1564   }
1565 
1566   if ((a < 0) || (path[a] != '.')) {
1567     a = path_len;
1568   }
1569 
1570   if (a + ext_len >= maxlen) {
1571     return false;
1572   }
1573 
1574   memcpy(path + a, ext, ext_len + 1);
1575   return true;
1576 }
1577 
1578 /**
1579  * Strip's trailing '.'s and adds the extension only when needed
1580  */
BLI_path_extension_ensure(char * path,size_t maxlen,const char * ext)1581 bool BLI_path_extension_ensure(char *path, size_t maxlen, const char *ext)
1582 {
1583 #ifdef DEBUG_STRSIZE
1584   memset(path, 0xff, sizeof(*path) * maxlen);
1585 #endif
1586   const size_t path_len = strlen(path);
1587   const size_t ext_len = strlen(ext);
1588   ssize_t a;
1589 
1590   /* first check the extension is already there */
1591   if ((ext_len <= path_len) && (STREQ(path + (path_len - ext_len), ext))) {
1592     return true;
1593   }
1594 
1595   for (a = path_len - 1; a >= 0; a--) {
1596     if (path[a] == '.') {
1597       path[a] = '\0';
1598     }
1599     else {
1600       break;
1601     }
1602   }
1603   a++;
1604 
1605   if (a + ext_len >= maxlen) {
1606     return false;
1607   }
1608 
1609   memcpy(path + a, ext, ext_len + 1);
1610   return true;
1611 }
1612 
BLI_path_filename_ensure(char * filepath,size_t maxlen,const char * filename)1613 bool BLI_path_filename_ensure(char *filepath, size_t maxlen, const char *filename)
1614 {
1615 #ifdef DEBUG_STRSIZE
1616   memset(filepath, 0xff, sizeof(*filepath) * maxlen);
1617 #endif
1618   char *c = (char *)BLI_path_slash_rfind(filepath);
1619   if (!c || ((c - filepath) < maxlen - (strlen(filename) + 1))) {
1620     strcpy(c ? &c[1] : filepath, filename);
1621     return true;
1622   }
1623   return false;
1624 }
1625 
1626 /**
1627  * Converts `/foo/bar.txt` to "/foo/" and `bar.txt`
1628  *
1629  * - Wont change \a string.
1630  * - Wont create any directories.
1631  * - Doesn't use CWD, or deal with relative paths.
1632  * - Only fill's in \a dir and \a file when they are non NULL.
1633  * */
BLI_split_dirfile(const char * string,char * dir,char * file,const size_t dirlen,const size_t filelen)1634 void BLI_split_dirfile(
1635     const char *string, char *dir, char *file, const size_t dirlen, const size_t filelen)
1636 {
1637 #ifdef DEBUG_STRSIZE
1638   memset(dir, 0xff, sizeof(*dir) * dirlen);
1639   memset(file, 0xff, sizeof(*file) * filelen);
1640 #endif
1641   const char *lslash_str = BLI_path_slash_rfind(string);
1642   const size_t lslash = lslash_str ? (size_t)(lslash_str - string) + 1 : 0;
1643 
1644   if (dir) {
1645     if (lslash) {
1646       /* +1 to include the slash and the last char */
1647       BLI_strncpy(dir, string, MIN2(dirlen, lslash + 1));
1648     }
1649     else {
1650       dir[0] = '\0';
1651     }
1652   }
1653 
1654   if (file) {
1655     BLI_strncpy(file, string + lslash, filelen);
1656   }
1657 }
1658 
1659 /**
1660  * Copies the parent directory part of string into *dir, max length dirlen.
1661  */
BLI_split_dir_part(const char * string,char * dir,const size_t dirlen)1662 void BLI_split_dir_part(const char *string, char *dir, const size_t dirlen)
1663 {
1664   BLI_split_dirfile(string, dir, NULL, dirlen, 0);
1665 }
1666 
1667 /**
1668  * Copies the leaf filename part of string into *file, max length filelen.
1669  */
BLI_split_file_part(const char * string,char * file,const size_t filelen)1670 void BLI_split_file_part(const char *string, char *file, const size_t filelen)
1671 {
1672   BLI_split_dirfile(string, NULL, file, 0, filelen);
1673 }
1674 
1675 /**
1676  * Returns a pointer to the last extension (e.g. the position of the last period).
1677  * Returns NULL if there is no extension.
1678  */
BLI_path_extension(const char * filepath)1679 const char *BLI_path_extension(const char *filepath)
1680 {
1681   const char *extension = strrchr(filepath, '.');
1682   if (extension == NULL) {
1683     return NULL;
1684   }
1685   if (BLI_path_slash_find(extension) != NULL) {
1686     /* There is a path separator in the extension, so the '.' was found in a
1687      * directory component and not in the filename. */
1688     return NULL;
1689   }
1690   return extension;
1691 }
1692 
1693 /**
1694  * Append a filename to a dir, ensuring slash separates.
1695  */
BLI_path_append(char * __restrict dst,const size_t maxlen,const char * __restrict file)1696 void BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__restrict file)
1697 {
1698   size_t dirlen = BLI_strnlen(dst, maxlen);
1699 
1700   /* inline BLI_path_slash_ensure */
1701   if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
1702     dst[dirlen++] = SEP;
1703     dst[dirlen] = '\0';
1704   }
1705 
1706   if (dirlen >= maxlen) {
1707     return; /* fills the path */
1708   }
1709 
1710   BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
1711 }
1712 
1713 /**
1714  * Simple appending of filename to dir, does not check for valid path!
1715  * Puts result into *dst, which may be same area as *dir.
1716  */
BLI_join_dirfile(char * __restrict dst,const size_t maxlen,const char * __restrict dir,const char * __restrict file)1717 void BLI_join_dirfile(char *__restrict dst,
1718                       const size_t maxlen,
1719                       const char *__restrict dir,
1720                       const char *__restrict file)
1721 {
1722 #ifdef DEBUG_STRSIZE
1723   memset(dst, 0xff, sizeof(*dst) * maxlen);
1724 #endif
1725   size_t dirlen = BLI_strnlen(dir, maxlen);
1726 
1727   /* args can't match */
1728   BLI_assert(!ELEM(dst, dir, file));
1729 
1730   if (dirlen == maxlen) {
1731     memcpy(dst, dir, dirlen);
1732     dst[dirlen - 1] = '\0';
1733     return; /* dir fills the path */
1734   }
1735 
1736   memcpy(dst, dir, dirlen + 1);
1737 
1738   if (dirlen + 1 >= maxlen) {
1739     return; /* fills the path */
1740   }
1741 
1742   /* inline BLI_path_slash_ensure */
1743   if ((dirlen > 0) && !ELEM(dst[dirlen - 1], SEP, ALTSEP)) {
1744     dst[dirlen++] = SEP;
1745     dst[dirlen] = '\0';
1746   }
1747 
1748   if (dirlen >= maxlen) {
1749     return; /* fills the path */
1750   }
1751 
1752   BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
1753 }
1754 
1755 /**
1756  * Join multiple strings into a path, ensuring only a single path separator between each,
1757  * and trailing slash is kept.
1758  *
1759  * \note If you want a trailing slash, add ``SEP_STR`` as the last path argument,
1760  * duplicate slashes will be cleaned up.
1761  */
BLI_path_join(char * __restrict dst,const size_t dst_len,const char * path,...)1762 size_t BLI_path_join(char *__restrict dst, const size_t dst_len, const char *path, ...)
1763 {
1764 #ifdef DEBUG_STRSIZE
1765   memset(dst, 0xff, sizeof(*dst) * dst_len);
1766 #endif
1767   if (UNLIKELY(dst_len == 0)) {
1768     return 0;
1769   }
1770   const size_t dst_last = dst_len - 1;
1771   size_t ofs = BLI_strncpy_rlen(dst, path, dst_len);
1772 
1773   if (ofs == dst_last) {
1774     return ofs;
1775   }
1776 
1777   /* remove trailing slashes, unless there are _only_ trailing slashes
1778    * (allow "//" as the first argument). */
1779   bool has_trailing_slash = false;
1780   if (ofs != 0) {
1781     size_t len = ofs;
1782     while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
1783       len -= 1;
1784     }
1785     if (len != 0) {
1786       ofs = len;
1787     }
1788     has_trailing_slash = (path[len] != '\0');
1789   }
1790 
1791   va_list args;
1792   va_start(args, path);
1793   while ((path = (const char *)va_arg(args, const char *))) {
1794     has_trailing_slash = false;
1795     const char *path_init = path;
1796     while (ELEM(path[0], SEP, ALTSEP)) {
1797       path++;
1798     }
1799     size_t len = strlen(path);
1800     if (len != 0) {
1801       while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
1802         len -= 1;
1803       }
1804 
1805       if (len != 0) {
1806         /* the very first path may have a slash at the end */
1807         if (ofs && !ELEM(dst[ofs - 1], SEP, ALTSEP)) {
1808           dst[ofs++] = SEP;
1809           if (ofs == dst_last) {
1810             break;
1811           }
1812         }
1813         has_trailing_slash = (path[len] != '\0');
1814         if (ofs + len >= dst_last) {
1815           len = dst_last - ofs;
1816         }
1817         memcpy(&dst[ofs], path, len);
1818         ofs += len;
1819         if (ofs == dst_last) {
1820           break;
1821         }
1822       }
1823     }
1824     else {
1825       has_trailing_slash = (path_init != path);
1826     }
1827   }
1828   va_end(args);
1829 
1830   if (has_trailing_slash) {
1831     if ((ofs != dst_last) && (ofs != 0) && (ELEM(dst[ofs - 1], SEP, ALTSEP) == 0)) {
1832       dst[ofs++] = SEP;
1833     }
1834   }
1835 
1836   BLI_assert(ofs <= dst_last);
1837   dst[ofs] = '\0';
1838 
1839   return ofs;
1840 }
1841 
1842 /**
1843  * like pythons os.path.basename()
1844  *
1845  * \return The pointer into \a path string immediately after last slash,
1846  * or start of \a path if none found.
1847  */
BLI_path_basename(const char * path)1848 const char *BLI_path_basename(const char *path)
1849 {
1850   const char *const filename = BLI_path_slash_rfind(path);
1851   return filename ? filename + 1 : path;
1852 }
1853 
1854 /**
1855  * Get an element of the path at an index, eg:
1856  * "/some/path/file.txt" where an index of...
1857  * - 0 or -3: "some"
1858  * - 1 or -2: "path"
1859  * - 2 or -1: "file.txt"
1860  *
1861  * Ignores multiple slashes at any point in the path (including start/end).
1862  */
BLI_path_name_at_index(const char * __restrict path,const int index,int * __restrict r_offset,int * __restrict r_len)1863 bool BLI_path_name_at_index(const char *__restrict path,
1864                             const int index,
1865                             int *__restrict r_offset,
1866                             int *__restrict r_len)
1867 {
1868   if (index >= 0) {
1869     int index_step = 0;
1870     int prev = -1;
1871     int i = 0;
1872     while (true) {
1873       const char c = path[i];
1874       if (ELEM(c, SEP, ALTSEP, '\0')) {
1875         if (prev + 1 != i) {
1876           prev += 1;
1877           if (index_step == index) {
1878             *r_offset = prev;
1879             *r_len = i - prev;
1880             /* printf("!!! %d %d\n", start, end); */
1881             return true;
1882           }
1883           index_step += 1;
1884         }
1885         if (c == '\0') {
1886           break;
1887         }
1888         prev = i;
1889       }
1890       i += 1;
1891     }
1892     return false;
1893   }
1894 
1895   /* negative number, reverse where -1 is the last element */
1896   int index_step = -1;
1897   int prev = strlen(path);
1898   int i = prev - 1;
1899   while (true) {
1900     const char c = i >= 0 ? path[i] : '\0';
1901     if (ELEM(c, SEP, ALTSEP, '\0')) {
1902       if (prev - 1 != i) {
1903         i += 1;
1904         if (index_step == index) {
1905           *r_offset = i;
1906           *r_len = prev - i;
1907           return true;
1908         }
1909         index_step -= 1;
1910       }
1911       if (c == '\0') {
1912         break;
1913       }
1914       prev = i;
1915     }
1916     i -= 1;
1917   }
1918   return false;
1919 }
1920 
1921 /**
1922  * Returns pointer to the leftmost path separator in string. Not actually used anywhere.
1923  */
BLI_path_slash_find(const char * string)1924 const char *BLI_path_slash_find(const char *string)
1925 {
1926   const char *const ffslash = strchr(string, '/');
1927   const char *const fbslash = strchr(string, '\\');
1928 
1929   if (!ffslash) {
1930     return fbslash;
1931   }
1932   if (!fbslash) {
1933     return ffslash;
1934   }
1935 
1936   return (ffslash < fbslash) ? ffslash : fbslash;
1937 }
1938 
1939 /**
1940  * Returns pointer to the rightmost path separator in string.
1941  */
BLI_path_slash_rfind(const char * string)1942 const char *BLI_path_slash_rfind(const char *string)
1943 {
1944   const char *const lfslash = strrchr(string, '/');
1945   const char *const lbslash = strrchr(string, '\\');
1946 
1947   if (!lfslash) {
1948     return lbslash;
1949   }
1950   if (!lbslash) {
1951     return lfslash;
1952   }
1953 
1954   return (lfslash > lbslash) ? lfslash : lbslash;
1955 }
1956 
1957 /**
1958  * Appends a slash to string if there isn't one there already.
1959  * Returns the new length of the string.
1960  */
BLI_path_slash_ensure(char * string)1961 int BLI_path_slash_ensure(char *string)
1962 {
1963   int len = strlen(string);
1964   if (len == 0 || string[len - 1] != SEP) {
1965     string[len] = SEP;
1966     string[len + 1] = '\0';
1967     return len + 1;
1968   }
1969   return len;
1970 }
1971 
1972 /**
1973  * Removes the last slash and everything after it to the end of string, if there is one.
1974  */
BLI_path_slash_rstrip(char * string)1975 void BLI_path_slash_rstrip(char *string)
1976 {
1977   int len = strlen(string);
1978   while (len) {
1979     if (string[len - 1] == SEP) {
1980       string[len - 1] = '\0';
1981       len--;
1982     }
1983     else {
1984       break;
1985     }
1986   }
1987 }
1988 
1989 /**
1990  * Changes to the path separators to the native ones for this OS.
1991  */
BLI_path_slash_native(char * path)1992 void BLI_path_slash_native(char *path)
1993 {
1994 #ifdef WIN32
1995   if (path && BLI_strnlen(path, 3) > 2) {
1996     BLI_str_replace_char(path + 2, '/', '\\');
1997   }
1998 #else
1999   BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), '\\', '/');
2000 #endif
2001 }
2002