Commit message: io-gif: Fail quickly when image dimensions are too big

Fail quickly when the dimensions would create an image that's bigger
than MAXINT bytes long.

See https://bugzilla.gnome.org/show_bug.cgi?id=765094

https://bugzilla.gnome.org/show_bug.cgi?id=785973
CVE: None
Vulnerability description: None

Project: gdk-pixbuf
File: io-gif.c

gif_set_get_extension (GifContext *context)
{
	context->state = GIF_GET_EXTENSION;
	context->extension_flag = TRUE;
	context->extension_label = 0;
	context->block_count = 0;
	context->block_ptr = 0;
}

Project: gdk-pixbuf
File: io-gif.c

set_gif_lzw_clear_code (GifContext *context)
{
	context->state = GIF_LZW_CLEAR_CODE;
	context->lzw_code_pending = -1;
}

Project: gdk-pixbuf
File: io-gif.c

gif_prepare_lzw (GifContext *context)
{
	gint i;

	if (!gif_read (context, &(context->lzw_set_code_size), 1)) {
		/*g_message (_("GIF: EOF / read error on image data\n"));*/
		return -1;
	}
        
        if (context->lzw_set_code_size > MAX_LZW_BITS) {
                g_set_error_literal (context->error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("GIF image is corrupt (incorrect LZW compression)"));
                return -2;
        }

	context->lzw_code_size = context->lzw_set_code_size + 1;
	context->lzw_clear_code = 1 << context->lzw_set_code_size;
	context->lzw_end_code = context->lzw_clear_code + 1;
	context->lzw_max_code_size = 2 * context->lzw_clear_code;
	context->lzw_max_code = context->lzw_clear_code + 2;
	context->lzw_fresh = TRUE;
	context->code_curbit = 0;
	context->code_lastbit = 0;
	context->code_last_byte = 0;
	context->code_done = FALSE;

        g_assert (context->lzw_clear_code <= 
                  G_N_ELEMENTS (context->lzw_table[0]));

	for (i = 0; i < context->lzw_clear_code; ++i) {
		context->lzw_table[0][i] = 0;
		context->lzw_table[1][i] = i;
	}
	for (; i < (1 << MAX_LZW_BITS); ++i)
		context->lzw_table[0][i] = context->lzw_table[1][0] = 0;

	context->lzw_sp = context->lzw_stack;
	gif_set_get_lzw (context);

	return 0;
}

Project: gdk-pixbuf
File: io-gif.c

gif_read (GifContext *context, guchar *buffer, size_t len)
{
	gboolean retval;
#ifdef IO_GIFDEBUG
	gint i;
#endif
	if (context->file) {
#ifdef IO_GIFDEBUG
		count += len;
		g_print ("Fsize :%d\tcount :%d\t", len, count);
#endif
		retval = (fread (buffer, 1, len, context->file) == len);

                if (!retval && ferror (context->file)) {
                        gint save_errno = errno;
                        g_set_error (context->error,
                                     G_FILE_ERROR,
                                     g_file_error_from_errno (save_errno),
                                     _("Failure reading GIF: %s"), 
                                     g_strerror (save_errno));
                }
                
#ifdef IO_GIFDEBUG
		if (len < 100) {
			for (i = 0; i < len; i++)
				g_print ("%d ", buffer[i]);
		}
		g_print ("\n");
#endif
                
		return retval;
	} else {
#ifdef IO_GIFDEBUG
/*  		g_print ("\tlooking for %d bytes.  size == %d, ptr == %d\n", len, context->size, context->ptr); */
#endif
		if ((context->size - context->ptr) >= len) {
#ifdef IO_GIFDEBUG
			count += len;
#endif
			memcpy (buffer, context->buf + context->ptr, len);
			context->ptr += len;
			context->amount_needed = 0;
#ifdef IO_GIFDEBUG
			g_print ("Psize :%d\tcount :%d\t", len, count);
			if (len < 100) {
				for (i = 0; i < len; i++)
					g_print ("%d ", buffer[i]);
			}
			g_print ("\n");
#endif
			return TRUE;
		}
		context->amount_needed = len - (context->size - context->ptr);
	}
	return FALSE;
}

