Commit message: Fix invalid read in gdImageCreateFromTiffPtr()

tiff_invalid_read.tiff is corrupt, and causes an invalid read in
gdImageCreateFromTiffPtr(), but not in gdImageCreateFromTiff(). The culprit
is dynamicGetbuf(), which doesn't check for out-of-bound reads. In this case,
dynamicGetbuf() is called with a negative dp->pos, but also positive buffer
overflows have to be handled, in which case 0 has to be returned (cf. commit
75e29a9).

Fixing dynamicGetbuf() exhibits that the corrupt TIFF would still create
the image, because the return value of TIFFReadRGBAImage() is not checked.
We do that, and let createFromTiffRgba() fail if TIFFReadRGBAImage() fails.

This issue had been reported by Ibrahim El-Sayed to security@libgd.org.

CVE-2016-6911
CVE: CVE-2016-6911
CWE: CWE-125
Vulnerability description: The dynamicGetbuf function in the GD Graphics Library (aka libgd) before 2.2.4 allows remote attackers to cause a denial of service (out-of-bounds read) via a crafted TIFF image.

Project: libgd

BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffCtx(gdIOCtx *infile)
{
	TIFF *tif;
	tiff_handle *th;

	uint16 bps, spp, photometric;
	uint16 orientation;
	int width, height;
	uint16 extra, *extra_types;
	uint16 planar;
	char	has_alpha, is_bw, is_gray;
	char	force_rgba = FALSE;
	char	save_transparent;
	int		image_type;
	int   ret;
	float res_float;

	gdImagePtr im = NULL;

	th = new_tiff_handle(infile);
	if (!th) {
		return NULL;
	}

	tif = TIFFClientOpen("", "rb", th, tiff_readproc,
	                     tiff_writeproc,
	                     tiff_seekproc,
	                     tiff_closeproc,
	                     tiff_sizeproc,
	                     tiff_mapproc,
	                     tiff_unmapproc);

	if (!tif) {
		gd_error("Cannot open TIFF image");
		gdFree(th);
		return NULL;
	}

	if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width)) {
		gd_error("TIFF error, Cannot read image width");
		goto error;
	}

	if (!TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) {
		gd_error("TIFF error, Cannot read image width");
		goto error;
	}

	TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bps);

	/* Unsupported bps, force to RGBA */
	if (1/*bps > 8 && bps != 16*/) {
		force_rgba = TRUE;
	}

	TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &spp);

	if (!TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types)) {
		extra = 0;
	}

	if (!TIFFGetField (tif, TIFFTAG_PHOTOMETRIC, &photometric)) {
		uint16 compression;
		if (TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression) &&
		        (compression == COMPRESSION_CCITTFAX3 ||
		         compression == COMPRESSION_CCITTFAX4 ||
		         compression == COMPRESSION_CCITTRLE ||
		         compression == COMPRESSION_CCITTRLEW)) {
			gd_error("Could not get photometric. "
			        "Image is CCITT compressed, assuming min-is-white");
			photometric = PHOTOMETRIC_MINISWHITE;
		} else {
			gd_error("Could not get photometric. "
			        "Assuming min-is-black");

			photometric = PHOTOMETRIC_MINISBLACK;
		}
	}
	save_transparent = FALSE;

	/* test if the extrasample represents an associated alpha channel... */
	if (extra > 0 && (extra_types[0] == EXTRASAMPLE_ASSOCALPHA)) {
		has_alpha = TRUE;
		save_transparent = FALSE;
		--extra;
	} else if (extra > 0 && (extra_types[0] == EXTRASAMPLE_UNASSALPHA)) {
		has_alpha = TRUE;
		save_transparent = TRUE;
		--extra;
	} else if (extra > 0 && (extra_types[0] == EXTRASAMPLE_UNSPECIFIED)) {
		/* assuming unassociated alpha if unspecified */
		gd_error("alpha channel type not defined, assuming alpha is not premultiplied");
		has_alpha = TRUE;
		save_transparent = TRUE;
		--extra;
	} else {
		has_alpha = FALSE;
	}

	if (photometric == PHOTOMETRIC_RGB && spp > 3 + extra) {
		has_alpha = TRUE;
		extra = spp - 4;
	} else if (photometric != PHOTOMETRIC_RGB && spp > 1 + extra) {
		has_alpha = TRUE;
		extra = spp - 2;
	}

	is_bw = FALSE;
	is_gray = FALSE;

	switch (photometric) {
	case PHOTOMETRIC_MINISBLACK:
	case PHOTOMETRIC_MINISWHITE:
		if (!has_alpha && bps == 1 && spp == 1) {
			image_type = GD_INDEXED;
			is_bw = TRUE;
		} else {
			image_type = GD_GRAY;
		}
		break;

	case PHOTOMETRIC_RGB:
		image_type = GD_RGB;
		break;

	case PHOTOMETRIC_PALETTE:
		image_type = GD_INDEXED;
		break;

	default:
		force_rgba = TRUE;
		break;
	}

	if (!TIFFGetField (tif, TIFFTAG_PLANARCONFIG, &planar)) {
		planar = PLANARCONFIG_CONTIG;
	}

	/* Force rgba if image plans are not contiguous */
	if (force_rgba || planar != PLANARCONFIG_CONTIG) {
		image_type = GD_RGB;
	}

	if (!force_rgba &&
	        (image_type == GD_PALETTE || image_type == GD_INDEXED || image_type == GD_GRAY)) {
		im = gdImageCreate(width, height);
		if (!im) goto error;
		readTiffColorMap(im, tif, is_bw, photometric);
	} else {
		im = gdImageCreateTrueColor(width, height);
		if (!im) goto error;
	}

