]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-ico.c
Catch invalid frame dimensions.
[~andy/gtk] / gdk-pixbuf / io-ico.c
index 83e2cf93bdcc8487a3be30a1e78899aa12a6c492..bac4d8e907a92b8e5a2fcf39982986df8b8729f4 100644 (file)
@@ -41,6 +41,7 @@ Known bugs:
 #include <string.h>
 #include "gdk-pixbuf-private.h"
 #include "gdk-pixbuf-io.h"
+#include <errno.h>
 
 \f
 
@@ -115,8 +116,8 @@ static void DumpBIH(unsigned char *BIH)
 
 /* Progressive loading */
 struct headerpair {
-       guint width;
-       guint height;
+       gint width;
+       gint height;
        guint depth;
        guint Negative;         /* Negative = 1 -> top down BMP,  
                                   Negative = 0 -> bottom up BMP */
@@ -138,8 +139,11 @@ struct ico_progressive_state {
        gint Lines;             /* # of finished lines */
 
        gint Type;              /*  
+                                  32 = RGBA
                                   24 = RGB
+                                  16 = 555 RGB
                                   8 = 8 bit colormapped
+                                  4 = 4 bpp colormapped
                                   1  = 1 bit bitonal 
                                 */
 
@@ -153,51 +157,84 @@ struct ico_progressive_state {
        GdkPixbuf *pixbuf;      /* Our "target" */
 };
 
-gpointer
+static gpointer
 gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
                                 ModuleUpdatedNotifyFunc updated_func,
-                                ModuleFrameDoneNotifyFunc frame_done_func,
-                                ModuleAnimationDoneNotifyFunc anim_done_func,
-                                gpointer user_data);
-void gdk_pixbuf__ico_image_stop_load(gpointer data);
-gboolean gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size);
-
+                                gpointer user_data,
+                                 GError **error);
+static gboolean gdk_pixbuf__ico_image_stop_load(gpointer data, GError **error);
+static gboolean gdk_pixbuf__ico_image_load_increment(gpointer data,
+                                                     const guchar * buf, guint size,
+                                                     GError **error);
+
+static void 
+context_free (struct ico_progressive_state *context)
+{
+       if (context->LineBuf != NULL)
+               g_free (context->LineBuf);
+       context->LineBuf = NULL;
+       if (context->HeaderBuf != NULL)
+               g_free (context->HeaderBuf);
 
+       if (context->pixbuf)
+               g_object_unref (context->pixbuf);
 
+       g_free (context);
+}
+                               
 /* Shared library entry point --> Can go when generic_image_load
    enters gdk-pixbuf-io */
-GdkPixbuf *
-gdk_pixbuf__ico_image_load(FILE * f)
+static GdkPixbuf *
+gdk_pixbuf__ico_image_load(FILE * f, GError **error)
 {
-       guchar *membuf;
+       guchar membuf [4096];
        size_t length;
        struct ico_progressive_state *State;
 
        GdkPixbuf *pb;
 
-       State = gdk_pixbuf__ico_image_begin_load(NULL, NULL, NULL, NULL, NULL);
-       membuf = g_malloc(4096);
+       State = gdk_pixbuf__ico_image_begin_load(NULL, NULL, NULL, error);
 
-       g_assert(membuf != NULL);
-
-       while (feof(f) == 0) {
+        if (State == NULL)
+               return NULL;
+        
+       while (!feof(f)) {
                length = fread(membuf, 1, 4096, f);
+               if (ferror (f)) {
+                       g_set_error (error,
+                                    G_FILE_ERROR,
+                                    g_file_error_from_errno (errno),
+                                    _("Failure reading ICO: %s"), g_strerror (errno));
+                       context_free (State);
+                       return NULL;
+               }
                if (length > 0)
-                       gdk_pixbuf__ico_image_load_increment(State, membuf, length);
-
+                        if (!gdk_pixbuf__ico_image_load_increment(State, membuf, length,
+                                                                  error)) {
+                               context_free (State);
+                                return NULL;
+                       }
        }
-       g_free(membuf);
        if (State->pixbuf != NULL)
-               gdk_pixbuf_ref(State->pixbuf);
+               g_object_ref (State->pixbuf);
+       else {
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                            _("ICO file was missing some data (perhaps it was truncated somehow?)"));
+               context_free (State);
+               return NULL;
+       }
 
        pb = State->pixbuf;
 
-       gdk_pixbuf__ico_image_stop_load(State);
+       gdk_pixbuf__ico_image_stop_load(State, NULL);
        return pb;
 }
 
 static void DecodeHeader(guchar *Data, gint Bytes,
-                        struct ico_progressive_state *State)
+                        struct ico_progressive_state *State,
+                        GError **error)
 {
 /* For ICO's we have to be very clever. There are multiple images possible
    in an .ICO. For now, we select (in order of priority):
@@ -213,11 +250,18 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        /* Step 1: The ICO header */
  
        IconCount = (Data[5] << 8) + (Data[4]);
+       
        State->HeaderSize = 6 + IconCount*16;
-       
+
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               State->HeaderBuf=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!State->HeaderBuf) {
+                       g_set_error (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                    _("Not enough memory to load icon"));
+                       return;
+               }
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes < State->HeaderSize)
@@ -251,14 +295,29 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                
                Ptr += 16;      
        } 