Project: gdk-pixbuf
File: io-gif.c

get_code (GifContext *context,
	  int   code_size)
{
	int i, j, ret;

	if ((context->code_curbit + code_size) >= context->code_lastbit){
		gif_set_lzw_fill_buffer (context);
		return -3;
	}

	ret = 0;
	for (i = context->code_curbit, j = 0; j < code_size; ++i, ++j)
		ret |= ((context->block_buf[i / 8] & (1 << (i % 8))) != 0) << j;

	context->code_curbit += code_size;

	return ret;
}

Project: gdk-pixbuf
File: io-gif.c

gif_get_lzw (GifContext *context)
{
	guchar *dest, *temp;
	gint lower_bound, upper_bound; /* bounds for emitting the area_updated signal */
	gboolean bound_flag;
	gint first_pass; /* bounds for emitting the area_updated signal */
	gint v;

	if (context->frame == NULL) {
                context->frame = g_new (GdkPixbufFrame, 1);

                context->frame->composited = NULL;
                context->frame->revert = NULL;
                
                if (context->frame_len == 0 || context->frame_height == 0) {
                        /* An empty frame, we just output a single transparent
                         * pixel at (0, 0).
                         */
                        context->x_offset = 0;
                        context->y_offset = 0;
                        context->frame_len = 1;
                        context->frame_height = 1;
                        context->frame->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
                        if (context->frame->pixbuf) {
                                guchar *pixels;

                                pixels = gdk_pixbuf_get_pixels (context->frame->pixbuf);
                                pixels[0] = 0;
                                pixels[1] = 0;
                                pixels[2] = 0;
                                pixels[3] = 0;
                        }
                } else {
                        int rowstride;
                        guint64 len;

                        rowstride = gdk_pixbuf_calculate_rowstride (GDK_COLORSPACE_RGB,
                                                                    TRUE,
                                                                    8,
                                                                    context->frame_len,
                                                                    context->frame_height);
                        if (rowstride > 0 &&
                            g_uint64_checked_mul (&len, rowstride, context->frame_height) &&
                            len <= G_MAXINT) {
                                context->frame->pixbuf =
                                        gdk_pixbuf_new (GDK_COLORSPACE_RGB,
                                                        TRUE,
                                                        8,
                                                        context->frame_len,
                                                        context->frame_height);
                        } else {
                                context->frame->pixbuf = NULL;
                        }
                }

                if (!context->frame->pixbuf) {
                        g_free (context->frame);
                        g_set_error_literal (context->error,
                                             GDK_PIXBUF_ERROR,
                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                             _("Not enough memory to load GIF file"));
                        return -2;
                }

                context->frame->x_offset = context->x_offset;
                context->frame->y_offset = context->y_offset;
                context->frame->need_recomposite = TRUE;
                
                /* GIF delay is in hundredths, we want thousandths */
                context->frame->delay_time = context->gif89.delay_time * 10;

                /* GIFs with delay time 0 are mostly broken, but they
                 * just want a default, "not that fast" delay.
                 */
                if (context->frame->delay_time == 0)
                        context->frame->delay_time = 100;

                /* No GIFs gets to play faster than 50 fps. They just
                 * lock up poor gtk.
                 */
                if (context->frame->delay_time < 20)
                        context->frame->delay_time = 20; /* 20 = "fast" */
                
                context->frame->elapsed = context->animation->total_time;
                context->animation->total_time += context->frame->delay_time;                
                
                switch (context->gif89.disposal) {
                case 0:
                case 1:
                        context->frame->action = GDK_PIXBUF_FRAME_RETAIN;
                        break;
                case 2:
                        context->frame->action = GDK_PIXBUF_FRAME_DISPOSE;
                        break;
                case 3:
                        context->frame->action = GDK_PIXBUF_FRAME_REVERT;
                        break;
                default:
                        context->frame->action = GDK_PIXBUF_FRAME_RETAIN;
                        break;
                }

                context->frame->bg_transparent = (context->gif89.transparent == context->background_index);
                
                context->animation->n_frames ++;
                context->animation->frames = g_list_append (context->animation->frames, context->frame);

                /* Only call prepare_func for the first frame */
		if (context->animation->frames->next == NULL) { 
                        if (context->animation->width == 0 )
                                context->animation->width = gdk_pixbuf_get_width(context->frame->pixbuf);
                        if (context->animation->height == 0)
                                context->animation->height = gdk_pixbuf_get_height (context->frame->pixbuf);

                        if (context->prepare_func)
                                (* context->prepare_func) (context->frame->pixbuf,
                                                           GDK_PIXBUF_ANIMATION (context->animation),
                                                           context->user_data);
                } else {
                        /* Otherwise init frame with last frame */
                        GList *link;
                        GdkPixbufFrame *prev_frame;
                        gint x, y, w, h;
                        
                        link = g_list_find (context->animation->frames, context->frame);

                        prev_frame = link->prev->data;

                        gdk_pixbuf_gif_anim_frame_composite (context->animation, prev_frame);

                        /* Composite failed */
                        if (prev_frame->composited == NULL) {
                                GdkPixbufFrame *frame = NULL;
                                link = g_list_first (context->animation->frames);
                                while (link != NULL) {
                                        frame = (GdkPixbufFrame *)link->data;
                                        if (frame != NULL) {
                                                if (frame->pixbuf != NULL)
                                                        g_object_unref (frame->pixbuf);
                                                if (frame->composited != NULL)
                                                        g_object_unref (frame->composited);
                                                if (frame->revert != NULL)
                                                        g_object_unref (frame->revert);
                                                g_free (frame);
                                        }
                                        link = link->next;
                                }
                                
                                g_list_free (context->animation->frames);
                                context->animation->frames = NULL;
                                
                                g_set_error_literal (context->error,
                                                     GDK_PIXBUF_ERROR,
                                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                                     _("Not enough memory to composite a frame in GIF file"));
                                return -2;
                        }
                    
                        x = context->frame->x_offset;
                        y = context->frame->y_offset;
                        w = gdk_pixbuf_get_width (context->frame->pixbuf);
                        h = gdk_pixbuf_get_height (context->frame->pixbuf);
                        if (clip_frame (context, &x, &y, &w, &h))
                                gdk_pixbuf_copy_area (prev_frame->composited,
                                                      x, y, w, h,
                                                      context->frame->pixbuf,
                                                      0, 0);
                }
        }

	dest = gdk_pixbuf_get_pixels (context->frame->pixbuf);

	bound_flag = FALSE;
	lower_bound = upper_bound = context->draw_ypos;
	first_pass = context->draw_pass;

	while (TRUE) {
                guchar (*cmap)[MAXCOLORMAPSIZE];

                if (context->frame_cmap_active)
                        cmap = context->frame_color_map;
                else
                        cmap = context->global_color_map;
                
		v = lzw_read_byte (context);
		if (v < 0) {
			goto finished_data;
		}
		bound_flag = TRUE;

                g_assert (gdk_pixbuf_get_has_alpha (context->frame->pixbuf));
                
                temp = dest + context->draw_ypos * gdk_pixbuf_get_rowstride (context->frame->pixbuf) + context->draw_xpos * 4;
                *temp = cmap [0][(guchar) v];
                *(temp+1) = cmap [1][(guchar) v];
                *(temp+2) = cmap [2][(guchar) v];
                *(temp+3) = (guchar) ((v == context->gif89.transparent) ? 0 : 255);

		if (context->prepare_func && context->frame_interlace)
			gif_fill_in_lines (context, dest, v);

		context->draw_xpos++;
                
		if (context->draw_xpos == context->frame_len) {
			context->draw_xpos = 0;
			if (context->frame_interlace) {
				switch (context->draw_pass) {
				case 0:
				case 1:
					context->draw_ypos += 8;
					break;
				case 2:
					context->draw_ypos += 4;
					break;
				case 3:
					context->draw_ypos += 2;
					break;
				}

				if (context->draw_ypos >= context->frame_height) {
					context->draw_pass++;
					switch (context->draw_pass) {
					case 1:
						context->draw_ypos = 4;
						break;
					case 2:
						context->draw_ypos = 2;
						break;
					case 3:
						context->draw_ypos = 1;
						break;
					default:
						goto done;
					}
				}
			} else {
				context->draw_ypos++;
			}
			if (context->draw_pass != first_pass) {
				if (context->draw_ypos > lower_bound) {
					lower_bound = 0;
					upper_bound = context->frame_height;
				} else {
                                        
				}
			} else
				upper_bound = context->draw_ypos;
		}
		if (context->draw_ypos >= context->frame_height)
			break;
	}

 done:

        context->state = GIF_GET_NEXT_STEP;

        v = 0;

 finished_data:
        
        if (bound_flag)
                context->frame->need_recomposite = TRUE;
        
	if (bound_flag && context->update_func) {
		if (lower_bound <= upper_bound && first_pass == context->draw_pass) {
                        maybe_update (context,
                                      context->frame->x_offset,
                                      context->frame->y_offset + lower_bound,
                                      gdk_pixbuf_get_width (context->frame->pixbuf),
                                      upper_bound - lower_bound + 1);
		} else {
			if (lower_bound <= upper_bound) {
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              gdk_pixbuf_get_height (context->frame->pixbuf));
			} else {
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              upper_bound);
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset + lower_bound,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              gdk_pixbuf_get_height (context->frame->pixbuf) - lower_bound);
			}
		}
	}

	if (context->state == GIF_GET_NEXT_STEP) {
                /* Will be freed with context->animation, we are just
                 * marking that we're done with it (no current frame)
                 */
		context->frame = NULL;
                context->frame_cmap_active = FALSE;

                if (context->stop_after_first_frame)
                        context->state =  GIF_DONE;
	}
	
	return v;
}

