1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3    caja-link.c: .desktop link files.
4 
5    Copyright (C) 2001 Red Hat, Inc.
6 
7    This program is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (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 historicalied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public
18    License along with this program; if not, write to the
19    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20    Boston, MA 02110-1301, USA.
21 
22    Authors: Jonathan Blandford <jrb@redhat.com>
23             Alexander Larsson <alexl@redhat.com>
24 */
25 
26 #include <config.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include <glib/gi18n.h>
31 #include <gio/gio.h>
32 
33 #include <eel/eel-vfs-extensions.h>
34 
35 #include "caja-link.h"
36 #include "caja-directory-notify.h"
37 #include "caja-directory.h"
38 #include "caja-file-utilities.h"
39 #include "caja-file.h"
40 #include "caja-program-choosing.h"
41 #include "caja-icon-names.h"
42 
43 #define MAIN_GROUP "Desktop Entry"
44 
45 #define CAJA_LINK_GENERIC_TAG	"Link"
46 #define CAJA_LINK_TRASH_TAG 	"X-caja-trash"
47 #define CAJA_LINK_MOUNT_TAG 	"FSDevice"
48 #define CAJA_LINK_HOME_TAG 		"X-caja-home"
49 
50 static gboolean
is_link_mime_type(const char * mime_type)51 is_link_mime_type (const char *mime_type)
52 {
53     if (mime_type != NULL &&
54             (g_ascii_strcasecmp (mime_type, "application/x-mate-app-info") == 0 ||
55              g_ascii_strcasecmp (mime_type, "application/x-desktop") == 0))
56     {
57         return TRUE;
58     }
59 
60     return FALSE;
61 }
62 
63 static gboolean
is_local_file_a_link(const char * uri)64 is_local_file_a_link (const char *uri)
65 {
66     gboolean link;
67     GFile *file;
68     GFileInfo *info;
69     GError *error;
70 
71     error = NULL;
72     link = FALSE;
73 
74     file = g_file_new_for_uri (uri);
75 
76     info = g_file_query_info (file,
77                               G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
78                               0, NULL, &error);
79     if (info)
80     {
81         link = is_link_mime_type (g_file_info_get_content_type (info));
82         g_object_unref (info);
83     }
84     else
85     {
86         g_warning ("Error getting info: %s\n", error->message);
87         g_error_free (error);
88     }
89 
90     g_object_unref (file);
91 
92     return link;
93 }
94 
95 static gboolean
_g_key_file_load_from_gfile(GKeyFile * key_file,GFile * file,GKeyFileFlags flags,GError ** error)96 _g_key_file_load_from_gfile (GKeyFile *key_file,
97                              GFile *file,
98                              GKeyFileFlags flags,
99                              GError **error)
100 {
101     char *data;
102     gsize len;
103     gboolean res;
104 
105     if (!g_file_load_contents (file, NULL, &data, &len, NULL, error))
106     {
107         return FALSE;
108     }
109 
110     res = g_key_file_load_from_data (key_file, data, len, flags, error);
111 
112     g_free (data);
113 
114     return res;
115 }
116 
117 static gboolean
_g_key_file_save_to_gfile(GKeyFile * key_file,GFile * file,GError ** error)118 _g_key_file_save_to_gfile (GKeyFile *key_file,
119                            GFile *file,
120                            GError  **error)
121 {
122     char *data;
123     gsize len;
124 
125     data = g_key_file_to_data (key_file, &len, error);
126     if (data == NULL)
127     {
128         return FALSE;
129     }
130 
131     if (!g_file_replace_contents (file,
132                                   data, len,
133                                   NULL, FALSE,
134                                   G_FILE_CREATE_NONE,
135                                   NULL, NULL, error))
136     {
137         g_free (data);
138         return FALSE;
139     }
140     g_free (data);
141     return TRUE;
142 }
143 
144 
145 
146 static GKeyFile *
_g_key_file_new_from_uri(const char * uri,GKeyFileFlags flags,GError ** error)147 _g_key_file_new_from_uri (const char *uri,
148                           GKeyFileFlags flags,
149                           GError **error)
150 {
151     GKeyFile *key_file;
152     GFile *file;
153 
154     file = g_file_new_for_uri (uri);
155     key_file = g_key_file_new ();
156     if (!_g_key_file_load_from_gfile (key_file, file, flags, error))
157     {
158         g_key_file_free (key_file);
159         key_file = NULL;
160     }
161     g_object_unref (file);
162     return key_file;
163 }
164 
165 static char *
slurp_key_string(const char * uri,const char * keyname,gboolean localize)166 slurp_key_string (const char *uri,
167                   const char *keyname,
168                   gboolean    localize)
169 {
170     GKeyFile *key_file;
171     char *result;
172 
173     key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
174     if (key_file == NULL)
175     {
176         return NULL;
177     }
178 
179     if (localize)
180     {
181         result = g_key_file_get_locale_string (key_file, MAIN_GROUP, keyname, NULL, NULL);
182     }
183     else
184     {
185         result = g_key_file_get_string (key_file, MAIN_GROUP, keyname, NULL);
186     }
187     g_key_file_free (key_file);
188 
189     return result;
190 }
191 
192 gboolean
caja_link_local_create(const char * directory_uri,const char * base_name,const char * display_name,const char * image,const char * target_uri,const GdkPoint * point,int screen,gboolean unique_filename,GError ** error)193 caja_link_local_create (const char     *directory_uri,
194                         const char     *base_name,
195                         const char     *display_name,
196                         const char     *image,
197                         const char     *target_uri,
198                         const GdkPoint *point,
199                         int             screen,
200                         gboolean        unique_filename,
201                         GError        **error)
202 {
203     char *real_directory_uri;
204     char *contents;
205     GFile *file;
206     GList dummy_list;
207     CajaFileChangesQueuePosition item;
208 
209     g_return_val_if_fail (directory_uri != NULL, FALSE);
210     g_return_val_if_fail (base_name != NULL, FALSE);
211     g_return_val_if_fail (display_name != NULL, FALSE);
212     g_return_val_if_fail (target_uri != NULL, FALSE);
213 
214     if (eel_uri_is_trash (directory_uri) ||
215             eel_uri_is_search (directory_uri))
216     {
217         return FALSE;
218     }
219 
220     if (eel_uri_is_desktop (directory_uri))
221     {
222         real_directory_uri = caja_get_desktop_directory_uri ();
223     }
224     else
225     {
226         real_directory_uri = g_strdup (directory_uri);
227     }
228 
229     if (unique_filename)
230     {
231         char *uri;
232 
233         uri = caja_ensure_unique_file_name (real_directory_uri,
234                                             base_name, ".desktop");
235         if (uri == NULL)
236         {
237             g_free (real_directory_uri);
238             return FALSE;
239         }
240         file = g_file_new_for_uri (uri);
241         g_free (uri);
242     }
243     else
244     {
245         char *link_name;
246         GFile *dir;
247 
248         link_name = g_strdup_printf ("%s.desktop", base_name);
249 
250         /* replace '/' with '-', just in case */
251         g_strdelimit (link_name, "/", '-');
252 
253         dir = g_file_new_for_uri (directory_uri);
254         file = g_file_get_child (dir, link_name);
255 
256         g_free (link_name);
257         g_object_unref (dir);
258     }
259 
260     g_free (real_directory_uri);
261 
262     contents = g_strdup_printf ("[Desktop Entry]\n"
263                                 "Encoding=UTF-8\n"
264                                 "Name=%s\n"
265                                 "Type=Link\n"
266                                 "URL=%s\n"
267                                 "%s%s\n",
268                                 display_name,
269                                 target_uri,
270                                 image != NULL ? "Icon=" : "",
271                                 image != NULL ? image : "");
272 
273 
274     if (!g_file_replace_contents (file,
275                                   contents, strlen (contents),
276                                   NULL, FALSE,
277                                   G_FILE_CREATE_NONE,
278                                   NULL, NULL, error))
279     {
280         g_free (contents);
281         g_object_unref (file);
282         return FALSE;
283     }
284     g_free (contents);
285 
286     dummy_list.data = file;
287     dummy_list.next = NULL;
288     dummy_list.prev = NULL;
289     caja_directory_notify_files_added (&dummy_list);
290 
291     if (point != NULL)
292     {
293         item.location = file;
294         item.set = TRUE;
295         item.point.x = point->x;
296         item.point.y = point->y;
297         item.screen = screen;
298         dummy_list.data = &item;
299         dummy_list.next = NULL;
300         dummy_list.prev = NULL;
301 
302         caja_directory_schedule_position_set (&dummy_list);
303     }
304 
305     g_object_unref (file);
306     return TRUE;
307 }
308 
309 static const char *
get_language(void)310 get_language (void)
311 {
312     const char * const *langs_pointer;
313     int i;
314 
315     langs_pointer = g_get_language_names ();
316     for (i = 0; langs_pointer[i] != NULL; i++)
317     {
318         /* find first without encoding */
319         if (strchr (langs_pointer[i], '.') == NULL)
320         {
321             return langs_pointer[i];
322         }
323     }
324     return NULL;
325 }
326 
327 static gboolean
caja_link_local_set_key(const char * uri,const char * key,const char * value,gboolean localize)328 caja_link_local_set_key (const char *uri,
329                          const char *key,
330                          const char *value,
331                          gboolean    localize)
332 {
333     gboolean success;
334     GKeyFile *key_file;
335     GFile *file;
336 
337     file = g_file_new_for_uri (uri);
338     key_file = g_key_file_new ();
339     if (!_g_key_file_load_from_gfile (key_file, file, G_KEY_FILE_KEEP_COMMENTS, NULL))
340     {
341         g_key_file_free (key_file);
342         g_object_unref (file);
343         return FALSE;
344     }
345     if (localize)
346     {
347         g_key_file_set_locale_string (key_file,
348                                       MAIN_GROUP,
349                                       key,
350                                       get_language (),
351                                       value);
352     }
353     else
354     {
355         g_key_file_set_string (key_file, MAIN_GROUP, key, value);
356     }
357 
358 
359     success = _g_key_file_save_to_gfile (key_file,  file, NULL);
360     g_key_file_free (key_file);
361     g_object_unref (file);
362     return success;
363 }
364 
365 gboolean
caja_link_local_set_text(const char * uri,const char * text)366 caja_link_local_set_text (const char *uri,
367                           const char *text)
368 {
369     return caja_link_local_set_key (uri, "Name", text, TRUE);
370 }
371 
372 
373 gboolean
caja_link_local_set_icon(const char * uri,const char * icon)374 caja_link_local_set_icon (const char        *uri,
375                           const char        *icon)
376 {
377     return caja_link_local_set_key (uri, "Icon", icon, FALSE);
378 }
379 
380 char *
caja_link_local_get_text(const char * path)381 caja_link_local_get_text (const char *path)
382 {
383     return slurp_key_string (path, "Name", TRUE);
384 }
385 
386 char *
caja_link_local_get_additional_text(const char * path)387 caja_link_local_get_additional_text (const char *path)
388 {
389     /* The comment field of current .desktop files is often bad.
390      * It just contains a copy of the name. This is probably because the
391      * panel shows the comment field as a tooltip.
392      */
393     return NULL;
394 #ifdef THIS_IS_NOT_USED_RIGHT_NOW
395     char *type;
396     char *retval;
397 
398     if (!is_local_file_a_link (uri))
399     {
400         return NULL;
401     }
402 
403     type = slurp_key_string (path, "Type", FALSE);
404     retval = NULL;
405     if (type == NULL)
406     {
407         return NULL;
408     }
409 
410     if (strcmp (type, "Application") == 0)
411     {
412         retval = slurp_key_string (path, "Comment", TRUE);
413     }
414 
415     g_free (type);
416 
417     return retval;
418 #endif
419 }
420 
421 static char *
caja_link_get_link_uri_from_desktop(GKeyFile * key_file,const char * desktop_file_uri)422 caja_link_get_link_uri_from_desktop (GKeyFile *key_file, const char *desktop_file_uri)
423 {
424     char *type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
425     if (type == NULL)
426     {
427         return NULL;
428     }
429 
430     char *retval = NULL;
431 
432     if (strcmp (type, "URL") == 0)
433     {
434         /* Some old broken desktop files use this nonstandard feature, we need handle it though */
435         retval = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL);
436     }
437     else if ((strcmp (type, CAJA_LINK_GENERIC_TAG) == 0) ||
438              (strcmp (type, CAJA_LINK_MOUNT_TAG) == 0) ||
439              (strcmp (type, CAJA_LINK_TRASH_TAG) == 0) ||
440              (strcmp (type, CAJA_LINK_HOME_TAG) == 0))
441     {
442         retval = g_key_file_get_string (key_file, MAIN_GROUP, "URL", NULL);
443     }
444     g_free (type);
445 
446     if (retval != NULL && desktop_file_uri != NULL)
447     {
448         /* Handle local file names.
449          * Ideally, we'd be able to use
450          * g_file_parse_name(), but it does not know how to resolve
451          * relative file names, since the base directory is unknown.
452          */
453         char *scheme = g_uri_parse_scheme (retval);
454         if (scheme == NULL)
455         {
456             GFile *file = g_file_new_for_uri (desktop_file_uri);
457             GFile *parent = g_file_get_parent (file);
458             g_object_unref (file);
459 
460             if (parent != NULL)
461             {
462                 file = g_file_resolve_relative_path (parent, retval);
463                 g_free (retval);
464                 retval = g_file_get_uri (file);
465                 g_object_unref (file);
466                 g_object_unref (parent);
467             }
468         }
469         else
470         {
471             g_free (scheme);
472         }
473     }
474 
475     return retval;
476 }
477 
478 static char *
caja_link_get_link_name_from_desktop(GKeyFile * key_file)479 caja_link_get_link_name_from_desktop (GKeyFile *key_file)
480 {
481     return g_key_file_get_locale_string (key_file, MAIN_GROUP, "Name", NULL, NULL);
482 }
483 
484 static char *
caja_link_get_link_icon_from_desktop(GKeyFile * key_file)485 caja_link_get_link_icon_from_desktop (GKeyFile *key_file)
486 {
487     char *icon_uri, *icon, *type;
488 
489     icon_uri = g_key_file_get_string (key_file, MAIN_GROUP, "X-Caja-Icon", NULL);
490     if (icon_uri != NULL)
491     {
492         return icon_uri;
493     }
494 
495     icon = g_key_file_get_string (key_file, MAIN_GROUP, "Icon", NULL);
496     if (icon != NULL)
497     {
498         if (!g_path_is_absolute (icon))
499         {
500             char *p;
501 
502             /* Strip out any extension on non-filename icons. Old desktop files may have this */
503             p = strchr (icon, '.');
504             /* Only strip known icon extensions */
505             if ((p != NULL) &&
506                     ((g_ascii_strcasecmp (p, ".png") == 0)
507                      || (g_ascii_strcasecmp (p, ".svn") == 0)
508                      || (g_ascii_strcasecmp (p, ".jpg") == 0)
509                      || (g_ascii_strcasecmp (p, ".xpm") == 0)
510                      || (g_ascii_strcasecmp (p, ".bmp") == 0)
511                      || (g_ascii_strcasecmp (p, ".jpeg") == 0)))
512             {
513                 *p = 0;
514             }
515         }
516         return icon;
517     }
518 
519     type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
520     if (g_strcmp0 (type, "Application") == 0)
521     {
522         icon = g_strdup ("mate-fs-executable");
523     }
524     else if (g_strcmp0 (type, "Link") == 0)
525     {
526         icon = g_strdup ("mate-dev-symlink");
527     }
528     else if (g_strcmp0 (type, "FSDevice") == 0)
529     {
530         icon = g_strdup ("mate-dev-harddisk");
531     }
532     else if (g_strcmp0 (type, "Directory") == 0)
533     {
534         icon = g_strdup (CAJA_ICON_FOLDER);
535     }
536     else if (g_strcmp0 (type, "Service") == 0 ||
537              g_strcmp0 (type, "ServiceType") == 0)
538     {
539         icon = g_strdup ("mate-fs-web");
540     }
541     else
542     {
543         icon = g_strdup ("mate-fs-regular");
544     }
545     g_free (type);
546 
547     return icon;
548 }
549 
550 char *
caja_link_local_get_link_uri(const char * uri)551 caja_link_local_get_link_uri (const char *uri)
552 {
553     GKeyFile *key_file;
554     char *retval;
555 
556     if (!is_local_file_a_link (uri))
557     {
558         return NULL;
559     }
560 
561     key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
562     if (key_file == NULL)
563     {
564         return NULL;
565     }
566 
567     retval = caja_link_get_link_uri_from_desktop (key_file, uri);
568     g_key_file_free (key_file);
569 
570     return retval;
571 }
572 
573 static gboolean
string_array_contains(char ** array,const char * str)574 string_array_contains (char **array,
575                        const char *str)
576 {
577     char **p;
578 
579     if (!array)
580         return FALSE;
581 
582     for (p = array; *p; p++)
583         if (g_ascii_strcasecmp (*p, str) == 0)
584         {
585             return TRUE;
586         }
587 
588     return FALSE;
589 }
590 
591 void
caja_link_get_link_info_given_file_contents(const char * file_contents,int link_file_size,const char * file_uri,char ** uri,char ** name,char ** icon,gboolean * is_launcher,gboolean * is_foreign)592 caja_link_get_link_info_given_file_contents (const char  *file_contents,
593         int          link_file_size,
594         const char  *file_uri,
595         char       **uri,
596         char       **name,
597         char       **icon,
598         gboolean    *is_launcher,
599         gboolean    *is_foreign)
600 {
601     GKeyFile *key_file;
602     char *type;
603     char **only_show_in;
604     char **not_show_in;
605 
606     key_file = g_key_file_new ();
607     if (!g_key_file_load_from_data (key_file,
608                                     file_contents,
609                                     link_file_size,
610                                     G_KEY_FILE_NONE,
611                                     NULL))
612     {
613         g_key_file_free (key_file);
614         return;
615     }
616 
617     *uri = caja_link_get_link_uri_from_desktop (key_file, file_uri);
618     *name = caja_link_get_link_name_from_desktop (key_file);
619     *icon = caja_link_get_link_icon_from_desktop (key_file);
620 
621     *is_launcher = FALSE;
622     type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
623     if (g_strcmp0 (type, "Application") == 0 &&
624             g_key_file_has_key (key_file, MAIN_GROUP, "Exec", NULL))
625     {
626         *is_launcher = TRUE;
627     }
628     g_free (type);
629 
630     *is_foreign = FALSE;
631     only_show_in = g_key_file_get_string_list (key_file, MAIN_GROUP,
632                    "OnlyShowIn", NULL, NULL);
633     if (only_show_in && !string_array_contains (only_show_in, "MATE"))
634     {
635         *is_foreign = TRUE;
636     }
637     g_strfreev (only_show_in);
638 
639     not_show_in = g_key_file_get_string_list (key_file, MAIN_GROUP,
640                   "NotShowIn", NULL, NULL);
641     if (not_show_in && string_array_contains (not_show_in, "MATE"))
642     {
643         *is_foreign = TRUE;
644     }
645     g_strfreev (not_show_in);
646 
647     g_key_file_free (key_file);
648 }
649