Commit message: gxps-images: clear the error before trying to load an image again

In gxps_images_get_image() we first try with the image file extension,
and if that fails then we try guessing the content type. If the image
load failed the first time, the GError might be filled already, so we
need to clear it before passing it to create functions again.
CVE: None
Vulnerability description: None

Project: libgxps
File: gxps-images.c

_jpeg_term_source (j_decompress_ptr cinfo)
{
}

Project: libgxps
File: gxps-images.c

fill_tiff_error (GError     **error,
		 const gchar *image_uri)
{
	if (_tiff_error) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading TIFF image %s: %s",
			     image_uri, _tiff_error);
		g_free (_tiff_error);
		_tiff_error = NULL;
	} else {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading TIFF image %s",
			     image_uri);
	}
}

Project: libgxps
File: gxps-images.c

png_warning_callback (png_structp png,
		      png_const_charp error_msg)
{
}

Project: libgxps
File: gxps-images.c

_jpeg_color_space_name (const J_COLOR_SPACE jpeg_color_space)
{
	switch (jpeg_color_space) {
	case JCS_UNKNOWN: return "UNKNOWN";
	case JCS_GRAYSCALE: return "GRAYSCALE";
	case JCS_RGB: return "RGB";
	case JCS_YCbCr: return "YCbCr";
	case JCS_CMYK: return "CMYK";
	case JCS_YCCK: return "YCCK";
	default: return "invalid";
	}
}

Project: libgxps
File: gxps-images.c

_jpeg_error_exit (j_common_ptr error)
{
	j_decompress_ptr cinfo = (j_decompress_ptr)error;
	struct _jpeg_src_mgr *src = (struct _jpeg_src_mgr *)cinfo->src;

	longjmp (src->setjmp_buffer, 1);
}

Project: libgxps
File: gxps-images.c

_tiff_push_handlers (void)
{
	orig_error_handler = TIFFSetErrorHandler (_tiff_error_handler);
	orig_warning_handler = TIFFSetWarningHandler (NULL);
}

Project: libgxps
File: gxps-images.c

_tiff_size (thandle_t handle)
{
	TiffBuffer *buffer = (TiffBuffer *)handle;

	return buffer->buffer_len;
}

Project: libgxps
File: gxps-images.c

_tiff_seek (thandle_t handle,
	    toff_t    offset,
	    int       whence)
{
	TiffBuffer *buffer = (TiffBuffer *)handle;

	switch (whence) {
	case SEEK_SET:
		if (offset > buffer->buffer_len)
			return -1;
		buffer->pos = offset;
		break;
	case SEEK_CUR:
		if (offset + buffer->pos >= buffer->buffer_len)
			return -1;
		buffer->pos += offset;
		break;
	case SEEK_END:
		if (offset + buffer->buffer_len > buffer->buffer_len)
			return -1;
		buffer->pos = buffer->buffer_len + offset;
		break;
	default:
		return -1;
	}

	return buffer->pos;
}

Project: libgxps
File: gxps-images.c

_jpeg_init_source (j_decompress_ptr cinfo)
{
}

Project: libgxps
File: gxps-images.c

png_error_callback (png_structp png_ptr,
		    png_const_charp error_msg)
{
	char **msg;

	msg = png_get_error_ptr (png_ptr);
	*msg = g_strdup (error_msg);
	longjmp (png_jmpbuf (png_ptr), 1);
}

Project: libgxps
File: gxps-images.c