Project: gdk-pixbuf
File: io-gif.c

gif_lzw_clear_code (GifContext *context)
{
	gint code;

	code = get_code (context, context->lzw_code_size);
	if (code == -3)
		return -0;

	context->lzw_firstcode = context->lzw_oldcode = code;
	context->lzw_code_pending = code;
	context->state = GIF_GET_LZW;
	return 0;
}

Project: gdk-pixbuf
File: io-gif.c

lzw_read_byte (GifContext *context)
{
	int code, incode;
	gint retval;
	gint my_retval;
	register int i;

	if (context->lzw_code_pending != -1) {
		retval = context->lzw_code_pending;
		context->lzw_code_pending = -1;
		return retval;
	}

	if (context->lzw_fresh) {
		context->lzw_fresh = FALSE;
		do {
			retval = get_code (context, context->lzw_code_size);
			if (retval < 0) {
				return retval;
			}

			context->lzw_firstcode = context->lzw_oldcode = retval;
		} while (context->lzw_firstcode == context->lzw_clear_code);
		return context->lzw_firstcode;
	}

	if (context->lzw_sp > context->lzw_stack) {
		my_retval = *--(context->lzw_sp);
		return my_retval;
	}

	while ((code = get_code (context, context->lzw_code_size)) >= 0) {
		if (code == context->lzw_clear_code) {
			for (i = 0; i < context->lzw_clear_code; ++i) {
				context->lzw_table[0][i] = 0;
				context->lzw_table[1][i] = i;
			}
			for (; i < (1 << MAX_LZW_BITS); ++i)
				context->lzw_table[0][i] = context->lzw_table[1][i] = 0;
			context->lzw_code_size = context->lzw_set_code_size + 1;
			context->lzw_max_code_size = 2 * context->lzw_clear_code;
			context->lzw_max_code = context->lzw_clear_code + 2;
			context->lzw_sp = context->lzw_stack;

			set_gif_lzw_clear_code (context);
			return -3;
		} else if (code == context->lzw_end_code) {
			int count;
			unsigned char buf[260];

                        /*  FIXME - we should handle this case */
                        g_set_error_literal (context->error,
                                             GDK_PIXBUF_ERROR,
                                             GDK_PIXBUF_ERROR_FAILED,
                                             _("GIF image loader cannot understand this image."));
                        return -2;
                        
			if (ZeroDataBlock) {
				return -2;
			}

			while ((count = GetDataBlock (context, buf)) > 0)
				;

			if (count != 0) {
				/*g_print (_("GIF: missing EOD in data stream (common occurence)"));*/
				return -2;
			}
		}

		incode = code;

		if (code >= context->lzw_max_code) {
                        CHECK_LZW_SP ();
			*(context->lzw_sp)++ = context->lzw_firstcode;
			code = context->lzw_oldcode;
		}

		while (code >= context->lzw_clear_code) {
                        if (code >= (1 << MAX_LZW_BITS)) {
                                g_set_error_literal (context->error,
                                                     GDK_PIXBUF_ERROR,
                                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                                     _("Bad code encountered"));
				return -2;
                        }
                        CHECK_LZW_SP ();
			*(context->lzw_sp)++ = context->lzw_table[1][code];

			if (code == context->lzw_table[0][code]) {
                                g_set_error_literal (context->error,
                                                     GDK_PIXBUF_ERROR,
                                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                                     _("Circular table entry in GIF file"));
				return -2;
			}
			code = context->lzw_table[0][code];
		}

                CHECK_LZW_SP ();
		*(context->lzw_sp)++ = context->lzw_firstcode = context->lzw_table[1][code];

		if ((code = context->lzw_max_code) < (1 << MAX_LZW_BITS)) {
			context->lzw_table[0][code] = context->lzw_oldcode;
			context->lzw_table[1][code] = context->lzw_firstcode;
			++context->lzw_max_code;
			if ((context->lzw_max_code >= context->lzw_max_code_size) &&
			    (context->lzw_max_code_size < (1 << MAX_LZW_BITS))) {
				context->lzw_max_code_size *= 2;
				++context->lzw_code_size;
			}
		}

		context->lzw_oldcode = incode;

		if (context->lzw_sp > context->lzw_stack) {
			my_retval = *--(context->lzw_sp);
			return my_retval;
		}
	}
	return code;
}