#ifdef DEBUG
	printf("force rgba: %i\n", force_rgba);
	printf("has_alpha: %i\n", has_alpha);
	printf("save trans: %i\n", save_transparent);
	printf("is_bw: %i\n", is_bw);
	printf("is_gray: %i\n", is_gray);
	printf("type: %i\n", image_type);
#else
	(void)is_gray;
	(void)save_transparent;
#endif

	if (force_rgba) {
		ret = createFromTiffRgba(tif, im);
	} else if (TIFFIsTiled(tif)) {
		ret = createFromTiffTiles(tif, im, bps, photometric, has_alpha, is_bw, extra);
	} else {
		ret = createFromTiffLines(tif, im, bps, photometric, has_alpha, is_bw, extra);
	}

	if (!ret) {
		gdImageDestroy(im);
		im = NULL;
		goto error;
	}

	if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &res_float)) { 
		im->res_x = (unsigned int)res_float;  //truncate
	}
	if (TIFFGetField(tif, TIFFTAG_YRESOLUTION, &res_float)) { 
		im->res_y = (unsigned int)res_float;  //truncate
	}

	if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &orientation)) {
		switch (orientation) {
		case ORIENTATION_TOPLEFT:
		case ORIENTATION_TOPRIGHT:
		case ORIENTATION_BOTRIGHT:
		case ORIENTATION_BOTLEFT:
			break;

		default:
			gd_error("Orientation %d not handled yet!", orientation);
			break;
		}
	}
error:
	TIFFClose(tif);
	gdFree(th);
	return im;
}

Project: libgd

BGD_DECLARE(gdImagePtr) gdImageCreateFromTiff(FILE *inFile)
{
	gdImagePtr im;
	gdIOCtx *in = gdNewFileCtx(inFile);
	if (in == NULL) return NULL;
	im = gdImageCreateFromTiffCtx(in);
	in->gd_free(in);
	return im;
}

Project: libgd

BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffPtr(int size, void *data)
{
	gdImagePtr im;
	gdIOCtx *in = gdNewDynamicCtxEx (size, data, 0);
	if (in == NULL) return NULL;
	im = gdImageCreateFromTiffCtx(in);
	in->gd_free(in);
	return im;
}

Project: libgd

static int checkColorMap(n, r, g, b)
int n;
uint16 *r, *g, *b;
{
	while (n-- > 0)
		if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256)
			return (16);
	return (8);
}

Project: libgd

