1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
3  *
4  * gimpconfig-path.c
5  * Copyright (C) 2001  Sven Neumann <sven@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <stdio.h>
25 #include <string.h>
26 
27 #include <gio/gio.h>
28 
29 #include "libgimpbase/gimpbase.h"
30 
31 #include "gimpconfig-error.h"
32 #include "gimpconfig-path.h"
33 
34 #include "libgimp/libgimp-intl.h"
35 
36 
37 /**
38  * SECTION: gimpconfig-path
39  * @title: GimpConfig-path
40  * @short_description: File path utilities for libgimpconfig.
41  *
42  * File path utilities for libgimpconfig.
43  **/
44 
45 
46 /**
47  * gimp_config_path_get_type:
48  *
49  * Reveals the object type
50  *
51  * Returns: the #GType for a GimpConfigPath string property
52  *
53  * Since: 2.4
54  **/
55 GType
gimp_config_path_get_type(void)56 gimp_config_path_get_type (void)
57 {
58   static GType path_type = 0;
59 
60   if (! path_type)
61     {
62       const GTypeInfo type_info = { 0, };
63 
64       path_type = g_type_register_static (G_TYPE_STRING, "GimpConfigPath",
65                                           &type_info, 0);
66     }
67 
68   return path_type;
69 }
70 
71 
72 /*
73  * GIMP_TYPE_PARAM_CONFIG_PATH
74  */
75 
76 #define GIMP_PARAM_SPEC_CONFIG_PATH(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_CONFIG_PATH, GimpParamSpecConfigPath))
77 
78 typedef struct _GimpParamSpecConfigPath GimpParamSpecConfigPath;
79 
80 struct _GimpParamSpecConfigPath
81 {
82   GParamSpecString    parent_instance;
83 
84   GimpConfigPathType  type;
85 };
86 
87 static void  gimp_param_config_path_class_init (GParamSpecClass *class);
88 
89 /**
90  * gimp_param_config_path_get_type:
91  *
92  * Reveals the object type
93  *
94  * Returns: the #GType for a directory path object
95  *
96  * Since: 2.4
97  **/
98 GType
gimp_param_config_path_get_type(void)99 gimp_param_config_path_get_type (void)
100 {
101   static GType spec_type = 0;
102 
103   if (! spec_type)
104     {
105       const GTypeInfo type_info =
106       {
107         sizeof (GParamSpecClass),
108         NULL, NULL,
109         (GClassInitFunc) gimp_param_config_path_class_init,
110         NULL, NULL,
111         sizeof (GimpParamSpecConfigPath),
112         0, NULL, NULL
113       };
114 
115       spec_type = g_type_register_static (G_TYPE_PARAM_STRING,
116                                           "GimpParamConfigPath",
117                                           &type_info, 0);
118     }
119 
120   return spec_type;
121 }
122 
123 static void
gimp_param_config_path_class_init(GParamSpecClass * class)124 gimp_param_config_path_class_init (GParamSpecClass *class)
125 {
126   class->value_type = GIMP_TYPE_CONFIG_PATH;
127 }
128 
129 /**
130  * gimp_param_spec_config_path:
131  * @name:          Canonical name of the param
132  * @nick:          Nickname of the param
133  * @blurb:         Brief description of param.
134  * @type:          a #GimpConfigPathType value.
135  * @default_value: Value to use if none is assigned.
136  * @flags:         a combination of #GParamFlags
137  *
138  * Creates a param spec to hold a filename, dir name,
139  * or list of file or dir names.
140  * See g_param_spec_internal() for more information.
141  *
142  * Returns: a newly allocated #GParamSpec instance
143  *
144  * Since: 2.4
145  **/
146 GParamSpec *
gimp_param_spec_config_path(const gchar * name,const gchar * nick,const gchar * blurb,GimpConfigPathType type,const gchar * default_value,GParamFlags flags)147 gimp_param_spec_config_path (const gchar        *name,
148                              const gchar        *nick,
149                              const gchar        *blurb,
150                              GimpConfigPathType  type,
151                              const gchar        *default_value,
152                              GParamFlags         flags)
153 {
154   GParamSpecString *pspec;
155 
156   pspec = g_param_spec_internal (GIMP_TYPE_PARAM_CONFIG_PATH,
157                                  name, nick, blurb, flags);
158 
159   pspec->default_value = g_strdup (default_value);
160 
161   GIMP_PARAM_SPEC_CONFIG_PATH (pspec)->type = type;
162 
163   return G_PARAM_SPEC (pspec);
164 }
165 
166 /**
167  * gimp_param_spec_config_path_type:
168  * @pspec:         A #GParamSpec for a path param
169  *
170  * Tells whether the path param encodes a filename,
171  * dir name, or list of file or dir names.
172  *
173  * Returns: a #GimpConfigPathType value
174  *
175  * Since: 2.4
176  **/
177 GimpConfigPathType
gimp_param_spec_config_path_type(GParamSpec * pspec)178 gimp_param_spec_config_path_type (GParamSpec *pspec)
179 {
180   g_return_val_if_fail (GIMP_IS_PARAM_SPEC_CONFIG_PATH (pspec), 0);
181 
182   return GIMP_PARAM_SPEC_CONFIG_PATH (pspec)->type;
183 }
184 
185 
186 /*
187  * GimpConfig path utilities
188  */
189 
190 static gchar        * gimp_config_path_expand_only   (const gchar  *path,
191                                                       GError      **error) G_GNUC_MALLOC;
192 static inline gchar * gimp_config_path_extract_token (const gchar **str);
193 static gchar        * gimp_config_path_unexpand_only (const gchar  *path) G_GNUC_MALLOC;
194 
195 
196 /**
197  * gimp_config_build_data_path:
198  * @name: directory name (in UTF-8 encoding)
199  *
200  * Creates a search path as it is used in the gimprc file.  The path
201  * returned by gimp_config_build_data_path() includes a directory
202  * below the user's gimp directory and one in the system-wide data
203  * directory.
204  *
205  * Note that you cannot use this path directly with gimp_path_parse().
206  * As it is in the gimprc notation, you first need to expand and
207  * recode it using gimp_config_path_expand().
208  *
209  * Returns: a newly allocated string
210  *
211  * Since: 2.4
212  **/
213 gchar *
gimp_config_build_data_path(const gchar * name)214 gimp_config_build_data_path (const gchar *name)
215 {
216   return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name,
217                       G_SEARCHPATH_SEPARATOR_S,
218                       "${gimp_data_dir}", G_DIR_SEPARATOR_S, name,
219                       NULL);
220 }
221 
222 /**
223  * gimp_config_build_plug_in_path:
224  * @name: directory name (in UTF-8 encoding)
225  *
226  * Creates a search path as it is used in the gimprc file.  The path
227  * returned by gimp_config_build_plug_in_path() includes a directory
228  * below the user's gimp directory and one in the system-wide plug-in
229  * directory.
230  *
231  * Note that you cannot use this path directly with gimp_path_parse().
232  * As it is in the gimprc notation, you first need to expand and
233  * recode it using gimp_config_path_expand().
234  *
235  * Returns: a newly allocated string
236  *
237  * Since: 2.4
238  **/
239 gchar *
gimp_config_build_plug_in_path(const gchar * name)240 gimp_config_build_plug_in_path (const gchar *name)
241 {
242   return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name,
243                       G_SEARCHPATH_SEPARATOR_S,
244                       "${gimp_plug_in_dir}", G_DIR_SEPARATOR_S, name,
245                       NULL);
246 }
247 
248 /**
249  * gimp_config_build_writable_path:
250  * @name: directory name (in UTF-8 encoding)
251  *
252  * Creates a search path as it is used in the gimprc file.  The path
253  * returned by gimp_config_build_writable_path() is just the writable
254  * parts of the search path constructed by gimp_config_build_data_path().
255  *
256  * Note that you cannot use this path directly with gimp_path_parse().
257  * As it is in the gimprc notation, you first need to expand and
258  * recode it using gimp_config_path_expand().
259  *
260  * Returns: a newly allocated string
261  *
262  * Since: 2.4
263  **/
264 gchar *
gimp_config_build_writable_path(const gchar * name)265 gimp_config_build_writable_path (const gchar *name)
266 {
267   return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name, NULL);
268 }
269 
270 
271 /**
272  * gimp_config_path_expand:
273  * @path:   a NUL-terminated string in UTF-8 encoding
274  * @recode: whether to convert to the filesystem's encoding
275  * @error:  return location for errors
276  *
277  * Paths as stored in gimprc and other config files have to be treated
278  * special.  The string may contain special identifiers such as for
279  * example ${gimp_dir} that have to be substituted before use. Also
280  * the user's filesystem may be in a different encoding than UTF-8
281  * (which is what is used for the gimprc). This function does the
282  * variable substitution for you and can also attempt to convert to
283  * the filesystem encoding.
284  *
285  * To reverse the expansion, use gimp_config_path_unexpand().
286  *
287  * Return value: a newly allocated NUL-terminated string
288  *
289  * Since: 2.4
290  **/
291 gchar *
gimp_config_path_expand(const gchar * path,gboolean recode,GError ** error)292 gimp_config_path_expand (const gchar  *path,
293                          gboolean      recode,
294                          GError      **error)
295 {
296   g_return_val_if_fail (path != NULL, NULL);
297   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
298 
299   if (recode)
300     {
301       gchar *retval;
302       gchar *expanded = gimp_config_path_expand_only (path, error);
303 
304       if (! expanded)
305         return NULL;
306 
307       retval = g_filename_from_utf8 (expanded, -1, NULL, NULL, error);
308 
309       g_free (expanded);
310 
311       return retval;
312     }
313 
314   return gimp_config_path_expand_only (path, error);
315 }
316 
317 /**
318  * gimp_config_path_expand_to_files:
319  * @path:  a NUL-terminated string in UTF-8 encoding
320  * @error: return location for errors
321  *
322  * Paths as stored in the gimprc have to be treated special. The
323  * string may contain special identifiers such as for example
324  * ${gimp_dir} that have to be substituted before use. Also the user's
325  * filesystem may be in a different encoding than UTF-8 (which is what
326  * is used for the gimprc).
327  *
328  * This function runs @path through gimp_config_path_expand() and
329  * gimp_path_parse(), then turns the filenames returned by
330  * gimp_path_parse() into GFile using g_file_new_for_path().
331  *
332  * Return value: a #GList of newly allocated #GFile objects.
333  *
334  * Since: 2.10
335  **/
336 GList *
gimp_config_path_expand_to_files(const gchar * path,GError ** error)337 gimp_config_path_expand_to_files (const gchar  *path,
338                                   GError      **error)
339 {
340   GList *files;
341   GList *list;
342   gchar *expanded;
343 
344   g_return_val_if_fail (path != NULL, NULL);
345   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
346 
347   expanded = gimp_config_path_expand (path, TRUE, error);
348 
349   if (! expanded)
350     return NULL;
351 
352   files = gimp_path_parse (expanded, 256, FALSE, NULL);
353 
354   g_free (expanded);
355 
356   for (list = files; list; list = g_list_next (list))
357     {
358       gchar *dir = list->data;
359 
360       list->data = g_file_new_for_path (dir);
361       g_free (dir);
362     }
363 
364   return files;
365 }
366 
367 /**
368  * gimp_config_path_unexpand:
369  * @path:   a NUL-terminated string
370  * @recode: whether @path is in filesystem encoding or UTF-8
371  * @error:  return location for errors
372  *
373  * The inverse operation of gimp_config_path_expand()
374  *
375  * This function takes a @path and tries to substitute the first
376  * elements by well-known special identifiers such as for example
377  * ${gimp_dir}. The unexpanded path can then be stored in gimprc and
378  * other config files.
379  *
380  * If @recode is %TRUE then @path is in local filesystem encoding,
381  * if @recode is %FALSE then @path is assumed to be UTF-8.
382  *
383  * Return value: a newly allocated NUL-terminated UTF-8 string
384  *
385  * Since: 2.10
386  **/
387 gchar *
gimp_config_path_unexpand(const gchar * path,gboolean recode,GError ** error)388 gimp_config_path_unexpand (const gchar  *path,
389                            gboolean      recode,
390                            GError      **error)
391 {
392   g_return_val_if_fail (path != NULL, NULL);
393   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
394 
395   if (recode)
396     {
397       gchar *retval;
398       gchar *utf8 = g_filename_to_utf8 (path, -1, NULL, NULL, error);
399 
400       if (! utf8)
401         return NULL;
402 
403       retval = gimp_config_path_unexpand_only (utf8);
404 
405       g_free (utf8);
406 
407       return retval;
408     }
409 
410   return gimp_config_path_unexpand_only (path);
411 }
412 
413 /**
414  * gimp_file_new_for_config_path:
415  * @path:   a NUL-terminated string in UTF-8 encoding
416  * @error:  return location for errors
417  *
418  * Expands @path using gimp_config_path_expand() and returns a #GFile
419  * for the expanded path.
420  *
421  * To reverse the expansion, use gimp_file_get_config_path().
422  *
423  * Return value: a newly allocated #GFile, or %NULL if the expansion failed.
424  *
425  * Since: 2.10
426  **/
427 GFile *
gimp_file_new_for_config_path(const gchar * path,GError ** error)428 gimp_file_new_for_config_path (const gchar  *path,
429                                GError      **error)
430 {
431   GFile *file = NULL;
432   gchar *expanded;
433 
434   g_return_val_if_fail (path != NULL, NULL);
435   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
436 
437   expanded = gimp_config_path_expand (path, TRUE, error);
438 
439   if (expanded)
440     {
441       file = g_file_new_for_path (expanded);
442       g_free (expanded);
443     }
444 
445   return file;
446 }
447 
448 /**
449  * gimp_file_get_config_path:
450  * @file:   a #GFile
451  * @error:  return location for errors
452  *
453  * Unexpands @file's path using gimp_config_path_unexpand() and
454  * returns the unexpanded path.
455  *
456  * The inverse operation of gimp_file_new_for_config_path().
457  *
458  * Return value: a newly allocated NUL-terminated UTF-8 string, or %NULL if
459  *               unexpanding failed.
460  *
461  * Since: 2.10
462  **/
463 gchar *
gimp_file_get_config_path(GFile * file,GError ** error)464 gimp_file_get_config_path (GFile   *file,
465                            GError **error)
466 {
467   gchar *unexpanded = NULL;
468   gchar *path;
469 
470   g_return_val_if_fail (G_IS_FILE (file), NULL);
471   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
472 
473   path = g_file_get_path (file);
474 
475   if (path)
476     {
477       unexpanded = gimp_config_path_unexpand (path, TRUE, error);
478       g_free (path);
479     }
480   else
481     {
482       g_set_error_literal (error, 0, 0,
483                            _("File has no path representation"));
484     }
485 
486   return unexpanded;
487 }
488 
489 
490 /*  private functions  */
491 
492 #define SUBSTS_ALLOC 4
493 
494 static gchar *
gimp_config_path_expand_only(const gchar * path,GError ** error)495 gimp_config_path_expand_only (const gchar  *path,
496                               GError      **error)
497 {
498   const gchar *home;
499   const gchar *p;
500   const gchar *s;
501   gchar       *n;
502   gchar       *token;
503   gchar       *filename = NULL;
504   gchar       *expanded = NULL;
505   gchar      **substs   = NULL;
506   guint        n_substs = 0;
507   gint         length   = 0;
508   gint         i;
509 
510   home = g_get_home_dir ();
511   if (home)
512     home = gimp_filename_to_utf8 (home);
513 
514   p = path;
515 
516   while (*p)
517     {
518       if (*p == '~' && home)
519         {
520           length += strlen (home);
521           p += 1;
522         }
523       else if ((token = gimp_config_path_extract_token (&p)) != NULL)
524         {
525           for (i = 0; i < n_substs; i++)
526             if (strcmp (substs[2*i], token) == 0)
527               break;
528 
529           if (i < n_substs)
530             {
531               s = substs[2*i+1];
532             }
533           else
534             {
535               s = NULL;
536 
537               if (strcmp (token, "gimp_dir") == 0)
538                 s = gimp_directory ();
539               else if (strcmp (token, "gimp_data_dir") == 0)
540                 s = gimp_data_directory ();
541               else if (strcmp (token, "gimp_plug_in_dir") == 0 ||
542                        strcmp (token, "gimp_plugin_dir") == 0)
543                 s = gimp_plug_in_directory ();
544               else if (strcmp (token, "gimp_sysconf_dir") == 0)
545                 s = gimp_sysconf_directory ();
546               else if (strcmp (token, "gimp_installation_dir") == 0)
547                 s = gimp_installation_directory ();
548               else if (strcmp (token, "gimp_cache_dir") == 0)
549                 s = gimp_cache_directory ();
550               else if (strcmp (token, "gimp_temp_dir") == 0)
551                 s = gimp_temp_directory ();
552 
553               if (!s)
554                 s = g_getenv (token);
555 
556 #ifdef G_OS_WIN32
557               /* The default user gimprc on Windows references
558                * ${TEMP}, but not all Windows installations have that
559                * environment variable, even if it should be kinda
560                * standard. So special-case it.
561                */
562               if (!s && strcmp (token, "TEMP") == 0)
563                 s = g_get_tmp_dir ();
564 #endif  /* G_OS_WIN32 */
565             }
566 
567           if (!s)
568             {
569               g_set_error (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE,
570                            _("Cannot expand ${%s}"), token);
571               g_free (token);
572               goto cleanup;
573             }
574 
575           if (n_substs % SUBSTS_ALLOC == 0)
576             substs = g_renew (gchar *, substs, 2 * (n_substs + SUBSTS_ALLOC));
577 
578           substs[2*n_substs]     = token;
579           substs[2*n_substs + 1] = (gchar *) gimp_filename_to_utf8 (s);
580 
581           length += strlen (substs[2*n_substs + 1]);
582 
583           n_substs++;
584         }
585       else
586         {
587           length += g_utf8_skip[(const guchar) *p];
588           p = g_utf8_next_char (p);
589         }
590     }
591 
592   if (n_substs == 0)
593     return g_strdup (path);
594 
595   expanded = g_new (gchar, length + 1);
596 
597   p = path;
598   n = expanded;
599 
600   while (*p)
601     {
602       if (*p == '~' && home)
603         {
604           *n = '\0';
605           strcat (n, home);
606           n += strlen (home);
607           p += 1;
608         }
609       else if ((token = gimp_config_path_extract_token (&p)) != NULL)
610         {
611           for (i = 0; i < n_substs; i++)
612             {
613               if (strcmp (substs[2*i], token) == 0)
614                 {
615                   s = substs[2*i+1];
616 
617                   *n = '\0';
618                   strcat (n, s);
619                   n += strlen (s);
620 
621                   break;
622                 }
623             }
624 
625           g_free (token);
626         }
627       else
628         {
629           *n++ = *p++;
630         }
631     }
632 
633   *n = '\0';
634 
635  cleanup:
636   for (i = 0; i < n_substs; i++)
637     g_free (substs[2*i]);
638 
639   g_free (substs);
640   g_free (filename);
641 
642   return expanded;
643 }
644 
645 static inline gchar *
gimp_config_path_extract_token(const gchar ** str)646 gimp_config_path_extract_token (const gchar **str)
647 {
648   const gchar *p;
649   gchar       *token;
650 
651   if (strncmp (*str, "${", 2))
652     return NULL;
653 
654   p = *str + 2;
655 
656   while (*p && (*p != '}'))
657     p = g_utf8_next_char (p);
658 
659   if (! *p)
660     return NULL;
661 
662   token = g_strndup (*str + 2, g_utf8_pointer_to_offset (*str + 2, p));
663 
664   *str = p + 1; /* after the closing bracket */
665 
666   return token;
667 }
668 
669 static gchar *
gimp_config_path_unexpand_only(const gchar * path)670 gimp_config_path_unexpand_only (const gchar *path)
671 {
672   const struct
673   {
674     const gchar *id;
675     const gchar *prefix;
676   }
677   identifiers[] =
678   {
679     { "${gimp_plug_in_dir}",      gimp_plug_in_directory () },
680     { "${gimp_data_dir}",         gimp_data_directory () },
681     { "${gimp_sysconf_dir}",      gimp_sysconf_directory () },
682     { "${gimp_installation_dir}", gimp_installation_directory () },
683     { "${gimp_cache_dir}",        gimp_cache_directory () },
684     { "${gimp_temp_dir}",         gimp_temp_directory () },
685     { "${gimp_dir}",              gimp_directory () }
686   };
687 
688   GList *files;
689   GList *list;
690   gchar *unexpanded;
691 
692   files = gimp_path_parse (path, 256, FALSE, NULL);
693 
694   for (list = files; list; list = g_list_next (list))
695     {
696       gchar *dir = list->data;
697       gint   i;
698 
699       for (i = 0; i < G_N_ELEMENTS (identifiers); i++)
700         {
701           if (g_str_has_prefix (dir, identifiers[i].prefix))
702             {
703               gchar *tmp = g_strconcat (identifiers[i].id,
704                                         dir + strlen (identifiers[i].prefix),
705                                         NULL);
706 
707               g_free (dir);
708               list->data = tmp;
709 
710               break;
711             }
712         }
713     }
714 
715   unexpanded = gimp_path_to_str (files);
716 
717   gimp_path_free (files);
718 
719   return unexpanded;
720 }
721