Project: gdk-pixbuf
File: io-gif.c

gif_set_prepare_lzw (GifContext *context)
{
	context->state = GIF_PREPARE_LZW;
	context->lzw_code_pending = -1;
}

Project: gdk-pixbuf
File: io-gif.c

gif_init (GifContext *context)
{
	unsigned char buf[16];
	char version[4];

	if (!gif_read (context, buf, 6)) {
		/* Unable to read magic number,
                 * gif_read() should have set error
                 */
		return -1;
	}

	if (strncmp ((char *) buf, "GIF", 3) != 0) {
		/* Not a GIF file */
                g_set_error_literal (context->error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("File does not appear to be a GIF file"));
		return -2;
	}

	strncpy (version, (char *) buf + 3, 3);
	version[3] = '\0';

	if ((strcmp (version, "87a") != 0) && (strcmp (version, "89a") != 0)) {
		/* bad version number, not '87a' or '89a' */
                g_set_error (context->error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Version %s of the GIF file format is not supported"),
                             version);
		return -2;
	}

	/* read the screen descriptor */
	if (!gif_read (context, buf, 7)) {
		/* Failed to read screen descriptor, error set */
		return -1;
	}

	context->width = LM_to_uint (buf[0], buf[1]);
	context->height = LM_to_uint (buf[2], buf[3]);
        /* The 4th byte is
         * high bit: whether to use the background index
         * next 3:   color resolution
         * next:     whether colormap is sorted by priority of allocation
         * last 3:   size of colormap
         */
	context->global_bit_pixel = 2 << (buf[4] & 0x07);
	context->global_color_resolution = (((buf[4] & 0x70) >> 3) + 1);
        context->has_global_cmap = (buf[4] & 0x80) != 0;
	context->background_index = buf[5];
	context->aspect_ratio = buf[6];

        /* Use background of transparent black as default, though if
         * one isn't set explicitly no one should ever use it.
         */
        context->animation->bg_red = 0;
        context->animation->bg_green = 0;
        context->animation->bg_blue = 0;

        context->animation->width = context->width;
        context->animation->height = context->height;

        if (context->size_func) {
                gint width, height;

                width = context->width;
                height = context->height;

                (*context->size_func) (&width, &height, context->user_data);

                if (width == 0 || height == 0) {
                        g_set_error_literal (context->error,
                                             GDK_PIXBUF_ERROR,
                                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                             _("Resulting GIF image has zero size"));
                        return -2;
                }
        }

	if (context->has_global_cmap) {
		gif_set_get_colormap (context);
	} else {
		context->state = GIF_GET_NEXT_STEP;
	}

#ifdef DUMP_IMAGE_DETAILS
        g_print (">Image width: %d height: %d global_cmap: %d background: %d\n",
                 context->width, context->height, context->has_global_cmap, context->background_index);
#endif
        
	return 0;
}