static int createFromTiffLines(TIFF *tif, gdImagePtr im, uint16 bps, uint16 photometric,
                               char has_alpha, char is_bw, int extra)
{
	uint16  planar;
	uint32 im_height, im_width, y;

	unsigned char *buffer;

	if (!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar)) {
		planar = PLANARCONFIG_CONTIG;
	}

	if (!TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &im_height)) {
		gd_error("Can't fetch TIFF height\n");
		return FALSE;
	}

	if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &im_width)) {
		gd_error("Can't fetch TIFF width \n");
		return FALSE;
	}

	buffer = (unsigned char *)gdMalloc(im_width * 4);
	if (!buffer) {
		return GD_FAILURE;
	}
	if (planar == PLANARCONFIG_CONTIG) {
		switch (bps) {
		case 16:
			/* TODO
			 * or simply use force_rgba
			 */
			break;

		case 8:
			for (y = 0; y < im_height; y++ ) {
				if (!TIFFReadScanline (tif, buffer, y, 0)) {
					gd_error("Error while reading scanline %i", y);
					break;
				}
				/* reading one line at a time */
				readTiff8bit(buffer, im, photometric, 0, y, im_width, 1, has_alpha, extra, 0);
			}
			break;

		default:
			if (is_bw) {
				for (y = 0; y < im_height; y++ ) {
					if (!TIFFReadScanline (tif, buffer, y, 0)) {
						gd_error("Error while reading scanline %i", y);
						break;
					}
					/* reading one line at a time */
					readTiffBw(buffer, im, photometric, 0, y, im_width, 1, has_alpha, extra, 0);
				}
			} else {
				/* TODO: implement some default reader or detect this case earlier > force_rgb */
			}
			break;
		}
	} else {
		/* TODO: implement a reader for separate panes. We detect this case earlier for now and use force_rgb */
	}

	gdFree(buffer);
	return GD_SUCCESS;
}

Project: libgd

static int createFromTiffTiles(TIFF *tif, gdImagePtr im, uint16 bps, uint16 photometric,
                               char has_alpha, char is_bw, int extra)
{
	uint16  planar;
	int im_width, im_height;
	int tile_width, tile_height;
	int  x, y, height, width;
	unsigned char *buffer;

	if (!TIFFGetField (tif, TIFFTAG_PLANARCONFIG, &planar)) {
		planar = PLANARCONFIG_CONTIG;
	}
	if (TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &im_width) == 0 ||
		TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &im_height) == 0 ||
		TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width) ==  0 ||
		TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height) == 0) {
		return FALSE;
	}

	buffer = (unsigned char *) gdMalloc (TIFFTileSize (tif));
	if (!buffer) {
		return FALSE;
	}

	for (y = 0; y < im_height; y += tile_height) {
		for (x = 0; x < im_width; x += tile_width) {
			TIFFReadTile(tif, buffer, x, y, 0, 0);
			width = MIN(im_width - x, tile_width);
			height = MIN(im_height - y, tile_height);
			if (bps == 16) {
			} else if (bps == 8) {
				readTiff8bit(buffer, im, photometric, x, y, width, height, has_alpha, extra, 0);
			} else if (is_bw) {
				readTiffBw(buffer, im, photometric, x, y, width, height, has_alpha, extra, 0);
			} else {
				/* TODO: implement some default reader or detect this case earlier use force_rgb */
			}
		}
	}
	gdFree(buffer);
	return TRUE;
}

Project: libgd

BGD_DECLARE(void) gdImageTiff(gdImagePtr im, FILE *outFile)
{
	gdIOCtx *out = gdNewFileCtx(outFile);
	if (out == NULL) return;
	gdImageTiffCtx(im, out); /* what's an fg again? */
	out->gd_free(out);
}

Project: libgd

BGD_DECLARE(void *) gdImageTiffPtr(gdImagePtr im, int *size)
{
	void *rv;
	gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
	if (out == NULL) return NULL;
	gdImageTiffCtx(im, out); /* what's an fg again? */
	rv = gdDPExtractData(out, size);
	out->gd_free(out);
	return rv;
}

Project: libgd

