1 /*
2  * gnome-thumbnail.c: Utilities for handling thumbnails
3  *
4  * Copyright (C) 2002 Red Hat, Inc.
5  * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
6  *
7  * This file is part of the Gnome Library.
8  *
9  * The Gnome Library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library 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.
13  *
14  * The Gnome Library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
21  * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  * Author: Alexander Larsson <alexl@redhat.com>
25  */
26 
27 #include <config.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <dirent.h>
34 #include <time.h>
35 #include <math.h>
36 #include <string.h>
37 #include <glib.h>
38 #include <stdio.h>
39 #include <errno.h>
40 
41 #define GDK_PIXBUF_ENABLE_BACKEND
42 #include <gdk-pixbuf/gdk-pixbuf.h>
43 
44 #define GNOME_DESKTOP_USE_UNSTABLE_API
45 #include "gnome-desktop-thumbnail.h"
46 #include "gnome-desktop-utils.h"
47 #include <glib/gstdio.h>
48 
49 #define SECONDS_BETWEEN_STATS 10
50 
51 struct _GnomeDesktopThumbnailFactoryPrivate {
52   GnomeDesktopThumbnailSize size;
53 
54   GMutex lock;
55 
56   GList *thumbnailers;
57   GHashTable *mime_types_map;
58   GList *monitors;
59 
60   GSettings *settings;
61   gboolean loaded : 1;
62   gboolean disabled : 1;
63   gchar **disabled_types;
64 
65   gboolean permissions_problem;
66   gboolean needs_chown;
67   uid_t real_uid;
68   gid_t real_gid;
69 };
70 
71 static const char *appname = "gnome-thumbnail-factory";
72 
73 static void gnome_desktop_thumbnail_factory_init          (GnomeDesktopThumbnailFactory      *factory);
74 static void gnome_desktop_thumbnail_factory_class_init    (GnomeDesktopThumbnailFactoryClass *class);
75 
76 G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
77 	       gnome_desktop_thumbnail_factory,
78 	       G_TYPE_OBJECT)
79 #define parent_class gnome_desktop_thumbnail_factory_parent_class
80 
81 #define GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE(object) \
82   (G_TYPE_INSTANCE_GET_PRIVATE ((object), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryPrivate))
83 
84 typedef struct {
85     gint width;
86     gint height;
87     gint input_width;
88     gint input_height;
89     gboolean preserve_aspect_ratio;
90 } SizePrepareContext;
91 
92 #define LOAD_BUFFER_SIZE 4096
93 
94 #define THUMBNAILER_ENTRY_GROUP "Thumbnailer Entry"
95 #define THUMBNAILER_EXTENSION   ".thumbnailer"
96 
97 typedef struct {
98     volatile gint ref_count;
99 
100     gchar *path;
101 
102     gchar  *try_exec;
103     gchar  *command;
104     gchar **mime_types;
105 } Thumbnailer;
106 
107 static Thumbnailer *
thumbnailer_ref(Thumbnailer * thumb)108 thumbnailer_ref (Thumbnailer *thumb)
109 {
110   g_return_val_if_fail (thumb != NULL, NULL);
111   g_return_val_if_fail (thumb->ref_count > 0, NULL);
112 
113   g_atomic_int_inc (&thumb->ref_count);
114   return thumb;
115 }
116 
117 static void
thumbnailer_unref(Thumbnailer * thumb)118 thumbnailer_unref (Thumbnailer *thumb)
119 {
120   g_return_if_fail (thumb != NULL);
121   g_return_if_fail (thumb->ref_count > 0);
122 
123   if (g_atomic_int_dec_and_test (&thumb->ref_count))
124     {
125       g_free (thumb->path);
126       g_free (thumb->try_exec);
127       g_free (thumb->command);
128       g_strfreev (thumb->mime_types);
129 
130       g_slice_free (Thumbnailer, thumb);
131     }
132 }
133 
134 static Thumbnailer *
thumbnailer_load(Thumbnailer * thumb)135 thumbnailer_load (Thumbnailer *thumb)
136 {
137   GKeyFile *key_file;
138   GError *error = NULL;
139 
140   key_file = g_key_file_new ();
141   if (!g_key_file_load_from_file (key_file, thumb->path, 0, &error))
142     {
143       g_warning ("Failed to load thumbnailer from \"%s\": %s\n", thumb->path, error->message);
144       g_error_free (error);
145       thumbnailer_unref (thumb);
146       g_key_file_free (key_file);
147 
148       return NULL;
149     }
150 
151   if (!g_key_file_has_group (key_file, THUMBNAILER_ENTRY_GROUP))
152     {
153       g_warning ("Invalid thumbnailer: missing group \"%s\"\n", THUMBNAILER_ENTRY_GROUP);
154       thumbnailer_unref (thumb);
155       g_key_file_free (key_file);
156 
157       return NULL;
158     }
159 
160   thumb->command = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "Exec", NULL);
161   if (!thumb->command)
162     {
163       g_warning ("Invalid thumbnailer: missing Exec key\n");
164       thumbnailer_unref (thumb);
165       g_key_file_free (key_file);
166 
167       return NULL;
168     }
169 
170   thumb->mime_types = g_key_file_get_string_list (key_file, THUMBNAILER_ENTRY_GROUP, "MimeType", NULL, NULL);
171   if (!thumb->mime_types)
172     {
173       g_warning ("Invalid thumbnailer: missing MimeType key\n");
174       thumbnailer_unref (thumb);
175       g_key_file_free (key_file);
176 
177       return NULL;
178     }
179 
180   thumb->try_exec = g_key_file_get_string (key_file, THUMBNAILER_ENTRY_GROUP, "TryExec", NULL);
181 
182   g_key_file_free (key_file);
183 
184   return thumb;
185 }
186 
187 static Thumbnailer *
thumbnailer_reload(Thumbnailer * thumb)188 thumbnailer_reload (Thumbnailer *thumb)
189 {
190   g_return_val_if_fail (thumb != NULL, NULL);
191 
192   g_free (thumb->command);
193   thumb->command = NULL;
194   g_strfreev (thumb->mime_types);
195   thumb->mime_types = NULL;
196   g_free (thumb->try_exec);
197   thumb->try_exec = NULL;
198 
199   return thumbnailer_load (thumb);
200 }
201 
202 static Thumbnailer *
thumbnailer_new(const gchar * path)203 thumbnailer_new (const gchar *path)
204 {
205   Thumbnailer *thumb;
206 
207   thumb = g_slice_new0 (Thumbnailer);
208   thumb->ref_count = 1;
209   thumb->path = g_strdup (path);
210 
211   return thumbnailer_load (thumb);
212 }
213 
214 static gboolean
thumbnailer_try_exec(Thumbnailer * thumb)215 thumbnailer_try_exec (Thumbnailer *thumb)
216 {
217   gchar *path;
218   gboolean retval;
219 
220   if (G_UNLIKELY (!thumb))
221     return FALSE;
222 
223   /* TryExec is optinal, but Exec isn't, so we assume
224    * the thumbnailer can be run when TryExec is not present
225    */
226   if (!thumb->try_exec)
227     return TRUE;
228 
229   path = g_find_program_in_path (thumb->try_exec);
230   retval = path != NULL;
231   g_free (path);
232 
233   return retval;
234 }
235 
236 static gpointer
init_thumbnailers_dirs(gpointer data)237 init_thumbnailers_dirs (gpointer data)
238 {
239   const gchar * const *data_dirs;
240   gchar **thumbs_dirs;
241   guint i, length;
242 
243   data_dirs = g_get_system_data_dirs ();
244   length = g_strv_length ((char **) data_dirs);
245 
246   thumbs_dirs = g_new (gchar *, length + 2);
247   thumbs_dirs[0] = g_build_filename (g_get_user_data_dir (), "thumbnailers", NULL);
248   for (i = 0; i < length; i++)
249     thumbs_dirs[i + 1] = g_build_filename (data_dirs[i], "thumbnailers", NULL);
250   thumbs_dirs[length + 1] = NULL;
251 
252   return thumbs_dirs;
253 }
254 
255 static const gchar * const *
get_thumbnailers_dirs(void)256 get_thumbnailers_dirs (void)
257 {
258   static GOnce once_init = G_ONCE_INIT;
259   return g_once (&once_init, init_thumbnailers_dirs, NULL);
260 }
261 
262 static void
size_prepared_cb(GdkPixbufLoader * loader,int width,int height,gpointer data)263 size_prepared_cb (GdkPixbufLoader *loader,
264 		  int              width,
265 		  int              height,
266 		  gpointer         data)
267 {
268   SizePrepareContext *info = data;
269 
270   g_return_if_fail (width > 0 && height > 0);
271 
272   info->input_width = width;
273   info->input_height = height;
274 
275   if (width < info->width && height < info->height) return;
276 
277   if (info->preserve_aspect_ratio &&
278       (info->width > 0 || info->height > 0)) {
279     if (info->width < 0)
280       {
281 	width = width * (double)info->height/(double)height;
282 	height = info->height;
283       }
284     else if (info->height < 0)
285       {
286 	height = height * (double)info->width/(double)width;
287 	width = info->width;
288       }
289     else if ((double)height * (double)info->width >
290 	     (double)width * (double)info->height) {
291       width = 0.5 + (double)width * (double)info->height / (double)height;
292       height = info->height;
293     } else {
294       height = 0.5 + (double)height * (double)info->width / (double)width;
295       width = info->width;
296     }
297   } else {
298     if (info->width > 0)
299       width = info->width;
300     if (info->height > 0)
301       height = info->height;
302   }
303 
304   gdk_pixbuf_loader_set_size (loader, width, height);
305 }
306 
307 static GdkPixbufLoader *
create_loader(GFile * file,const guchar * data,gsize size)308 create_loader (GFile        *file,
309                const guchar *data,
310                gsize         size)
311 {
312   GdkPixbufLoader *loader;
313   GError *error = NULL;
314   char *mime_type;
315   char *filename;
316 
317   loader = NULL;
318 
319   /* need to specify the type here because the gdk_pixbuf_loader_write
320      doesn't have access to the filename in order to correct detect
321      the image type. */
322   filename = g_file_get_basename (file);
323   mime_type = g_content_type_guess (filename, data, size, NULL);
324   g_free (filename);
325 
326   if (mime_type != NULL) {
327     loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, &error);
328   }
329 
330   if (loader == NULL) {
331     g_warning ("Unable to create loader for mime type %s: %s", mime_type, error->message);
332     g_clear_error (&error);
333     loader = gdk_pixbuf_loader_new ();
334   }
335   g_free (mime_type);
336 
337   return loader;
338 }
339 
340 static GdkPixbuf *
_gdk_pixbuf_new_from_uri_at_scale(const char * uri,gint width,gint height,gboolean preserve_aspect_ratio)341 _gdk_pixbuf_new_from_uri_at_scale (const char *uri,
342 				   gint        width,
343 				   gint        height,
344 				   gboolean    preserve_aspect_ratio)
345 {
346     gboolean result;
347     guchar buffer[LOAD_BUFFER_SIZE];
348     gsize bytes_read;
349     GdkPixbufLoader *loader = NULL;
350     GdkPixbuf *pixbuf;
351     GdkPixbufAnimation *animation;
352     GdkPixbufAnimationIter *iter;
353     gboolean has_frame;
354     SizePrepareContext info;
355     GFile *file;
356     GFileInfo *file_info;
357     GInputStream *input_stream;
358     GError *error = NULL;
359 
360     g_return_val_if_fail (uri != NULL, NULL);
361 
362     input_stream = NULL;
363 
364     file = g_file_new_for_uri (uri);
365 
366     /* First see if we can get an input stream via preview::icon  */
367     file_info = g_file_query_info (file,
368                                    G_FILE_ATTRIBUTE_PREVIEW_ICON,
369                                    G_FILE_QUERY_INFO_NONE,
370                                    NULL,  /* GCancellable */
371                                    NULL); /* return location for GError */
372     if (file_info != NULL) {
373         GObject *object;
374 
375         object = g_file_info_get_attribute_object (file_info,
376                                                    G_FILE_ATTRIBUTE_PREVIEW_ICON);
377         if (object != NULL && G_IS_LOADABLE_ICON (object)) {
378             input_stream = g_loadable_icon_load (G_LOADABLE_ICON (object),
379                                                  0,     /* size */
380                                                  NULL,  /* return location for type */
381                                                  NULL,  /* GCancellable */
382                                                  NULL); /* return location for GError */
383         }
384         g_object_unref (file_info);
385     }
386 
387     if (input_stream == NULL) {
388         input_stream = G_INPUT_STREAM (g_file_read (file, NULL, &error));
389         if (input_stream == NULL) {
390             if (error != NULL) {
391                 g_warning ("Unable to create an input stream for %s: %s", uri, error->message);
392                 g_clear_error (&error);
393             }
394 	    g_object_unref (file);
395             return NULL;
396         }
397     }
398 
399     has_frame = FALSE;
400 
401     result = FALSE;
402     while (!has_frame) {
403 
404 	bytes_read = g_input_stream_read (input_stream,
405 					  buffer,
406 					  sizeof (buffer),
407 					  NULL,
408 					  &error);
409         if (error != NULL) {
410             g_warning ("Error reading from %s: %s", uri, error->message);
411             g_clear_error (&error);
412         }
413 	if (bytes_read == -1) {
414 	    break;
415 	}
416 	result = TRUE;
417 	if (bytes_read == 0) {
418 	    break;
419 	}
420 
421         if (loader == NULL) {
422             loader = create_loader (file, buffer, bytes_read);
423             if (1 <= width || 1 <= height) {
424               info.width = width;
425               info.height = height;
426               info.input_width = info.input_height = 0;
427               info.preserve_aspect_ratio = preserve_aspect_ratio;
428               g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
429             }
430             g_assert (loader != NULL);
431         }
432 
433 	if (!gdk_pixbuf_loader_write (loader,
434 				      (unsigned char *)buffer,
435 				      bytes_read,
436 				      &error)) {
437             g_warning ("Error creating thumbnail for %s: %s", uri, error->message);
438             g_clear_error (&error);
439 	    result = FALSE;
440 	    break;
441 	}
442 
443 	animation = gdk_pixbuf_loader_get_animation (loader);
444 	if (animation) {
445 		iter = gdk_pixbuf_animation_get_iter (animation, NULL);
446 		if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
447 			has_frame = TRUE;
448 		}
449 		g_object_unref (iter);
450 	}
451     }
452 
453     if (!GDK_IS_PIXBUF_LOADER (loader)) {
454         g_input_stream_close (input_stream, NULL, NULL);
455         g_object_unref (input_stream);
456         g_object_unref (file);
457         return NULL;
458     }
459 
460     gdk_pixbuf_loader_close (loader, NULL);
461 
462     if (!result) {
463 	g_object_unref (G_OBJECT (loader));
464 	g_input_stream_close (input_stream, NULL, NULL);
465 	g_object_unref (input_stream);
466 	g_object_unref (file);
467 	return NULL;
468     }
469 
470     g_input_stream_close (input_stream, NULL, NULL);
471     g_object_unref (input_stream);
472     g_object_unref (file);
473 
474     pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
475     if (pixbuf != NULL) {
476 	g_object_ref (G_OBJECT (pixbuf));
477 	g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
478 			   GINT_TO_POINTER (info.input_width));
479 	g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
480 			   GINT_TO_POINTER (info.input_height));
481     }
482     g_object_unref (G_OBJECT (loader));
483 
484     return pixbuf;
485 }
486 
487 static void
gnome_desktop_thumbnail_factory_finalize(GObject * object)488 gnome_desktop_thumbnail_factory_finalize (GObject *object)
489 {
490   GnomeDesktopThumbnailFactory *factory;
491   GnomeDesktopThumbnailFactoryPrivate *priv;
492 
493   factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);
494 
495   priv = factory->priv;
496 
497   g_clear_pointer (&priv->thumbnailers, thumbnailer_unref);
498   g_clear_pointer (&priv->mime_types_map, g_hash_table_destroy);
499 
500   if (priv->monitors)
501     {
502       g_list_free_full (priv->monitors, (GDestroyNotify)g_object_unref);
503       priv->monitors = NULL;
504     }
505 
506   g_mutex_clear (&priv->lock);
507 
508   g_clear_pointer (&priv->disabled_types, g_strfreev);
509   g_clear_object (&priv->settings);
510 
511   if (G_OBJECT_CLASS (parent_class)->finalize)
512     (* G_OBJECT_CLASS (parent_class)->finalize) (object);
513 }
514 
515 /* These should be called with the lock held */
516 static void
gnome_desktop_thumbnail_factory_register_mime_types(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)517 gnome_desktop_thumbnail_factory_register_mime_types (GnomeDesktopThumbnailFactory *factory,
518                                                      Thumbnailer                  *thumb)
519 {
520   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
521   gint i;
522 
523   for (i = 0; thumb->mime_types[i]; i++)
524     {
525       if (!g_hash_table_lookup (priv->mime_types_map, thumb->mime_types[i]))
526         g_hash_table_insert (priv->mime_types_map,
527                              g_strdup (thumb->mime_types[i]),
528                              thumbnailer_ref (thumb));
529     }
530 }
531 
532 static void
gnome_desktop_thumbnail_factory_add_thumbnailer(GnomeDesktopThumbnailFactory * factory,Thumbnailer * thumb)533 gnome_desktop_thumbnail_factory_add_thumbnailer (GnomeDesktopThumbnailFactory *factory,
534                                                  Thumbnailer                  *thumb)
535 {
536   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
537 
538   gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
539   priv->thumbnailers = g_list_prepend (priv->thumbnailers, thumb);
540 }
541 
542 static gboolean
gnome_desktop_thumbnail_factory_is_disabled(GnomeDesktopThumbnailFactory * factory,const gchar * mime_type)543 gnome_desktop_thumbnail_factory_is_disabled (GnomeDesktopThumbnailFactory *factory,
544                                              const gchar                  *mime_type)
545 {
546   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
547   guint i;
548 
549   if (priv->disabled)
550     return TRUE;
551 
552   if (!priv->disabled_types)
553     return FALSE;
554 
555   for (i = 0; priv->disabled_types[i]; i++)
556     {
557       if (g_strcmp0 (priv->disabled_types[i], mime_type) == 0)
558         return TRUE;
559     }
560 
561   return FALSE;
562 }
563 
564 static gboolean
remove_thumbnailer_from_mime_type_map(gchar * key,Thumbnailer * value,gchar * path)565 remove_thumbnailer_from_mime_type_map (gchar       *key,
566                                        Thumbnailer *value,
567                                        gchar       *path)
568 {
569   return (strcmp (value->path, path) == 0);
570 }
571 
572 
573 static void
update_or_create_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)574 update_or_create_thumbnailer (GnomeDesktopThumbnailFactory *factory,
575                               const gchar                  *path)
576 {
577   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
578   GList *l;
579   Thumbnailer *thumb;
580   gboolean found = FALSE;
581 
582   g_mutex_lock (&priv->lock);
583 
584   for (l = priv->thumbnailers; l && !found; l = g_list_next (l))
585     {
586       thumb = (Thumbnailer *)l->data;
587 
588       if (strcmp (thumb->path, path) == 0)
589         {
590           found = TRUE;
591 
592           /* First remove the mime_types associated to this thumbnailer */
593           g_hash_table_foreach_remove (priv->mime_types_map,
594                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
595                                        (gpointer)path);
596           if (!thumbnailer_reload (thumb))
597               priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
598           else
599               gnome_desktop_thumbnail_factory_register_mime_types (factory, thumb);
600         }
601     }
602 
603   if (!found)
604     {
605       thumb = thumbnailer_new (path);
606       if (thumb)
607         gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
608     }
609 
610   g_mutex_unlock (&priv->lock);
611 }
612 
613 static void
remove_thumbnailer(GnomeDesktopThumbnailFactory * factory,const gchar * path)614 remove_thumbnailer (GnomeDesktopThumbnailFactory *factory,
615                     const gchar                  *path)
616 {
617   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
618   GList *l;
619   Thumbnailer *thumb;
620 
621   g_mutex_lock (&priv->lock);
622 
623   for (l = priv->thumbnailers; l; l = g_list_next (l))
624     {
625       thumb = (Thumbnailer *)l->data;
626 
627       if (strcmp (thumb->path, path) == 0)
628         {
629           priv->thumbnailers = g_list_delete_link (priv->thumbnailers, l);
630           g_hash_table_foreach_remove (priv->mime_types_map,
631                                        (GHRFunc)remove_thumbnailer_from_mime_type_map,
632                                        (gpointer)path);
633           thumbnailer_unref (thumb);
634 
635           break;
636         }
637     }
638 
639   g_mutex_unlock (&priv->lock);
640 }
641 
642 static void
thumbnailers_directory_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,GnomeDesktopThumbnailFactory * factory)643 thumbnailers_directory_changed (GFileMonitor                 *monitor,
644                                 GFile                        *file,
645                                 GFile                        *other_file,
646                                 GFileMonitorEvent             event_type,
647                                 GnomeDesktopThumbnailFactory *factory)
648 {
649   gchar *path;
650 
651   switch (event_type)
652     {
653     case G_FILE_MONITOR_EVENT_CREATED:
654     case G_FILE_MONITOR_EVENT_CHANGED:
655     case G_FILE_MONITOR_EVENT_DELETED:
656       path = g_file_get_path (file);
657       if (!g_str_has_suffix (path, THUMBNAILER_EXTENSION))
658         {
659           g_free (path);
660           return;
661         }
662 
663       if (event_type == G_FILE_MONITOR_EVENT_DELETED)
664         remove_thumbnailer (factory, path);
665       else
666         update_or_create_thumbnailer (factory, path);
667 
668       g_free (path);
669       break;
670     default:
671       break;
672     }
673 }
674 
675 static void
gnome_desktop_thumbnail_factory_load_thumbnailers(GnomeDesktopThumbnailFactory * factory)676 gnome_desktop_thumbnail_factory_load_thumbnailers (GnomeDesktopThumbnailFactory *factory)
677 {
678   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
679   const gchar * const *dirs;
680   guint i;
681 
682   if (priv->loaded)
683     return;
684 
685   dirs = get_thumbnailers_dirs ();
686   for (i = 0; dirs[i]; i++)
687     {
688       const gchar *path = dirs[i];
689       GDir *dir;
690       GFile *dir_file;
691       GFileMonitor *monitor;
692       const gchar *dirent;
693 
694       dir = g_dir_open (path, 0, NULL);
695       if (!dir)
696         continue;
697 
698       /* Monitor dir */
699       dir_file = g_file_new_for_path (path);
700       monitor = g_file_monitor_directory (dir_file,
701                                           G_FILE_MONITOR_NONE,
702                                           NULL, NULL);
703       if (monitor)
704         {
705           g_signal_connect (monitor, "changed",
706                             G_CALLBACK (thumbnailers_directory_changed),
707                             factory);
708           priv->monitors = g_list_prepend (priv->monitors, monitor);
709         }
710       g_object_unref (dir_file);
711 
712       while ((dirent = g_dir_read_name (dir)))
713         {
714           Thumbnailer *thumb;
715           gchar       *filename;
716 
717           if (!g_str_has_suffix (dirent, THUMBNAILER_EXTENSION))
718             continue;
719 
720           filename = g_build_filename (path, dirent, NULL);
721           thumb = thumbnailer_new (filename);
722           g_free (filename);
723 
724           if (thumb)
725             gnome_desktop_thumbnail_factory_add_thumbnailer (factory, thumb);
726         }
727 
728       g_dir_close (dir);
729     }
730 
731   priv->loaded = TRUE;
732 }
733 
734 static void
external_thumbnailers_disabled_all_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)735 external_thumbnailers_disabled_all_changed_cb (GSettings                    *settings,
736                                                const gchar                  *key,
737                                                GnomeDesktopThumbnailFactory *factory)
738 {
739   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
740 
741   g_mutex_lock (&priv->lock);
742 
743   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
744   if (priv->disabled)
745     {
746       g_strfreev (priv->disabled_types);
747       priv->disabled_types = NULL;
748     }
749   else
750     {
751       priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
752       gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
753     }
754 
755   g_mutex_unlock (&priv->lock);
756 }
757 
758 static void
external_thumbnailers_disabled_changed_cb(GSettings * settings,const gchar * key,GnomeDesktopThumbnailFactory * factory)759 external_thumbnailers_disabled_changed_cb (GSettings                    *settings,
760                                            const gchar                  *key,
761                                            GnomeDesktopThumbnailFactory *factory)
762 {
763   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
764 
765   g_mutex_lock (&priv->lock);
766 
767   if (!priv->disabled)
768     {
769       g_strfreev (priv->disabled_types);
770       priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
771     }
772 
773   g_mutex_unlock (&priv->lock);
774 }
775 
776 /* There are various different behavior depending on whether the application
777    ran with sudo, pkexec, under the root user, and 'sudo su'...
778 
779    - sudo (program) keeps the user's home folder (and their .cache folder)
780    - pkexec (program) uses root's .cache folder
781    - root terminal, running (program) uses root's .cache folder
782    - sudo su, then running (program), uses root's .cache folder
783 
784    Using sudo, or sudo su, SUDO_UID and friends are set in the environment
785    Using pkexec, PKEXEC_UID is set
786 
787    root terminal and pkexec cases don't need any extra work - they're thumbnailing
788    with the correct permissions (root-owned in root's .cache folder)
789 
790    sudo and sudo su need help, and each in a different way:
791        - sudo su gives a false positive, since SUDO_UID is set, *but* the program
792          is using root's .cache folder, so we really don't want to fix these
793        - sudo (program) wants to use the user's cache folder, but will end up writing
794          them as root:root files, instead of user:user.  So, in this case, we make sure
795          to chmod those files to be owned by the original user, and not root.
796 */
797 
798 static void
get_user_info(GnomeDesktopThumbnailFactory * factory,gboolean * adjust,uid_t * uid,gid_t * gid)799 get_user_info (GnomeDesktopThumbnailFactory *factory,
800                                    gboolean *adjust,
801                                       uid_t *uid,
802                                       gid_t *gid)
803 {
804     struct passwd *pwent;
805 
806     pwent = gnome_desktop_get_session_user_pwent ();
807 
808     *uid = pwent->pw_uid;
809     *gid = pwent->pw_gid;
810 
811     /* Only can (and need to) adjust if we're root, but
812        this process will be writing to the session user's
813        home folder */
814 
815     *adjust = geteuid () == 0 &&
816               g_strcmp0 (pwent->pw_dir, g_get_home_dir ()) == 0;
817 }
818 
819 static void
gnome_desktop_thumbnail_factory_init(GnomeDesktopThumbnailFactory * factory)820 gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
821 {
822   GnomeDesktopThumbnailFactoryPrivate *priv;
823 
824   factory->priv = GNOME_DESKTOP_THUMBNAIL_FACTORY_GET_PRIVATE (factory);
825 
826   priv = factory->priv;
827   priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
828 
829   priv->mime_types_map = g_hash_table_new_full (g_str_hash,
830                                                 g_str_equal,
831                                                 (GDestroyNotify)g_free,
832                                                 (GDestroyNotify)thumbnailer_unref);
833 
834   get_user_info (factory, &priv->needs_chown, &priv->real_uid, &priv->real_gid);
835 
836   priv->permissions_problem = !gnome_desktop_thumbnail_cache_check_permissions (NULL, TRUE);
837 
838   g_mutex_init (&priv->lock);
839 
840   priv->settings = g_settings_new ("org.cinnamon.desktop.thumbnailers");
841   priv->disabled = g_settings_get_boolean (priv->settings, "disable-all");
842   if (!priv->disabled)
843     priv->disabled_types = g_settings_get_strv (priv->settings, "disable");
844   g_signal_connect (priv->settings, "changed::disable-all",
845                     G_CALLBACK (external_thumbnailers_disabled_all_changed_cb),
846                     factory);
847   g_signal_connect (priv->settings, "changed::disable",
848                     G_CALLBACK (external_thumbnailers_disabled_changed_cb),
849                     factory);
850 
851   if (!priv->disabled)
852     gnome_desktop_thumbnail_factory_load_thumbnailers (factory);
853 }
854 
855 static void
gnome_desktop_thumbnail_factory_class_init(GnomeDesktopThumbnailFactoryClass * class)856 gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
857 {
858   GObjectClass *gobject_class;
859 
860   gobject_class = G_OBJECT_CLASS (class);
861 
862   gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
863 
864   g_type_class_add_private (class, sizeof (GnomeDesktopThumbnailFactoryPrivate));
865 }
866 
867 /**
868  * gnome_desktop_thumbnail_factory_new:
869  * @size: The thumbnail size to use
870  *
871  * Creates a new #GnomeDesktopThumbnailFactory.
872  *
873  * This function must be called on the main thread.
874  *
875  * Return value: a new #GnomeDesktopThumbnailFactory
876  *
877  * Since: 2.2
878  **/
879 GnomeDesktopThumbnailFactory *
gnome_desktop_thumbnail_factory_new(GnomeDesktopThumbnailSize size)880 gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
881 {
882   GnomeDesktopThumbnailFactory *factory;
883 
884   factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
885 
886   factory->priv->size = size;
887 
888   return factory;
889 }
890 
891 /**
892  * gnome_desktop_thumbnail_factory_lookup:
893  * @factory: a #GnomeDesktopThumbnailFactory
894  * @uri: the uri of a file
895  * @mtime: the mtime of the file
896  *
897  * Tries to locate an existing thumbnail for the file specified.
898  *
899  * Usage of this function is threadsafe.
900  *
901  * Return value: The absolute path of the thumbnail, or %NULL if none exist.
902  *
903  * Since: 2.2
904  **/
905 char *
gnome_desktop_thumbnail_factory_lookup(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)906 gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
907 					const char            *uri,
908 					time_t                 mtime)
909 {
910   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
911   char *path, *file;
912   GChecksum *checksum;
913   guint8 digest[16];
914   gsize digest_len = sizeof (digest);
915   GdkPixbuf *pixbuf;
916   gboolean res;
917 
918   g_return_val_if_fail (uri != NULL, NULL);
919 
920   res = FALSE;
921 
922   checksum = g_checksum_new (G_CHECKSUM_MD5);
923   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
924 
925   g_checksum_get_digest (checksum, digest, &digest_len);
926   g_assert (digest_len == 16);
927 
928   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
929 
930   path = g_build_filename (g_get_user_cache_dir (),
931 			   "thumbnails",
932 			   (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
933 			   file,
934 			   NULL);
935   g_free (file);
936 
937   pixbuf = gdk_pixbuf_new_from_file (path, NULL);
938   if (pixbuf != NULL)
939     {
940       res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
941       g_object_unref (pixbuf);
942     }
943 
944   g_checksum_free (checksum);
945 
946   if (res)
947     return path;
948 
949   g_free (path);
950   return FALSE;
951 }
952 
953 /**
954  * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
955  * @factory: a #GnomeDesktopThumbnailFactory
956  * @uri: the uri of a file
957  * @mtime: the mtime of the file
958  *
959  * Tries to locate an failed thumbnail for the file specified. Writing
960  * and looking for failed thumbnails is important to avoid to try to
961  * thumbnail e.g. broken images several times.
962  *
963  * Usage of this function is threadsafe.
964  *
965  * Return value: TRUE if there is a failed thumbnail for the file.
966  *
967  * Since: 2.2
968  **/
969 gboolean
gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)970 gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
971 							    const char            *uri,
972 							    time_t                 mtime)
973 {
974   char *path, *file;
975   GdkPixbuf *pixbuf;
976   gboolean res;
977   GChecksum *checksum;
978   guint8 digest[16];
979   gsize digest_len = sizeof (digest);
980 
981   checksum = g_checksum_new (G_CHECKSUM_MD5);
982   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
983 
984   g_checksum_get_digest (checksum, digest, &digest_len);
985   g_assert (digest_len == 16);
986 
987   res = FALSE;
988 
989   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
990 
991   path = g_build_filename (g_get_user_cache_dir (),
992 			   "thumbnails/fail",
993 			   appname,
994 			   file,
995 			   NULL);
996   g_free (file);
997 
998   pixbuf = gdk_pixbuf_new_from_file (path, NULL);
999   g_free (path);
1000 
1001   if (pixbuf)
1002     {
1003       res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
1004       g_object_unref (pixbuf);
1005     }
1006 
1007   g_checksum_free (checksum);
1008 
1009   return res;
1010 }
1011 
1012 static gboolean
mimetype_supported_by_gdk_pixbuf(const char * mime_type)1013 mimetype_supported_by_gdk_pixbuf (const char *mime_type)
1014 {
1015     guint i;
1016     static gsize formats_hash = 0;
1017     gchar *key;
1018     gboolean result;
1019 
1020     if (g_once_init_enter (&formats_hash)) {
1021         GSList *formats, *list;
1022         GHashTable *hash;
1023 
1024         hash = g_hash_table_new_full (g_str_hash,
1025                                       (GEqualFunc) g_content_type_equals,
1026                                       g_free, NULL);
1027 
1028         formats = gdk_pixbuf_get_formats ();
1029         list = formats;
1030 
1031         while (list) {
1032             GdkPixbufFormat *format = list->data;
1033             gchar **mime_types;
1034 
1035             mime_types = gdk_pixbuf_format_get_mime_types (format);
1036 
1037             for (i = 0; mime_types[i] != NULL; i++)
1038               {
1039                     g_hash_table_insert (hash,
1040                                          (gpointer) g_content_type_from_mime_type (mime_types[i]),
1041                                          GUINT_TO_POINTER (1));
1042               }
1043 
1044             g_strfreev (mime_types);
1045             list = list->next;
1046         }
1047 
1048         g_slist_free (formats);
1049 
1050         g_once_init_leave (&formats_hash, (gsize) hash);
1051     }
1052 
1053     key = g_content_type_from_mime_type (mime_type);
1054     if (g_hash_table_lookup ((void*)formats_hash, key))
1055             result = TRUE;
1056     else
1057             result = FALSE;
1058     g_free (key);
1059 
1060     return result;
1061 }
1062 
1063 /**
1064  * gnome_desktop_thumbnail_factory_can_thumbnail:
1065  * @factory: a #GnomeDesktopThumbnailFactory
1066  * @uri: the uri of a file
1067  * @mime_type: the mime type of the file
1068  * @mtime: the mtime of the file
1069  *
1070  * Returns TRUE if this GnomeIconFactory can (at least try) to thumbnail
1071  * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
1072  *
1073  * Usage of this function is threadsafe.
1074  *
1075  * Return value: TRUE if the file can be thumbnailed.
1076  *
1077  * Since: 2.2
1078  **/
1079 gboolean
gnome_desktop_thumbnail_factory_can_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type,time_t mtime)1080 gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
1081 					       const char            *uri,
1082 					       const char            *mime_type,
1083 					       time_t                 mtime)
1084 {
1085   gboolean have_script = FALSE;
1086 
1087 
1088   if (factory->priv->permissions_problem)
1089     return FALSE;
1090 
1091   /* Don't thumbnail thumbnails */
1092   if (uri &&
1093       strncmp (uri, "file:/", 6) == 0 &&
1094       strstr (uri, "/thumbnails/") != NULL)
1095     return FALSE;
1096 
1097   if (!mime_type)
1098     return FALSE;
1099 
1100   if (gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type))
1101     {
1102       return FALSE;
1103     }
1104 
1105   g_mutex_lock (&factory->priv->lock);
1106 
1107   Thumbnailer *thumb;
1108   thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1109   have_script = thumbnailer_try_exec (thumb);
1110 
1111   g_mutex_unlock (&factory->priv->lock);
1112 
1113   if (have_script || mimetype_supported_by_gdk_pixbuf (mime_type))
1114     {
1115       return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
1116                                                                           uri,
1117                                                                           mtime);
1118     }
1119 
1120   return FALSE;
1121 }
1122 
1123 static char *
expand_thumbnailing_script(const char * script,const int size,const char * inuri,const char * outfile)1124 expand_thumbnailing_script (const char *script,
1125 			    const int   size,
1126 			    const char *inuri,
1127 			    const char *outfile)
1128 {
1129   GString *str;
1130   const char *p, *last;
1131   char *localfile, *quoted;
1132   gboolean got_in;
1133 
1134   str = g_string_new (NULL);
1135 
1136   got_in = FALSE;
1137   last = script;
1138   while ((p = strchr (last, '%')) != NULL)
1139     {
1140       g_string_append_len (str, last, p - last);
1141       p++;
1142 
1143       switch (*p) {
1144       case 'u':
1145 	quoted = g_shell_quote (inuri);
1146 	g_string_append (str, quoted);
1147 	g_free (quoted);
1148 	got_in = TRUE;
1149 	p++;
1150 	break;
1151       case 'i':
1152 	localfile = g_filename_from_uri (inuri, NULL, NULL);
1153 	if (localfile)
1154 	  {
1155 	    quoted = g_shell_quote (localfile);
1156 	    g_string_append (str, quoted);
1157 	    got_in = TRUE;
1158 	    g_free (quoted);
1159 	    g_free (localfile);
1160 	  }
1161 	p++;
1162 	break;
1163       case 'o':
1164 	quoted = g_shell_quote (outfile);
1165 	g_string_append (str, quoted);
1166 	g_free (quoted);
1167 	p++;
1168 	break;
1169       case 's':
1170 	g_string_append_printf (str, "%d", size);
1171 	p++;
1172 	break;
1173       case '%':
1174 	g_string_append_c (str, '%');
1175 	p++;
1176 	break;
1177       case 0:
1178       default:
1179 	break;
1180       }
1181       last = p;
1182     }
1183   g_string_append (str, last);
1184 
1185   if (got_in)
1186     return g_string_free (str, FALSE);
1187 
1188   g_string_free (str, TRUE);
1189   return NULL;
1190 }
1191 
1192 /**
1193  * gnome_desktop_thumbnail_factory_generate_thumbnail:
1194  * @factory: a #GnomeDesktopThumbnailFactory
1195  * @uri: the uri of a file
1196  * @mime_type: the mime type of the file
1197  *
1198  * Tries to generate a thumbnail for the specified file. If it succeeds
1199  * it returns a pixbuf that can be used as a thumbnail.
1200  *
1201  * Usage of this function is threadsafe.
1202  *
1203  * Return value: (transfer full): thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
1204  *
1205  * Since: 2.2
1206  **/
1207 GdkPixbuf *
gnome_desktop_thumbnail_factory_generate_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,const char * mime_type)1208 gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
1209 						    const char            *uri,
1210 						    const char            *mime_type)
1211 {
1212   GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
1213   char *script, *expanded_script;
1214   int width, height, size;
1215   int original_width = 0;
1216   int original_height = 0;
1217   char dimension[12];
1218   double scale;
1219   int exit_status;
1220   char *tmpname;
1221   gboolean disabled = FALSE;
1222 
1223   g_return_val_if_fail (uri != NULL, NULL);
1224   g_return_val_if_fail (mime_type != NULL, NULL);
1225 
1226   /* Doesn't access any volatile fields in factory, so it's threadsafe */
1227 
1228   size = 128;
1229   if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
1230     size = 256;
1231 
1232   pixbuf = NULL;
1233 
1234   script = NULL;
1235   g_mutex_lock (&factory->priv->lock);
1236 
1237   disabled = gnome_desktop_thumbnail_factory_is_disabled (factory, mime_type);
1238 
1239   if (!disabled)
1240     {
1241       Thumbnailer *thumb;
1242 
1243       thumb = g_hash_table_lookup (factory->priv->mime_types_map, mime_type);
1244       if (thumb)
1245         script = g_strdup (thumb->command);
1246     }
1247   g_mutex_unlock (&factory->priv->lock);
1248 
1249   if (script)
1250     {
1251       int fd;
1252 
1253       fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
1254 
1255       if (fd != -1)
1256 	{
1257 	  close (fd);
1258 
1259 	  expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
1260 	  if (expanded_script != NULL &&
1261 	      g_spawn_command_line_sync (expanded_script,
1262 					 NULL, NULL, &exit_status, NULL) &&
1263 	      exit_status == 0)
1264 	    {
1265 	      pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
1266 	    }
1267 
1268 	  g_free (expanded_script);
1269 	  g_unlink (tmpname);
1270 	  g_free (tmpname);
1271 	}
1272 
1273       g_free (script);
1274     }
1275 
1276   /* Fall back to gdk-pixbuf */
1277   if (pixbuf == NULL && !disabled)
1278     {
1279       pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
1280 
1281       if (pixbuf != NULL)
1282         {
1283           original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1284                                                                "gnome-original-width"));
1285           original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
1286                                                                 "gnome-original-height"));
1287         }
1288     }
1289 
1290   if (pixbuf == NULL)
1291     return NULL;
1292 
1293   /* The pixbuf loader may attach an "orientation" option to the pixbuf,
1294      if the tiff or exif jpeg file had an orientation tag. Rotate/flip
1295      the pixbuf as specified by this tag, if present. */
1296   tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
1297   g_object_unref (pixbuf);
1298   pixbuf = tmp_pixbuf;
1299 
1300   width = gdk_pixbuf_get_width (pixbuf);
1301   height = gdk_pixbuf_get_height (pixbuf);
1302 
1303   if (width > size || height > size)
1304     {
1305       const gchar *orig_width, *orig_height;
1306       scale = (double)size / MAX (width, height);
1307 
1308       scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
1309 						  floor (width * scale + 0.5),
1310 						  floor (height * scale + 0.5));
1311 
1312       orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
1313       orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
1314 
1315       if (orig_width != NULL) {
1316 	      gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
1317       }
1318       if (orig_height != NULL) {
1319 	      gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
1320       }
1321 
1322       g_object_unref (pixbuf);
1323       pixbuf = scaled;
1324     }
1325 
1326   if (original_width > 0) {
1327 	  g_snprintf (dimension, sizeof (dimension), "%i", original_width);
1328 	  gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
1329   }
1330   if (original_height > 0) {
1331 	  g_snprintf (dimension, sizeof (dimension), "%i", original_height);
1332 	  gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
1333   }
1334 
1335   return pixbuf;
1336 }
1337 
1338 static void
maybe_fix_ownership(GnomeDesktopThumbnailFactory * factory,const gchar * path)1339 maybe_fix_ownership (GnomeDesktopThumbnailFactory *factory, const gchar *path)
1340 {
1341     if (factory->priv->needs_chown) {
1342         G_GNUC_UNUSED int res;
1343 
1344         res = chown (path,
1345                      factory->priv->real_uid,
1346                      factory->priv->real_gid);
1347     }
1348 }
1349 
1350 static gboolean
make_thumbnail_dirs(GnomeDesktopThumbnailFactory * factory)1351 make_thumbnail_dirs (GnomeDesktopThumbnailFactory *factory)
1352 {
1353   char *thumbnail_dir;
1354   char *image_dir;
1355   gboolean res;
1356 
1357   res = FALSE;
1358 
1359   thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
1360 				    "thumbnails",
1361 				    NULL);
1362   if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1363     {
1364       g_mkdir (thumbnail_dir, 0700);
1365       maybe_fix_ownership (factory, thumbnail_dir);
1366       res = TRUE;
1367     }
1368 
1369   image_dir = g_build_filename (thumbnail_dir,
1370 				(factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1371 				NULL);
1372   if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
1373     {
1374       g_mkdir (image_dir, 0700);
1375       maybe_fix_ownership (factory, image_dir);
1376       res = TRUE;
1377     }
1378 
1379   g_free (thumbnail_dir);
1380   g_free (image_dir);
1381 
1382   return res;
1383 }
1384 
1385 static gboolean
make_thumbnail_fail_dirs(GnomeDesktopThumbnailFactory * factory)1386 make_thumbnail_fail_dirs (GnomeDesktopThumbnailFactory *factory)
1387 {
1388   char *thumbnail_dir;
1389   char *fail_dir;
1390   char *app_dir;
1391   gboolean res;
1392 
1393   res = FALSE;
1394 
1395   thumbnail_dir = g_build_filename (g_get_user_cache_dir (),
1396 				    "thumbnails",
1397 				    NULL);
1398   if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
1399     {
1400       g_mkdir (thumbnail_dir, 0700);
1401       maybe_fix_ownership (factory, thumbnail_dir);
1402       res = TRUE;
1403     }
1404 
1405   fail_dir = g_build_filename (thumbnail_dir,
1406 			       "fail",
1407 			       NULL);
1408   if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
1409     {
1410       g_mkdir (fail_dir, 0700);
1411       maybe_fix_ownership (factory, fail_dir);
1412       res = TRUE;
1413     }
1414 
1415   app_dir = g_build_filename (fail_dir,
1416 			      appname,
1417 			      NULL);
1418   if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
1419     {
1420       g_mkdir (app_dir, 0700);
1421       maybe_fix_ownership (factory, app_dir);
1422       res = TRUE;
1423     }
1424 
1425   g_free (thumbnail_dir);
1426   g_free (fail_dir);
1427   g_free (app_dir);
1428 
1429   return res;
1430 }
1431 
1432 
1433 /**
1434  * gnome_desktop_thumbnail_factory_save_thumbnail:
1435  * @factory: a #GnomeDesktopThumbnailFactory
1436  * @thumbnail: the thumbnail as a pixbuf
1437  * @uri: the uri of a file
1438  * @original_mtime: the modification time of the original file
1439  *
1440  * Saves @thumbnail at the right place. If the save fails a
1441  * failed thumbnail is written.
1442  *
1443  * Usage of this function is threadsafe.
1444  *
1445  * Since: 2.2
1446  **/
1447 void
gnome_desktop_thumbnail_factory_save_thumbnail(GnomeDesktopThumbnailFactory * factory,GdkPixbuf * thumbnail,const char * uri,time_t original_mtime)1448 gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
1449 						GdkPixbuf             *thumbnail,
1450 						const char            *uri,
1451 						time_t                 original_mtime)
1452 {
1453   GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
1454   char *path, *file;
1455   char *tmp_path;
1456   const char *width, *height;
1457   int tmp_fd;
1458   char mtime_str[21];
1459   gboolean saved_ok;
1460   GChecksum *checksum;
1461   guint8 digest[16];
1462   gsize digest_len = sizeof (digest);
1463   GError *error;
1464 
1465   checksum = g_checksum_new (G_CHECKSUM_MD5);
1466   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1467 
1468   g_checksum_get_digest (checksum, digest, &digest_len);
1469   g_assert (digest_len == 16);
1470 
1471   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1472 
1473   path = g_build_filename (g_get_user_cache_dir (),
1474 			   "thumbnails",
1475 			   (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1476 			   file,
1477 			   NULL);
1478 
1479   g_free (file);
1480 
1481   g_checksum_free (checksum);
1482 
1483   tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1484 
1485   tmp_fd = g_mkstemp (tmp_path);
1486   if (tmp_fd == -1 &&
1487       make_thumbnail_dirs (factory))
1488     {
1489       g_free (tmp_path);
1490       tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1491       tmp_fd = g_mkstemp (tmp_path);
1492     }
1493 
1494   if (tmp_fd == -1)
1495     {
1496       gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1497       g_free (tmp_path);
1498       g_free (path);
1499       return;
1500     }
1501   close (tmp_fd);
1502 
1503   g_snprintf (mtime_str, 21, "%ld",  original_mtime);
1504   width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
1505   height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
1506 
1507   error = NULL;
1508   if (width != NULL && height != NULL)
1509     saved_ok  = gdk_pixbuf_save (thumbnail,
1510 				 tmp_path,
1511 				 "png", &error,
1512 				 "tEXt::Thumb::Image::Width", width,
1513 				 "tEXt::Thumb::Image::Height", height,
1514 				 "tEXt::Thumb::URI", uri,
1515 				 "tEXt::Thumb::MTime", mtime_str,
1516 				 "tEXt::Software", "GNOME::ThumbnailFactory",
1517 				 NULL);
1518   else
1519     saved_ok  = gdk_pixbuf_save (thumbnail,
1520 				 tmp_path,
1521 				 "png", &error,
1522 				 "tEXt::Thumb::URI", uri,
1523 				 "tEXt::Thumb::MTime", mtime_str,
1524 				 "tEXt::Software", "GNOME::ThumbnailFactory",
1525 				 NULL);
1526 
1527 
1528   if (saved_ok)
1529     {
1530       g_chmod (tmp_path, 0600);
1531       g_rename (tmp_path, path);
1532       maybe_fix_ownership (factory, path);
1533     }
1534   else
1535     {
1536       g_warning ("Failed to create thumbnail %s: %s", tmp_path, error->message);
1537       gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
1538       g_unlink (tmp_path);
1539       g_clear_error (&error);
1540     }
1541 
1542   g_free (path);
1543   g_free (tmp_path);
1544 }
1545 
1546 /**
1547  * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
1548  * @factory: a #GnomeDesktopThumbnailFactory
1549  * @uri: the uri of a file
1550  * @mtime: the modification time of the file
1551  *
1552  * Creates a failed thumbnail for the file so that we don't try
1553  * to re-thumbnail the file later.
1554  *
1555  * Usage of this function is threadsafe.
1556  *
1557  * Since: 2.2
1558  **/
1559 void
gnome_desktop_thumbnail_factory_create_failed_thumbnail(GnomeDesktopThumbnailFactory * factory,const char * uri,time_t mtime)1560 gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
1561 							 const char            *uri,
1562 							 time_t                 mtime)
1563 {
1564   char *path, *file;
1565   char *tmp_path;
1566   int tmp_fd;
1567   char mtime_str[21];
1568   gboolean saved_ok;
1569   GdkPixbuf *pixbuf;
1570   GChecksum *checksum;
1571   guint8 digest[16];
1572   gsize digest_len = sizeof (digest);
1573 
1574   checksum = g_checksum_new (G_CHECKSUM_MD5);
1575   g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
1576 
1577   g_checksum_get_digest (checksum, digest, &digest_len);
1578   g_assert (digest_len == 16);
1579 
1580   file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
1581 
1582   path = g_build_filename (g_get_user_cache_dir (),
1583 			   "thumbnails/fail",
1584 			   appname,
1585 			   file,
1586 			   NULL);
1587   g_free (file);
1588 
1589   g_checksum_free (checksum);
1590 
1591   tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1592 
1593   tmp_fd = g_mkstemp (tmp_path);
1594   if (tmp_fd == -1 &&
1595       make_thumbnail_fail_dirs (factory))
1596     {
1597       g_free (tmp_path);
1598       tmp_path = g_strconcat (path, ".XXXXXX", NULL);
1599       tmp_fd = g_mkstemp (tmp_path);
1600     }
1601 
1602   if (tmp_fd == -1)
1603     {
1604       g_free (tmp_path);
1605       g_free (path);
1606       return;
1607     }
1608   close (tmp_fd);
1609 
1610   g_snprintf (mtime_str, 21, "%ld",  mtime);
1611   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
1612   saved_ok  = gdk_pixbuf_save (pixbuf,
1613 			       tmp_path,
1614 			       "png", NULL,
1615 			       "tEXt::Thumb::URI", uri,
1616 			       "tEXt::Thumb::MTime", mtime_str,
1617 			       "tEXt::Software", "GNOME::ThumbnailFactory",
1618 			       NULL);
1619   g_object_unref (pixbuf);
1620   if (saved_ok)
1621     {
1622       g_chmod (tmp_path, 0600);
1623       g_rename(tmp_path, path);
1624       maybe_fix_ownership (factory, path);
1625     }
1626 
1627   g_free (path);
1628   g_free (tmp_path);
1629 }
1630 
1631 /**
1632  * gnome_desktop_thumbnail_md5:
1633  * @uri: an uri
1634  *
1635  * Calculates the MD5 checksum of the uri. This can be useful
1636  * if you want to manually handle thumbnail files.
1637  *
1638  * Return value: A string with the MD5 digest of the uri string.
1639  *
1640  * Since: 2.2
1641  * Deprecated: 2.22: Use #GChecksum instead
1642  **/
1643 char *
gnome_desktop_thumbnail_md5(const char * uri)1644 gnome_desktop_thumbnail_md5 (const char *uri)
1645 {
1646   return g_compute_checksum_for_data (G_CHECKSUM_MD5,
1647                                       (const guchar *) uri,
1648                                       strlen (uri));
1649 }
1650 
1651 /**
1652  * gnome_desktop_thumbnail_path_for_uri:
1653  * @uri: an uri
1654  * @size: a thumbnail size
1655  *
1656  * Returns the filename that a thumbnail of size @size for @uri would have.
1657  *
1658  * Return value: an absolute filename
1659  *
1660  * Since: 2.2
1661  **/
1662 char *
gnome_desktop_thumbnail_path_for_uri(const char * uri,GnomeDesktopThumbnailSize size)1663 gnome_desktop_thumbnail_path_for_uri (const char         *uri,
1664 				      GnomeDesktopThumbnailSize  size)
1665 {
1666   char *md5;
1667   char *file;
1668   char *path;
1669 
1670   md5 = gnome_desktop_thumbnail_md5 (uri);
1671   file = g_strconcat (md5, ".png", NULL);
1672   g_free (md5);
1673 
1674   path = g_build_filename (g_get_user_cache_dir (),
1675 			   "thumbnails",
1676 			   (size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
1677 			   file,
1678 			   NULL);
1679 
1680   g_free (file);
1681 
1682   return path;
1683 }
1684 
1685 /**
1686  * gnome_desktop_thumbnail_has_uri:
1687  * @pixbuf: an loaded thumbnail pixbuf
1688  * @uri: a uri
1689  *
1690  * Returns whether the thumbnail has the correct uri embedded in the
1691  * Thumb::URI option in the png.
1692  *
1693  * Return value: TRUE if the thumbnail is for @uri
1694  *
1695  * Since: 2.2
1696  **/
1697 gboolean
gnome_desktop_thumbnail_has_uri(GdkPixbuf * pixbuf,const char * uri)1698 gnome_desktop_thumbnail_has_uri (GdkPixbuf          *pixbuf,
1699 				 const char         *uri)
1700 {
1701   const char *thumb_uri;
1702 
1703   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1704   if (!thumb_uri)
1705     return FALSE;
1706 
1707   return strcmp (uri, thumb_uri) == 0;
1708 }
1709 
1710 /**
1711  * gnome_desktop_thumbnail_is_valid:
1712  * @pixbuf: an loaded thumbnail #GdkPixbuf
1713  * @uri: a uri
1714  * @mtime: the mtime
1715  *
1716  * Returns whether the thumbnail has the correct uri and mtime embedded in the
1717  * png options.
1718  *
1719  * Return value: TRUE if the thumbnail has the right @uri and @mtime
1720  *
1721  * Since: 2.2
1722  **/
1723 gboolean
gnome_desktop_thumbnail_is_valid(GdkPixbuf * pixbuf,const char * uri,time_t mtime)1724 gnome_desktop_thumbnail_is_valid (GdkPixbuf          *pixbuf,
1725 				  const char         *uri,
1726 				  time_t              mtime)
1727 {
1728   const char *thumb_uri, *thumb_mtime_str;
1729   time_t thumb_mtime;
1730 
1731   thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
1732   if (!thumb_uri)
1733     return FALSE;
1734   if (strcmp (uri, thumb_uri) != 0)
1735     return FALSE;
1736 
1737   thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
1738   if (!thumb_mtime_str)
1739     return FALSE;
1740   thumb_mtime = atol (thumb_mtime_str);
1741   if (mtime != thumb_mtime)
1742     return FALSE;
1743 
1744   return TRUE;
1745 }
1746 
1747 static void
fix_owner(const gchar * path,uid_t uid,gid_t gid)1748 fix_owner (const gchar *path, uid_t uid, gid_t gid)
1749 {
1750     G_GNUC_UNUSED int res;
1751 
1752     res = chown (path, uid, gid);
1753 }
1754 
1755 static gboolean
access_ok(const gchar * path,uid_t uid,gid_t gid)1756 access_ok (const gchar *path, uid_t uid, gid_t gid)
1757 {
1758     /* user mode will always trip on this */
1759     if (g_access (path, R_OK|W_OK) != 0) {
1760         if (errno != ENOENT && errno != EFAULT)
1761             return FALSE;
1762         else
1763             return TRUE;
1764     }
1765 
1766     /* root will need to check against the real user */
1767 
1768     GStatBuf buf;
1769 
1770     gint ret = g_stat (path, &buf);
1771 
1772     if (ret == 0) {
1773         if (buf.st_uid != uid || buf.st_gid != gid || (buf.st_mode & (S_IRUSR|S_IWUSR)) == 0)
1774             return FALSE;
1775     }
1776 
1777     return TRUE;
1778 }
1779 
1780 static void
recursively_fix_file(const gchar * path,uid_t uid,gid_t gid)1781 recursively_fix_file (const gchar *path, uid_t uid, gid_t gid)
1782 {
1783     if (!access_ok (path, uid, gid))
1784         fix_owner (path, uid, gid);
1785 
1786     if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
1787         GDir *dir = g_dir_open (path, 0, NULL);
1788 
1789         if (dir) {
1790             const char *name;
1791 
1792             while ((name = g_dir_read_name (dir))) {
1793                 gchar *filename;
1794 
1795                 filename = g_build_filename (path, name, NULL);
1796                 recursively_fix_file (filename, uid, gid);
1797                 g_free (filename);
1798             }
1799 
1800             g_dir_close (dir);
1801         }
1802     }
1803 }
1804 
1805 static gboolean
recursively_check_file(const gchar * path,uid_t uid,gid_t gid)1806 recursively_check_file (const gchar *path, uid_t uid, gid_t gid)
1807 {
1808     if (!access_ok (path, uid, gid))
1809         return FALSE;
1810 
1811     gboolean ret = TRUE;
1812 
1813     if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
1814         GDir *dir = g_dir_open (path, 0, NULL);
1815 
1816         if (dir) {
1817             const char *name;
1818 
1819             while ((name = g_dir_read_name (dir))) {
1820                 gchar *filename;
1821                 filename = g_build_filename (path, name, NULL);
1822 
1823                 if (!recursively_check_file (filename, uid, gid)) {
1824                     ret = FALSE;
1825                 }
1826 
1827                 g_free (filename);
1828 
1829                 if (!ret)
1830                     break;
1831             }
1832 
1833             g_dir_close (dir);
1834         }
1835     }
1836 
1837     return ret;
1838 }
1839 
1840 static gboolean
check_subfolder_permissions_only(const gchar * path,uid_t uid,gid_t gid)1841 check_subfolder_permissions_only (const gchar *path, uid_t uid, gid_t gid)
1842 {
1843     gboolean ret = TRUE;
1844 
1845     GDir *dir = g_dir_open (path, 0, NULL);
1846 
1847     if (dir) {
1848         const char *name;
1849 
1850         while ((name = g_dir_read_name (dir))) {
1851             gchar *filename;
1852             filename = g_build_filename (path, name, NULL);
1853 
1854             if (!access_ok (filename, uid, gid)) {
1855                 ret = FALSE;
1856             }
1857 
1858             g_free (filename);
1859 
1860             if (!ret)
1861                 break;
1862         }
1863 
1864         g_dir_close (dir);
1865     }
1866 
1867     return ret;
1868 }
1869 
1870 /**
1871  * gnome_desktop_cache_fix_permissions:
1872  *
1873  * Fixes any file or folder ownership issues for the *currently
1874  * logged-in* user.  Note - this may not be the same as the uid
1875  * of the user running this operation.
1876  *
1877  **/
1878 
1879 void
gnome_desktop_thumbnail_cache_fix_permissions(void)1880 gnome_desktop_thumbnail_cache_fix_permissions (void)
1881 {
1882     struct passwd *pwent;
1883 
1884     pwent = gnome_desktop_get_session_user_pwent ();
1885 
1886     gchar *cache_dir = g_build_filename (pwent->pw_dir, ".cache", "thumbnails", NULL);
1887 
1888     if (!access_ok (cache_dir, pwent->pw_uid, pwent->pw_gid))
1889         fix_owner (cache_dir, pwent->pw_uid, pwent->pw_gid);
1890 
1891     recursively_fix_file (cache_dir, pwent->pw_uid, pwent->pw_gid);
1892 
1893     g_free (cache_dir);
1894 }
1895 
1896 /**
1897  * gnome_desktop_cache_check_permissions:
1898  * @factory: (allow-none): an optional GnomeDesktopThumbnailFactory
1899  * @quick: if TRUE, only do a quick check of directory ownersip
1900  * This is more serious than thumbnail ownership issues, and is faster.
1901  *
1902  * Returns whether there are any ownership issues with the thumbnail
1903  * folders and existing thumbnails for the *currently logged-in* user.
1904  * Note - this may not be the same as the uid of the user running this
1905  * check.
1906  *
1907  * if factory is not NULL, factory->priv->permissions_problem will be
1908  * set to the result of the check.
1909  *
1910  * Return value: TRUE if everything checks out in these folders for the
1911  * logged in user.
1912  *
1913  **/
1914 
1915 gboolean
gnome_desktop_thumbnail_cache_check_permissions(GnomeDesktopThumbnailFactory * factory,gboolean quick)1916 gnome_desktop_thumbnail_cache_check_permissions (GnomeDesktopThumbnailFactory *factory, gboolean quick)
1917 {
1918     gboolean checks_out = TRUE;
1919 
1920     struct passwd *pwent;
1921     pwent = gnome_desktop_get_session_user_pwent ();
1922 
1923     gchar *cache_dir = g_build_filename (pwent->pw_dir, ".cache", "thumbnails", NULL);
1924 
1925     if (!access_ok (cache_dir, pwent->pw_uid, pwent->pw_gid)) {
1926         checks_out = FALSE;
1927         goto out;
1928     }
1929 
1930     if (quick) {
1931         checks_out = check_subfolder_permissions_only (cache_dir, pwent->pw_uid, pwent->pw_gid);
1932     } else {
1933         checks_out = recursively_check_file (cache_dir, pwent->pw_uid, pwent->pw_gid);
1934     }
1935 
1936 out:
1937     g_free (cache_dir);
1938 
1939     if (factory)
1940         factory->priv->permissions_problem = !checks_out;
1941 
1942     return checks_out;
1943 }
1944