gxps_images_create_from_jpeg (GXPSArchive *zip,
			      const gchar *image_uri,
			      GError     **error)
{
#ifdef HAVE_LIBJPEG
	GInputStream                 *stream;
	struct jpeg_error_mgr         error_mgr;
	struct jpeg_decompress_struct cinfo;
	struct _jpeg_src_mgr          src;
	GXPSImage                    *image;
	guchar                       *data;
	gint                          stride;
	JSAMPARRAY                    lines;
	gint                          jpeg_stride;
	gint                          i;

	stream = gxps_archive_open (zip, image_uri);
	if (!stream) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_SOURCE_NOT_FOUND,
			     "Image source %s not found in archive",
			     image_uri);
		return NULL;
	}

	jpeg_std_error (&error_mgr);
	error_mgr.error_exit = _jpeg_error_exit;

	jpeg_create_decompress (&cinfo);
	cinfo.err = &error_mgr;

	src.stream = stream;
	src.buffer = (JOCTET *)	(*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
							   JPEG_PROG_BUF_SIZE * sizeof (JOCTET));

	src.pub.init_source = _jpeg_init_source;
	src.pub.fill_input_buffer = _jpeg_fill_input_buffer;
	src.pub.skip_input_data = _jpeg_skip_input_data;
	src.pub.resync_to_restart = jpeg_resync_to_restart;
	src.pub.term_source = _jpeg_term_source;
	src.pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
	src.pub.next_input_byte = NULL; /* until buffer loaded */
	cinfo.src = (struct jpeg_source_mgr *)&src;

	if (setjmp (src.setjmp_buffer)) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading JPEG image %s",
			     image_uri);
		g_object_unref (stream);
		return NULL;
	}

	jpeg_read_header (&cinfo, TRUE);

	cinfo.do_fancy_upsampling = FALSE;
	jpeg_start_decompress (&cinfo);

	image = g_slice_new (GXPSImage);
	image->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
						     cinfo.output_width,
						     cinfo.output_height);
	image->res_x = 96;
	image->res_y = 96;
	if (cairo_surface_status (image->surface)) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading JPEG image %s: %s",
			     image_uri,
			     cairo_status_to_string (cairo_surface_status (image->surface)));
		jpeg_destroy_decompress (&cinfo);
		gxps_image_free (image);
		g_object_unref (stream);

		return NULL;
	}

	data = cairo_image_surface_get_data (image->surface);
	stride = cairo_image_surface_get_stride (image->surface);
	jpeg_stride = cinfo.output_width * cinfo.out_color_components;
	lines = cinfo.mem->alloc_sarray((j_common_ptr) &cinfo, JPOOL_IMAGE, jpeg_stride, 4);

	while (cinfo.output_scanline < cinfo.output_height) {
		gint n_lines, x;

		n_lines = jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height);
		for (i = 0; i < n_lines; i++) {
			JSAMPLE *line = lines[i];
			guchar  *p = data;

			for (x = 0; x < cinfo.output_width; x++) {
				switch (cinfo.out_color_space) {
				case JCS_RGB:
					p[0] = line[2];
					p[1] = line[1];
					p[2] = line[0];
					p[3] = 0xff;
					break;
                                case JCS_GRAYSCALE:
                                        p[0] = line[0];
                                        p[1] = line[0];
                                        p[2] = line[0];
                                        p[3] = 0xff;
                                        break;
				default:
					GXPS_DEBUG (g_message ("Unsupported jpeg color space %s",
                                                               _jpeg_color_space_name (cinfo.out_color_space)));

					gxps_image_free (image);
					jpeg_destroy_decompress (&cinfo);
					g_object_unref (stream);
					return NULL;
				}
				line += cinfo.out_color_components;
				p += 4;
			}

			data += stride;
		}
	}

	if (cinfo.density_unit == 1) { /* dots/inch */
		image->res_x = cinfo.X_density;
		image->res_y = cinfo.Y_density;
	} else if (cinfo.density_unit == 2) { /* dots/cm */
		image->res_x = cinfo.X_density * CENTIMETERS_PER_INCH;
		image->res_y = cinfo.Y_density * CENTIMETERS_PER_INCH;
	}

	jpeg_finish_decompress (&cinfo);
	jpeg_destroy_decompress (&cinfo);
	g_object_unref (stream);

	cairo_surface_mark_dirty (image->surface);

	if (cairo_surface_status (image->surface)) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading JPEG image %s: %s",
			     image_uri,
			     cairo_status_to_string (cairo_surface_status (image->surface)));
		gxps_image_free (image);

		return NULL;
	}

	return image;
#else
	return NULL;
#endif /* HAVE_LIBJPEG */
}

Project: libgxps
File: gxps-images.c

