libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

thumbnailgtk_extractor.c (10229B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2005, 2009, 2012 Vidyut Samanta and Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19  */
     20 /**
     21  * @file plugins/thumbnailgtk_extractor.c
     22  * @author Christian Grothoff
     23  * @brief this extractor produces a binary (!) encoded
     24  * thumbnail of images (using gdk pixbuf).  The bottom
     25  * of the file includes a decoder method that can be used
     26  * to reproduce the 128x128 PNG thumbnails.  We use
     27  * libmagic to test if the input data is actually an
     28  * image before trying to give it to gdk-pixbuf.
     29  */
     30 #include "platform.h"
     31 #include "extractor.h"
     32 #include <magic.h>
     33 #include <glib.h>
     34 #include <gdk-pixbuf/gdk-pixbuf.h>
     35 
     36 /**
     37  * Target size for the thumbnails (width and height).
     38  */
     39 #define THUMBSIZE 128
     40 
     41 /**
     42  * Maximum image size supported (to avoid unreasonable
     43  * allocations)
     44  */
     45 #define MAX_IMAGE_SIZE (32 * 1024 * 1024)
     46 
     47 /**
     48  * Largest decoded image we are willing to let gdk-pixbuf allocate,
     49  * in pixels.  At four bytes per pixel this caps the decode at 128 MB.
     50  * Anything past it gets its dimensions reported but no thumbnail.
     51  */
     52 #define MAX_IMAGE_PIXELS (32 * 1024 * 1024)
     53 
     54 
     55 /**
     56  * Global handle to MAGIC data.
     57  */
     58 static magic_t magic;
     59 
     60 
     61 /**
     62  * What #handle_size_prepared saw in the image header, so that the
     63  * dimensions we report are the ones the file claims and not the ones
     64  * we asked the loader to decode at.
     65  */
     66 struct SizeInfo
     67 {
     68   /**
     69    * Width from the image header, in pixels.
     70    */
     71   gint width;
     72 
     73   /**
     74    * Height from the image header, in pixels.
     75    */
     76   gint height;
     77 
     78   /**
     79    * Set once the header announces more than #MAX_IMAGE_PIXELS pixels.
     80    * The caller stops feeding the loader when it sees this, which is
     81    * what actually prevents the allocation.
     82    */
     83   int too_big;
     84 };
     85 
     86 
     87 /**
     88  * Called by the loader once it has parsed the image header and knows
     89  * how big the image claims to be, but before it decodes any of it.
     90  *
     91  * #MAX_IMAGE_SIZE bounds the *compressed* file, which says nothing at
     92  * all about the decoded size: GIF stores its dimensions in 16 bits, so
     93  * a four-kilobyte file can legally announce 65535x65535 and cost
     94  * 65535 * 65535 * 4 bytes -- about 17 GB -- to decode.
     95  *
     96  * Note that #gdk_pixbuf_loader_set_size does NOT help here.  It is
     97  * documented as scaling the image while loading, but the GIF loader
     98  * decodes at full size and calls #gdk_pixbuf_scale_simple itself
     99  * afterwards, so the peak allocation is unchanged -- measured at 16 GB
    100  * for the reproducer either way.  The only thing that keeps the memory
    101  * from being allocated is to stop handing the loader data, which is
    102  * why this merely raises a flag and the feeding loop acts on it.
    103  *
    104  * @param loader the loader parsing the image
    105  * @param width width the image header claims
    106  * @param height height the image header claims
    107  * @param cls our `struct SizeInfo *'
    108  */
    109 static void
    110 handle_size_prepared (GdkPixbufLoader *loader,
    111                       gint width,
    112                       gint height,
    113                       gpointer cls)
    114 {
    115   struct SizeInfo *si = cls;
    116 
    117   (void) loader;
    118   si->width = width;
    119   si->height = height;
    120   if ( (0 >= width) ||
    121        (0 >= height) ||
    122        (((gint64) width) * ((gint64) height) > MAX_IMAGE_PIXELS) )
    123     si->too_big = 1;
    124 }
    125 
    126 
    127 /**
    128  * Main method for the gtk-thumbnailer plugin.
    129  *
    130  * @param ec extraction context
    131  */
    132 void
    133 EXTRACTOR_thumbnailgtk_extract_method (struct EXTRACTOR_ExtractContext *ec);
    134 
    135 void
    136 EXTRACTOR_thumbnailgtk_extract_method (struct EXTRACTOR_ExtractContext *ec)
    137 {
    138   GdkPixbufLoader *loader;
    139   struct SizeInfo si;
    140   GdkPixbuf *in;
    141   GdkPixbuf *out;
    142   size_t length;
    143   char *thumb;
    144   unsigned long width;
    145   unsigned long height;
    146   char format[64];
    147   void *data;
    148   uint64_t size;
    149   size_t off;
    150   ssize_t iret;
    151   void *buf;
    152   const char *mime;
    153 
    154   if (-1 == (iret = ec->read (ec->cls,
    155                               &data,
    156                               16 * 1024)))
    157     return;
    158   if (NULL == (mime = magic_buffer (magic, data, iret)))
    159     return;
    160   if (0 != strncmp (mime,
    161                     "image/",
    162                     strlen ("image/")))
    163     return; /* not an image */
    164 
    165   /* read entire image into memory */
    166   size = ec->get_size (ec->cls);
    167   if (UINT64_MAX == size)
    168     size = MAX_IMAGE_SIZE; /* unknown size, cap at max */
    169   if (size > MAX_IMAGE_SIZE)
    170     return; /* FAR too big to be an image */
    171   if (NULL == (buf = malloc (size)))
    172     return; /* too big to fit into memory on this system */
    173 
    174   /* start with data already read */
    175   memcpy (buf, data, iret);
    176   off = iret;
    177   while (off < size)
    178   {
    179     iret = ec->read (ec->cls, &data, size - off);
    180     if (iret <= 0)
    181     {
    182       /* io error */
    183       free (buf);
    184       return;
    185     }
    186     memcpy (buf + off, data, iret);
    187     off += iret;
    188   }
    189 
    190   loader = gdk_pixbuf_loader_new ();
    191   si.width = 0;
    192   si.height = 0;
    193   si.too_big = 0;
    194   g_signal_connect (loader,
    195                     "size-prepared",
    196                     G_CALLBACK (&handle_size_prepared),
    197                     &si);
    198   /* Feed the loader in chunks rather than in one call, so that
    199      #handle_size_prepared -- which fires as soon as the header has been
    200      parsed -- can stop us before the image body is decoded.  A single
    201      write of the whole buffer would decode everything before returning
    202      and the flag would come too late to be of any use. */
    203   off = 0;
    204   while (off < size)
    205   {
    206     size_t chunk = size - off;
    207 
    208     if (chunk > 4096)
    209       chunk = 4096;
    210     if (! gdk_pixbuf_loader_write (loader,
    211                                    ((const guchar *) buf) + off,
    212                                    chunk,
    213                                    NULL))
    214       break;
    215     if (si.too_big)
    216       break;
    217     off += chunk;
    218   }
    219   free (buf);
    220   gdk_pixbuf_loader_close (loader,
    221                            NULL);
    222   if (si.too_big)
    223   {
    224     /* Report what the header claimed, but do not decode it. */
    225     if (0 < si.width)
    226     {
    227       snprintf (format,
    228                 sizeof (format),
    229                 "%ux%u",
    230                 (unsigned int) si.width,
    231                 (unsigned int) si.height);
    232       (void) ec->proc (ec->cls,
    233                        "thumbnailgtk",
    234                        EXTRACTOR_METATYPE_IMAGE_DIMENSIONS,
    235                        EXTRACTOR_METAFORMAT_UTF8,
    236                        "text/plain",
    237                        format,
    238                        strlen (format) + 1);
    239     }
    240     g_object_unref (loader);
    241     return;
    242   }
    243   in = gdk_pixbuf_loader_get_pixbuf (loader);
    244   if (NULL == in)
    245   {
    246     g_object_unref (loader);
    247     return;
    248   }
    249   g_object_ref (in);
    250   g_object_unref (loader);
    251   /* The pixbuf may have been decoded smaller than the file asked for
    252      (see #handle_size_prepared), so report what the header said. */
    253   height = (0 < si.height) ? (unsigned long) si.height
    254            : (unsigned long) gdk_pixbuf_get_height (in);
    255   width = (0 < si.width) ? (unsigned long) si.width
    256           : (unsigned long) gdk_pixbuf_get_width (in);
    257   snprintf (format,
    258             sizeof (format),
    259             "%ux%u",
    260             (unsigned int) width,
    261             (unsigned int) height);
    262   if (0 != ec->proc (ec->cls,
    263                      "thumbnailgtk",
    264                      EXTRACTOR_METATYPE_IMAGE_DIMENSIONS,
    265                      EXTRACTOR_METAFORMAT_UTF8,
    266                      "text/plain",
    267                      format,
    268                      strlen (format) + 1))
    269   {
    270     g_object_unref (in);
    271     return;
    272   }
    273   if ((height <= THUMBSIZE) && (width <= THUMBSIZE))
    274   {
    275     g_object_unref (in);
    276     return;
    277   }
    278   if (height > THUMBSIZE)
    279   {
    280     width = width * THUMBSIZE / height;
    281     height = THUMBSIZE;
    282   }
    283   if (width > THUMBSIZE)
    284   {
    285     height = height * THUMBSIZE / width;
    286     width = THUMBSIZE;
    287   }
    288   if ( (0 == height) || (0 == width) )
    289   {
    290     g_object_unref (in);
    291     return;
    292   }
    293   out = gdk_pixbuf_scale_simple (in, width, height,
    294                                  GDK_INTERP_BILINEAR);
    295   g_object_unref (in);
    296   thumb = NULL;
    297   length = 0;
    298   if (NULL == out)
    299     return;
    300   if (! gdk_pixbuf_save_to_buffer (out, &thumb,
    301                                    &length,
    302                                    "png", NULL,
    303                                    "compression", "9",
    304                                    NULL))
    305   {
    306     g_object_unref (out);
    307     return;
    308   }
    309   g_object_unref (out);
    310   if (NULL == thumb)
    311     return;
    312   ec->proc (ec->cls,
    313             "thumbnailgtk",
    314             EXTRACTOR_METATYPE_THUMBNAIL,
    315             EXTRACTOR_METAFORMAT_BINARY,
    316             "image/png",
    317             thumb, length);
    318   free (thumb);
    319 }
    320 
    321 
    322 /**
    323  * This plugin sometimes is installed under the alias 'thumbnail'.
    324  * So we need to provide a second entry method.
    325  *
    326  * @param ec extraction context
    327  */
    328 void
    329 EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec);
    330 
    331 void
    332 EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec)
    333 {
    334   EXTRACTOR_thumbnailgtk_extract_method (ec);
    335 }
    336 
    337 
    338 /**
    339  * Initialize glib and load magic file.
    340  */
    341 void __attribute__ ((constructor))
    342 thumbnailgtk_gobject_init (void);
    343 
    344 void __attribute__ ((constructor))
    345 thumbnailgtk_gobject_init ()
    346 {
    347 #if ! GLIB_CHECK_VERSION (2, 35, 0)
    348   g_type_init ();
    349 #endif
    350   magic = magic_open (MAGIC_MIME_TYPE);
    351   if (0 != magic_load (magic, NULL))
    352   {
    353     /* FIXME: how to deal with errors? */
    354   }
    355 }
    356 
    357 
    358 /**
    359  * Destructor for the library, cleans up.
    360  */
    361 void __attribute__ ((destructor))
    362 thumbnailgtk_ltdl_fini (void);
    363 
    364 void __attribute__ ((destructor))
    365 thumbnailgtk_ltdl_fini ()
    366 {
    367   if (NULL != magic)
    368   {
    369     magic_close (magic);
    370     magic = NULL;
    371   }
    372 }
    373 
    374 
    375 /* end of thumbnailgtk_extractor.c */