1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "path.h"
21 
22 #ifndef _WIN32
23 #include <pwd.h> /* getpwnam() */
24 #endif
25 #include <unistd.h>
26 
27 #include <assert.h> /* assert() */
28 #ifdef _WIN32
29 #include <ctype.h>
30 #endif
31 #include <errno.h> /* errno */
32 #include <stddef.h> /* NULL size_t */
33 #include <stdio.h>  /* snprintf() */
34 #include <stdlib.h> /* malloc() free() */
35 #include <string.h> /* memset() strcat() strcmp() strcasecmp() strdup()
36                        strncmp() strncasecmp() strncat() strchr() strcpy()
37                        strlen() strrchr() */
38 
39 #include "../cfg/config.h"
40 #include "../compat/fs_limits.h"
41 #include "../compat/os.h"
42 #include "../int/path_env.h"
43 #include "env.h"
44 #include "fs.h"
45 #include "str.h"
46 #include "utils.h"
47 
48 static int skip_dotdir_if_any(const char *path[], int has_parent);
49 static char * try_replace_tilde(const char path[]);
50 static char * find_ext_dot(const char path[]);
51 
52 void
chosp(char path[])53 chosp(char path[])
54 {
55 	size_t len;
56 
57 	if(path[0] == '\0')
58 	{
59 		return;
60 	}
61 
62 	len = strlen(path);
63 #ifndef _WIN32
64 	if(path[len - 1] == '/')
65 #else
66 	if(path[len - 1] == '/' || path[len - 1] == '\\')
67 #endif
68 	{
69 		path[len - 1] = '\0';
70 	}
71 }
72 
73 int
ends_with_slash(const char * str)74 ends_with_slash(const char *str)
75 {
76 	return str[0] != '\0' && str[strlen(str) - 1] == '/';
77 }
78 
79 int
path_starts_with(const char path[],const char prefix[])80 path_starts_with(const char path[], const char prefix[])
81 {
82 	size_t len = strlen(prefix);
83 
84 	/* Special case for "/", because it doesn't fit general pattern (slash
85 	 * shouldn't be stripped or we get empty prefix). */
86 	if(prefix[0] == '/' && prefix[1] == '\0')
87 	{
88 		return (path[0] == '/');
89 	}
90 
91 	if(len > 0U && prefix[len - 1] == '/')
92 	{
93 		--len;
94 	}
95 
96 	return strnoscmp(path, prefix, len) == 0
97 	    && (path[len] == '\0' || path[len] == '/');
98 }
99 
100 int
paths_are_equal(const char s[],const char t[])101 paths_are_equal(const char s[], const char t[])
102 {
103 	/* Some additional space is allocated for adding slashes. */
104 	char s_can[strlen(s) + 8];
105 	char t_can[strlen(t) + 8];
106 
107 	canonicalize_path(s, s_can, sizeof(s_can));
108 	canonicalize_path(t, t_can, sizeof(t_can));
109 
110 	return stroscmp(s_can, t_can) == 0;
111 }
112 
113 void
canonicalize_path(const char directory[],char buf[],size_t buf_size)114 canonicalize_path(const char directory[], char buf[], size_t buf_size)
115 {
116 	/* Source string pointer. */
117 	const char *p = directory;
118 	/* Destination string pointer. */
119 	char *q = buf - 1;
120 
121 	memset(buf, '\0', buf_size);
122 
123 #ifdef _WIN32
124 	/* Handle first component of a UNC path. */
125 	if(p[0] == '/' && p[1] == '/' && p[2] != '/')
126 	{
127 		strcpy(buf, "//");
128 		q = buf + 1;
129 		p += 2;
130 		while(*p != '\0' && *p != '/')
131 		{
132 			*++q = *p++;
133 		}
134 		buf = q + 1;
135 	}
136 #endif
137 
138 	while(*p != '\0' && (size_t)((q + 1) - buf) < buf_size - 1U)
139 	{
140 		const int prev_dir_present = (q != buf - 1 && *q == '/');
141 		if(skip_dotdir_if_any(&p, prev_dir_present))
142 		{
143 			/* skip_dotdir_if_any() function did all the job for us. */
144 		}
145 		else if(prev_dir_present &&
146 				(strncmp(p, "../", 3) == 0 || strcmp(p, "..") == 0) &&
147 				!(ends_with(buf, "/../") || strcmp(buf, "../") == 0))
148 		{
149 			/* Remove the last path component added. */
150 #ifdef _WIN32
151 			/* Special handling of Windows disk name. */
152 			if(*(q - 1) != ':')
153 #endif
154 			{
155 				++p;
156 				--q;
157 				while(q >= buf && *q != '/')
158 				{
159 					--q;
160 				}
161 			}
162 		}
163 		else if(*p == '/')
164 		{
165 			/* Don't add more than one slash between path components.  And don't add
166 			 * leading slash if initial path is relative. */
167 			if(!prev_dir_present && !(q == buf - 1 && *directory != '/'))
168 			{
169 				*++q = '/';
170 			}
171 		}
172 		else
173 		{
174 			/* Copy current path component till the end (slash or end of
175 			 * line/buffer). */
176 			*++q = *p;
177 			while(p[1] != '\0' && p[1] != '/' &&
178 					(size_t)((q + 1) - buf) < buf_size - 1U)
179 			{
180 				*++q = *++p;
181 			}
182 		}
183 
184 		++p;
185 	}
186 
187 	/* If initial path is relative and we ended up with an empty path, produce
188 	 * "./". */
189 	if(*directory != '/' && q < buf && (size_t)((q + 1) - buf) < buf_size - 2U)
190 	{
191 		*++q = '.';
192 	}
193 
194 	if((*directory != '\0' && q < buf) || (q >= buf && *q != '/'))
195 	{
196 		*++q = '/';
197 	}
198 
199 	*++q = '\0';
200 }
201 
202 /* Checks whether *path begins with current directory component ('./') and moves
203  * *path to the last character of such component (to slash if present) or to the
204  * previous of the last character depending on has_parent and following
205  * contents of the path.  When has_parent is zero the function normalizes
206  * '\.\.\.+/?' prefix on Windows to '\./?'.  Returns non-zero if a path
207  * component was fully skipped. */
208 static int
skip_dotdir_if_any(const char * path[],int has_parent)209 skip_dotdir_if_any(const char *path[], int has_parent)
210 {
211 	const char *tail;
212 
213 	size_t dot_count = 0U;
214 	while((*path)[dot_count] == '.')
215 	{
216 		++dot_count;
217 	}
218 
219 #ifndef _WIN32
220 	if(dot_count != 1U)
221 #else
222 	if(dot_count == 0U || dot_count == 2U)
223 #endif
224 	{
225 		return 0;
226 	}
227 
228 	tail = &(*path)[dot_count];
229 	if(tail[0] == '/' || tail[0] == '\0')
230 	{
231 		const int cut_off = (has_parent || (tail[0] == '/' && tail[1] == '.'));
232 		if(!cut_off)
233 		{
234 			--dot_count;
235 		}
236 
237 		/* Possibly step back one character to do not fall out of the string. */
238 		*path += dot_count - ((*path)[dot_count] == '\0' ? 1 : 0);
239 		return cut_off;
240 	}
241 	return 0;
242 }
243 
244 const char *
make_rel_path(const char path[],const char base[])245 make_rel_path(const char path[], const char base[])
246 {
247 	static char buf[PATH_MAX + 1];
248 
249 #ifdef _WIN32
250 	if(path[1] == ':' && base[1] == ':' && path[0] != base[0])
251 	{
252 		canonicalize_path(path, buf, sizeof(buf));
253 		return buf;
254 	}
255 #endif
256 
257 	/* Attempt to resolve all symbolic links in paths, this should give both the
258 	 * shortest and valid relative path. */
259 	char path_real[PATH_MAX + 1], base_real[PATH_MAX + 1];
260 	if(os_realpath(path, path_real) == path_real)
261 	{
262 		path = path_real;
263 	}
264 	if(os_realpath(base, base_real) == base_real)
265 	{
266 		base = base_real;
267 	}
268 
269 	const char *p = path, *b = base;
270 	int i;
271 	int nslashes;
272 
273 	while(p[0] != '\0' && p[1] != '\0' && b[0] != '\0' && b[1] != '\0')
274 	{
275 		const char *op = p, *ob = b;
276 		if((p = strchr(p + 1, '/')) == NULL)
277 			p = path + strlen(path);
278 		if((b = strchr(b + 1, '/')) == NULL)
279 			b = base + strlen(base);
280 		if(p - path != b - base || strnoscmp(path, base, p - path) != 0)
281 		{
282 			p = op;
283 			b = ob;
284 			break;
285 		}
286 	}
287 
288 	canonicalize_path(b, buf, sizeof(buf));
289 	chosp(buf);
290 
291 	nslashes = 0;
292 	for(i = 0; buf[i] != '\0'; i++)
293 		if(buf[i] == '/')
294 			nslashes++;
295 
296 	buf[0] = '\0';
297 	while(nslashes-- > 0)
298 		strcat(buf, "../");
299 	if(*p == '/')
300 		p++;
301 	if(*p != '\0')
302 	{
303 		canonicalize_path(p, buf + strlen(buf), sizeof(buf) - strlen(buf));
304 	}
305 	chosp(buf);
306 
307 	if(buf[0] == '\0')
308 		strcpy(buf, ".");
309 
310 	return buf;
311 }
312 
313 int
is_path_absolute(const char * path)314 is_path_absolute(const char *path)
315 {
316 #ifdef _WIN32
317 	if(isalpha(path[0]) && path[1] == ':')
318 		return 1;
319 	if(path[0] == '/' && path[1] == '/')
320 		return 1;
321 #endif
322 	return (path[0] == '/');
323 }
324 
325 int
is_root_dir(const char * path)326 is_root_dir(const char *path)
327 {
328 #ifdef _WIN32
329 	if(isalpha(path[0]) && stroscmp(path + 1, ":/") == 0)
330 		return 1;
331 
332 	if(path[0] == '/' && path[1] == '/' && path[2] != '\0')
333 	{
334 		char *p = strchr(path + 2, '/');
335 		if(p == NULL || p[1] == '\0')
336 			return 1;
337 	}
338 #endif
339 	return (path[0] == '/' && path[1] == '\0');
340 }
341 
342 int
is_unc_root(const char * path)343 is_unc_root(const char *path)
344 {
345 #ifdef _WIN32
346 	if(is_unc_path(path) && path[2] != '\0')
347 	{
348 		char *p = strchr(path + 2, '/');
349 		if(p == NULL || p[1] == '\0')
350 			return 1;
351 	}
352 	return 0;
353 #else
354 	return 0;
355 #endif
356 }
357 
358 char *
shell_like_escape(const char string[],int type)359 shell_like_escape(const char string[], int type)
360 {
361 	size_t len;
362 	size_t i;
363 	char *ret, *dup;
364 
365 	len = strlen(string);
366 
367 	dup = ret = malloc(len*3 + 2 + 1);
368 
369 	if(*string == '-')
370 	{
371 		*dup++ = '.';
372 		*dup++ = '/';
373 	}
374 
375 	for(i = 0; i < len; i++, string++, dup++)
376 	{
377 		switch(*string)
378 		{
379 			case '%':
380 				if(type == 1)
381 				{
382 					*dup++ = '%';
383 				}
384 				break;
385 
386 			/* Escape the following characters anywhere in the line. */
387 			case '\'':
388 			case '\\':
389 			case '\r':
390 			case '\t':
391 			case '"':
392 			case ';':
393 			case ' ':
394 			case '?':
395 			case '|':
396 			case '[':
397 			case ']':
398 			case '{':
399 			case '}':
400 			case '<':
401 			case '>':
402 			case '`':
403 			case '!':
404 			case '$':
405 			case '&':
406 			case '*':
407 			case '(':
408 			case ')':
409 			case '#':
410 				*dup++ = '\\';
411 				break;
412 
413 			case '\n':
414 				if(type != 0)
415 				{
416 					break;
417 				}
418 
419 				*dup++ = '"';
420 				*dup++ = '\n';
421 				*dup = '"';
422 				continue;
423 
424 			/* Escape the following characters only at the beginning of the line. */
425 			case '~':
426 			case '=': /* Command-path expansion in zsh. */
427 				if(dup == ret)
428 					*dup++ = '\\';
429 				break;
430 		}
431 		*dup = *string;
432 	}
433 	*dup = '\0';
434 	return ret;
435 }
436 
437 char *
replace_home_part(const char path[])438 replace_home_part(const char path[])
439 {
440 	char *result = replace_home_part_strict(path);
441 	if(!is_root_dir(result))
442 	{
443 		chosp(result);
444 	}
445 	return result;
446 }
447 
448 char *
replace_home_part_strict(const char path[])449 replace_home_part_strict(const char path[])
450 {
451 	static char buf[PATH_MAX + 1];
452 	size_t len;
453 
454 	len = strlen(cfg.home_dir) - 1;
455 	if(strnoscmp(path, cfg.home_dir, len) == 0 &&
456 			(path[len] == '\0' || path[len] == '/'))
457 	{
458 		strncat(strcpy(buf, "~"), path + len, sizeof(buf) - strlen(buf) - 1);
459 	}
460 	else
461 	{
462 		copy_str(buf, sizeof(buf), path);
463 	}
464 
465 	return buf;
466 }
467 
468 char *
expand_tilde(const char path[])469 expand_tilde(const char path[])
470 {
471 	char *const expanded_path = try_replace_tilde(path);
472 	if(expanded_path == path)
473 	{
474 		return strdup(path);
475 	}
476 	return expanded_path;
477 }
478 
479 char *
replace_tilde(char path[])480 replace_tilde(char path[])
481 {
482 	char *const expanded_path = try_replace_tilde(path);
483 	if(expanded_path != path)
484 	{
485 		free(path);
486 	}
487 	return expanded_path;
488 }
489 
490 /* Tries to expands tilde in the front of the path.  Returns the path or newly
491  * allocated string without tilde. */
492 static char *
try_replace_tilde(const char path[])493 try_replace_tilde(const char path[])
494 {
495 #ifndef _WIN32
496 	char name[NAME_MAX + 1];
497 	const char *p;
498 	char *result;
499 	struct passwd *pw;
500 #endif
501 
502 	if(path[0] != '~')
503 	{
504 		return (char *)path;
505 	}
506 
507 	if(path[1] == '\0' || path[1] == '/')
508 	{
509 		char *const result = format_str("%s%s", cfg.home_dir,
510 				(path[1] == '/') ? (path + 2) : "");
511 		return result;
512 	}
513 
514 #ifndef _WIN32
515 	if((p = strchr(path, '/')) == NULL)
516 	{
517 		p = path + strlen(path);
518 		copy_str(name, sizeof(name), path + 1);
519 	}
520 	else
521 	{
522 		copy_str(name, p - (path + 1) + 1, path + 1);
523 		p++;
524 	}
525 
526 	if((pw = getpwnam(name)) == NULL)
527 	{
528 		return (char *)path;
529 	}
530 
531 	chosp(pw->pw_dir);
532 	result = join_paths(pw->pw_dir, p);
533 
534 	return result;
535 #else
536 	return (char *)path;
537 #endif
538 }
539 
540 char *
get_last_path_component(const char path[])541 get_last_path_component(const char path[])
542 {
543 	char *slash = strrchr(path, '/');
544 	if(slash == NULL)
545 	{
546 		slash = (char *)path;
547 	}
548 	else if(slash[1] == '\0')
549 	{
550 		/* Skip slashes. */
551 		while(slash > path && slash[0] == '/')
552 		{
553 			--slash;
554 		}
555 		/* Skip until slash. */
556 		while(slash > path && slash[-1] != '/')
557 		{
558 			--slash;
559 		}
560 	}
561 	else
562 	{
563 		++slash;
564 	}
565 	return slash;
566 }
567 
568 void
remove_last_path_component(char path[])569 remove_last_path_component(char path[])
570 {
571 	char *slash;
572 
573 	/* Get rid of trailing slashes, if any (in rather inefficient way). */
574 	while(ends_with_slash(path))
575 	{
576 		chosp(path);
577 	}
578 
579 	slash = strrchr(path, '/');
580 	if(slash == NULL)
581 	{
582 		/* At most one item of path is left. */
583 		path[0] = '\0';
584 		return;
585 	}
586 
587 	/* Take care to do not turn path in root into no path (should become just
588 	 * root). */
589 	slash[1] = '\0';
590 	if(!is_root_dir(path))
591 	{
592 		slash[0] = '\0';
593 	}
594 }
595 
596 int
is_path_well_formed(const char * path)597 is_path_well_formed(const char *path)
598 {
599 #ifndef _WIN32
600 	return strchr(path, '/') != NULL;
601 #else
602 	return is_unc_path(path) || (strlen(path) >= 2 && path[1] == ':' &&
603 			drive_exists(path[0]));
604 #endif
605 }
606 
607 void
ensure_path_well_formed(char * path)608 ensure_path_well_formed(char *path)
609 {
610 	if(is_path_well_formed(path))
611 	{
612 		return;
613 	}
614 
615 #ifndef _WIN32
616 	strcpy(path, "/");
617 #else
618 	strcpy(path, env_get("SYSTEMDRIVE"));
619 	strcat(path, "/");
620 #endif
621 }
622 
623 void
to_canonic_path(const char path[],const char base[],char buf[],size_t buf_len)624 to_canonic_path(const char path[], const char base[], char buf[],
625 		size_t buf_len)
626 {
627 	if(!is_path_absolute(path))
628 	{
629 		char full_path[PATH_MAX + 1];
630 		/* Assert is not level above to keep "." in curr_dir in tests, but this
631 		 * should be possible to change. */
632 		assert(is_path_absolute(base) && "Base path has to be absolute.");
633 		snprintf(full_path, sizeof(full_path), "%s/%s", base, path);
634 		canonicalize_path(full_path, buf, buf_len);
635 	}
636 	else
637 	{
638 		canonicalize_path(path, buf, buf_len);
639 	}
640 
641 	if(!is_root_dir(buf))
642 	{
643 		chosp(buf);
644 	}
645 }
646 
647 int
contains_slash(const char * path)648 contains_slash(const char *path)
649 {
650 	char *slash_pos = strchr(path, '/');
651 #ifdef _WIN32
652 	if(slash_pos == NULL)
653 		slash_pos = strchr(path, '\\');
654 #endif
655 	return slash_pos != NULL;
656 }
657 
658 char *
find_slashr(const char * path)659 find_slashr(const char *path)
660 {
661 	char *result = strrchr(path, '/');
662 #ifdef _WIN32
663 	if(result == NULL)
664 		result = strrchr(path, '\\');
665 #endif
666 	return result;
667 }
668 
669 char *
cut_extension(char path[])670 cut_extension(char path[])
671 {
672 	char *e;
673 	char *ext;
674 
675 	if((ext = strrchr(path, '.')) == NULL)
676 		return path + strlen(path);
677 
678 	*ext = '\0';
679 	if((e = strrchr(path, '.')) != NULL && stroscmp(e + 1, "tar") == 0)
680 	{
681 		*ext = '.';
682 		ext = e;
683 	}
684 	*ext = '\0';
685 
686 	return ext + 1;
687 }
688 
689 void
split_ext(char path[],int * root_len,const char ** ext_pos)690 split_ext(char path[], int *root_len, const char **ext_pos)
691 {
692 	char *const dot = find_ext_dot(path);
693 
694 	if(dot == NULL)
695 	{
696 		const size_t len = strlen(path);
697 
698 		*ext_pos = path + len;
699 		*root_len = len;
700 
701 		return;
702 	}
703 
704 	*dot = '\0';
705 	*ext_pos = dot + 1;
706 	*root_len = dot - path;
707 }
708 
709 char *
get_ext(const char path[])710 get_ext(const char path[])
711 {
712 	char *const dot = find_ext_dot(path);
713 
714 	if(dot == NULL)
715 	{
716 		return (char *)path + strlen(path);
717 	}
718 
719 	return dot + 1;
720 }
721 
722 /* Gets extension for file path.  Returns pointer to the dot or NULL if file
723  * name has no extension. */
724 static char *
find_ext_dot(const char path[])725 find_ext_dot(const char path[])
726 {
727 	const char *const slash = strrchr(path, '/');
728 	char *const dot = strrchr(path, '.');
729 
730 	const int no_ext = dot == NULL
731 	                || (slash != NULL && dot < slash)
732 	                || dot == path
733 	                || dot == slash + 1;
734 
735 	return no_ext ? NULL : dot;
736 }
737 
738 void
exclude_file_name(char path[])739 exclude_file_name(char path[])
740 {
741 	if(path_exists(path, DEREF) && !is_valid_dir(path))
742 	{
743 		remove_last_path_component(path);
744 	}
745 }
746 
747 int
is_parent_dir(const char path[])748 is_parent_dir(const char path[])
749 {
750 	return path[0] == '.' && path[1] == '.' &&
751 		((path[2] == '/' && path[3] == '\0') || path[2] == '\0');
752 }
753 
754 int
is_builtin_dir(const char name[])755 is_builtin_dir(const char name[])
756 {
757 	return name[0] == '.'
758 	    && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
759 }
760 
761 int
find_cmd_in_path(const char cmd[],size_t path_len,char path[])762 find_cmd_in_path(const char cmd[], size_t path_len, char path[])
763 {
764 	size_t i;
765 	size_t paths_count;
766 	char **paths;
767 
768 	paths = get_paths(&paths_count);
769 	for(i = 0; i < paths_count; i++)
770 	{
771 		char tmp_path[PATH_MAX + 1];
772 		snprintf(tmp_path, sizeof(tmp_path), "%s/%s", paths[i], cmd);
773 
774 		/* Need to check for executable, not just a file, as this additionally
775 		 * checks for path with different executable extensions on Windows. */
776 		if(executable_exists(tmp_path))
777 		{
778 			if(path != NULL)
779 			{
780 				copy_str(path, path_len, tmp_path);
781 			}
782 			return 0;
783 		}
784 	}
785 	return 1;
786 }
787 
788 void
generate_tmp_file_name(const char prefix[],char buf[],size_t buf_len)789 generate_tmp_file_name(const char prefix[], char buf[], size_t buf_len)
790 {
791 	snprintf(buf, buf_len, "%s/%s", get_tmpdir(), prefix);
792 	system_to_internal_slashes(buf);
793 	copy_str(buf, buf_len, make_name_unique(buf));
794 }
795 
796 const char *
get_tmpdir(void)797 get_tmpdir(void)
798 {
799 	return env_get_one_of_def("/tmp/", "TMPDIR", "TEMP", "TEMPDIR", "TMP",
800 			(const char *)NULL);
801 }
802 
803 void
build_path(char buf[],size_t buf_len,const char p1[],const char p2[])804 build_path(char buf[], size_t buf_len, const char p1[], const char p2[])
805 {
806 	snprintf(buf, buf_len, "%s%s%s", p1, ends_with_slash(p1) ? "" : "/", p2);
807 }
808 
809 char *
join_paths(const char p1[],const char p2[])810 join_paths(const char p1[], const char p2[])
811 {
812 	const char *slash = (ends_with_slash(p1) ? "" : "/");
813 	p2 = skip_char(p2, '/');
814 	return format_str("%s%s%s", p1, slash, p2);
815 }
816 
817 #ifdef _WIN32
818 
819 int
is_unc_path(const char path[])820 is_unc_path(const char path[])
821 {
822 	return (path[0] == '/' && path[1] == '/' && path[2] != '/');
823 }
824 
825 void
system_to_internal_slashes(char path[])826 system_to_internal_slashes(char path[])
827 {
828 	replace_char(path, '\\', '/');
829 }
830 
831 void
internal_to_system_slashes(char path[])832 internal_to_system_slashes(char path[])
833 {
834 	replace_char(path, '/', '\\');
835 }
836 
837 #endif
838 
839 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
840 /* vim: set cinoptions+=t0 filetype=c : */
841