-               
+
+       if (State->DIBoffset < 0) {
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                            _("Invalid header in icon"));
+               return;
+       }
+
        /* We now have a winner, pointed to in State->DIBoffset,
           so we know how many bytes are in the "header" part. */
              
        State->HeaderSize = State->DIBoffset + 40; /* 40 = sizeof(InfoHeader) */
        
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               State->HeaderBuf=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!State->HeaderBuf) {
+                       g_set_error (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                    _("Not enough memory to load icon"));
+                       return;
+               }
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes<State->HeaderSize) 
@@ -274,9 +333,23 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                
        State->Header.width =
            (int)(BIH[7] << 24) + (BIH[6] << 16) + (BIH[5] << 8) + (BIH[4]);
+       if (State->Header.width == 0) {
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                            _("Icon has zero width"));
+               return;
+       }
        State->Header.height =
            (int)(BIH[11] << 24) + (BIH[10] << 16) + (BIH[9] << 8) + (BIH[8])/2;
            /* /2 because the BIH height includes the transparency mask */
+       if (State->Header.height == 0) {
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                            _("Icon has zero height"));
+               return;
+       }
        State->Header.depth = (BIH[15] << 8) + (BIH[14]);;
 
        State->Type = State->Header.depth;      
@@ -300,7 +373,14 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        State->HeaderSize+=I;
        
        if (State->HeaderSize>State->BytesInHeaderBuf) {
-               State->HeaderBuf=g_realloc(State->HeaderBuf,State->HeaderSize);
+               State->HeaderBuf=g_try_realloc(State->HeaderBuf,State->HeaderSize);
+               if (!State->HeaderBuf) {
+                       g_set_error (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                    _("Not enough memory to load icon"));
+                       return;
+               }
                State->BytesInHeaderBuf = State->HeaderSize;
        }
        if (Bytes < State->HeaderSize)