tiff_handle * new_tiff_handle(gdIOCtx *g)
{
	tiff_handle * t;

	if (!g) {
		gd_error("Cannot create a new tiff handle, missing Ctx argument");
		return NULL;
	}

	t = (tiff_handle *) gdMalloc(sizeof(tiff_handle));
	if (!t) {
		gd_error("Failed to allocate a new tiff handle");
		return NULL;
	}

	t->size = 0;
	t->pos = 0;
	t->ctx = g;
	t->written = 0;

	return t;
}

Project: libgd

static void readTiffBw (const unsigned char *src,
			gdImagePtr im,
			uint16       photometric,
			int          startx,
			int          starty,
			int          width,
			int          height,
			char         has_alpha,
			int          extra,
			int          align)
{
	int x = startx, y = starty;

	(void)has_alpha;
	(void)extra;
	(void)align;

	for (y = starty; y < starty + height; y++) {
		for (x = startx; x < startx + width; x++) {
			register unsigned char curr = *src++;
			register unsigned char mask;

			if (photometric == PHOTOMETRIC_MINISWHITE) {
				curr = ~curr;
			}
			for (mask = 0x80; mask != 0 && x < startx + width; mask >>= 1) {
				gdImageSetPixel(im, x, y, ((curr & mask) != 0)?0:1);
			}
		}
	}
}

Project: libgd

static int readTiffColorMap(gdImagePtr im, TIFF *tif, char is_bw, int photometric)
{
	uint16 *redcmap, *greencmap, *bluecmap;
	uint16 bps;
	int i;

	if (is_bw) {
		if (photometric == PHOTOMETRIC_MINISWHITE) {
			gdImageColorAllocate(im, 255,255,255);
			gdImageColorAllocate(im, 0, 0, 0);
		} else {
			gdImageColorAllocate(im, 0, 0, 0);
			gdImageColorAllocate(im, 255,255,255);
		}
	} else {
		uint16 min_sample_val, max_sample_val;

		if (!TIFFGetField(tif, TIFFTAG_MINSAMPLEVALUE, &min_sample_val)) {
			min_sample_val = 0;
		}
		if (!TIFFGetField(tif, TIFFTAG_MAXSAMPLEVALUE, &max_sample_val)) {
			max_sample_val = 255;
		}

		if (photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE) {
			/* TODO: use TIFFTAG_MINSAMPLEVALUE and TIFFTAG_MAXSAMPLEVALUE */
			/* Gray level palette */
			for (i=min_sample_val; i <= max_sample_val; i++) {
				gdImageColorAllocate(im, i,i,i);
			}
			return GD_SUCCESS;

		} else if (!TIFFGetField(tif, TIFFTAG_COLORMAP, &redcmap, &greencmap, &bluecmap)) {
			gd_error("Cannot read the color map");
			return GD_FAILURE;
		}

		TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps);

#define	CVT(x)		(((x) * 255) / ((1L<<16)-1))
		if (checkColorMap(1<<bps, redcmap, greencmap, bluecmap) == 16) {
			for (i = (1<<bps)-1; i > 0; i--) {
				redcmap[i] = CVT(redcmap[i]);
				greencmap[i] = CVT(greencmap[i]);
				bluecmap[i] = CVT(bluecmap[i]);
			}
		}
		for (i = 0; i < 256; i++) {
			gdImageColorAllocate(im, redcmap[i], greencmap[i], bluecmap[i]);
		}
#undef CVT
	}
	return GD_SUCCESS;
}

Project: libgd