Project: gdk-pixbuf
File: io-gif.c

gif_lzw_fill_buffer (GifContext *context)
{
	gint retval;

	if (context->code_done) {
		if (context->code_curbit >= context->code_lastbit) {
                        g_set_error_literal (context->error,
                                             GDK_PIXBUF_ERROR,
                                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                             _("GIF file was missing some data (perhaps it was truncated somehow?)"));

			return -2;
		}
                /* Is this supposed to be an error or what? */
		/* g_message ("trying to read more data after we've done stuff\n"); */
                g_set_error (context->error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_FAILED,
                             _("Internal error in the GIF loader (%s)"),
                             G_STRLOC);
                
		return -2;
	}

	context->block_buf[0] = context->block_buf[context->code_last_byte - 2];
	context->block_buf[1] = context->block_buf[context->code_last_byte - 1];

	retval = get_data_block (context, &context->block_buf[2], NULL);

	if (retval == -1)
		return -1;

	if (context->block_count == 0)
		context->code_done = TRUE;

	context->code_last_byte = 2 + context->block_count;
	context->code_curbit = (context->code_curbit - context->code_lastbit) + 16;
	context->code_lastbit = (2 + context->block_count) * 8;

	context->state = context->old_state;
	return 0;
}

Project: gdk-pixbuf
File: io-gif.c