premultiply_data (png_structp   png,
                  png_row_infop row_info,
                  png_bytep     data)
{
	unsigned int i;

	for (i = 0; i < row_info->rowbytes; i += 4) {
		uint8_t *base  = &data[i];
		uint8_t  alpha = base[3];
		uint32_t p;

		if (alpha == 0) {
			p = 0;
		} else {
			uint8_t red   = base[0];
			uint8_t green = base[1];
			uint8_t blue  = base[2];

			if (alpha != 0xff) {
				red   = multiply_alpha (alpha, red);
				green = multiply_alpha (alpha, green);
				blue  = multiply_alpha (alpha, blue);
			}
			p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
		}
		memcpy (base, &p, sizeof (uint32_t));
	}
}

Project: libgxps
File: gxps-images.c

gxps_images_create_from_png (GXPSArchive *zip,
			     const gchar *image_uri,
			     GError     **error)
{
#ifdef HAVE_LIBPNG
	GInputStream  *stream;
	GXPSImage     *image = NULL;
	char          *png_err_msg = NULL;
	png_struct    *png;
	png_info      *info;
	png_byte      *data = NULL;
	png_byte     **row_pointers = NULL;
	png_uint_32    png_width, png_height;
	int            depth, color_type, interlace, stride;
	unsigned int   i;
	cairo_format_t format;
	cairo_status_t status;

	stream = gxps_archive_open (zip, image_uri);
	if (!stream) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_SOURCE_NOT_FOUND,
			     "Image source %s not found in archive",
			     image_uri);
		return NULL;
	}

	png = png_create_read_struct (PNG_LIBPNG_VER_STRING,
				      &png_err_msg,
				      png_error_callback,
				      png_warning_callback);
	if (png == NULL) {
		fill_png_error (error, image_uri, NULL);
		g_object_unref (stream);
		return NULL;
	}

	info = png_create_info_struct (png);
	if (info == NULL) {
		fill_png_error (error, image_uri, NULL);
		g_object_unref (stream);
		png_destroy_read_struct (&png, NULL, NULL);
		return NULL;
	}

	png_set_read_fn (png, stream, _read_png);

	if (setjmp (png_jmpbuf (png))) {
		fill_png_error (error, image_uri, png_err_msg);
		g_free (png_err_msg);
		g_object_unref (stream);
		png_destroy_read_struct (&png, &info, NULL);
		gxps_image_free (image);
		g_free (row_pointers);
		g_free (data);
		return NULL;
	}

	png_read_info (png, info);

	png_get_IHDR (png, info,
		      &png_width, &png_height, &depth,
		      &color_type, &interlace, NULL, NULL);

	/* convert palette/gray image to rgb */
	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb (png);

	/* expand gray bit depth if needed */
	if (color_type == PNG_COLOR_TYPE_GRAY)
		png_set_expand_gray_1_2_4_to_8 (png);

	/* transform transparency to alpha */
	if (png_get_valid (png, info, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha (png);

	if (depth == 16)
		png_set_strip_16 (png);

	if (depth < 8)
		png_set_packing (png);

	/* convert grayscale to RGB */
	if (color_type == PNG_COLOR_TYPE_GRAY ||
	    color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb (png);

	if (interlace != PNG_INTERLACE_NONE)
		png_set_interlace_handling (png);

	png_set_filler (png, 0xff, PNG_FILLER_AFTER);

	/* recheck header after setting EXPAND options */
	png_read_update_info (png, info);
	png_get_IHDR (png, info,
		      &png_width, &png_height, &depth,
		      &color_type, &interlace, NULL, NULL);
	if (depth != 8 ||
	    !(color_type == PNG_COLOR_TYPE_RGB ||
              color_type == PNG_COLOR_TYPE_RGB_ALPHA)) {
		fill_png_error (error, image_uri, NULL);
		g_object_unref (stream);
		png_destroy_read_struct (&png, &info, NULL);
		return NULL;
	}

	switch (color_type) {
	default:
		g_assert_not_reached();
		/* fall-through just in case ;-) */

	case PNG_COLOR_TYPE_RGB_ALPHA:
		format = CAIRO_FORMAT_ARGB32;
		png_set_read_user_transform_fn (png, premultiply_data);
		break;

	case PNG_COLOR_TYPE_RGB:
		format = CAIRO_FORMAT_RGB24;
		png_set_read_user_transform_fn (png, convert_bytes_to_data);
		break;
	}

	stride = cairo_format_stride_for_width (format, png_width);
	if (stride < 0 || png_height >= INT_MAX / stride) {
		fill_png_error (error, image_uri, NULL);
		g_object_unref (stream);
		png_destroy_read_struct (&png, &info, NULL);
		return NULL;
	}

	image = g_slice_new0 (GXPSImage);
	image->res_x = png_get_x_pixels_per_meter (png, info) * METERS_PER_INCH;
	if (image->res_x == 0)
		image->res_x = 96;
	image->res_y = png_get_y_pixels_per_meter (png, info) * METERS_PER_INCH;
	if (image->res_y == 0)
		image->res_y = 96;

	data = g_malloc (png_height * stride);
	row_pointers = g_new (png_byte *, png_height);

	for (i = 0; i < png_height; i++)
		row_pointers[i] = &data[i * stride];

	png_read_image (png, row_pointers);
	png_read_end (png, info);
	png_destroy_read_struct (&png, &info, NULL);
	g_object_unref (stream);
	g_free (row_pointers);

	image->surface = cairo_image_surface_create_for_data (data, format,
							      png_width, png_height,
							      stride);
	if (cairo_surface_status (image->surface)) {
		fill_png_error (error, image_uri, NULL);
		gxps_image_free (image);
		g_free (data);
		return NULL;
	}

	status = cairo_surface_set_user_data (image->surface,
					      &image_data_cairo_key,
					      data,
					      (cairo_destroy_func_t) g_free);
	if (status) {
		fill_png_error (error, image_uri, NULL);
		gxps_image_free (image);
		g_free (data);
		return NULL;
	}

	return image;
#else
    return NULL;
#endif  /* HAVE_LIBPNG */
}

Project: libgxps
File: gxps-images.c

_tiff_pop_handlers (void)
{
	TIFFSetErrorHandler (orig_error_handler);
	TIFFSetWarningHandler (orig_warning_handler);
}

Project: libgxps
File: gxps-images.c

_tiff_map_file (thandle_t handle,
		tdata_t  *buf,
		toff_t   *size)
{
	TiffBuffer *buffer = (TiffBuffer *)handle;

	*buf = buffer->buffer;
	*size = buffer->buffer_len;

	return 0;
}

Project: libgxps
File: gxps-images.c

_tiff_close (thandle_t context)
{
	return 0;
}

Project: libgxps
File: gxps-images.c

gxps_images_guess_content_type (GXPSArchive *zip,
				const gchar *image_uri)
{
	GInputStream *stream;
	guchar        buffer[1024];
	gssize        bytes_read;
	gchar        *mime_type;

	stream = gxps_archive_open (zip, image_uri);
	if (!stream)
		return NULL;

	bytes_read = g_input_stream_read (stream, buffer, 1024, NULL, NULL);
	mime_type = g_content_type_guess (NULL, buffer, bytes_read, NULL);
	g_object_unref (stream);

	return mime_type;
}

Project: libgxps
File: gxps-images.c

_read_png (png_structp png_ptr,
	   png_bytep data,
	   png_size_t len)
{
	GInputStream *stream;

	stream = png_get_io_ptr (png_ptr);
	g_input_stream_read (stream, data, len, NULL, NULL);
}

Project: libgxps
File: gxps-images.c

gxps_image_free (GXPSImage *image)
{
	if (G_UNLIKELY (!image))
		return;

	if (G_LIKELY (image->surface))
		cairo_surface_destroy (image->surface);

	g_slice_free (GXPSImage, image);
}

Project: libgxps
File: gxps-images.c

_jpeg_fill_input_buffer (j_decompress_ptr cinfo)
{
	struct _jpeg_src_mgr *src = (struct _jpeg_src_mgr *)cinfo->src;
	gssize num_bytes;

	num_bytes = g_input_stream_read (src->stream, src->buffer, JPEG_PROG_BUF_SIZE, NULL, NULL);
	if (num_bytes <= 0) {
		/* Insert a fake EOI marker */
		src->buffer[0] = (JOCTET) 0xFF;
		src->buffer[1] = (JOCTET) JPEG_EOI;
	}

	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = num_bytes;

	return TRUE;
}

Project: libgxps
File: gxps-images.c

_tiff_write (thandle_t handle,
	     tdata_t   buf,
	     tsize_t   size)
{
	return -1;
}

Project: libgxps
File: gxps-images.c

multiply_alpha (int alpha, int color)
{
	int temp = (alpha * color) + 0x80;

	return ((temp + (temp >> 8)) >> 8);
}

Project: libgxps
File: gxps-images.c

fill_png_error (GError      **error,
		const gchar  *image_uri,
		const gchar  *msg)
{
	if (msg) {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading PNG image %s: %s",
			     image_uri, msg);
	} else {
		g_set_error (error,
			     GXPS_ERROR,
			     GXPS_ERROR_IMAGE,
			     "Error loading PNG image %s",
			     image_uri);
	}
}

Project: libgxps
File: gxps-images.c

_tiff_unmap_file (thandle_t handle,
		  tdata_t   data,
		  toff_t    offset)
{
}

Project: libgxps
File: gxps-images.c

_tiff_read (thandle_t handle,
	    tdata_t   buf,
	    tsize_t   size)
{
	TiffBuffer *buffer = (TiffBuffer *)handle;

	if (buffer->pos + size > buffer->buffer_len)
		return 0;

	memcpy (buf, buffer->buffer + buffer->pos, size);
	buffer->pos += size;

	return size;
}

Project: libgxps
File: gxps-images.c

_tiff_error_handler (const char *mod,
		     const char *fmt,
		     va_list     ap)
{
	if (G_UNLIKELY (_tiff_error))
		return;

	_tiff_error = g_strdup_vprintf (fmt, ap);
}

Project: libgxps
File: gxps-images.c

gxps_images_get_image (GXPSArchive *zip,
		       const gchar *image_uri,
		       GError     **error)
{
	GXPSImage *image = NULL;
	gchar *image_uri_lower;

	/* First try with extensions,
	 * as it's recommended by the spec
	 * (2.1.5 Image Parts)
	 */
	image_uri_lower = g_utf8_strdown (image_uri, -1);
	if (g_str_has_suffix (image_uri_lower, ".png")) {
		image = gxps_images_create_from_png (zip, image_uri, error);
	} else if (g_str_has_suffix (image_uri_lower, ".jpg")) {
		image = gxps_images_create_from_jpeg (zip, image_uri, error);
	} else if (g_str_has_suffix (image_uri_lower, ".tif")) {
		image = gxps_images_create_from_tiff (zip, image_uri, error);
	} else if (g_str_has_suffix (image_uri_lower, "wdp")) {
		GXPS_DEBUG (g_message ("Unsupported image format windows media photo"));
		g_free (image_uri_lower);
		return NULL;
	}

	g_free (image_uri_lower);

	if (!image) {
		gchar *mime_type;

                g_clear_error(error);

		mime_type = gxps_images_guess_content_type (zip, image_uri);
		if (g_strcmp0 (mime_type, "image/png") == 0) {
			image = gxps_images_create_from_png (zip, image_uri, error);
		} else if (g_strcmp0 (mime_type, "image/jpeg") == 0) {
			image = gxps_images_create_from_jpeg (zip, image_uri, error);
		} else if (g_strcmp0 (mime_type, "image/tiff") == 0) {
			image = gxps_images_create_from_tiff (zip, image_uri, error);
		} else {
			GXPS_DEBUG (g_message ("Unsupported image format: %s", mime_type));
		}
		g_free (mime_type);
	}

	return image;
}

Project: libgxps
File: gxps-images.c

convert_bytes_to_data (png_structp png, png_row_infop row_info, png_bytep data)
{
	unsigned int i;

	for (i = 0; i < row_info->rowbytes; i += 4) {
		uint8_t *base  = &data[i];
		uint8_t  red   = base[0];
		uint8_t  green = base[1];
		uint8_t  blue  = base[2];
		uint32_t pixel;

		pixel = (0xff << 24) | (red << 16) | (green << 8) | (blue << 0);
		memcpy (base, &pixel, sizeof (uint32_t));
	}
}

Project: libgxps
File: gxps-images.c

_jpeg_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
	struct _jpeg_src_mgr *src = (struct _jpeg_src_mgr *)cinfo->src;

	if (num_bytes > 0) {
		while (num_bytes > (long) src->pub.bytes_in_buffer) {
			num_bytes -= (long) src->pub.bytes_in_buffer;
			_jpeg_fill_input_buffer (cinfo);
		}
		src->pub.next_input_byte += (size_t) num_bytes;
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
	}
}