@@ -308,7 +388,12 @@ static void DecodeHeader(guchar *Data, gint Bytes,
 
        if ((BIH[16] != 0) || (BIH[17] != 0) || (BIH[18] != 0)
            || (BIH[19] != 0)) {
-               g_assert(0); /* Compressed icons aren't allowed */
+               /* FIXME: is this the correct message? */
+                g_set_error (error,
+                             GDK_PIXBUF_ERROR,
+                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                             _("Compressed icons are not supported"));
+               return;
        }
 
        /* Negative heights mean top-down pixel-order */
@@ -319,18 +404,29 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        if (State->Header.width < 0) {
                State->Header.width = -State->Header.width;
        }
+       g_assert (State->Header.width > 0);
+       g_assert (State->Header.height > 0);
 
-       if (State->Type == 24)
+        if (State->Type == 32)
+                State->LineWidth = State->Header.width * 4;
+        else if (State->Type == 24)
                State->LineWidth = State->Header.width * 3;
-       if (State->Type == 8)
+        else if (State->Type == 16)
+                State->LineWidth = State->Header.height * 2;
+        else if (State->Type == 8)
                State->LineWidth = State->Header.width * 1;
-       if (State->Type == 4) {
+        else if (State->Type == 4)
                State->LineWidth = (State->Header.width+1)/2;
-       }
-       if (State->Type == 1) {
+        else if (State->Type == 1) {
                State->LineWidth = State->Header.width / 8;
                if ((State->Header.width & 7) != 0)
                        State->LineWidth++;
+        } else {
+          g_set_error (error,
+                       GDK_PIXBUF_ERROR,
+                       GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                       _("Unsupported icon type"));
+          return;
        }
 
        /* Pad to a 32 bit boundary */
@@ -338,8 +434,16 @@ static void DecodeHeader(guchar *Data, gint Bytes,
                State->LineWidth = (State->LineWidth / 4) * 4 + 4;
 
 
-       if (State->LineBuf == NULL)
-               State->LineBuf = g_malloc(State->LineWidth);
+       if (State->LineBuf == NULL) {
+               State->LineBuf = g_try_malloc(State->LineWidth);
+               if (!State->LineBuf) {
+                       g_set_error (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                    _("Not enough memory to load icon"));
+                       return;
+               }
+       }
 
        g_assert(State->LineBuf != NULL);
 
@@ -347,12 +451,20 @@ static void DecodeHeader(guchar *Data, gint Bytes,
        if (State->pixbuf == NULL) {
                State->pixbuf =
                    gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
-                                  (gint) State->Header.width,
-                                  (gint) State->Header.height);
+                                  State->Header.width,
+                                  State->Header.height);
+               if (!State->pixbuf) {
+                       g_set_error (error,
+                                    GDK_PIXBUF_ERROR,
+                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                    _("Not enough memory to load icon"));
+                       return;
+               }
 
                if (State->prepared_func != NULL)
                        /* Notify the client that we are ready to go */
                        (*State->prepared_func) (State->pixbuf,
+                                                 NULL,
                                                 State->user_data);
 
        }
@@ -365,12 +477,11 @@ static void DecodeHeader(guchar *Data, gint Bytes,
  * return context (opaque to user)
  */
 
-gpointer
+static gpointer
 gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
                                 ModuleUpdatedNotifyFunc updated_func,
-                                ModuleFrameDoneNotifyFunc frame_done_func,
-                                ModuleAnimationDoneNotifyFunc anim_done_func,
-                                gpointer user_data)
+                                gpointer user_data,
+                                 GError **error)
 {
        struct ico_progressive_state *context;
 
@@ -380,7 +491,14 @@ gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
        context->user_data = user_data;
 
        context->HeaderSize = 54;
-       context->HeaderBuf = g_malloc(14 + 40 + 4*256 + 512);
+       context->HeaderBuf = g_try_malloc(14 + 40 + 4*256 + 512);
+       if (!context->HeaderBuf) {
+               g_set_error (error,
+                            GDK_PIXBUF_ERROR,
+                            GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                            _("Not enough memory to load ICO file"));
+               return NULL;
+       }
        /* 4*256 for the colormap */
        context->BytesInHeaderBuf = 14 + 40 + 4*256 + 512 ;
        context->HeaderDone = 0;
@@ -406,26 +524,45 @@ gdk_pixbuf__ico_image_begin_load(ModulePreparedNotifyFunc prepared_func,
  *
  * free context, unref gdk_pixbuf
  */
-void gdk_pixbuf__ico_image_stop_load(gpointer data)
+gboolean gdk_pixbuf__ico_image_stop_load(gpointer data,
+                                         GError **error)
 {
        struct ico_progressive_state *context =
            (struct ico_progressive_state *) data;
 
+        /* FIXME this thing needs to report errors if
+         * we have unused image data
+         */
 
-       g_return_if_fail(context != NULL);
-
-       if (context->LineBuf != NULL)
-               g_free(context->LineBuf);
-       context->LineBuf = NULL;
-       if (context->HeaderBuf != NULL)
-               g_free(context->HeaderBuf);
-
-       if (context->pixbuf)
-               gdk_pixbuf_unref(context->pixbuf);
-
-       g_free(context);
+       g_return_val_if_fail(context != NULL, TRUE);
+       
+       context_free (context);
+        return TRUE;
 }
 
+static void
+OneLine32 (struct ico_progressive_state *context)
+{
+        gint X;
+        guchar *Pixels;
+
+        X = 0;
+        if (context->Header.Negative == 0)
+                Pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride *
+                          (context->Header.height - context->Lines - 1));
+        else
+                Pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride *
+                          context->Lines);
+        while (X < context->Header.width) {
+                Pixels[X * 4 + 0] = context->LineBuf[X * 4 + 2];
+                Pixels[X * 4 + 1] = context->LineBuf[X * 4 + 1];
+                Pixels[X * 4 + 2] = context->LineBuf[X * 4 + 0];
+                Pixels[X * 4 + 3] = context->LineBuf[X * 4 + 3];
+                X++;
+        }
+}
 
 static void OneLine24(struct ico_progressive_state *context)
 {
@@ -450,6 +587,44 @@ static void OneLine24(struct ico_progressive_state *context)
 
 }
 
+static void
+OneLine16 (struct ico_progressive_state *context)
+{
+        int i;
+        guchar *pixels;
+        guchar *src;
+
+        if (context->Header.Negative == 0)
+                pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride * (context->Header.height - context->Lines - 1));
+        else
+                pixels = (context->pixbuf->pixels +
+                          context->pixbuf->rowstride * context->Lines);
+
+        src = context->LineBuf;
+
+        for (i = 0; i < context->Header.width; i++) {
+                int v, r, g, b;
+
+                v = (int) src[0] | ((int) src[1] << 8);
+                src += 2;
+
+                /* Extract 5-bit RGB values */
+
+                r = (v >> 10) & 0x1f;
+                g = (v >> 5) & 0x1f;
+                b = v & 0x1f;
+
+                /* Fill the rightmost bits to form 8-bit values */
+
+                *pixels++ = (r << 3) | (r >> 2);
+                *pixels++ = (g << 3) | (g >> 2);
+                *pixels++ = (b << 3) | (b >> 2);
+                pixels++; /* skip alpha channel */
+        }
+}
+
+
 static void OneLine8(struct ico_progressive_state *context)
 {
        gint X;
@@ -587,19 +762,22 @@ static void OneLine(struct ico_progressive_state *context)
        }
                
        if (context->Lines <context->Header.height) {           
-
-               if (context->Type == 24)
+                if (context->Type == 32)
+                        OneLine32 (context);
+               else if (context->Type == 24)
                        OneLine24(context);
-               if (context->Type == 8)
+                else if (context->Type == 16)
+                        OneLine16 (context);
+               else if (context->Type == 8)
                        OneLine8(context);
-               if (context->Type == 4)
+               else if (context->Type == 4)
                        OneLine4(context);
-               if (context->Type == 1)
+               else if (context->Type == 1)
                        OneLine1(context);
+               else 
+                       g_assert_not_reached ();
        } else
