1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25 
26 #include <pwd.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/param.h>
32 #include <dirent.h>
33 #include <utime.h>
34 
35 #include <glib.h>
36 #include <gtk/gtk.h>	/* for locale warning dialog */
37 
38 #include "main.h"
39 #include "ui_fileops.h"
40 
41 #include "ui_utildlg.h"	/* for locale warning dialog */
42 #include "md5-util.h"
43 
44 #include "filefilter.h"
45 #include "layout.h"
46 #include "utilops.h"
47 #include "secure_save.h"
48 
49 /*
50  *-----------------------------------------------------------------------------
51  * generic file information and manipulation routines (public)
52  *-----------------------------------------------------------------------------
53  */
54 
55 
56 
print_term(gboolean err,const gchar * text_utf8)57 void print_term(gboolean err, const gchar *text_utf8)
58 {
59 	gchar *text_l;
60 
61 	text_l = g_locale_from_utf8(text_utf8, -1, NULL, NULL, NULL);
62 	if (err)
63 		{
64 		fputs((text_l) ? text_l : text_utf8, stderr);
65 		}
66 	else
67 		{
68 		fputs((text_l) ? text_l : text_utf8, stdout);
69 		}
70 	if(command_line && command_line->ssi)
71 		secure_fputs(command_line->ssi, (text_l) ? text_l : text_utf8);
72 	g_free(text_l);
73 }
74 
encoding_dialog(const gchar * path)75 static void encoding_dialog(const gchar *path)
76 {
77 	static gboolean warned_user = FALSE;
78 	GenericDialog *gd;
79 	GString *string;
80 	const gchar *lc;
81 	const gchar *bf;
82 
83 	if (warned_user) return;
84 	warned_user = TRUE;
85 
86 	lc = getenv("LANG");
87 	bf = getenv("G_BROKEN_FILENAMES");
88 
89 	string = g_string_new("");
90 	g_string_append(string, _("One or more filenames are not encoded with the preferred locale character set.\n"));
91 	g_string_append_printf(string, _("Operations on, and display of these files with %s may not succeed.\n"), PACKAGE);
92 	g_string_append(string, "\n");
93 	g_string_append(string, _("If your filenames are not encoded in utf-8, try setting the environment variable G_BROKEN_FILENAMES=1\n"));
94 	if (bf)
95 		g_string_append_printf(string, _("It appears G_BROKEN_FILENAMES is set to %s\n"), bf);
96 	else
97 		g_string_append(string, _("It appears G_BROKEN_FILENAMES is not set\n"));
98 	g_string_append(string, "\n");
99 	g_string_append_printf(string, _("The locale appears to be set to \"%s\"\n(set by the LANG environment variable)\n"), (lc) ? lc : "undefined");
100 	if (lc && (strstr(lc, "UTF-8") || strstr(lc, "utf-8")))
101 		{
102 		gchar *name;
103 		name = g_convert(path, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
104 		string = g_string_append(string, _("\nPreferred encoding appears to be UTF-8, however the file:\n"));
105 		g_string_append_printf(string, "\"%s\"\n", (name) ? name : _("[name not displayable]"));
106 
107 		if (g_utf8_validate(path, -1, NULL))
108 			g_string_append_printf(string, _("\"%s\" is encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
109 		else
110 			g_string_append_printf(string, _("\"%s\" is not encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
111 		g_string_append(string, "\n");
112 		g_free(name);
113 		}
114 
115 	gd = generic_dialog_new(_("Filename encoding locale mismatch"),
116 				"locale warning", NULL, TRUE, NULL, NULL);
117 	generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
118 
119 	generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
120 				   _("Filename encoding locale mismatch"), string->str, TRUE);
121 
122 	gtk_widget_show(gd->dialog);
123 
124 	g_string_free(string, TRUE);
125 }
126 
127 #if GQ_DEBUG_PATH_UTF8
path_to_utf8_debug(const gchar * path,const gchar * file,gint line)128 gchar *path_to_utf8_debug(const gchar *path, const gchar *file, gint line)
129 #else
130 gchar *path_to_utf8(const gchar *path)
131 #endif
132 {
133 	gchar *utf8;
134 	GError *error = NULL;
135 
136 	if (!path) return NULL;
137 
138 	utf8 = g_filename_to_utf8(path, -1, NULL, NULL, &error);
139 	if (error)
140 		{
141 #if GQ_DEBUG_PATH_UTF8
142 		log_printf("%s:%d: Unable to convert filename to UTF-8:\n%s\n%s\n", file, line, path, error->message);
143 #else
144 		log_printf("Unable to convert filename to UTF-8:\n%s\n%s\n", path, error->message);
145 #endif
146 		g_error_free(error);
147 		encoding_dialog(path);
148 		}
149 	if (!utf8)
150 		{
151 		/* just let it through, but bad things may happen */
152 		utf8 = g_strdup(path);
153 		}
154 
155 	return utf8;
156 }
157 
158 #if GQ_DEBUG_PATH_UTF8
path_from_utf8_debug(const gchar * utf8,const gchar * file,gint line)159 gchar *path_from_utf8_debug(const gchar *utf8, const gchar *file, gint line)
160 #else
161 gchar *path_from_utf8(const gchar *utf8)
162 #endif
163 {
164 	gchar *path;
165 	GError *error = NULL;
166 
167 	if (!utf8) return NULL;
168 
169 	path = g_filename_from_utf8(utf8, -1, NULL, NULL, &error);
170 	if (error)
171 		{
172 #if GQ_DEBUG_PATH_UTF8
173 		log_printf("%s:%d: Unable to convert filename to locale from UTF-8:\n%s\n%s\n", file, line, utf8, error->message);
174 #else
175 		log_printf("Unable to convert filename to locale from UTF-8:\n%s\n%s\n", utf8, error->message);
176 #endif
177 		g_error_free(error);
178 		}
179 	if (!path)
180 		{
181 		/* if invalid UTF-8, text probaby still in original form, so just copy it */
182 		path = g_strdup(utf8);
183 		}
184 
185 	return path;
186 }
187 
188 /* first we try the HOME environment var, if that doesn't work, we try g_get_homedir(). */
homedir(void)189 const gchar *homedir(void)
190 {
191 	static gchar *home = NULL;
192 
193 	if (!home)
194 		home = path_to_utf8(getenv("HOME"));
195 
196 	if (!home)
197 		home = path_to_utf8(g_get_home_dir());
198 
199 	DEBUG_1("Home directory: %s", home);
200 
201 	return home;
202 }
203 
xdg_dir_get(const gchar * key,const gchar * fallback)204 static gchar *xdg_dir_get(const gchar *key, const gchar *fallback)
205 {
206 	gchar *dir = getenv(key);
207 
208 	if (!dir || dir[0] == '\0')
209 		{
210     		return g_build_filename(homedir(), fallback, NULL);
211     		}
212 
213 	DEBUG_1("Got xdg %s: %s", key, dir);
214 
215 	return path_to_utf8(dir);
216 }
217 
xdg_data_home_get(void)218 const gchar *xdg_data_home_get(void)
219 {
220 	static const gchar *xdg_data_home = NULL;
221 
222 	if (xdg_data_home) return xdg_data_home;
223 
224 	xdg_data_home = xdg_dir_get("XDG_DATA_HOME", ".local/share");
225 
226 	return xdg_data_home;
227 }
228 
xdg_config_home_get(void)229 const gchar *xdg_config_home_get(void)
230 {
231 	static const gchar *xdg_config_home = NULL;
232 
233 	if (xdg_config_home) return xdg_config_home;
234 
235 	xdg_config_home = xdg_dir_get("XDG_CONFIG_HOME", ".config");
236 
237 	return xdg_config_home;
238 }
239 
xdg_cache_home_get(void)240 const gchar *xdg_cache_home_get(void)
241 {
242 	static const gchar *xdg_cache_home = NULL;
243 
244 	if (xdg_cache_home) return xdg_cache_home;
245 
246 	xdg_cache_home = xdg_dir_get("XDG_CACHE_HOME", ".cache");
247 
248 	return xdg_cache_home;
249 }
250 
get_rc_dir(void)251 const gchar *get_rc_dir(void)
252 {
253 	static gchar *rc_dir = NULL;
254 
255 	if (rc_dir) return rc_dir;
256 
257 	if (USE_XDG)
258 		{
259 		rc_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, NULL);
260 		}
261 	else
262 		{
263 		rc_dir = g_build_filename(homedir(), GQ_RC_DIR, NULL);
264 		}
265 
266 	return rc_dir;
267 }
268 
get_collections_dir(void)269 const gchar *get_collections_dir(void)
270 {
271 	static gchar *collections_dir = NULL;
272 
273 	if (collections_dir) return collections_dir;
274 
275 	if (USE_XDG)
276 		{
277 		collections_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_COLLECTIONS_DIR, NULL);
278 		}
279 	else
280 		{
281 		collections_dir = g_build_filename(get_rc_dir(), GQ_COLLECTIONS_DIR, NULL);
282 		}
283 
284 	return collections_dir;
285 }
286 
get_trash_dir(void)287 const gchar *get_trash_dir(void)
288 {
289 	static gchar *trash_dir = NULL;
290 
291 	if (trash_dir) return trash_dir;
292 
293 	if (USE_XDG)
294 		{
295 		trash_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_TRASH_DIR, NULL);
296 		}
297 	else
298 		{
299 		trash_dir = g_build_filename(get_rc_dir(), GQ_TRASH_DIR, NULL);
300 	}
301 
302 	return trash_dir;
303 }
304 
get_window_layouts_dir(void)305 const gchar *get_window_layouts_dir(void)
306 {
307 	static gchar *window_layouts_dir = NULL;
308 
309 	if (window_layouts_dir) return window_layouts_dir;
310 
311 	if (USE_XDG)
312 		{
313 		window_layouts_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, GQ_WINDOW_LAYOUTS_DIR, NULL);
314 		}
315 	else
316 		{
317 		window_layouts_dir = g_build_filename(get_rc_dir(), GQ_WINDOW_LAYOUTS_DIR, NULL);
318 		}
319 
320 	return window_layouts_dir;
321 }
322 
stat_utf8(const gchar * s,struct stat * st)323 gboolean stat_utf8(const gchar *s, struct stat *st)
324 {
325 	gchar *sl;
326 	gboolean ret;
327 
328 	if (!s) return FALSE;
329 	sl = path_from_utf8(s);
330 	ret = (stat(sl, st) == 0);
331 	g_free(sl);
332 
333 	return ret;
334 }
335 
lstat_utf8(const gchar * s,struct stat * st)336 gboolean lstat_utf8(const gchar *s, struct stat *st)
337 {
338 	gchar *sl;
339 	gboolean ret;
340 
341 	if (!s) return FALSE;
342 	sl = path_from_utf8(s);
343 	ret = (lstat(sl, st) == 0);
344 	g_free(sl);
345 
346 	return ret;
347 }
348 
isname(const gchar * s)349 gboolean isname(const gchar *s)
350 {
351 	struct stat st;
352 
353 	return stat_utf8(s, &st);
354 }
355 
isfile(const gchar * s)356 gboolean isfile(const gchar *s)
357 {
358 	struct stat st;
359 
360 	return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
361 }
362 
isdir(const gchar * s)363 gboolean isdir(const gchar *s)
364 {
365 	struct stat st;
366 
367 	return (stat_utf8(s, &st) && S_ISDIR(st.st_mode));
368 }
369 
islink(const gchar * s)370 gboolean islink(const gchar *s)
371 {
372 	struct stat st;
373 
374 	return (lstat_utf8(s, &st) && S_ISLNK(st.st_mode));
375 }
376 
filesize(const gchar * s)377 gint64 filesize(const gchar *s)
378 {
379 	struct stat st;
380 
381 	if (!stat_utf8(s, &st)) return 0;
382 	return st.st_size;
383 }
384 
filetime(const gchar * s)385 time_t filetime(const gchar *s)
386 {
387 	struct stat st;
388 
389 	if (!stat_utf8(s, &st)) return 0;
390 	return st.st_mtime;
391 }
392 
filetime_set(const gchar * s,time_t tval)393 gboolean filetime_set(const gchar *s, time_t tval)
394 {
395 	gboolean ret = FALSE;
396 
397 	if (tval > 0)
398 		{
399 		struct utimbuf ut;
400 		gchar *sl;
401 
402 		ut.actime = ut.modtime = tval;
403 
404 		sl = path_from_utf8(s);
405 		ret = (utime(sl, &ut) == 0);
406 		g_free(sl);
407 		}
408 
409 	return ret;
410 }
411 
is_readable_file(const gchar * s)412 gboolean is_readable_file(const gchar *s)
413 {
414 	if (!s || !s[0] || !isfile(s)) return FALSE;
415 	return access_file(s, R_OK);
416 }
417 
access_file(const gchar * s,gint mode)418 gboolean access_file(const gchar *s, gint mode)
419 {
420 	gchar *sl;
421 	gint ret;
422 
423 	if (!s || !s[0]) return FALSE;
424 
425 	sl = path_from_utf8(s);
426 	ret = (access(sl, mode) == 0);
427 	g_free(sl);
428 
429 	return ret;
430 }
431 
unlink_file(const gchar * s)432 gboolean unlink_file(const gchar *s)
433 {
434 	gchar *sl;
435 	gboolean ret;
436 
437 	if (!s) return FALSE;
438 
439 	sl = path_from_utf8(s);
440 	ret = (unlink(sl) == 0);
441 	g_free(sl);
442 
443 	return ret;
444 }
445 
symlink_utf8(const gchar * source,const gchar * target)446 gboolean symlink_utf8(const gchar *source, const gchar *target)
447 {
448 	gchar *sl;
449 	gchar *tl;
450 	gboolean ret;
451 
452 	if (!source || !target) return FALSE;
453 
454 	sl = path_from_utf8(source);
455 	tl = path_from_utf8(target);
456 
457 	ret = (symlink(sl, tl) == 0);
458 
459 	g_free(sl);
460 	g_free(tl);
461 
462 	return ret;
463 }
464 
mkdir_utf8(const gchar * s,gint mode)465 gboolean mkdir_utf8(const gchar *s, gint mode)
466 {
467 	gchar *sl;
468 	gboolean ret;
469 
470 	if (!s) return FALSE;
471 
472 	sl = path_from_utf8(s);
473 	ret = (mkdir(sl, mode) == 0);
474 	g_free(sl);
475 	return ret;
476 }
477 
rmdir_utf8(const gchar * s)478 gboolean rmdir_utf8(const gchar *s)
479 {
480 	gchar *sl;
481 	gboolean ret;
482 
483 	if (!s) return FALSE;
484 
485 	sl = path_from_utf8(s);
486 	ret = (rmdir(sl) == 0);
487 	g_free(sl);
488 
489 	return ret;
490 }
491 
copy_file_attributes(const gchar * s,const gchar * t,gint perms,gint mtime)492 gboolean copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
493 {
494 	struct stat st;
495 	gchar *sl, *tl;
496 	gboolean ret = FALSE;
497 
498 	if (!s || !t) return FALSE;
499 
500 	sl = path_from_utf8(s);
501 	tl = path_from_utf8(t);
502 
503 	if (stat(sl, &st) == 0)
504 		{
505 		struct utimbuf tb;
506 
507 		ret = TRUE;
508 
509 		/* set the dest file attributes to that of source (ignoring errors) */
510 
511 		if (perms)
512 			{
513 			ret = chown(tl, st.st_uid, st.st_gid);
514 			/* Ignores chown errors, while still doing chown
515 			   (so root still can copy files preserving ownership) */
516 			ret = TRUE;
517 			if (chmod(tl, st.st_mode) < 0) {
518                             struct stat st2;
519                             if (stat(tl, &st2) != 0 || st2.st_mode != st.st_mode) {
520                                 ret = FALSE;
521                             }
522                         }
523 			}
524 
525 		tb.actime = st.st_atime;
526 		tb.modtime = st.st_mtime;
527 		if (mtime && utime(tl, &tb) < 0) ret = FALSE;
528 		}
529 
530 	g_free(sl);
531 	g_free(tl);
532 
533 	return ret;
534 }
535 
536 /* paths are in filesystem encoding */
hard_linked(const gchar * a,const gchar * b)537 static gboolean hard_linked(const gchar *a, const gchar *b)
538 {
539 	struct stat sta;
540 	struct stat stb;
541 
542 	if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
543 
544 	return (sta.st_dev == stb.st_dev &&
545 		sta.st_ino == stb.st_ino);
546 }
547 
copy_file(const gchar * s,const gchar * t)548 gboolean copy_file(const gchar *s, const gchar *t)
549 {
550 	FILE *fi = NULL;
551 	FILE *fo = NULL;
552 	gchar *sl = NULL;
553 	gchar *tl = NULL;
554 	gchar *randname = NULL;
555 	gchar buf[16384];
556 	size_t b;
557 	gint ret = FALSE;
558 	gint fd = -1;
559 
560 	sl = path_from_utf8(s);
561 	tl = path_from_utf8(t);
562 
563 	if (hard_linked(sl, tl))
564 		{
565 		ret = TRUE;
566 		goto end;
567 		}
568 
569 	/* Do not dereference absolute symlinks, but copy them "as is".
570 	* For a relative symlink, we don't know how to properly change it when
571 	* copied/moved to another dir to keep pointing it to same target as
572 	* a relative symlink, so we turn it into absolute symlink using
573 	* realpath() instead. */
574 	struct stat st;
575 	if (lstat_utf8(sl, &st) && S_ISLNK(st.st_mode))
576 		{
577 		gchar *link_target;
578 		ssize_t i;
579 
580 		link_target = g_malloc(st.st_size + 1);
581 		i = readlink(sl, link_target, st.st_size);
582 		if (i<0)
583 			{
584 			g_free(link_target);
585 			goto orig_copy;  // try a "normal" copy
586 			}
587 		link_target[st.st_size] = '\0';
588 
589 		if (link_target[0] != G_DIR_SEPARATOR) // if it is a relative symlink
590 			{
591 			gchar *absolute;
592 
593 			gchar *lastslash = strrchr(sl, G_DIR_SEPARATOR);
594 			gint len = lastslash - sl + 1;
595 
596 			absolute = g_malloc(len + st.st_size + 1);
597 			strncpy(absolute, sl, len);
598 			strcpy(absolute + len, link_target);
599 			g_free(link_target);
600 			link_target = absolute;
601 
602 			gchar *realPath;
603 			realPath = realpath(link_target, NULL);
604 
605 			if (realPath != NULL) // successfully resolved into an absolute path
606 				{
607 				g_free(link_target);
608 				link_target = g_strdup(realPath);
609 				g_free(realPath);
610 				}
611 			else                 // could not get absolute path, got some error instead
612 				{
613 				g_free(link_target);
614 				goto orig_copy;  // so try a "normal" copy
615 				}
616 			}
617 
618 		if (stat_utf8(tl, &st)) unlink(tl); // first try to remove directory entry in destination directory if such entry exists
619 
620 		gint success = (symlink(link_target, tl) == 0);
621 		g_free(link_target);
622 
623 		if (success)
624 			{
625 			ret = TRUE;
626 			goto end;
627 			}
628 		} // if symlink did not succeed, continue on to try a copy procedure
629 	orig_copy:
630 
631 	fi = fopen(sl, "rb");
632 	if (!fi) goto end;
633 
634 	/* First we write to a temporary file, then we rename it on success,
635 	   and attributes from original file are copied */
636 	randname = g_strconcat(tl, ".tmp_XXXXXX", NULL);
637 	if (!randname) goto end;
638 
639 	fd = g_mkstemp(randname);
640 	if (fd == -1) goto end;
641 
642 	fo = fdopen(fd, "wb");
643 	if (!fo) {
644 		close(fd);
645 		goto end;
646 	}
647 
648 	while ((b = fread(buf, sizeof(gchar), sizeof(buf), fi)) && b != 0)
649 		{
650 		if (fwrite(buf, sizeof(gchar), b, fo) != b)
651 			{
652 			unlink(randname);
653 			goto end;
654 			}
655 		}
656 
657 	fclose(fi); fi = NULL;
658 	fclose(fo); fo = NULL;
659 
660 	if (rename(randname, tl) < 0) {
661 		unlink(randname);
662 		goto end;
663 	}
664 
665 	ret = copy_file_attributes(s, t, TRUE, TRUE);
666 
667 end:
668 	if (fi) fclose(fi);
669 	if (fo) fclose(fo);
670 	if (sl) g_free(sl);
671 	if (tl) g_free(tl);
672 	if (randname) g_free(randname);
673 	return ret;
674 }
675 
move_file(const gchar * s,const gchar * t)676 gboolean move_file(const gchar *s, const gchar *t)
677 {
678 	gchar *sl, *tl;
679 	gboolean ret = TRUE;
680 
681 	if (!s || !t) return FALSE;
682 
683 	sl = path_from_utf8(s);
684 	tl = path_from_utf8(t);
685 	if (rename(sl, tl) < 0)
686 		{
687 		/* this may have failed because moving a file across filesystems
688 		was attempted, so try copy and delete instead */
689 		if (copy_file(s, t))
690 			{
691 			if (unlink(sl) < 0)
692 				{
693 				/* err, now we can't delete the source file so return FALSE */
694 				ret = FALSE;
695 				}
696 			}
697 		else
698 			{
699 			ret = FALSE;
700 			}
701 		}
702 	g_free(sl);
703 	g_free(tl);
704 
705 	return ret;
706 }
707 
rename_file(const gchar * s,const gchar * t)708 gboolean rename_file(const gchar *s, const gchar *t)
709 {
710 	gchar *sl, *tl;
711 	gboolean ret;
712 
713 	if (!s || !t) return FALSE;
714 
715 	sl = path_from_utf8(s);
716 	tl = path_from_utf8(t);
717 	ret = (rename(sl, tl) == 0);
718 	g_free(sl);
719 	g_free(tl);
720 
721 	return ret;
722 }
723 
get_current_dir(void)724 gchar *get_current_dir(void)
725 {
726 	gchar *pathl;
727 	gchar *path8;
728 
729 	pathl = g_get_current_dir();
730 	path8 = path_to_utf8(pathl);
731 	g_free(pathl);
732 
733 	return path8;
734 }
735 
list_free_wrapper(void * data,void * userdata)736 void list_free_wrapper(void *data, void *userdata)
737 {
738 	g_free(data);
739 }
740 
string_list_free(GList * list)741 void string_list_free(GList *list)
742 {
743 	g_list_foreach(list, (GFunc)list_free_wrapper, NULL);
744 	g_list_free(list);
745 }
746 
string_list_copy(const GList * list)747 GList *string_list_copy(const GList *list)
748 {
749 	GList *new_list = NULL;
750 	GList *work = (GList *) list;
751 
752 	while (work)
753 		{
754 		gchar *path;
755 
756 		path = work->data;
757 		work = work->next;
758 
759 		new_list = g_list_prepend(new_list, g_strdup(path));
760 		}
761 
762 	return g_list_reverse(new_list);
763 }
764 
unique_filename(const gchar * path,const gchar * ext,const gchar * divider,gboolean pad)765 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gboolean pad)
766 {
767 	gchar *unique;
768 	gint n = 1;
769 
770 	if (!ext) ext = "";
771 	if (!divider) divider = "";
772 
773 	unique = g_strconcat(path, ext, NULL);
774 	while (isname(unique))
775 		{
776 		g_free(unique);
777 		if (pad)
778 			{
779 			unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
780 			}
781 		else
782 			{
783 			unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
784 			}
785 		n++;
786 		if (n > 999)
787 			{
788 			/* well, we tried */
789 			g_free(unique);
790 			return NULL;
791 			}
792 		}
793 
794 	return unique;
795 }
796 
unique_filename_simple(const gchar * path)797 gchar *unique_filename_simple(const gchar *path)
798 {
799 	gchar *unique;
800 	const gchar *name;
801 	const gchar *ext;
802 
803 	if (!path) return NULL;
804 
805 	name = filename_from_path(path);
806 	if (!name) return NULL;
807 
808 	ext = registered_extension_from_path(name);
809 
810 	if (!ext)
811 		{
812 		unique = unique_filename(path, NULL, "_", TRUE);
813 		}
814 	else
815 		{
816 		gchar *base;
817 
818 		base = remove_extension_from_path(path);
819 		unique = unique_filename(base, ext, "_", TRUE);
820 		g_free(base);
821 		}
822 
823 	return unique;
824 }
825 
filename_from_path(const gchar * path)826 const gchar *filename_from_path(const gchar *path)
827 {
828 	const gchar *base;
829 
830 	if (!path) return NULL;
831 
832 	base = strrchr(path, G_DIR_SEPARATOR);
833 	if (base) return base + 1;
834 
835 	return path;
836 }
837 
remove_level_from_path(const gchar * path)838 gchar *remove_level_from_path(const gchar *path)
839 {
840 	const gchar *base;
841 
842 	if (!path) return g_strdup("");
843 
844 	base = strrchr(path, G_DIR_SEPARATOR);
845 	/* Take account of a file being in the root ( / ) folder - ensure the returned value
846 	 * is at least one character long */
847 	if (base) return g_strndup(path, (strlen(path)-strlen(base)) == 0 ? 1 : (strlen(path)-strlen(base)));
848 
849 	return g_strdup("");
850 }
851 
file_extension_match(const gchar * path,const gchar * ext)852 gboolean file_extension_match(const gchar *path, const gchar *ext)
853 {
854 	gint p;
855 	gint e;
856 
857 	if (!path) return FALSE;
858 	if (!ext) return TRUE;
859 
860 	p = strlen(path);
861 	e = strlen(ext);
862 
863 	/* FIXME: utf8 */
864 	return (p > e && g_ascii_strncasecmp(path + p - e, ext, e) == 0);
865 }
866 
remove_extension_from_path(const gchar * path)867 gchar *remove_extension_from_path(const gchar *path)
868 {
869 	const gchar *reg_ext;
870 
871 	if (!path) return NULL;
872 
873 	reg_ext = registered_extension_from_path(path);
874 
875 	return g_strndup(path, strlen(path) - (reg_ext == NULL ? 0 : strlen(reg_ext)));
876 }
877 
parse_out_relatives(gchar * path)878 void parse_out_relatives(gchar *path)
879 {
880 	gint s, t;
881 
882 	if (!path) return;
883 
884 	s = t = 0;
885 
886 	while (path[s] != '\0')
887 		{
888 		if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.')
889 			{
890 			/* /. occurence, let's see more */
891 			gint p = s + 2;
892 
893 			if (path[p] == G_DIR_SEPARATOR || path[p] == '\0')
894 				{
895 				/* /./ or /., just skip this part */
896 				s = p;
897 				continue;
898 				}
899 			else if (path[p] == '.' && (path[p+1] == G_DIR_SEPARATOR || path[p+1] == '\0'))
900 				{
901 				/* /../ or /.., remove previous part, ie. /a/b/../ becomes /a/ */
902 				s = p + 1;
903 				if (t > 0) t--;
904 				while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
905 				continue;
906 				}
907 			}
908 
909 		if (s != t) path[t] = path[s];
910 		t++;
911 		s++;
912 		}
913 
914 	if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
915 	if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
916 	path[t] = '\0';
917 }
918 
file_in_path(const gchar * name)919 gboolean file_in_path(const gchar *name)
920 {
921 	gchar *path;
922 	gchar *namel;
923 	gint p, l;
924 	gboolean ret = FALSE;
925 
926 	if (!name) return FALSE;
927 	path = g_strdup(getenv("PATH"));
928 	if (!path) return FALSE;
929 	namel = path_from_utf8(name);
930 
931 	p = 0;
932 	l = strlen(path);
933 	while (p < l && !ret)
934 		{
935 		gchar *f;
936 		gint e = p;
937 		while (path[e] != ':' && path[e] != '\0') e++;
938 		path[e] = '\0';
939 		e++;
940 		f = g_build_filename(path + p, namel, NULL);
941 		if (isfile(f)) ret = TRUE;
942 		g_free(f);
943 		p = e;
944 		}
945 	g_free(namel);
946 	g_free(path);
947 
948 	return ret;
949 }
950 
recursive_mkdir_if_not_exists(const gchar * path,mode_t mode)951 gboolean recursive_mkdir_if_not_exists(const gchar *path, mode_t mode)
952 {
953 	if (!path) return FALSE;
954 
955 	if (!isdir(path))
956 		{
957 		gchar *npath = g_strdup(path);
958 		gchar *p = npath;
959 
960 		while (p[0] != '\0')
961 			{
962 			p++;
963 			if (p[0] == G_DIR_SEPARATOR || p[0] == '\0')
964 				{
965 				gboolean end = TRUE;
966 
967 				if (p[0] != '\0')
968 					{
969 					p[0] = '\0';
970 					end = FALSE;
971 					}
972 
973 				if (!isdir(npath))
974 					{
975 					DEBUG_1("creating sub dir:%s", npath);
976 					if (!mkdir_utf8(npath, mode))
977 						{
978 						log_printf("create dir failed: %s\n", npath);
979 						g_free(npath);
980 						return FALSE;
981 						}
982 					}
983 
984 				if (!end) p[0] = G_DIR_SEPARATOR;
985 				}
986 			}
987 		g_free(npath);
988 		}
989 
990 	return TRUE;
991 }
992 
993 /* does filename utf8 to filesystem encoding first */
md5_get_digest_from_file_utf8(const gchar * path,guchar digest[16])994 gboolean md5_get_digest_from_file_utf8(const gchar *path, guchar digest[16])
995 {
996 	gboolean success;
997 	gchar *pathl;
998 
999 	pathl = path_from_utf8(path);
1000 	success = md5_get_digest_from_file(pathl, digest);
1001 	g_free(pathl);
1002 
1003 	return success;
1004 }
1005 
1006 
md5_text_from_file_utf8(const gchar * path,const gchar * error_text)1007 gchar *md5_text_from_file_utf8(const gchar *path, const gchar *error_text)
1008 {
1009 	guchar digest[16];
1010 
1011 	if (!md5_get_digest_from_file_utf8(path, digest)) return g_strdup(error_text);
1012 
1013 	return md5_digest_to_text(digest);
1014 }
1015 
1016 /* Download web file
1017  */
1018 typedef struct _WebData WebData;
1019 struct _WebData
1020 {
1021 	GenericDialog *gd;
1022 	GCancellable *cancellable;
1023 	LayoutWindow *lw;
1024 
1025 	GtkWidget *progress;
1026 	GFile *tmp_g_file;
1027 	GFile *web_file;
1028 };
1029 
web_file_async_ready_cb(GObject * source_object,GAsyncResult * res,gpointer data)1030 static void web_file_async_ready_cb(GObject *source_object, GAsyncResult *res, gpointer data)
1031 {
1032 	GError *error = NULL;
1033 	WebData* web = data;
1034 	gchar *tmp_filename;
1035 
1036 	if (!g_cancellable_is_cancelled(web->cancellable))
1037 		{
1038 		generic_dialog_close(web->gd);
1039 		}
1040 
1041 	if (g_file_copy_finish(G_FILE(source_object), res, &error))
1042 		{
1043 		tmp_filename = g_file_get_parse_name(web->tmp_g_file);
1044 		g_free(tmp_filename);
1045 		layout_set_path(web->lw, g_file_get_path(web->tmp_g_file));
1046 		}
1047 	else
1048 		{
1049 		file_util_warning_dialog(_("Web file download failed"), error->message, GTK_STOCK_DIALOG_ERROR, NULL);
1050 		}
1051 
1052 	g_object_unref(web->tmp_g_file);
1053 	web->tmp_g_file = NULL;
1054 	g_object_unref(web->cancellable);
1055 	g_object_unref(web->web_file);
1056 }
1057 
web_file_progress_cb(goffset current_num_bytes,goffset total_num_bytes,gpointer data)1058 static void web_file_progress_cb(goffset current_num_bytes, goffset total_num_bytes, gpointer data)
1059 {
1060 	WebData* web = data;
1061 
1062 	if (!g_cancellable_is_cancelled(web->cancellable))
1063 		{
1064 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(web->progress), (gdouble)current_num_bytes / total_num_bytes);
1065 		}
1066 }
1067 
download_web_file_cancel_button_cb(GenericDialog * gd,gpointer data)1068 static void download_web_file_cancel_button_cb(GenericDialog *gd, gpointer data)
1069 {
1070 	WebData* web = data;
1071 
1072 	g_cancellable_cancel(web->cancellable);
1073 }
1074 
download_web_file(const gchar * text,gboolean minimized,gpointer data)1075 gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data)
1076 {
1077 	gchar *scheme;
1078 	LayoutWindow *lw = data;
1079 	gchar *tmp_dir;
1080 	GError *error = NULL;
1081 	WebData *web;
1082 	gchar *base;
1083 	gboolean ret;
1084 	gchar *message;
1085 	FileFormatClass format_class;
1086 
1087 	scheme = g_uri_parse_scheme(text);
1088 	if (g_strcmp0("http", scheme) == 0 || g_strcmp0("https", scheme) == 0)
1089 		{
1090 		format_class = filter_file_get_class(text);
1091 
1092 		if (format_class == FORMAT_CLASS_IMAGE || format_class == FORMAT_CLASS_RAWIMAGE || format_class == FORMAT_CLASS_VIDEO || format_class == FORMAT_CLASS_DOCUMENT)
1093 			{
1094 			tmp_dir = g_dir_make_tmp("geeqie_XXXXXX", &error);
1095 			if (error)
1096 				{
1097 				log_printf("Error: could not create temporary file n%s\n", error->message);
1098 				g_error_free(error);
1099 				error = NULL;
1100 				ret = TRUE;
1101 				}
1102 			else
1103 				{
1104 				web = g_new0(WebData, 1);
1105 				web->lw = lw;
1106 
1107 				web->web_file = g_file_new_for_uri(text);
1108 
1109 				base = g_strdup(g_file_get_basename(web->web_file));
1110 				web->tmp_g_file = g_file_new_for_path(g_build_filename(tmp_dir, base, NULL));
1111 
1112 				web->gd = generic_dialog_new(_("Download web file"), "download_web_file", NULL, TRUE, download_web_file_cancel_button_cb, web);
1113 
1114 				message = g_strconcat(_("Downloading "), base, NULL);
1115 				generic_dialog_add_message(web->gd, GTK_STOCK_DIALOG_INFO, message, NULL, FALSE);
1116 
1117 				web->progress = gtk_progress_bar_new();
1118 				gtk_box_pack_start(GTK_BOX(web->gd->vbox), web->progress, FALSE, FALSE, 0);
1119 				gtk_widget_show(web->progress);
1120 				if (minimized)
1121 					{
1122 					gtk_window_iconify(GTK_WINDOW(web->gd->dialog));
1123 					}
1124 
1125 				gtk_widget_show(web->gd->dialog);
1126 				web->cancellable = g_cancellable_new();
1127 				g_file_copy_async(web->web_file, web->tmp_g_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_LOW, web->cancellable, web_file_progress_cb, web, web_file_async_ready_cb, web);
1128 
1129 				g_free(base);
1130 				g_free(message);
1131 				ret = TRUE;
1132 				}
1133 			}
1134 		}
1135 	else
1136 		{
1137 		ret = FALSE;
1138 		}
1139 
1140 	g_free(scheme);
1141 	return ret;
1142 
1143 }
1144 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
1145