gif_main_loop (GifContext *context)
{
	gint retval = 0;

	do {
		switch (context->state) {
		case GIF_START:
                        LOG("start\n");
			retval = gif_init (context);
			break;

		case GIF_GET_COLORMAP:
                        LOG("get_colormap\n");
			retval = gif_get_colormap (context);
			if (retval == 0)
				context->state = GIF_GET_NEXT_STEP;
			break;

		case GIF_GET_NEXT_STEP:
                        LOG("next_step\n");
			retval = gif_get_next_step (context);
			break;

		case GIF_GET_FRAME_INFO:
                        LOG("frame_info\n");
			retval = gif_get_frame_info (context);
			break;

		case GIF_GET_EXTENSION:
                        LOG("get_extension\n");
			retval = gif_get_extension (context);
			if (retval == 0)
				context->state = GIF_GET_NEXT_STEP;
			break;

		case GIF_GET_COLORMAP2:
                        LOG("get_colormap2\n");
			retval = gif_get_colormap2 (context);
			if (retval == 0)
				gif_set_prepare_lzw (context);
			break;

		case GIF_PREPARE_LZW:
                        LOG("prepare_lzw\n");
			retval = gif_prepare_lzw (context);
			break;

		case GIF_LZW_FILL_BUFFER:
                        LOG("fill_buffer\n");
			retval = gif_lzw_fill_buffer (context);
			break;

		case GIF_LZW_CLEAR_CODE:
                        LOG("clear_code\n");
			retval = gif_lzw_clear_code (context);
			break;

		case GIF_GET_LZW:
                        LOG("get_lzw\n");
			retval = gif_get_lzw (context);
			break;

		case GIF_DONE:
                        LOG("done\n");
                        /* fall through */
		default:
			retval = 0;
			goto done;
		};
	} while ((retval == 0) || (retval == -3));
 done:
	return retval;
}