-       {
                OneLineTransp(context);
-       }
        
        context->Lines++;
        if (context->Lines>=context->Header.height) {
@@ -632,8 +810,11 @@ static void OneLine(struct ico_progressive_state *context)
  *
  * append image data onto inrecrementally built output image
  */
-gboolean
-gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
+static gboolean
+gdk_pixbuf__ico_image_load_increment(gpointer data,
+                                     const guchar * buf,
+                                     guint size,
+                                     GError **error)
 {
        struct ico_progressive_state *context =
            (struct ico_progressive_state *) data;
@@ -655,9 +836,8 @@ gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
                        size -= BytesToCopy;
                        buf += BytesToCopy;
                        context->HeaderDone += BytesToCopy;
-
-               }  else
-               {  
+               } 
+               else {
                        BytesToCopy =
                            context->LineWidth - context->LineDone;
                        if (BytesToCopy > size)
@@ -679,12 +859,25 @@ gdk_pixbuf__ico_image_load_increment(gpointer data, guchar * buf, guint size)
 
                }
 
-               if (context->HeaderDone >= 6)
+               if (context->HeaderDone >= 6) {
+                       GError *decode_err = NULL;
                        DecodeHeader(context->HeaderBuf,
-                                    context->HeaderDone, context);
-
-
+                                    context->HeaderDone, context, &decode_err);
+                       if (decode_err) {
+                               g_propagate_error (error, decode_err);
+                               return FALSE;
+                       }
+               }
        }
 
        return TRUE;
 }
+
+void
+gdk_pixbuf__ico_fill_vtable (GdkPixbufModule *module)
+{
+  module->load = gdk_pixbuf__ico_image_load;
+  module->begin_load = gdk_pixbuf__ico_image_begin_load;
+  module->stop_load = gdk_pixbuf__ico_image_stop_load;
+  module->load_increment = gdk_pixbuf__ico_image_load_increment;
+}