Merge branch '4642_fix_overflow'
[midnight-commander.git] / lib / vfs / path.c
blobc88d3c4fc9c98672bf5475a06c478ecac3e0a76e
1 /*
2 Virtual File System path handlers
4 Copyright (C) 2011-2025
5 Free Software Foundation, Inc.
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2011, 2013
9 Andrew Borodin <aborodin@vmail.ru>, 2013-2022
11 This file is part of the Midnight Commander.
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <https://www.gnu.org/licenses/>.
27 /**
28 * \file
29 * \brief Source: Virtual File System: path handlers
30 * \author Slava Zanko
31 * \date 2011
34 #include <config.h>
36 #include <errno.h>
38 #include "lib/global.h"
39 #include "lib/strutil.h"
40 #include "lib/util.h" // mc_build_filename()
41 #include "lib/serialize.h"
43 #include "vfs.h"
44 #include "utilvfs.h"
45 #include "xdirentry.h"
46 #include "path.h"
48 extern GPtrArray *vfs__classes_list;
50 /*** global variables ****************************************************************************/
52 /*** file scope macro definitions ****************************************************************/
54 /*** file scope type declarations ****************************************************************/
56 /*** forward declarations (file scope functions) *************************************************/
58 /*** file scope variables ************************************************************************/
60 /* --------------------------------------------------------------------------------------------- */
61 /*** file scope functions ************************************************************************/
62 /* --------------------------------------------------------------------------------------------- */
64 static gboolean
65 path_magic (const char *path)
67 struct stat buf;
69 return (stat (path, &buf) != 0);
72 /* --------------------------------------------------------------------------------------------- */
74 /**
75 * Splits path extracting vfs part.
77 * Splits path
78 * \verbatim /p1#op/inpath \endverbatim
79 * into
80 * \verbatim inpath,op; \endverbatim
81 * returns which vfs it is.
82 * What is left in path is p1. You still want to g_free(path), you DON'T
83 * want to free neither *inpath nor *op
86 static struct vfs_class *
87 _vfs_split_with_semi_skip_count (char *path, const char **inpath, const char **op,
88 size_t skip_count)
90 char *semi;
91 char *slash;
92 struct vfs_class *ret;
94 if (path == NULL)
95 vfs_die ("Cannot split NULL");
97 semi = strrstr_skip_count (path, "#", skip_count);
99 if ((semi == NULL) || (!path_magic (path)))
100 return NULL;
102 slash = strchr (semi, PATH_SEP);
103 *semi = '\0';
105 if (op != NULL)
106 *op = NULL;
108 if (inpath != NULL)
109 *inpath = NULL;
111 if (slash != NULL)
112 *slash = '\0';
114 ret = vfs_prefix_to_class (semi + 1);
115 if (ret != NULL)
117 if (op != NULL)
118 *op = semi + 1;
119 if (inpath != NULL)
120 *inpath = slash != NULL ? slash + 1 : NULL;
121 return ret;
124 if (slash != NULL)
125 *slash = PATH_SEP;
127 *semi = '#';
128 ret = _vfs_split_with_semi_skip_count (path, inpath, op, skip_count + 1);
129 return ret;
132 /* --------------------------------------------------------------------------------------------- */
134 * remove //, /./ and /../
136 * @return newly allocated string
139 static char *
140 vfs_canon (const char *path)
142 char *result;
144 if (path == NULL)
145 vfs_die ("Cannot canonicalize NULL");
147 if (!IS_PATH_SEP (*path))
149 // Relative to current directory
151 char *local;
153 #ifdef HAVE_CHARSET
154 if (g_str_has_prefix (path, VFS_ENCODING_PREFIX))
157 encoding prefix placed at start of string without the leading slash
158 should be autofixed by adding the leading slash
160 local = mc_build_filename (PATH_SEP_STR, path, (char *) NULL);
162 else
163 #endif
165 const char *curr_dir;
167 curr_dir = vfs_get_current_dir ();
168 local = mc_build_filename (curr_dir, path, (char *) NULL);
170 result = vfs_canon (local);
171 g_free (local);
173 else
175 // Absolute path
177 result = g_strdup (path);
178 canonicalize_pathname (result);
181 return result;
184 /* --------------------------------------------------------------------------------------------- */
185 /** Extract the hostname and username from the path
187 * Format of the path is [user@]hostname:port/remote-dir, e.g.:
189 * ftp://sunsite.unc.edu/pub/linux
190 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
191 * ftp://tsx-11.mit.edu:8192/
192 * ftp://joe@foo.edu:11321/private
193 * ftp://joe:password@foo.se
195 * @param path_element is an input string to be parsed
196 * @param path is an input string to be parsed
198 * @return g_malloc()ed url info.
199 * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
200 * is not set, then the current login name is supplied.
201 * Return value is a g_malloc()ed structure with the pathname relative to the
202 * host.
205 static void
206 vfs_path_url_split (vfs_path_element_t *path_element, const char *path)
208 char *pcopy;
209 char *colon, *at, *rest;
211 path_element->port = 0;
213 pcopy = g_strdup (path);
215 // search for any possible user
216 at = strrchr (pcopy, '@');
218 // We have a username
219 if (at == NULL)
220 rest = pcopy;
221 else
223 const char *pend;
224 char *inner_colon;
226 pend = strchr (at, '\0');
227 *at = '\0';
229 inner_colon = strchr (pcopy, ':');
230 if (inner_colon != NULL)
232 *inner_colon = '\0';
233 inner_colon++;
234 path_element->password = g_strdup (inner_colon);
237 if (*pcopy != '\0')
238 path_element->user = g_strdup (pcopy);
240 if (pend == at + 1)
241 rest = at;
242 else
243 rest = at + 1;
246 // Check if the host comes with a port spec, if so, chop it
247 if (*rest != '[')
248 colon = strchr (rest, ':');
249 else
251 colon = strchr (++rest, ']');
252 if (colon != NULL)
254 *colon = '\0';
255 colon++;
256 *colon = '\0';
257 path_element->ipv6 = TRUE;
261 if (colon != NULL)
263 *colon = '\0';
264 // cppcheck-suppress invalidscanf
265 if (sscanf (colon + 1, "%d", &path_element->port) == 1)
267 if (path_element->port <= 0 || path_element->port >= 65536)
268 path_element->port = 0;
270 else
271 while (*(++colon) != '\0')
273 switch (*colon)
275 case 'C':
276 path_element->port = 1;
277 break;
278 case 'r':
279 path_element->port = 2;
280 break;
281 default:
282 break;
286 path_element->host = g_strdup (rest);
287 g_free (pcopy);
290 /* --------------------------------------------------------------------------------------------- */
292 * get VFS class for the given name
294 * @param class_name name of class
296 * @return pointer to class structure or NULL if class not found
299 static struct vfs_class *
300 vfs_get_class_by_name (const char *class_name)
302 guint i;
304 if (class_name == NULL)
305 return NULL;
307 for (i = 0; i < vfs__classes_list->len; i++)
309 struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
310 if ((vfs->name != NULL) && (strcmp (vfs->name, class_name) == 0))
311 return vfs;
314 return NULL;
317 /* --------------------------------------------------------------------------------------------- */
319 * Check if path string contain URL-like elements
321 * @param path_str path
323 * @return TRUE if path is deprecated or FALSE otherwise
326 static gboolean
327 vfs_path_is_str_path_deprecated (const char *path_str)
329 return strstr (path_str, VFS_PATH_URL_DELIMITER) == NULL;
332 /* --------------------------------------------------------------------------------------------- */
333 /** Split path string to path elements by deprecated algorithm.
335 * @param path_str VFS-path
337 * @return pointer to newly created vfs_path_t object with filled path elements array.
340 static vfs_path_t *
341 vfs_path_from_str_deprecated_parser (char *path)
343 vfs_path_t *vpath;
344 vfs_path_element_t *element;
345 struct vfs_class *class;
346 const char *local, *op;
348 vpath = vfs_path_new (FALSE);
350 while ((class = _vfs_split_with_semi_skip_count (path, &local, &op, 0)) != NULL)
352 char *url_params;
353 element = g_new0 (vfs_path_element_t, 1);
354 element->class = class;
355 if (local == NULL)
356 local = "";
357 element->path = vfs_translate_path_n (local);
359 #ifdef HAVE_CHARSET
360 element->encoding = vfs_get_encoding (local, -1);
361 element->dir.converter =
362 (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
363 #endif
365 url_params = strchr (op, ':'); // skip VFS prefix
366 if (url_params != NULL)
368 *url_params = '\0';
369 url_params++;
370 vfs_path_url_split (element, url_params);
373 if (*op != '\0')
374 element->vfs_prefix = g_strdup (op);
376 g_array_prepend_val (vpath->path, element);
378 if (path[0] != '\0')
380 element = g_new0 (vfs_path_element_t, 1);
381 element->class = g_ptr_array_index (vfs__classes_list, 0);
382 element->path = vfs_translate_path_n (path);
384 #ifdef HAVE_CHARSET
385 element->encoding = vfs_get_encoding (path, -1);
386 element->dir.converter =
387 (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
388 #endif
389 g_array_prepend_val (vpath->path, element);
392 return vpath;
395 /* --------------------------------------------------------------------------------------------- */
396 /** Split path string to path elements by URL algorithm.
398 * @param path_str VFS-path
399 * @param flags flags for converter
401 * @return pointer to newly created vfs_path_t object with filled path elements array.
404 static vfs_path_t *
405 vfs_path_from_str_uri_parser (char *path)
407 gboolean path_is_absolute;
408 vfs_path_t *vpath;
409 vfs_path_element_t *element;
410 char *url_delimiter;
412 if (path == NULL)
413 return vfs_path_new (FALSE);
415 path_is_absolute = IS_PATH_SEP (*path);
416 #ifdef HAVE_CHARSET
417 path_is_absolute = path_is_absolute || g_str_has_prefix (path, VFS_ENCODING_PREFIX);
418 #endif
420 vpath = vfs_path_new (!path_is_absolute);
422 while ((url_delimiter = g_strrstr (path, VFS_PATH_URL_DELIMITER)) != NULL)
424 char *vfs_prefix_start;
425 char *real_vfs_prefix_start = url_delimiter;
427 while (real_vfs_prefix_start > path && !IS_PATH_SEP (*real_vfs_prefix_start))
428 real_vfs_prefix_start--;
429 vfs_prefix_start = real_vfs_prefix_start;
431 if (IS_PATH_SEP (*vfs_prefix_start))
432 vfs_prefix_start++;
434 *url_delimiter = '\0';
436 element = g_new0 (vfs_path_element_t, 1);
437 element->class = vfs_prefix_to_class (vfs_prefix_start);
438 element->vfs_prefix = g_strdup (vfs_prefix_start);
440 url_delimiter += strlen (VFS_PATH_URL_DELIMITER);
442 if (element->class != NULL && (element->class->flags & VFSF_REMOTE) != 0)
444 char *slash_pointer;
446 slash_pointer = strchr (url_delimiter, PATH_SEP);
447 if (slash_pointer == NULL)
448 element->path = g_strdup ("");
449 else
451 element->path = vfs_translate_path_n (slash_pointer + 1);
452 #ifdef HAVE_CHARSET
453 element->encoding = vfs_get_encoding (slash_pointer, -1);
454 #endif
455 *slash_pointer = '\0';
457 vfs_path_url_split (element, url_delimiter);
459 else
461 element->path = vfs_translate_path_n (url_delimiter);
462 #ifdef HAVE_CHARSET
463 element->encoding = vfs_get_encoding (url_delimiter, -1);
464 #endif
466 #ifdef HAVE_CHARSET
467 element->dir.converter =
468 (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
469 #endif
470 g_array_prepend_val (vpath->path, element);
472 if ((real_vfs_prefix_start > path && IS_PATH_SEP (*real_vfs_prefix_start))
473 || (real_vfs_prefix_start == path && !IS_PATH_SEP (*real_vfs_prefix_start)))
474 *real_vfs_prefix_start = '\0';
475 else
476 *(real_vfs_prefix_start + 1) = '\0';
479 if (path[0] != '\0')
481 element = g_new0 (vfs_path_element_t, 1);
482 element->class = g_ptr_array_index (vfs__classes_list, 0);
483 element->path = vfs_translate_path_n (path);
484 #ifdef HAVE_CHARSET
485 element->encoding = vfs_get_encoding (path, -1);
486 element->dir.converter =
487 (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
488 #endif
489 g_array_prepend_val (vpath->path, element);
492 return vpath;
495 /* --------------------------------------------------------------------------------------------- */
497 * Add element's class info to result string (such as VFS name, host, encoding etc)
498 * This function used as helper only in vfs_path_tokens_get() function
500 * @param element current path element
501 * @param ret_tokens total tikens for return
502 * @param element_tokens accumulated element-only tokens
505 static void
506 vfs_path_tokens_add_class_info (const vfs_path_element_t *element, GString *ret_tokens,
507 GString *element_tokens)
509 if (((element->class->flags & VFSF_LOCAL) == 0 || ret_tokens->len > 0)
510 && element_tokens->len > 0)
512 GString *url_str;
514 if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
515 g_string_append_c (ret_tokens, PATH_SEP);
517 g_string_append (ret_tokens, element->vfs_prefix);
518 g_string_append (ret_tokens, VFS_PATH_URL_DELIMITER);
520 url_str = vfs_path_build_url_params_str (element, TRUE);
521 if (url_str != NULL)
523 g_string_append_len (ret_tokens, url_str->str, url_str->len);
524 g_string_append_c (ret_tokens, PATH_SEP);
525 g_string_free (url_str, TRUE);
529 #ifdef HAVE_CHARSET
530 if (element->encoding != NULL)
532 if (ret_tokens->len > 0 && !IS_PATH_SEP (ret_tokens->str[ret_tokens->len - 1]))
533 g_string_append (ret_tokens, PATH_SEP_STR);
534 g_string_append (ret_tokens, VFS_ENCODING_PREFIX);
535 g_string_append (ret_tokens, element->encoding);
536 g_string_append (ret_tokens, PATH_SEP_STR);
538 #endif
540 g_string_append (ret_tokens, element_tokens->str);
543 /* --------------------------------------------------------------------------------------------- */
545 * Strip path to home dir.
546 * @param dir pointer to string contains full path
549 static char *
550 vfs_path_strip_home (const char *dir)
552 const char *home_dir = mc_config_get_home_dir ();
554 if (home_dir != NULL)
556 size_t len;
558 len = strlen (home_dir);
560 if (strncmp (dir, home_dir, len) == 0 && (IS_PATH_SEP (dir[len]) || dir[len] == '\0'))
561 return g_strdup_printf ("~%s", dir + len);
564 return g_strdup (dir);
567 /* --------------------------------------------------------------------------------------------- */
568 /*** public functions ****************************************************************************/
569 /* --------------------------------------------------------------------------------------------- */
571 #define vfs_append_from_path(appendfrom, is_relative) \
573 if ((flags & VPF_STRIP_HOME) && element_index == 0 \
574 && (element->class->flags & VFSF_LOCAL) != 0) \
576 char *stripped_home_str; \
577 stripped_home_str = vfs_path_strip_home (appendfrom); \
578 g_string_append (buffer, stripped_home_str); \
579 g_free (stripped_home_str); \
581 else \
583 if (!is_relative && !IS_PATH_SEP (*appendfrom) && *appendfrom != '\0' \
584 && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1]))) \
585 g_string_append_c (buffer, PATH_SEP); \
586 g_string_append (buffer, appendfrom); \
591 * Convert first elements_count elements from vfs_path_t to string representation with flags.
593 * @param vpath pointer to vfs_path_t object
594 * @param elements_count count of first elements for convert
595 * @param flags for converter
597 * @return pointer to newly created string.
600 char *
601 vfs_path_to_str_flags (const vfs_path_t *vpath, int elements_count, vfs_path_flag_t flags)
603 int element_index;
604 GString *buffer;
605 #ifdef HAVE_CHARSET
606 GString *recode_buffer = NULL;
607 #endif
609 if (vpath == NULL)
610 return NULL;
612 if (elements_count == 0 || elements_count > vfs_path_elements_count (vpath))
613 elements_count = vfs_path_elements_count (vpath);
615 if (elements_count < 0)
616 elements_count = vfs_path_elements_count (vpath) + elements_count;
618 buffer = g_string_new ("");
620 for (element_index = 0; element_index < elements_count; element_index++)
622 const vfs_path_element_t *element;
623 gboolean is_relative = vpath->relative && (element_index == 0);
625 element = vfs_path_get_by_index (vpath, element_index);
626 if (element->vfs_prefix != NULL)
628 GString *url_str;
630 if (!is_relative && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
631 g_string_append_c (buffer, PATH_SEP);
633 g_string_append (buffer, element->vfs_prefix);
634 g_string_append (buffer, VFS_PATH_URL_DELIMITER);
636 url_str = vfs_path_build_url_params_str (element, (flags & VPF_STRIP_PASSWORD) == 0);
637 if (url_str != NULL)
639 g_string_append_len (buffer, url_str->str, url_str->len);
640 g_string_append_c (buffer, PATH_SEP);
641 g_string_free (url_str, TRUE);
645 #ifdef HAVE_CHARSET
646 if ((flags & VPF_RECODE) == 0 && vfs_path_element_need_cleanup_converter (element))
648 if ((flags & VPF_HIDE_CHARSET) == 0)
650 if ((!is_relative)
651 && (buffer->len == 0 || !IS_PATH_SEP (buffer->str[buffer->len - 1])))
652 g_string_append (buffer, PATH_SEP_STR);
653 g_string_append (buffer, VFS_ENCODING_PREFIX);
654 g_string_append (buffer, element->encoding);
657 if (recode_buffer == NULL)
658 recode_buffer = g_string_sized_new (32);
659 else
660 g_string_set_size (recode_buffer, 0);
662 str_vfs_convert_from (element->dir.converter, element->path, recode_buffer);
663 vfs_append_from_path (recode_buffer->str, is_relative);
665 else
666 #endif
668 vfs_append_from_path (element->path, is_relative);
672 #ifdef HAVE_CHARSET
673 if (recode_buffer != NULL)
674 g_string_free (recode_buffer, TRUE);
675 #endif
677 return g_string_free (buffer, FALSE);
680 #undef vfs_append_from_path
682 /* --------------------------------------------------------------------------------------------- */
684 * Convert first elements_count elements from vfs_path_t to string representation.
686 * @param vpath pointer to vfs_path_t object
687 * @param elements_count count of first elements for convert
689 * @return pointer to newly created string.
692 char *
693 vfs_path_to_str_elements_count (const vfs_path_t *vpath, int elements_count)
695 return vfs_path_to_str_flags (vpath, elements_count, VPF_NONE);
698 /* --------------------------------------------------------------------------------------------- */
700 * Split path string to path elements with flags for change parce process.
702 * @param path_str VFS-path
703 * @param flags flags for parser
705 * @return pointer to newly created vfs_path_t object with filled path elements array.
708 vfs_path_t *
709 vfs_path_from_str_flags (const char *path_str, vfs_path_flag_t flags)
711 vfs_path_t *vpath;
712 char *path;
714 if (path_str == NULL)
715 return NULL;
717 if ((flags & VPF_NO_CANON) == 0)
718 path = vfs_canon (path_str);
719 else
720 path = g_strdup (path_str);
722 if (path == NULL)
723 return NULL;
725 if ((flags & VPF_USE_DEPRECATED_PARSER) != 0 && vfs_path_is_str_path_deprecated (path))
726 vpath = vfs_path_from_str_deprecated_parser (path);
727 else
728 vpath = vfs_path_from_str_uri_parser (path);
730 vpath->str = vfs_path_to_str_flags (vpath, 0, flags);
731 g_free (path);
733 return vpath;
736 /* --------------------------------------------------------------------------------------------- */
738 * Split path string to path elements.
740 * @param path_str VFS-path
742 * @return pointer to newly created vfs_path_t object with filled path elements array.
745 vfs_path_t *
746 vfs_path_from_str (const char *path_str)
748 return vfs_path_from_str_flags (path_str, VPF_NONE);
751 /* --------------------------------------------------------------------------------------------- */
753 * Create new vfs_path_t object.
755 * @return pointer to newly created vfs_path_t object.
758 vfs_path_t *
759 vfs_path_new (gboolean relative)
761 vfs_path_t *vpath;
763 vpath = g_new0 (vfs_path_t, 1);
764 vpath->path = g_array_new (FALSE, TRUE, sizeof (vfs_path_element_t *));
765 vpath->relative = relative;
767 return vpath;
770 /* --------------------------------------------------------------------------------------------- */
772 * Get count of path elements.
774 * @param vpath pointer to vfs_path_t object
776 * @return count of path elements.
780 vfs_path_elements_count (const vfs_path_t *vpath)
782 return (vpath != NULL && vpath->path != NULL) ? vpath->path->len : 0;
785 /* --------------------------------------------------------------------------------------------- */
787 * Add vfs_path_element_t object to end of list in vfs_path_t object
788 * @param vpath pointer to vfs_path_t object
789 * @param path_element pointer to vfs_path_element_t object
792 void
793 vfs_path_add_element (vfs_path_t *vpath, const vfs_path_element_t *path_element)
795 g_array_append_val (vpath->path, path_element);
796 g_free (vpath->str);
797 vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
800 /* --------------------------------------------------------------------------------------------- */
802 * Get one path element by index.
804 * @param vpath pointer to vfs_path_t object.
805 * May be NULL. In this case NULL is returned and errno set to 0.
806 * @param element_index element index. May have negative value (in this case count was started at
807 * the end of list). If @element_index is out of range, NULL is returned and
808 * errno set to EINVAL.
810 * @return path element
813 const vfs_path_element_t *
814 vfs_path_get_by_index (const vfs_path_t *vpath, int element_index)
816 int n;
818 if (vpath == NULL)
820 errno = 0;
821 return NULL;
824 n = vfs_path_elements_count (vpath);
826 if (element_index < 0)
827 element_index += n;
829 if (element_index < 0 || element_index > n)
831 errno = EINVAL;
832 return NULL;
835 return g_array_index (vpath->path, vfs_path_element_t *, element_index);
838 /* --------------------------------------------------------------------------------------------- */
840 * Clone one path element
842 * @param element pointer to vfs_path_element_t object
844 * @return Newly allocated path element
847 vfs_path_element_t *
848 vfs_path_element_clone (const vfs_path_element_t *element)
850 vfs_path_element_t *new_element = g_new (vfs_path_element_t, 1);
852 new_element->user = g_strdup (element->user);
853 new_element->password = g_strdup (element->password);
854 new_element->host = g_strdup (element->host);
855 new_element->ipv6 = element->ipv6;
856 new_element->port = element->port;
857 new_element->path = g_strdup (element->path);
858 new_element->class = element->class;
859 new_element->vfs_prefix = g_strdup (element->vfs_prefix);
860 #ifdef HAVE_CHARSET
861 new_element->encoding = g_strdup (element->encoding);
862 if (vfs_path_element_need_cleanup_converter (element) && element->encoding != NULL)
863 new_element->dir.converter = str_crt_conv_from (element->encoding);
864 else
865 new_element->dir.converter = element->dir.converter;
866 #endif
867 new_element->dir.info = element->dir.info;
869 return new_element;
872 /* --------------------------------------------------------------------------------------------- */
874 * Free one path element.
876 * @param element pointer to vfs_path_element_t object
880 void
881 vfs_path_element_free (vfs_path_element_t *element)
883 if (element == NULL)
884 return;
886 g_free (element->user);
887 g_free (element->password);
888 g_free (element->host);
889 g_free (element->path);
890 g_free (element->vfs_prefix);
892 #ifdef HAVE_CHARSET
893 g_free (element->encoding);
895 if (vfs_path_element_need_cleanup_converter (element))
896 str_close_conv (element->dir.converter);
897 #endif
899 g_free (element);
902 /* --------------------------------------------------------------------------------------------- */
904 * Clone path
906 * @param vpath pointer to vfs_path_t object
908 * @return Newly allocated path object
911 vfs_path_t *
912 vfs_path_clone (const vfs_path_t *vpath)
914 vfs_path_t *new_vpath;
915 int vpath_element_index;
917 if (vpath == NULL)
918 return NULL;
920 new_vpath = vfs_path_new (vpath->relative);
922 for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
923 vpath_element_index++)
925 vfs_path_element_t *path_element;
927 path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, vpath_element_index));
928 g_array_append_val (new_vpath->path, path_element);
930 new_vpath->str = g_strdup (vpath->str);
932 return new_vpath;
935 /* --------------------------------------------------------------------------------------------- */
937 * Free vfs_path_t object.
939 * @param vpath pointer to vfs_path_t object
940 * @param free_str if TRUE the string representation of vpath is freed as well
942 * @return the string representation of vpath (i.e. NULL if free_str is TRUE)
945 char *
946 vfs_path_free (vfs_path_t *vpath, gboolean free_str)
948 int vpath_element_index;
949 char *ret;
951 if (vpath == NULL)
952 return NULL;
954 for (vpath_element_index = 0; vpath_element_index < vfs_path_elements_count (vpath);
955 vpath_element_index++)
957 vfs_path_element_t *path_element;
959 path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, vpath_element_index);
960 vfs_path_element_free (path_element);
963 g_array_free (vpath->path, TRUE);
965 if (!free_str)
966 ret = vpath->str;
967 else
969 g_free (vpath->str);
970 ret = NULL;
973 g_free (vpath);
975 return ret;
978 /* --------------------------------------------------------------------------------------------- */
980 * Remove one path element by index
982 * @param vpath pointer to vfs_path_t object
983 * @param element_index element index. May have negative value (in this case count was started at
984 * the end of list).
988 void
989 vfs_path_remove_element_by_index (vfs_path_t *vpath, int element_index)
991 vfs_path_element_t *element;
993 if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 1))
994 return;
996 if (element_index < 0)
997 element_index = vfs_path_elements_count (vpath) + element_index;
999 element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, element_index);
1000 vpath->path = g_array_remove_index (vpath->path, element_index);
1001 vfs_path_element_free (element);
1002 g_free (vpath->str);
1003 vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
1006 /* --------------------------------------------------------------------------------------------- */
1007 /** Return VFS class for the given prefix */
1009 struct vfs_class *
1010 vfs_prefix_to_class (const char *prefix)
1012 guint i;
1014 // Avoid first class (localfs) that would accept any prefix
1015 for (i = 1; i < vfs__classes_list->len; i++)
1017 struct vfs_class *vfs;
1019 vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
1020 if (vfs->which != NULL)
1022 if (vfs->which (vfs, prefix) == -1)
1023 continue;
1024 return vfs;
1027 if (vfs->prefix != NULL && strncmp (prefix, vfs->prefix, strlen (vfs->prefix)) == 0)
1028 return vfs;
1031 return NULL;
1034 /* --------------------------------------------------------------------------------------------- */
1036 #ifdef HAVE_CHARSET
1038 /** get encoding after last #enc: or NULL, if part does not contain #enc:
1040 * @param path null-terminated string
1041 * @param len the maximum length of path, where #enc: should be searched
1043 * @return newly allocated string.
1046 char *
1047 vfs_get_encoding (const char *path, ssize_t len)
1049 char *semi;
1051 // try found #enc:
1052 semi = g_strrstr_len (path, len, VFS_ENCODING_PREFIX);
1053 if (semi == NULL)
1054 return NULL;
1056 if (semi == path || IS_PATH_SEP (semi[-1]))
1058 char *slash;
1060 semi += strlen (VFS_ENCODING_PREFIX); // skip "#enc:"
1061 slash = strchr (semi, PATH_SEP);
1062 if (slash != NULL)
1063 return g_strndup (semi, slash - semi);
1064 return g_strdup (semi);
1067 return vfs_get_encoding (path, semi - path);
1070 /* --------------------------------------------------------------------------------------------- */
1072 * Check if need cleanup charset converter for vfs_path_element_t
1074 * @param element part of path
1076 * @return TRUE if need cleanup converter or FALSE otherwise
1079 gboolean
1080 vfs_path_element_need_cleanup_converter (const vfs_path_element_t *element)
1082 return (element->dir.converter != str_cnv_from_term && element->dir.converter != INVALID_CONV);
1085 /* --------------------------------------------------------------------------------------------- */
1087 * Change encoding for last part (vfs_path_element_t) of vpath
1089 * @param vpath pointer to path structure
1090 * encoding name of charset
1092 * @return pointer to path structure (for use function in another functions)
1094 vfs_path_t *
1095 vfs_path_change_encoding (vfs_path_t *vpath, const char *encoding)
1097 vfs_path_element_t *path_element;
1099 path_element = (vfs_path_element_t *) vfs_path_get_by_index (vpath, -1);
1100 // don't add current encoding
1101 if ((path_element->encoding != NULL) && (strcmp (encoding, path_element->encoding) == 0))
1102 return vpath;
1104 g_free (path_element->encoding);
1105 path_element->encoding = g_strdup (encoding);
1107 if (vfs_path_element_need_cleanup_converter (path_element))
1108 str_close_conv (path_element->dir.converter);
1110 path_element->dir.converter = str_crt_conv_from (path_element->encoding);
1112 g_free (vpath->str);
1113 vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
1114 return vpath;
1117 #endif
1119 /* --------------------------------------------------------------------------------------------- */
1122 * Serialize vfs_path_t object to string
1124 * @param vpath data for serialization
1125 * @param error contain pointer to object for handle error code and message
1127 * @return serialized vpath as newly allocated string
1130 char *
1131 vfs_path_serialize (const vfs_path_t *vpath, GError **mcerror)
1133 mc_config_t *cpath;
1134 ssize_t element_index;
1135 char *ret_value;
1137 mc_return_val_if_error (mcerror, FALSE);
1139 if ((vpath == NULL) || (vfs_path_elements_count (vpath) == 0))
1141 mc_propagate_error (mcerror, 0, "%s", "vpath object is empty");
1142 return NULL;
1145 cpath = mc_config_init (NULL, FALSE);
1147 for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
1149 char groupname[BUF_TINY];
1150 const vfs_path_element_t *element;
1152 g_snprintf (groupname, sizeof (groupname), "path-element-%zd", element_index);
1153 element = vfs_path_get_by_index (vpath, element_index);
1154 // convert one element to config group
1156 mc_config_set_string_raw (cpath, groupname, "path", element->path);
1157 mc_config_set_string_raw (cpath, groupname, "class-name", element->class->name);
1158 #ifdef HAVE_CHARSET
1159 mc_config_set_string_raw (cpath, groupname, "encoding", element->encoding);
1160 #endif
1161 mc_config_set_string_raw (cpath, groupname, "vfs_prefix", element->vfs_prefix);
1163 mc_config_set_string_raw (cpath, groupname, "user", element->user);
1164 mc_config_set_string_raw (cpath, groupname, "password", element->password);
1165 mc_config_set_string_raw (cpath, groupname, "host", element->host);
1166 if (element->port != 0)
1167 mc_config_set_int (cpath, groupname, "port", element->port);
1170 ret_value = mc_serialize_config (cpath, mcerror);
1171 mc_config_deinit (cpath);
1172 return ret_value;
1175 /* --------------------------------------------------------------------------------------------- */
1177 * Deserialize string to vfs_path_t object
1179 * @param data data for serialization
1180 * @param error contain pointer to object for handle error code and message
1182 * @return newly allocated vfs_path_t object
1185 vfs_path_t *
1186 vfs_path_deserialize (const char *data, GError **mcerror)
1188 mc_config_t *cpath;
1189 size_t element_index;
1190 vfs_path_t *vpath;
1192 mc_return_val_if_error (mcerror, FALSE);
1194 cpath = mc_deserialize_config (data, mcerror);
1195 if (cpath == NULL)
1196 return NULL;
1198 vpath = vfs_path_new (FALSE);
1200 for (element_index = 0;; element_index++)
1202 struct vfs_class *eclass;
1203 vfs_path_element_t *element;
1204 char *cfg_value;
1205 char groupname[BUF_TINY];
1207 g_snprintf (groupname, sizeof (groupname), "path-element-%zu", element_index);
1208 if (!mc_config_has_group (cpath, groupname))
1209 break;
1211 cfg_value = mc_config_get_string_raw (cpath, groupname, "class-name", NULL);
1212 eclass = vfs_get_class_by_name (cfg_value);
1213 if (eclass == NULL)
1215 vfs_path_free (vpath, TRUE);
1216 g_set_error (mcerror, MC_ERROR, 0, "Unable to find VFS class by name '%s'", cfg_value);
1217 g_free (cfg_value);
1218 mc_config_deinit (cpath);
1219 return NULL;
1221 g_free (cfg_value);
1223 element = g_new0 (vfs_path_element_t, 1);
1224 element->class = eclass;
1225 element->path = mc_config_get_string_raw (cpath, groupname, "path", NULL);
1227 #ifdef HAVE_CHARSET
1228 element->encoding = mc_config_get_string_raw (cpath, groupname, "encoding", NULL);
1229 element->dir.converter =
1230 (element->encoding != NULL) ? str_crt_conv_from (element->encoding) : INVALID_CONV;
1231 #endif
1233 element->vfs_prefix = mc_config_get_string_raw (cpath, groupname, "vfs_prefix", NULL);
1235 element->user = mc_config_get_string_raw (cpath, groupname, "user", NULL);
1236 element->password = mc_config_get_string_raw (cpath, groupname, "password", NULL);
1237 element->host = mc_config_get_string_raw (cpath, groupname, "host", NULL);
1238 element->port = mc_config_get_int (cpath, groupname, "port", 0);
1240 vpath->path = g_array_append_val (vpath->path, element);
1243 mc_config_deinit (cpath);
1244 if (vfs_path_elements_count (vpath) == 0)
1246 vfs_path_free (vpath, TRUE);
1247 g_set_error (mcerror, MC_ERROR, 0, "No any path elements found");
1248 return NULL;
1250 vpath->str = vfs_path_to_str_flags (vpath, 0, VPF_NONE);
1252 return vpath;
1255 /* --------------------------------------------------------------------------------------------- */
1257 * Build vfs_path_t object from arguments.
1259 * @param first_element of path
1260 * @param ... path tokens, terminated by NULL
1262 * @return newly allocated vfs_path_t object
1265 vfs_path_t *
1266 vfs_path_build_filename (const char *first_element, ...)
1268 va_list args;
1269 char *str_path;
1270 vfs_path_t *vpath;
1272 if (first_element == NULL)
1273 return NULL;
1275 va_start (args, first_element);
1276 str_path = mc_build_filenamev (first_element, args);
1277 va_end (args);
1278 vpath = vfs_path_from_str (str_path);
1279 g_free (str_path);
1280 return vpath;
1283 /* --------------------------------------------------------------------------------------------- */
1285 * Append tokens to path object
1287 * @param vpath path object
1288 * @param first_element of path
1289 * @param ... NULL-terminated strings
1291 * @return newly allocated path object
1294 vfs_path_t *
1295 vfs_path_append_new (const vfs_path_t *vpath, const char *first_element, ...)
1297 va_list args;
1298 char *str_path;
1299 const char *result_str;
1300 vfs_path_t *ret_vpath;
1302 if (vpath == NULL || first_element == NULL)
1303 return NULL;
1305 va_start (args, first_element);
1306 str_path = mc_build_filenamev (first_element, args);
1307 va_end (args);
1309 result_str = vfs_path_as_str (vpath);
1310 ret_vpath = vfs_path_build_filename (result_str, str_path, (char *) NULL);
1311 g_free (str_path);
1313 return ret_vpath;
1316 /* --------------------------------------------------------------------------------------------- */
1319 * Append vpath_t tokens to path object
1321 * @param first_vpath vpath objects
1322 * @param ... NULL-terminated vpath objects
1324 * @return newly allocated path object
1327 vfs_path_t *
1328 vfs_path_append_vpath_new (const vfs_path_t *first_vpath, ...)
1330 va_list args;
1331 vfs_path_t *ret_vpath;
1332 const vfs_path_t *current_vpath = first_vpath;
1334 if (first_vpath == NULL)
1335 return NULL;
1337 ret_vpath = vfs_path_new (FALSE);
1339 va_start (args, first_vpath);
1342 int vindex;
1344 for (vindex = 0; vindex < vfs_path_elements_count (current_vpath); vindex++)
1346 vfs_path_element_t *path_element;
1348 path_element = vfs_path_element_clone (vfs_path_get_by_index (current_vpath, vindex));
1349 g_array_append_val (ret_vpath->path, path_element);
1351 current_vpath = va_arg (args, const vfs_path_t *);
1353 while (current_vpath != NULL);
1354 va_end (args);
1356 ret_vpath->str = vfs_path_to_str_flags (ret_vpath, 0, VPF_NONE);
1358 return ret_vpath;
1361 /* --------------------------------------------------------------------------------------------- */
1364 * get tokens count in path.
1366 * @param vpath path object
1368 * @return count of tokens
1371 size_t
1372 vfs_path_tokens_count (const vfs_path_t *vpath)
1374 size_t count_tokens = 0;
1375 int element_index;
1377 if (vpath == NULL)
1378 return 0;
1380 for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
1382 const vfs_path_element_t *element;
1383 const char *token, *prev_token;
1385 element = vfs_path_get_by_index (vpath, element_index);
1387 for (prev_token = element->path; (token = strchr (prev_token, PATH_SEP)) != NULL;
1388 prev_token = token + 1)
1390 // skip empty substring
1391 if (token != prev_token)
1392 count_tokens++;
1395 if (*prev_token != '\0')
1396 count_tokens++;
1399 return count_tokens;
1402 /* --------------------------------------------------------------------------------------------- */
1405 * Get subpath by tokens
1407 * @param vpath path object
1408 * @param start_position first token for got/ Started from 0.
1409 * If negative, then position will be relative to end of path
1410 * @param length count of tokens
1412 * @return newly allocated string with path tokens separated by slash
1415 char *
1416 vfs_path_tokens_get (const vfs_path_t *vpath, ssize_t start_position, ssize_t length)
1418 GString *ret_tokens, *element_tokens;
1419 int element_index;
1420 size_t tokens_count = vfs_path_tokens_count (vpath);
1422 if (vpath == NULL)
1423 return NULL;
1425 if (length == 0)
1426 length = tokens_count;
1428 if (length < 0)
1429 length = tokens_count + length;
1431 if (start_position < 0)
1432 start_position = (ssize_t) tokens_count + start_position;
1434 if (start_position < 0)
1435 return NULL;
1437 if (start_position >= (ssize_t) tokens_count)
1438 return NULL;
1440 if (start_position + (ssize_t) length > (ssize_t) tokens_count)
1441 length = tokens_count - start_position;
1443 ret_tokens = g_string_sized_new (32);
1444 element_tokens = g_string_sized_new (32);
1446 for (element_index = 0; element_index < vfs_path_elements_count (vpath); element_index++)
1448 const vfs_path_element_t *element;
1449 char **path_tokens, **iterator;
1451 g_string_assign (element_tokens, "");
1452 element = vfs_path_get_by_index (vpath, element_index);
1453 path_tokens = g_strsplit (element->path, PATH_SEP_STR, -1);
1455 for (iterator = path_tokens; *iterator != NULL; iterator++)
1457 if (**iterator != '\0')
1459 if (start_position == 0)
1461 if (length == 0)
1463 vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
1464 g_string_free (element_tokens, TRUE);
1465 g_strfreev (path_tokens);
1466 return g_string_free (ret_tokens, FALSE);
1468 length--;
1469 if (element_tokens->len != 0)
1470 g_string_append_c (element_tokens, PATH_SEP);
1471 g_string_append (element_tokens, *iterator);
1473 else
1474 start_position--;
1477 g_strfreev (path_tokens);
1478 vfs_path_tokens_add_class_info (element, ret_tokens, element_tokens);
1481 g_string_free (element_tokens, TRUE);
1482 return g_string_free (ret_tokens, !(start_position == 0 && length == 0));
1485 /* --------------------------------------------------------------------------------------------- */
1487 * Get subpath by tokens
1489 * @param vpath path object
1490 * @param start_position first token for got/ Started from 0.
1491 * If negative, then position will be relative to end of path
1492 * @param length count of tokens
1494 * @return newly allocated path object with path tokens separated by slash
1497 vfs_path_t *
1498 vfs_path_vtokens_get (const vfs_path_t *vpath, ssize_t start_position, ssize_t length)
1500 char *str_tokens;
1501 vfs_path_t *ret_vpath = NULL;
1503 str_tokens = vfs_path_tokens_get (vpath, start_position, length);
1504 if (str_tokens != NULL)
1506 ret_vpath = vfs_path_from_str_flags (str_tokens, VPF_NO_CANON);
1507 g_free (str_tokens);
1509 return ret_vpath;
1512 /* --------------------------------------------------------------------------------------------- */
1515 * Build URL parameters (such as user:pass @ host:port) from one path element object
1517 * @param element path element
1518 * @param keep_password TRUE or FALSE
1520 * @return newly allocated non-empty string or NULL
1523 GString *
1524 vfs_path_build_url_params_str (const vfs_path_element_t *element, gboolean keep_password)
1526 GString *buffer;
1528 if (element == NULL)
1529 return NULL;
1531 buffer = g_string_sized_new (64);
1533 if (element->user != NULL)
1534 g_string_append (buffer, element->user);
1536 if (element->password != NULL && keep_password)
1538 g_string_append_c (buffer, ':');
1539 g_string_append (buffer, element->password);
1542 if (element->host != NULL)
1544 if ((element->user != NULL) || (element->password != NULL))
1545 g_string_append_c (buffer, '@');
1546 if (element->ipv6)
1547 g_string_append_c (buffer, '[');
1548 g_string_append (buffer, element->host);
1549 if (element->ipv6)
1550 g_string_append_c (buffer, ']');
1552 if (element->port != 0)
1553 g_string_append_printf (buffer, ":%d", element->port);
1556 if (buffer->len != 0)
1557 return buffer;
1559 g_string_free (buffer, TRUE);
1560 return NULL;
1563 /* --------------------------------------------------------------------------------------------- */
1565 * Build pretty string representation of one path_element_t object
1567 * @param element path element
1569 * @return newly allocated string
1572 GString *
1573 vfs_path_element_build_pretty_path_str (const vfs_path_element_t *element)
1575 GString *url_params, *pretty_path;
1577 pretty_path = g_string_new (element->class->prefix);
1578 g_string_append (pretty_path, VFS_PATH_URL_DELIMITER);
1580 url_params = vfs_path_build_url_params_str (element, FALSE);
1581 if (url_params != NULL)
1583 g_string_append_len (pretty_path, url_params->str, url_params->len);
1584 g_string_free (url_params, TRUE);
1587 if (!IS_PATH_SEP (*element->path))
1588 g_string_append_c (pretty_path, PATH_SEP);
1590 return g_string_append (pretty_path, element->path);
1593 /* --------------------------------------------------------------------------------------------- */
1595 * Compare two path objects as strings
1597 * @param vpath1 first path object
1598 * @param vpath2 second vpath object
1600 * @return integer value like to strcmp.
1603 gboolean
1604 vfs_path_equal (const vfs_path_t *vpath1, const vfs_path_t *vpath2)
1606 const char *path1, *path2;
1607 gboolean ret_val;
1609 if (vpath1 == NULL || vpath2 == NULL)
1610 return FALSE;
1612 path1 = vfs_path_as_str (vpath1);
1613 path2 = vfs_path_as_str (vpath2);
1615 ret_val = strcmp (path1, path2) == 0;
1617 return ret_val;
1620 /* --------------------------------------------------------------------------------------------- */
1622 * Compare two path objects as strings
1624 * @param vpath1 first path object
1625 * @param vpath2 second vpath object
1626 * @param len number of first 'len' characters
1628 * @return integer value like to strcmp.
1631 gboolean
1632 vfs_path_equal_len (const vfs_path_t *vpath1, const vfs_path_t *vpath2, size_t len)
1634 const char *path1, *path2;
1635 gboolean ret_val;
1637 if (vpath1 == NULL || vpath2 == NULL)
1638 return FALSE;
1640 path1 = vfs_path_as_str (vpath1);
1641 path2 = vfs_path_as_str (vpath2);
1643 ret_val = strncmp (path1, path2, len) == 0;
1645 return ret_val;
1648 /* --------------------------------------------------------------------------------------------- */
1650 * Calculate path length in string representation
1652 * @param vpath path object
1654 * @return length of path
1657 size_t
1658 vfs_path_len (const vfs_path_t *vpath)
1660 if (vpath == NULL)
1661 return 0;
1663 return strlen (vpath->str);
1666 /* --------------------------------------------------------------------------------------------- */
1668 * Convert relative vpath object to absolute
1670 * @param vpath path object
1672 * @return absolute path object
1675 vfs_path_t *
1676 vfs_path_to_absolute (const vfs_path_t *vpath)
1678 vfs_path_t *absolute_vpath;
1679 const char *path_str;
1681 if (!vpath->relative)
1682 return vfs_path_clone (vpath);
1684 path_str = vfs_path_as_str (vpath);
1685 absolute_vpath = vfs_path_from_str (path_str);
1686 return absolute_vpath;
1689 /* --------------------------------------------------------------------------------------------- */