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