void tiffWriter(gdImagePtr image, gdIOCtx *out, int bitDepth)
{
	int x, y;
	int i;
	int r, g, b, a;
	TIFF *tiff;
	int width, height;
	int color;
	char *scan;
	int samplesPerPixel = 3;
	int bitsPerSample;
	int transparentColorR = -1;
	int transparentColorG = -1;
	int transparentColorB = -1;
	uint16 extraSamples[1];
	uint16 *colorMapRed = NULL;
	uint16 *colorMapGreen = NULL;
	uint16 *colorMapBlue = NULL;

	tiff_handle *th;

	th = new_tiff_handle(out);
	if (!th) {
		return;
	}
	extraSamples[0] = EXTRASAMPLE_ASSOCALPHA;

	/* read in the width/height of gd image */
	width = gdImageSX(image);
	height = gdImageSY(image);

	/* reset clip region to whole image */
	gdImageSetClip(image, 0, 0, width, height);

	/* handle old-style single-colour mapping to 100% transparency */
	if(image->transparent != -1) {
		/* set our 100% transparent colour value */
		transparentColorR = gdImageRed(image, image->transparent);
		transparentColorG = gdImageGreen(image, image->transparent);
		transparentColorB = gdImageBlue(image, image->transparent);
	}

	/* Open tiff file writing routines, but use special read/write/seek
	 * functions so that tiff lib writes correct bits of tiff content to
	 * correct areas of file opened and modifieable by the gdIOCtx functions
	 */
	tiff = TIFFClientOpen("", "w", th,	tiff_readproc,
			      tiff_writeproc,
			      tiff_seekproc,
			      tiff_closeproc,
			      tiff_sizeproc,
			      tiff_mapproc,
			      tiff_unmapproc);

	TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width);
	TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height);
	TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
	TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC,
		     (bitDepth == 24) ? PHOTOMETRIC_RGB : PHOTOMETRIC_PALETTE);

	bitsPerSample = (bitDepth == 24 || bitDepth == 8) ? 8 : 1;
	TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bitsPerSample);

	TIFFSetField(tiff, TIFFTAG_XRESOLUTION, (float)image->res_x);
	TIFFSetField(tiff, TIFFTAG_YRESOLUTION, (float)image->res_y);

	/* build the color map for 8 bit images */
	if(bitDepth != 24) {
		colorMapRed   = (uint16 *) gdMalloc(3 * (1 << bitsPerSample));
		if (!colorMapRed) {
			gdFree(th);
			return;
		}
		colorMapGreen = (uint16 *) gdMalloc(3 * (1 << bitsPerSample));
		if (!colorMapGreen) {
			gdFree(colorMapRed);
			gdFree(th);
			return;
		}
		colorMapBlue  = (uint16 *) gdMalloc(3 *  (1 << bitsPerSample));
		if (!colorMapBlue) {
			gdFree(colorMapRed);
			gdFree(colorMapGreen);
			gdFree(th);
			return;
		}

		for(i = 0; i < image->colorsTotal; i++) {
			colorMapRed[i]   = gdImageRed(image,i) + (gdImageRed(image,i) * 256);
			colorMapGreen[i] = gdImageGreen(image,i)+(gdImageGreen(image,i)*256);
			colorMapBlue[i]  = gdImageBlue(image,i) + (gdImageBlue(image,i)*256);
		}

		TIFFSetField(tiff, TIFFTAG_COLORMAP, colorMapRed, colorMapGreen,
			     colorMapBlue);
		samplesPerPixel = 1;
	}

	/* here, we check if the 'save alpha' flag is set on the source gd image */
	if ((bitDepth == 24) &&
	    (image->saveAlphaFlag || image->transparent != -1)) {
		/* so, we need to store the alpha values too!
		 * Also, tell TIFF what the extra sample means (associated alpha) */
		samplesPerPixel = 4;
		TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel);
		TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, extraSamples);
	} else {
		TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel);
	}

	TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1);

	if(overflow2(width, samplesPerPixel)) {
		if (colorMapRed)   gdFree(colorMapRed);
		if (colorMapGreen) gdFree(colorMapGreen);
		if (colorMapBlue)  gdFree(colorMapBlue);
		gdFree(th);
		return;
	}

	if(!(scan = (char *)gdMalloc(width * samplesPerPixel))) {
		if (colorMapRed)   gdFree(colorMapRed);
		if (colorMapGreen) gdFree(colorMapGreen);
		if (colorMapBlue)  gdFree(colorMapBlue);
		gdFree(th);
		return;
	}

	/* loop through y-coords, and x-coords */
	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			/* generate scan line for writing to tiff */
			color = gdImageGetPixel(image, x, y);

			a = (127 - gdImageAlpha(image, color)) * 2;
			a = (a == 0xfe) ? 0xff : a & 0xff;
			b = gdImageBlue(image, color);
			g = gdImageGreen(image, color);
			r = gdImageRed(image, color);

			/* if this pixel has the same RGB as the transparent colour,
			 * then set alpha fully transparent */
			if (transparentColorR == r &&
			    transparentColorG == g &&
			    transparentColorB == b) {
				a = 0x00;
			}

			if(bitDepth != 24) {
				/* write out 1 or 8 bit value in 1 byte
				 * (currently treats 1bit as 8bit) */
				scan[(x * samplesPerPixel) + 0] = color;
			} else {
				/* write out 24 bit value in 3 (or 4 if transparent) bytes */
				if(image->saveAlphaFlag || image->transparent != -1) {
					scan[(x * samplesPerPixel) + 3] = a;
				}

				scan[(x * samplesPerPixel) + 2] = b;
				scan[(x * samplesPerPixel) + 1] = g;
				scan[(x * samplesPerPixel) + 0] = r;
			}
		}

		/* Write the scan line to the tiff */
		if(TIFFWriteEncodedStrip(tiff, y, scan, width * samplesPerPixel) == -1) {
			if (colorMapRed)   gdFree(colorMapRed);
			if (colorMapGreen) gdFree(colorMapGreen);
			if (colorMapBlue)  gdFree(colorMapBlue);
			gdFree(th);
			/* error handler here */
			gd_error("Could not create TIFF\n");
			return;
		}
	}

	/* now cloase and free up resources */
	TIFFClose(tiff);
	gdFree(scan);
	gdFree(th);

	if(bitDepth != 24) {
		gdFree(colorMapRed);
		gdFree(colorMapGreen);
		gdFree(colorMapBlue);
	}
}

