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 */