1 /* Virtual File System path handlers
2 Copyright (C) 2011 Free Software Foundation, Inc.
5 Slava Zanko <slavazanko@gmail.com>, 2011
7 This file is part of the Midnight Commander.
9 The Midnight Commander is free software; you can redistribute it
10 and/or modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
14 The Midnight Commander is distributed in the hope that it will be
15 useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 * \brief Source: Virtual File System: path handlers
35 #include "lib/global.h"
36 #include "lib/strutil.h"
37 #include "lib/util.h" /* concat_dir_and_file */
38 #include "lib/serialize.h"
42 #include "xdirentry.h"
45 extern GPtrArray
*vfs__classes_list
;
47 /*** global variables ****************************************************************************/
49 /*** file scope macro definitions ****************************************************************/
51 /*** file scope type declarations ****************************************************************/
53 /*** file scope variables ************************************************************************/
55 /*** file scope functions ************************************************************************/
56 /* --------------------------------------------------------------------------------------------- */
59 path_magic (const char *path
)
63 return (stat (path
, &buf
) != 0);
66 /* --------------------------------------------------------------------------------------------- */
69 * Splits path extracting vfs part.
72 * \verbatim /p1#op/inpath \endverbatim
74 * \verbatim inpath,op; \endverbatim
75 * returns which vfs it is.
76 * What is left in path is p1. You still want to g_free(path), you DON'T
77 * want to free neither *inpath nor *op
80 static struct vfs_class
*
81 _vfs_split_with_semi_skip_count (char *path
, const char **inpath
, const char **op
,
86 struct vfs_class
*ret
;
89 vfs_die ("Cannot split NULL");
91 semi
= strrstr_skip_count (path
, "#", skip_count
);
93 if ((semi
== NULL
) || (!path_magic (path
)))
96 slash
= strchr (semi
, PATH_SEP
);
108 ret
= vfs_prefix_to_class (semi
+ 1);
114 *inpath
= slash
!= NULL
? slash
+ 1 : NULL
;
122 ret
= _vfs_split_with_semi_skip_count (path
, inpath
, op
, skip_count
+ 1);
126 /* --------------------------------------------------------------------------------------------- */
128 * remove //, /./ and /../
130 * @return newly allocated string
134 vfs_canon (const char *path
)
137 vfs_die ("Cannot canonicalize NULL");
139 /* Relative to current directory */
140 if (*path
!= PATH_SEP
)
142 char *local
, *result
, *curr_dir
;
144 curr_dir
= vfs_get_current_dir ();
145 local
= concat_dir_and_file (curr_dir
, path
);
148 result
= vfs_canon (local
);
154 * So we have path of following form:
155 * /p1/p2#op/.././././p3#op/p4. Good luck.
158 char *result
= g_strdup (path
);
159 canonicalize_pathname (result
);
164 /* --------------------------------------------------------------------------------------------- */
166 * Build URL parameters (such as user:pass@host:port) from one path element object
168 * @param element path element
170 * @return newly allocated string
174 vfs_path_build_url_params_str (vfs_path_element_t
* element
)
181 buffer
= g_string_new ("");
183 if (element
->user
!= NULL
)
184 g_string_append (buffer
, element
->user
);
186 if (element
->password
!= NULL
)
188 g_string_append_c (buffer
, ':');
189 g_string_append (buffer
, element
->password
);
192 if (element
->host
!= NULL
)
194 if ((element
->user
!= NULL
) || (element
->password
!= NULL
))
195 g_string_append_c (buffer
, '@');
196 g_string_append (buffer
, element
->host
);
199 if ((element
->port
) != 0 && (element
->host
!= NULL
))
201 g_string_append_c (buffer
, ':');
202 g_string_append_printf (buffer
, "%d", element
->port
);
205 return g_string_free (buffer
, FALSE
);
208 /* --------------------------------------------------------------------------------------------- */
209 /** get encoding after last #enc: or NULL, if part does not contain #enc:
213 * @return newly allocated string.
217 vfs_get_encoding (const char *path
)
223 work
= g_strdup (path
);
225 /* try found #enc: */
226 semi
= g_strrstr (work
, VFS_ENCODING_PREFIX
);
228 if (semi
!= NULL
&& (semi
== work
|| *(semi
- 1) == PATH_SEP
))
230 semi
+= strlen (VFS_ENCODING_PREFIX
); /* skip "#enc:" */
231 slash
= strchr (semi
, PATH_SEP
);
235 g_strlcpy (result
, semi
, sizeof (result
));
237 return g_strdup (result
);
246 /* --------------------------------------------------------------------------------------------- */
247 /** Extract the hostname and username from the path
249 * Format of the path is [user@]hostname:port/remote-dir, e.g.:
251 * ftp://sunsite.unc.edu/pub/linux
252 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
253 * ftp://tsx-11.mit.edu:8192/
254 * ftp://joe@foo.edu:11321/private
255 * ftp://joe:password@foo.se
257 * @param path_element is an input string to be parsed
258 * @param path is an input string to be parsed
260 * @return g_malloc()ed url info.
261 * If the user is empty, e.g. ftp://@roxanne/private, and URL_USE_ANONYMOUS
262 * is not set, then the current login name is supplied.
263 * Return value is a g_malloc()ed structure with the pathname relative to the
268 vfs_path_url_split (vfs_path_element_t
* path_element
, const char *path
)
272 char *dir
, *colon
, *inner_colon
, *at
, *rest
;
274 path_element
->port
= 0;
276 pcopy
= g_strdup (path
);
277 pend
= pcopy
+ strlen (pcopy
);
280 /* search for any possible user */
281 at
= strrchr (pcopy
, '@');
283 /* We have a username */
289 inner_colon
= strchr (pcopy
, ':');
290 if (inner_colon
!= NULL
)
294 path_element
->password
= g_strdup (inner_colon
);
298 path_element
->user
= g_strdup (pcopy
);
306 /* Check if the host comes with a port spec, if so, chop it */
308 colon
= strchr (rest
, ':');
311 colon
= strchr (++rest
, ']');
323 if (sscanf (colon
+ 1, "%d", &path_element
->port
) == 1)
325 if (path_element
->port
<= 0 || path_element
->port
>= 65536)
326 path_element
->port
= 0;
329 while (*(++colon
) != '\0')
334 path_element
->port
= 1;
337 path_element
->port
= 2;
342 path_element
->host
= g_strdup (rest
);
346 /* --------------------------------------------------------------------------------------------- */
348 * get VFS class for the given name
350 * @param class_name name of class
352 * @return pointer to class structure or NULL if class not found
355 static struct vfs_class
*
356 vfs_get_class_by_name (const char *class_name
)
360 if (class_name
== NULL
)
363 for (i
= 0; i
< vfs__classes_list
->len
; i
++)
365 struct vfs_class
*vfs
= (struct vfs_class
*) g_ptr_array_index (vfs__classes_list
, i
);
366 if ((vfs
->name
!= NULL
) && (strcmp (vfs
->name
, class_name
) == 0))
373 /* --------------------------------------------------------------------------------------------- */
375 * Check if path string contain URL-like elements
377 * @param path_str path
379 * @return TRUE if path is deprecated or FALSE otherwise
383 vfs_path_is_str_path_deprecated (const char *path_str
)
385 return strstr (path_str
, VFS_PATH_URL_DELIMITER
) == NULL
;
388 /* --------------------------------------------------------------------------------------------- */
389 /** Split path string to path elements by deprecated algorithm.
391 * @param path_str VFS-path
393 * @return pointer to newly created vfs_path_t object with filled path elements array.
397 vfs_path_from_str_deprecated_parser (char *path
)
400 vfs_path_element_t
*element
;
401 struct vfs_class
*class;
402 const char *local
, *op
;
404 vpath
= vfs_path_new ();
406 while ((class = _vfs_split_with_semi_skip_count (path
, &local
, &op
, 0)) != NULL
)
409 element
= g_new0 (vfs_path_element_t
, 1);
410 element
->class = class;
413 element
->path
= vfs_translate_path_n (local
);
415 element
->encoding
= vfs_get_encoding (local
);
416 element
->dir
.converter
= INVALID_CONV
;
418 url_params
= strchr (op
, ':'); /* skip VFS prefix */
419 if (url_params
!= NULL
)
423 vfs_path_url_split (element
, url_params
);
427 element
->vfs_prefix
= g_strdup (op
);
429 vpath
->path
= g_list_prepend (vpath
->path
, element
);
433 element
= g_new0 (vfs_path_element_t
, 1);
434 element
->class = g_ptr_array_index (vfs__classes_list
, 0);
435 element
->path
= vfs_translate_path_n (path
);
437 element
->encoding
= vfs_get_encoding (path
);
438 element
->dir
.converter
= INVALID_CONV
;
439 vpath
->path
= g_list_prepend (vpath
->path
, element
);
445 /* --------------------------------------------------------------------------------------------- */
446 /** Split path string to path elements by URL algorithm.
448 * @param path_str VFS-path
450 * @return pointer to newly created vfs_path_t object with filled path elements array.
454 vfs_path_from_str_uri_parser (char *path
)
457 vfs_path_element_t
*element
;
461 vpath
= vfs_path_new ();
463 while ((url_delimiter
= g_strrstr (path
, VFS_PATH_URL_DELIMITER
)) != NULL
)
465 char *vfs_prefix_start
;
466 char *real_vfs_prefix_start
= url_delimiter
;
468 struct vfs_s_subclass
*sub
= NULL
;
470 while (real_vfs_prefix_start
> path
&& *(real_vfs_prefix_start
) != PATH_SEP
)
471 real_vfs_prefix_start
--;
472 vfs_prefix_start
= real_vfs_prefix_start
;
474 if (*(vfs_prefix_start
) == PATH_SEP
)
475 vfs_prefix_start
+= 1;
477 *url_delimiter
= '\0';
479 element
= g_new0 (vfs_path_element_t
, 1);
480 element
->class = vfs_prefix_to_class (vfs_prefix_start
);
481 element
->vfs_prefix
= g_strdup (vfs_prefix_start
);
483 element
->dir
.converter
= INVALID_CONV
;
485 url_delimiter
+= strlen (VFS_PATH_URL_DELIMITER
);
486 sub
= VFSDATA (element
);
487 if (sub
!= NULL
&& sub
->flags
& VFS_S_REMOTE
)
489 slash_pointer
= strchr (url_delimiter
, PATH_SEP
);
490 if (slash_pointer
== NULL
)
492 element
->path
= g_strdup ("");
496 element
->path
= vfs_translate_path_n (slash_pointer
+ 1);
497 element
->encoding
= vfs_get_encoding (slash_pointer
);
498 *slash_pointer
= '\0';
500 vfs_path_url_split (element
, url_delimiter
);
504 element
->path
= vfs_translate_path_n (url_delimiter
);
505 element
->encoding
= vfs_get_encoding (url_delimiter
);
507 vpath
->path
= g_list_prepend (vpath
->path
, element
);
509 if (real_vfs_prefix_start
> path
&& *(real_vfs_prefix_start
) == PATH_SEP
)
510 *real_vfs_prefix_start
= '\0';
512 *(real_vfs_prefix_start
+ 1) = '\0';
517 element
= g_new0 (vfs_path_element_t
, 1);
518 element
->class = g_ptr_array_index (vfs__classes_list
, 0);
519 element
->path
= vfs_translate_path_n (path
);
520 element
->encoding
= vfs_get_encoding (path
);
521 element
->dir
.converter
= INVALID_CONV
;
522 vpath
->path
= g_list_prepend (vpath
->path
, element
);
528 /* --------------------------------------------------------------------------------------------- */
529 /*** public functions ****************************************************************************/
530 /* --------------------------------------------------------------------------------------------- */
532 * Convert first elements_count elements from vfs_path_t to string representation.
534 * @param vpath pointer to vfs_path_t object
535 * @param elements_count count of first elements for convert
537 * @return pointer to newly created string.
541 vfs_path_to_str_elements_count (const vfs_path_t
* vpath
, int elements_count
)
549 if (elements_count
> vfs_path_elements_count (vpath
))
550 elements_count
= vfs_path_elements_count (vpath
);
552 if (elements_count
< 0)
553 elements_count
= vfs_path_elements_count (vpath
) + elements_count
;
555 buffer
= g_string_new ("");
557 for (element_index
= 0; element_index
< elements_count
; element_index
++)
559 vfs_path_element_t
*element
= vfs_path_get_by_index (vpath
, element_index
);
561 if (element
->vfs_prefix
!= NULL
)
565 if (buffer
->str
[buffer
->len
- 1] != '/')
566 g_string_append_c (buffer
, '/');
568 g_string_append (buffer
, element
->vfs_prefix
);
569 g_string_append (buffer
, VFS_PATH_URL_DELIMITER
);
571 url_str
= vfs_path_build_url_params_str (element
);
572 if (*url_str
!= '\0')
573 g_string_append (buffer
, url_str
);
578 if (element
->encoding
!= NULL
)
580 if (buffer
->str
[buffer
->len
- 1] != PATH_SEP
)
581 g_string_append (buffer
, PATH_SEP_STR
);
582 g_string_append (buffer
, VFS_ENCODING_PREFIX
);
583 g_string_append (buffer
, element
->encoding
);
585 if ((*element
->path
!= PATH_SEP
) && (*element
->path
!= '\0')
586 && (buffer
->str
[buffer
->len
- 1] != PATH_SEP
))
587 g_string_append_c (buffer
, PATH_SEP
);
589 g_string_append (buffer
, element
->path
);
591 return g_string_free (buffer
, FALSE
);
594 /* --------------------------------------------------------------------------------------------- */
596 * Convert vfs_path_t to string representation.
598 * @param vpath pointer to vfs_path_t object
600 * @return pointer to newly created string.
603 vfs_path_to_str (const vfs_path_t
* vpath
)
605 return vfs_path_to_str_elements_count (vpath
, vfs_path_elements_count (vpath
));
608 /* --------------------------------------------------------------------------------------------- */
610 * Split path string to path elements.
612 * @param path_str VFS-path
614 * @return pointer to newly created vfs_path_t object with filled path elements array.
618 vfs_path_from_str (const char *path_str
)
623 if (path_str
== NULL
)
626 path
= vfs_canon (path_str
);
630 if (vfs_path_is_str_path_deprecated (path
))
631 vpath
= vfs_path_from_str_deprecated_parser (path
);
633 vpath
= vfs_path_from_str_uri_parser (path
);
640 /* --------------------------------------------------------------------------------------------- */
642 * Create new vfs_path_t object.
644 * @return pointer to newly created vfs_path_t object.
651 vpath
= g_new0 (vfs_path_t
, 1);
655 /* --------------------------------------------------------------------------------------------- */
657 * Get count of path elements.
659 * @param vpath pointer to vfs_path_t object
661 * @return count of path elements.
665 vfs_path_elements_count (const vfs_path_t
* vpath
)
667 return (vpath
!= NULL
&& vpath
->path
!= NULL
) ? g_list_length (vpath
->path
) : 0;
670 /* --------------------------------------------------------------------------------------------- */
672 * Get one path element by index.
674 * @param vpath pointer to vfs_path_t object
675 * @param element_index element index. May have negative value (in this case count was started at the end of list).
677 * @return path element.
681 vfs_path_get_by_index (const vfs_path_t
* vpath
, int element_index
)
683 if (element_index
< 0)
684 element_index
+= vfs_path_elements_count (vpath
);
686 if (element_index
< 0)
687 vfs_die ("vfs_path_get_by_index: incorrect index!");
689 return g_list_nth_data (vpath
->path
, element_index
);
692 /* --------------------------------------------------------------------------------------------- */
694 * Clone one path element
696 * @param element pointer to vfs_path_element_t object
698 * @return Newly allocated path element
702 vfs_path_element_clone (const vfs_path_element_t
* element
)
704 vfs_path_element_t
*new_element
= g_new0 (vfs_path_element_t
, 1);
705 memcpy (new_element
, element
, sizeof (vfs_path_element_t
));
707 new_element
->user
= g_strdup (element
->user
);
708 new_element
->password
= g_strdup (element
->password
);
709 new_element
->host
= g_strdup (element
->host
);
710 new_element
->path
= g_strdup (element
->path
);
711 new_element
->encoding
= g_strdup (element
->encoding
);
712 if (vfs_path_element_need_cleanup_converter (element
) && new_element
->encoding
!= NULL
)
713 new_element
->dir
.converter
= str_crt_conv_from (new_element
->encoding
);
714 new_element
->vfs_prefix
= g_strdup (element
->vfs_prefix
);
719 /* --------------------------------------------------------------------------------------------- */
721 * Free one path element.
723 * @param element pointer to vfs_path_element_t object
728 vfs_path_element_free (vfs_path_element_t
* element
)
733 g_free (element
->user
);
734 g_free (element
->password
);
735 g_free (element
->host
);
736 g_free (element
->path
);
737 g_free (element
->encoding
);
738 g_free (element
->vfs_prefix
);
740 if (vfs_path_element_need_cleanup_converter (element
))
742 str_close_conv (element
->dir
.converter
);
748 /* --------------------------------------------------------------------------------------------- */
752 * @param vpath pointer to vfs_path_t object
754 * @return Newly allocated path object
758 vfs_path_clone (const vfs_path_t
* vpath
)
760 vfs_path_t
*new_vpath
;
761 int vpath_element_index
;
765 new_vpath
= vfs_path_new ();
766 for (vpath_element_index
= 0; vpath_element_index
< vfs_path_elements_count (vpath
);
767 vpath_element_index
++)
770 g_list_append (new_vpath
->path
,
771 vfs_path_element_clone (vfs_path_get_by_index
772 (vpath
, vpath_element_index
)));
778 /* --------------------------------------------------------------------------------------------- */
780 * Free vfs_path_t object.
782 * @param vpath pointer to vfs_path_t object
787 vfs_path_free (vfs_path_t
* path
)
791 g_list_foreach (path
->path
, (GFunc
) vfs_path_element_free
, NULL
);
792 g_list_free (path
->path
);
796 /* --------------------------------------------------------------------------------------------- */
798 * Remove one path element by index
800 * @param vpath pointer to vfs_path_t object
801 * @param element_index element index. May have negative value (in this case count was started at the end of list).
806 vfs_path_remove_element_by_index (vfs_path_t
* vpath
, int element_index
)
808 vfs_path_element_t
*element
;
810 if ((vpath
== NULL
) || (vfs_path_elements_count (vpath
) == 1))
813 if (element_index
< 0)
814 element_index
= vfs_path_elements_count (vpath
) + element_index
;
816 element
= g_list_nth_data (vpath
->path
, element_index
);
817 vpath
->path
= g_list_remove (vpath
->path
, element
);
818 vfs_path_element_free (element
);
821 /* --------------------------------------------------------------------------------------------- */
822 /** Return VFS class for the given prefix */
825 vfs_prefix_to_class (const char *prefix
)
829 /* Avoid first class (localfs) that would accept any prefix */
830 for (i
= 1; i
< vfs__classes_list
->len
; i
++)
832 struct vfs_class
*vfs
= (struct vfs_class
*) g_ptr_array_index (vfs__classes_list
, i
);
833 if (vfs
->which
!= NULL
)
835 if (vfs
->which (vfs
, prefix
) == -1)
840 if (vfs
->prefix
!= NULL
&& strncmp (prefix
, vfs
->prefix
, strlen (vfs
->prefix
)) == 0)
847 /* --------------------------------------------------------------------------------------------- */
849 * Check if need cleanup charset converter for vfs_path_element_t
851 * @param element part of path
853 * @return TRUE if need cleanup converter or FALSE otherwise
857 vfs_path_element_need_cleanup_converter (const vfs_path_element_t
* element
)
859 return (element
->dir
.converter
!= str_cnv_from_term
&& element
->dir
.converter
!= INVALID_CONV
);
862 /* --------------------------------------------------------------------------------------------- */
864 * Serialize vfs_path_t object to string
866 * @param vpath data for serialization
867 * @param error contain pointer to object for handle error code and message
869 * @return serialized vpath as newly allocated string
873 vfs_path_serialize (const vfs_path_t
* vpath
, GError
** error
)
875 mc_config_t
*cpath
= mc_config_init (NULL
);
876 ssize_t element_index
;
879 if ((vpath
== NULL
) || (vfs_path_elements_count (vpath
) == 0))
881 g_set_error (error
, MC_ERROR
, -1, "vpath object is empty");
885 for (element_index
= 0; element_index
< vfs_path_elements_count (vpath
); element_index
++)
887 char *groupname
= g_strdup_printf ("path-element-%zd", element_index
);
888 vfs_path_element_t
*element
= vfs_path_get_by_index (vpath
, element_index
);
890 /* convert one element to config group */
892 mc_config_set_string_raw (cpath
, groupname
, "path", element
->path
);
893 mc_config_set_string_raw (cpath
, groupname
, "class-name", element
->class->name
);
894 mc_config_set_string_raw (cpath
, groupname
, "encoding", element
->encoding
);
896 mc_config_set_string_raw (cpath
, groupname
, "vfs_prefix", element
->vfs_prefix
);
898 mc_config_set_string_raw (cpath
, groupname
, "user", element
->user
);
899 mc_config_set_string_raw (cpath
, groupname
, "password", element
->password
);
900 mc_config_set_string_raw (cpath
, groupname
, "host", element
->host
);
901 if (element
->port
!= 0)
902 mc_config_set_int (cpath
, groupname
, "port", element
->port
);
907 ret_value
= mc_serialize_config (cpath
, error
);
908 mc_config_deinit (cpath
);
912 /* --------------------------------------------------------------------------------------------- */
914 * Deserialize string to vfs_path_t object
916 * @param data data for serialization
917 * @param error contain pointer to object for handle error code and message
919 * @return newly allocated vfs_path_t object
923 vfs_path_deserialize (const char *data
, GError
** error
)
925 mc_config_t
*cpath
= mc_deserialize_config (data
, error
);
926 size_t element_index
= 0;
932 vpath
= vfs_path_new ();
936 vfs_path_element_t
*element
;
940 groupname
= g_strdup_printf ("path-element-%zd", element_index
);
941 if (!mc_config_has_group (cpath
, groupname
))
947 element
= g_new0 (vfs_path_element_t
, 1);
948 element
->dir
.converter
= INVALID_CONV
;
950 cfg_value
= mc_config_get_string_raw (cpath
, groupname
, "class-name", NULL
);
951 element
->class = vfs_get_class_by_name (cfg_value
);
952 if (element
->class == NULL
)
955 vfs_path_free (vpath
);
956 g_set_error (error
, MC_ERROR
, -1, "Unable to find VFS class by name '%s'", cfg_value
);
958 mc_config_deinit (cpath
);
963 element
->path
= mc_config_get_string_raw (cpath
, groupname
, "path", NULL
);
964 element
->encoding
= mc_config_get_string_raw (cpath
, groupname
, "encoding", NULL
);
966 element
->vfs_prefix
= mc_config_get_string_raw (cpath
, groupname
, "vfs_prefix", NULL
);
968 element
->user
= mc_config_get_string_raw (cpath
, groupname
, "user", NULL
);
969 element
->password
= mc_config_get_string_raw (cpath
, groupname
, "password", NULL
);
970 element
->host
= mc_config_get_string_raw (cpath
, groupname
, "host", NULL
);
971 element
->port
= mc_config_get_int (cpath
, groupname
, "port", 0);
973 vpath
->path
= g_list_append (vpath
->path
, element
);
979 mc_config_deinit (cpath
);
980 if (vfs_path_elements_count (vpath
) == 0)
982 vfs_path_free (vpath
);
983 g_set_error (error
, MC_ERROR
, -1, "No any path elements found");
990 /* --------------------------------------------------------------------------------------------- */