Project: libgd

static int tiff_closeproc(thandle_t clientdata)
{
	(void)clientdata;
	/*tiff_handle *th = (tiff_handle *)clientdata;
	gdIOCtx *ctx = th->ctx;

	(ctx->gd_free)(ctx);*/

	return 0;
}

Project: libgd

static int tiff_mapproc(thandle_t h, tdata_t *d, toff_t *o)
{
	(void)h;
	(void)d;
	(void)o;
	return 0;
}

Project: libgd

static tsize_t tiff_readproc(thandle_t clientdata, tdata_t data, tsize_t size)
{
	tiff_handle *th = (tiff_handle *)clientdata;
	gdIOCtx *ctx = th->ctx;

	size = (ctx->getBuf)(ctx, data, size);

	return size;
}

Project: libgd

static toff_t tiff_seekproc(thandle_t clientdata, toff_t offset, int from)
{
	tiff_handle *th = (tiff_handle *)clientdata;
	gdIOCtx *ctx = th->ctx;
	int result;

	switch(from) {
	default:
	case SEEK_SET:
		/* just use offset */
		break;

	case SEEK_END:
		/* invert offset, so that it is from start, not end as supplied */
		offset = th->size + offset;
		break;

	case SEEK_CUR:
		/* add current position to translate it to 'from start',
		 * not from durrent as supplied
		 */
		offset += th->pos;
		break;
	}

	/* now, move pos in both io context and buf */
	if((result = (ctx->seek)(ctx, offset))) {
		th->pos = offset;
	}

	return result ? offset : (toff_t)-1;
}

Project: libgd

static toff_t tiff_sizeproc(thandle_t clientdata)
{
	tiff_handle *th = (tiff_handle *)clientdata;
	return th->size;
}

Project: libgd

static void tiff_unmapproc(thandle_t h, tdata_t d, toff_t o)
{
	(void)h;
	(void)d;
	(void)o;
}

Project: libgd

static tsize_t tiff_writeproc(thandle_t clientdata, tdata_t data, tsize_t size)
{
	tiff_handle *th = (tiff_handle *)clientdata;
	gdIOCtx *ctx = th->ctx;

	size = (ctx->putBuf)(ctx, data, size);
	if(size + th->pos>th->size) {
		th->size = size + th->pos;
		th->pos += size;
	}

	return size;
}