libtiff: Add TIFFOpenExt(), TIFFOpenWExt() and TIFFFdOpenExt() with re-entrant error handlers

From 91e95ee9ae9cc91b5e657cb078827c361da9b1ae Mon Sep 17 00:00:00 2001
From: Even Rouault <[EMAIL REDACTED]>
Date: Thu, 10 Nov 2022 17:27:08 +0100
Subject: [PATCH] Add TIFFOpenExt(), TIFFOpenWExt() and TIFFFdOpenExt() with
 re-entrant error handlers

Rename TIFFClientOpenEx() to TIFFClientOpenExt()

Rework signature of the re-entrant error handlers and of
TIFFSetWarningHandlerExt() and TIFFSetErrorHandlerExt()

Use structures that can be extended as extra argument.

Leverages and ammends https://gitlab.com/libtiff/libtiff/-/merge_requests/409
---
 libtiff/libtiff.def        |   9 ++-
 libtiff/libtiff.map        |   9 ++-
 libtiff/tif_error.c        |   9 ++-
 libtiff/tif_open.c         |  55 ++++++++++----
 libtiff/tif_unix.c         |  99 ++++++++++++++++++++++--
 libtiff/tif_warning.c      |   9 ++-
 libtiff/tif_win32.c        |  98 ++++++++++++++++++++++--
 libtiff/tiffio.h           |  50 +++++++++---
 libtiff/tiffiop.h          |   5 +-
 test/CMakeLists.txt        |   5 ++
 test/Makefile.am           |   4 +-
 test/test_error_handlers.c | 151 +++++++++++++++++++++++++++++++++++++
 12 files changed, 450 insertions(+), 53 deletions(-)
 create mode 100644 test/test_error_handlers.c

diff --git a/libtiff/libtiff.def b/libtiff/libtiff.def
index ee5486a6..2555a176 100644
--- a/libtiff/libtiff.def
+++ b/libtiff/libtiff.def
@@ -5,7 +5,7 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFCheckpointDirectory
 	TIFFCleanup
 	TIFFClientOpen
-	TIFFClientOpenEx
+	TIFFClientOpenExt
 	TIFFClientdata
 	TIFFClose
 	TIFFComputeStrip
@@ -25,7 +25,9 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFDeferStrileArrayWriting
 	TIFFError
 	TIFFErrorExt
+	TIFFErrorExtR
 	TIFFFdOpen
+	TIFFFdOpenExt
 	TIFFFieldDataType
 	TIFFFieldName
 	TIFFFieldPassCount
@@ -78,7 +80,9 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFNumberOfStrips
 	TIFFNumberOfTiles
 	TIFFOpen
+	TIFFOpenExt
 	TIFFOpenW
+	TIFFOpenWExt
 	TIFFPrintDirectory
 	TIFFRGBAImageBegin
 	TIFFRGBAImageEnd
@@ -117,6 +121,7 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFSetDirectory
 	TIFFSetErrorHandler
 	TIFFSetErrorHandlerExt
+	TIFFSetErrorHandlerExtR
 	TIFFSetField
 	TIFFSetFileName
 	TIFFSetFileno
@@ -125,6 +130,7 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFSetTagExtender
 	TIFFSetWarningHandler
 	TIFFSetWarningHandlerExt
+	TIFFSetWarningHandlerExtR
 	TIFFSetWriteOffset
 	TIFFSetupStrips
 	TIFFStripSize
@@ -156,6 +162,7 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFVTileSize64
 	TIFFWarning
 	TIFFWarningExt
+	TIFFWarningExtR
 	TIFFWriteBufferSetup
 	TIFFWriteCheck
 	TIFFWriteCustomDirectory
diff --git a/libtiff/libtiff.map b/libtiff/libtiff.map
index 4c66ed41..39afafac 100644
--- a/libtiff/libtiff.map
+++ b/libtiff/libtiff.map
@@ -202,5 +202,12 @@ LIBTIFF_4.4 {
 
 LIBTIFF_4.5 {
     _TIFFClampDoubleToUInt32;
-    TIFFClientOpenEx;
+    TIFFClientOpenExt;
+    TIFFFdOpenExt;
+    TIFFOpenExt;
+    TIFFOpenWExt;
+    TIFFErrorExtR;
+    TIFFWarningExtR;
+    TIFFSetErrorHandlerExtR;
+    TIFFSetWarningHandlerExtR;
 } LIBTIFF_4.4;
diff --git a/libtiff/tif_error.c b/libtiff/tif_error.c
index 2bd74d52..000654e0 100644
--- a/libtiff/tif_error.c
+++ b/libtiff/tif_error.c
@@ -77,8 +77,11 @@ TIFFErrorExt(thandle_t fd, const char* module, const char* fmt, ...)
 	}
 }
 
-void TIFFSetErrorHandlerExtR(TIFF* tif, TIFFErrorHandlerExtR handler) {
-  if (tif) tif->tif_errorhandler = handler;
+void TIFFSetErrorHandlerExtR(TIFF* tif, TIFFErrorHandlerExtR handler, void* errorhandler_user_data) {
+  if (tif) {
+      tif->tif_errorhandler_user_data = errorhandler_user_data;
+      tif->tif_errorhandler = handler;
+  }
 }
 
 void TIFFErrorExtR(TIFF* tif, const char* module, const char* fmt, ...)
@@ -86,7 +89,7 @@ void TIFFErrorExtR(TIFF* tif, const char* module, const char* fmt, ...)
 	va_list ap;
 	if (tif && tif->tif_errorhandler) {
 		va_start(ap, fmt);
-		int stop = (*tif->tif_errorhandler)(tif->tif_clientdata, module, fmt, ap);
+		int stop = (*tif->tif_errorhandler)(tif, tif->tif_errorhandler_user_data, module, fmt, ap);
 		va_end(ap);
 		if (stop) return;
 	}
diff --git a/libtiff/tif_open.c b/libtiff/tif_open.c
index c16a5e66..f95f5163 100644
--- a/libtiff/tif_open.c
+++ b/libtiff/tif_open.c
@@ -79,29 +79,52 @@ TIFFClientOpen(
 	TIFFMapFileProc mapproc,
 	TIFFUnmapFileProc unmapproc
 ) {
-  return TIFFClientOpenEx(name, mode, clientdata, readproc, writeproc, seekproc, closeproc,
-                          sizeproc, mapproc, unmapproc, NULL, NULL);
+  TIFFClientOpenExtStruct arguments = {
+      .version = 1,
+      .readproc = readproc,
+      .writeproc = writeproc,
+      .seekproc = seekproc,
+      .closeproc = closeproc,
+      .sizeproc = sizeproc,
+      .mapproc = mapproc,
+      .unmapproc = unmapproc,
+      .errorhandler = NULL,
+      .errorhandler_user_data = NULL,
+      .warnhandler = NULL,
+      .warnhandler_user_data = NULL
+  };
+  return TIFFClientOpenExt(name, mode, clientdata, &arguments);
 }
 
 TIFF*
-TIFFClientOpenEx(
+TIFFClientOpenExt(
 	const char* name, const char* mode,
 	thandle_t clientdata,
-	TIFFReadWriteProc readproc,
-	TIFFReadWriteProc writeproc,
-	TIFFSeekProc seekproc,
-	TIFFCloseProc closeproc,
-	TIFFSizeProc sizeproc,
-	TIFFMapFileProc mapproc,
-	TIFFUnmapFileProc unmapproc,
-	TIFFErrorHandlerExtR errorhandler,
-	TIFFErrorHandlerExtR warnhandler)
+    TIFFClientOpenExtStruct* arguments)
 {
-	static const char module[] = "TIFFClientOpenEx";
+	static const char module[] = "TIFFClientOpenExt";
 	TIFF *tif;
 	int m;
 	const char* cp;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(clientdata, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(clientdata, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+    TIFFReadWriteProc readproc = arguments->readproc;
+    TIFFReadWriteProc writeproc = arguments->writeproc;
+    TIFFSeekProc seekproc = arguments->seekproc;
+    TIFFCloseProc closeproc = arguments->closeproc;
+    TIFFSizeProc sizeproc = arguments->sizeproc;
+    TIFFMapFileProc mapproc = arguments->mapproc;
+    TIFFUnmapFileProc unmapproc = arguments->unmapproc;
+
 	/* The following are configuration checks. They should be redundant, but should not
 	 * compile to any actual code in an optimised release build anyway. If any of them
 	 * fail, (makefile-based or other) configuration is not correct */
@@ -152,8 +175,10 @@ TIFFClientOpenEx(
 	tif->tif_sizeproc = sizeproc;
 	tif->tif_mapproc = mapproc ? mapproc : _tiffDummyMapProc;
 	tif->tif_unmapproc = unmapproc ? unmapproc : _tiffDummyUnmapProc;
-    tif->tif_errorhandler = errorhandler;
-    tif->tif_warnhandler = warnhandler;
+    tif->tif_errorhandler = arguments->errorhandler;
+    tif->tif_errorhandler_user_data = arguments->errorhandler_user_data;
+    tif->tif_warnhandler = arguments->warnhandler;
+    tif->tif_warnhandler_user_data = arguments->warnhandler_user_data;
 
 	if (!readproc || !writeproc || !seekproc || !closeproc || !sizeproc) {
 		TIFFErrorExtR(tif, module,
diff --git a/libtiff/tif_unix.c b/libtiff/tif_unix.c
index cb543542..34ad36c8 100644
--- a/libtiff/tif_unix.c
+++ b/libtiff/tif_unix.c
@@ -202,15 +202,51 @@ _tiffUnmapProc(thandle_t fd, void* base, toff_t size)
 TIFF*
 TIFFFdOpen(int fd, const char* name, const char* mode)
 {
+    TIFFOpenExtStruct arguments = {
+      .version = 1,
+      .errorhandler = NULL,
+      .errorhandler_user_data = NULL,
+      .warnhandler = NULL,
+      .warnhandler_user_data = NULL
+    };
+    return TIFFFdOpenExt(fd, name, mode, &arguments);
+}
+
+TIFF*
+TIFFFdOpenExt(int fd, const char* name, const char* mode, TIFFOpenExtStruct* arguments)
+{
+    static const char module[] = "TIFFFdOpenExt";
 	TIFF* tif;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+    TIFFClientOpenExtStruct client_arguments = {
+        .version = 1,
+        .readproc = _tiffReadProc,
+        .writeproc = _tiffWriteProc,
+        .seekproc = _tiffSeekProc,
+        .closeproc = _tiffCloseProc,
+        .sizeproc = _tiffSizeProc,
+        .mapproc = _tiffMapProc,
+        .unmapproc = _tiffUnmapProc,
+        .errorhandler = arguments->errorhandler,
+        .errorhandler_user_data = arguments->errorhandler_user_data,
+        .warnhandler = arguments->warnhandler,
+        .warnhandler_user_data = arguments->warnhandler_user_data
+    };
+
 	fd_as_handle_union_t fdh;
 	fdh.fd = fd;
-	tif = TIFFClientOpen(name, mode,
-	    fdh.h,
-	    _tiffReadProc, _tiffWriteProc,
-	    _tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
-	    _tiffMapProc, _tiffUnmapProc);
+	tif = TIFFClientOpenExt(name, mode,
+	    fdh.h, &client_arguments);
 	if (tif)
 		tif->tif_fd = fd;
 	return (tif);
@@ -221,11 +257,35 @@ TIFFFdOpen(int fd, const char* name, const char* mode)
  */
 TIFF*
 TIFFOpen(const char* name, const char* mode)
+{
+    TIFFOpenExtStruct arguments = {
+        .version = 1,
+        .errorhandler = NULL,
+        .errorhandler_user_data = NULL,
+        .warnhandler = NULL,
+        .warnhandler_user_data = NULL
+    };
+    return TIFFOpenExt(name, mode, &arguments);
+}
+
+TIFF*
+TIFFOpenExt(const char* name, const char* mode, TIFFOpenExtStruct* arguments)
 {
 	static const char module[] = "TIFFOpen";
 	int m, fd;
 	TIFF* tif;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+
 	m = _TIFFgetMode(mode, module);
 	if (m == -1)
 		return ((TIFF*)0);
@@ -245,7 +305,7 @@ TIFFOpen(const char* name, const char* mode)
 		return ((TIFF *)0);
 	}
 
-	tif = TIFFFdOpen((int)fd, name, mode);
+	tif = TIFFFdOpenExt((int)fd, name, mode, arguments);
 	if(!tif)
 		close(fd);
 	return tif;
@@ -258,6 +318,18 @@ TIFFOpen(const char* name, const char* mode)
  */
 TIFF*
 TIFFOpenW(const wchar_t* name, const char* mode)
+{
+    TIFFOpenExtStruct arguments = {
+        .version = 1,
+        .errorhandler = NULL,
+        .errorhandler_user_data = NULL,
+        .warnhandler = NULL,
+        .warnhandler_user_data = NULL
+    };
+    return TIFFOpenWEx(name, mode, &arguments);
+}
+TIFF*
+TIFFOpenWExt(const wchar_t* name, const char* mode, TIFFOpenExtStruct* arguments)
 {
 	static const char module[] = "TIFFOpenW";
 	int m, fd;
@@ -265,6 +337,17 @@ TIFFOpenW(const wchar_t* name, const char* mode)
 	char *mbname;
 	TIFF* tif;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+
 	m = _TIFFgetMode(mode, module);
 	if (m == -1)
 		return ((TIFF*)0);
@@ -294,8 +377,8 @@ TIFFOpenW(const wchar_t* name, const char* mode)
 				    NULL, NULL);
 	}
 
-	tif = TIFFFdOpen((int)fd, (mbname != NULL) ? mbname : "<unknown>",
-			 mode);
+	tif = TIFFFdOpenExt((int)fd, (mbname != NULL) ? mbname : "<unknown>",
+			 mode, arguments);
 	
 	_TIFFfree(mbname);
 	
diff --git a/libtiff/tif_warning.c b/libtiff/tif_warning.c
index aa26faca..d952bdfd 100644
--- a/libtiff/tif_warning.c
+++ b/libtiff/tif_warning.c
@@ -77,8 +77,11 @@ TIFFWarningExt(thandle_t fd, const char* module, const char* fmt, ...)
 	}
 }
 
-void TIFFSetWarningHandlerExtR(TIFF* tif, TIFFErrorHandlerExtR handler) {
-  if (tif) tif->tif_warnhandler = handler;
+void TIFFSetWarningHandlerExtR(TIFF* tif, TIFFErrorHandlerExtR handler, void* warnhandler_user_data) {
+  if (tif) {
+      tif->tif_warnhandler_user_data = warnhandler_user_data;
+      tif->tif_warnhandler = handler;
+  }
 }
 
 void TIFFWarningExtR(TIFF* tif, const char* module, const char* fmt, ...)
@@ -86,7 +89,7 @@ void TIFFWarningExtR(TIFF* tif, const char* module, const char* fmt, ...)
 	va_list ap;
 	if (tif && tif->tif_warnhandler) {
 		va_start(ap, fmt);
-		int stop = (*tif->tif_warnhandler)(tif->tif_clientdata, module, fmt, ap);
+		int stop = (*tif->tif_warnhandler)(tif, tif->tif_warnhandler_user_data, module, fmt, ap);
 		va_end(ap);
 		if (stop) return;
 	}
diff --git a/libtiff/tif_win32.c b/libtiff/tif_win32.c
index d617a0f4..2e62f080 100644
--- a/libtiff/tif_win32.c
+++ b/libtiff/tif_win32.c
@@ -232,9 +232,32 @@ _tiffUnmapProc(thandle_t fd, void* base, toff_t size)
 TIFF*
 TIFFFdOpen(int ifd, const char* name, const char* mode)
 {
+    TIFFOpenExtStruct arguments;
+    arguments.version = 1;
+    arguments.errorhandler = NULL;
+    arguments.warnhandler = NULL;
+    return TIFFFdOpenExt(ifd, name, mode, &arguments);
+}
+
+TIFF*
+TIFFFdOpenExt(int ifd, const char* name, const char* mode, TIFFOpenExtStruct* arguments)
+{
+    static const char module[] = "TIFFFdOpenExt";
 	TIFF* tif;
 	int fSuppressMap;
 	int m;
+
+    if( arguments == NULL )
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if( arguments->version < 1 )
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+
 	fSuppressMap=0;
 	for (m=0; mode[m]!=0; m++)
 	{
@@ -244,11 +267,21 @@ TIFFFdOpen(int ifd, const char* name, const char* mode)
 			break;
 		}
 	}
-	tif = TIFFClientOpen(name, mode, thandle_from_int(ifd),
-			_tiffReadProc, _tiffWriteProc,
-			_tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
-			fSuppressMap ? _tiffDummyMapProc : _tiffMapProc,
-			fSuppressMap ? _tiffDummyUnmapProc : _tiffUnmapProc);
+
+    TIFFClientOpenExtStruct client_arguments;
+    client_arguments.version = 1;
+    client_arguments.readproc = _tiffReadProc;
+    client_arguments.writeproc = _tiffWriteProc;
+    client_arguments.seekproc = _tiffSeekProc;
+    client_arguments.closeproc = _tiffCloseProc;
+    client_arguments.sizeproc = _tiffSizeProc;
+    client_arguments.mapproc = fSuppressMap ? _tiffDummyMapProc : _tiffMapProc;
+    client_arguments.unmapproc = fSuppressMap ? _tiffDummyUnmapProc : _tiffUnmapProc;
+    client_arguments.errorhandler = arguments->errorhandler;
+    client_arguments.warnhandler = arguments->warnhandler;
+
+	tif = TIFFClientOpenExt(name, mode, thandle_from_int(ifd),
+			&client_arguments);
 	if (tif)
 		tif->tif_fd = ifd;
 	return (tif);
@@ -261,6 +294,19 @@ TIFFFdOpen(int ifd, const char* name, const char* mode)
  */
 TIFF*
 TIFFOpen(const char* name, const char* mode)
+{
+    TIFFOpenExtStruct arguments = {
+        .version = 1,
+        .errorhandler = NULL,
+        .errorhandler_user_data = NULL,
+        .warnhandler = NULL,
+        .warnhandler_user_data = NULL
+    };
+    return TIFFOpenExt(name, mode, &arguments);
+}
+
+TIFF*
+TIFFOpenExt(const char* name, const char* mode, TIFFOpenExtStruct* arguments)
 {
 	static const char module[] = "TIFFOpen";
 	thandle_t fd;
@@ -268,6 +314,17 @@ TIFFOpen(const char* name, const char* mode)
 	DWORD dwMode;
 	TIFF* tif;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+
 	m = _TIFFgetMode(mode, module);
 
 	switch(m) {
@@ -289,7 +346,7 @@ TIFFOpen(const char* name, const char* mode)
 		return ((TIFF *)0);
 	}
 
-	tif = TIFFFdOpen(thandle_to_int(fd), name, mode);
+	tif = TIFFFdOpenExt(thandle_to_int(fd), name, mode, arguments);
 	if(!tif)
 		CloseHandle(fd);
 	return tif;
@@ -300,6 +357,19 @@ TIFFOpen(const char* name, const char* mode)
  */
 TIFF*
 TIFFOpenW(const wchar_t* name, const char* mode)
+{
+    TIFFOpenExtStruct arguments = {
+        .version = 1,
+        .errorhandler = NULL,
+        .errorhandler_user_data = NULL,
+        .warnhandler = NULL,
+        .warnhandler_user_data = NULL
+    };
+    return TIFFOpenWExt(name, mode, &arguments);
+}
+
+TIFF*
+TIFFOpenWExt(const wchar_t* name, const char* mode, TIFFOpenExtStruct* arguments)
 {
 	static const char module[] = "TIFFOpenW";
 	thandle_t fd;
@@ -309,6 +379,17 @@ TIFFOpenW(const wchar_t* name, const char* mode)
 	char *mbname;
 	TIFF *tif;
 
+    if (arguments == NULL)
+    {
+        TIFFErrorExt(0, module, "arguments should NOT be NULL");
+        return NULL;
+    }
+    if (arguments->version < 1)
+    {
+        TIFFErrorExt(0, module, "arguments->version should be >= 1");
+        return NULL;
+    }
+
 	m = _TIFFgetMode(mode, module);
 
 	switch(m) {
@@ -344,8 +425,9 @@ TIFFOpenW(const wchar_t* name, const char* mode)
 				    NULL, NULL);
 	}
 
-	tif = TIFFFdOpen(thandle_to_int(fd),
-			 (mbname != NULL) ? mbname : "<unknown>", mode);
+	tif = TIFFFdOpenExt(thandle_to_int(fd),
+			 (mbname != NULL) ? mbname : "<unknown>", mode,
+             arguments);
 	if(!tif)
 		CloseHandle(fd);
 
diff --git a/libtiff/tiffio.h b/libtiff/tiffio.h
index 0d6e4af2..36abdc4d 100644
--- a/libtiff/tiffio.h
+++ b/libtiff/tiffio.h
@@ -274,7 +274,7 @@ extern "C" {
 #endif
 typedef void (*TIFFErrorHandler)(const char*, const char*, va_list);
 typedef void (*TIFFErrorHandlerExt)(thandle_t, const char*, const char*, va_list);
-typedef int (*TIFFErrorHandlerExtR)(thandle_t, const char*, const char*, va_list);
+typedef int (*TIFFErrorHandlerExtR)(TIFF*, void* user_data, const char*, const char*, va_list);
 typedef tmsize_t (*TIFFReadWriteProc)(thandle_t, void*, tmsize_t);
 typedef toff_t (*TIFFSeekProc)(thandle_t, toff_t, int);
 typedef int (*TIFFCloseProc)(thandle_t);
@@ -460,28 +460,58 @@ extern TIFFErrorHandlerExt TIFFSetErrorHandlerExt(TIFFErrorHandlerExt);
 extern TIFFErrorHandler TIFFSetWarningHandler(TIFFErrorHandler);
 extern TIFFErrorHandlerExt TIFFSetWarningHandlerExt(TIFFErrorHandlerExt);
 
-extern void TIFFSetErrorHandlerExtR(TIFF*, TIFFErrorHandlerExtR);
-extern void TIFFSetWarningHandlerExtR(TIFF*, TIFFErrorHandlerExtR);
+extern void TIFFSetErrorHandlerExtR(TIFF*, TIFFErrorHandlerExtR, void* errorhandler_user_data);
+extern void TIFFSetWarningHandlerExtR(TIFF*, TIFFErrorHandlerExtR, void* warnhandler_user_data);
+extern void TIFFWarningExtR(TIFF*, const char*, const char*, ...) TIFF_ATTRIBUTE((__format__ (__printf__,3,4)));
+extern void TIFFErrorExtR(TIFF*, const char*, const char*, ...) TIFF_ATTRIBUTE((__format__ (__printf__,3,4)));
+
+typedef struct TIFFOpenExtStruct
+{
+    int                  version; /* should be set to 1 for now. */
+
+    TIFFErrorHandlerExtR errorhandler; /* may be NULL */
+    void*                errorhandler_user_data; /* may be NULL */
+    TIFFErrorHandlerExtR warnhandler; /* may be NULL */
+    void*                warnhandler_user_data; /* may be NULL */
+    /* If new fields are added, they should be handled by nVersion == 2 */
+} TIFFOpenExtStruct;
 
 extern TIFF* TIFFOpen(const char*, const char*);
+extern TIFF* TIFFOpenExt(const char*, const char*, TIFFOpenExtStruct* arguments);
 # ifdef __WIN32__
 extern TIFF* TIFFOpenW(const wchar_t*, const char*);
+extern TIFF* TIFFOpenWExt(const wchar_t*, const char*, TIFFOpenExtStruct* arguments);
 # endif /* __WIN32__ */
 extern TIFF* TIFFFdOpen(int, const char*, const char*);
+extern TIFF* TIFFFdOpenExt(int, const char*, const char*, TIFFOpenExtStruct* arguments);
 extern TIFF* TIFFClientOpen(const char*, const char*,
 	    thandle_t,
 	    TIFFReadWriteProc, TIFFReadWriteProc,
 	    TIFFSeekProc, TIFFCloseProc,
 	    TIFFSizeProc,
 	    TIFFMapFileProc, TIFFUnmapFileProc);
-extern TIFF* TIFFClientOpenEx(const char*, const char*,
-	    thandle_t,
-	    TIFFReadWriteProc, TIFFReadWriteProc,
-	    TIFFSeekProc, TIFFCloseProc,
-	    TIFFSizeProc,
-	    TIFFMapFileProc, TIFFUnmapFileProc,
-            TIFFErrorHandlerExtR, TIFFErrorHandlerExtR);
 
+typedef struct TIFFClientOpenExtStruct
+{
+    int                  version; /* should be set to 1 for now. */
+
+    TIFFReadWriteProc    readproc; /* must *NOT* be NULL */
+    TIFFReadWriteProc    writeproc; /* must *NOT* be NULL */
+    TIFFSeekProc         seekproc; /* must *NOT* be NULL */
+    TIFFCloseProc        closeproc; /* must *NOT* be NULL */
+    TIFFSizeProc         sizeproc; /* must *NOT* be NULL */
+    TIFFMapFileProc      mapproc; /* may be NULL */
+    TIFFUnmapFileProc    unmapproc; /* may be NULL */
+    TIFFErrorHandlerExtR errorhandler; /* may be NULL */
+    void*                errorhandler_user_data; /* may be NULL */
+    TIFFErrorHandlerExtR warnhandler; /* may be NULL */
+    void*                warnhandler_user_data; /* may be NULL */
+    /* If new fields are added, they should be handled by nVersion == 2 */
+} TIFFClientOpenExtStruct;
+
+extern TIFF* TIFFClientOpenExt(const char*, const char*,
+                               thandle_t,
+                               TIFFClientOpenExtStruct* arguments);
 extern TIFFExtendProc TIFFSetTagExtender(TIFFExtendProc);
 extern uint32_t TIFFComputeTile(TIFF* tif, uint32_t x, uint32_t y, uint32_t z, uint16_t s);
 extern int TIFFCheckTile(TIFF* tif, uint32_t x, uint32_t y, uint32_t z, uint16_t s);
diff --git a/libtiff/tiffiop.h b/libtiff/tiffiop.h
index 1cca4f54..7fbeef4b 100644
--- a/libtiff/tiffiop.h
+++ b/libtiff/tiffiop.h
@@ -199,7 +199,9 @@ struct tiff {
 	size_t               tif_nfieldscompat;
 	/* Error handler support */
     TIFFErrorHandlerExtR  tif_errorhandler;
+    void*                 tif_errorhandler_user_data;
     TIFFErrorHandlerExtR  tif_warnhandler;
+    void*                 tif_warnhandler_user_data;
 };
 
 #define isPseudoTag(t) (t > 0xffff)            /* is tag value normal or pseudo */
@@ -321,9 +323,6 @@ typedef size_t TIFFIOSize_t;
 #if defined(__cplusplus)
 extern "C" {
 #endif
-extern void TIFFWarningExtR(TIFF*, const char*, const char*, ...) TIFF_ATTRIBUTE((__format__ (__printf__,3,4)));
-extern void TIFFErrorExtR(TIFF*, const char*, const char*, ...) TIFF_ATTRIBUTE((__format__ (__printf__,3,4)));
-
 extern int _TIFFgetMode(const char* mode, const char* module);
 extern int _TIFFNoRowEncode(TIFF* tif, uint8_t* pp, tmsize_t cc, uint16_t s);
 extern int _TIFFNoStripEncode(TIFF* tif, uint8_t* pp, tmsize_t cc, uint16_t s);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3554b214..d7c18584 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -175,6 +175,11 @@ target_sources(testtypes PRIVATE testtypes.c)
 target_link_libraries(testtypes PRIVATE tiff tiff_port)
 list(APPEND simple_tests testtypes)
 
+add_executable(test_error_handlers ../placeholder.h)
+target_sources(test_error_handlers PRIVATE test_error_handlers.c)
+target_link_libraries(test_error_handlers PRIVATE tiff tiff_port)
+list(APPEND simple_tests test_error_handlers)
+
 if(WEBP_SUPPORT AND EMSCRIPTEN)
   # Emscripten is pretty finnicky about linker flags.
   # It needs --shared-memory if and only if atomics or bulk-memory is used.
diff --git a/test/Makefile.am b/test/Makefile.am
index 09a83065..bcb69a4f 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -75,7 +75,7 @@ endif
 if TIFF_TESTS
 check_PROGRAMS = \
 	ascii_tag long_tag short_tag strip_rw rewrite custom_dir custom_dir_EXIF_231 \
-	defer_strile_loading defer_strile_writing test_directory \
+	defer_strile_loading defer_strile_writing test_directory test_error_handlers \
 	testtypes test_signed_tags $(JPEG_DEPENDENT_CHECK_PROG) $(STATIC_CHECK_PROGS)
 endif
 
@@ -246,6 +246,8 @@ defer_strile_writing_SOURCES = defer_strile_writing.c
 defer_strile_writing_LDADD = $(LIBTIFF)
 test_directory_SOURCES = test_directory.c
 test_directory_LDADD = $(LIBTIFF)
+test_error_handlers_SOURCES = test_error_handlers.c
+test_error_handlers_LDADD = $(LIBTIFF)
 
 AM_CPPFLAGS = -I$(top_srcdir)/libtiff
 
diff --git a/test/test_error_handlers.c b/test/test_error_handlers.c
new file mode 100644
index 00000000..56faa29e
--- /dev/null
+++ b/test/test_error_handlers.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2022, Even Rouault <even.rouault at spatialys.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and 
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that (i) the above copyright notices and this permission notice appear in
+ * all copies of the software and related documentation, and (ii) the names of
+ * Sam Leffler and Silicon Graphics may not be used in any advertising or
+ * publicity relating to the software without the specific, prior written
+ * permission of Sam Leffler and Silicon Graphics.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
+ * 
+ * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
+ * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
+ * OF THIS SOFTWARE.
+ */
+
+/*
+ * TIFF Library
+ *
+ * Test error handlers
+ */
+
+#include "tif_config.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "tiffio.h"
+
+#define ERROR_STRING_SIZE 1024
+
+typedef struct MyErrorHandlerUserDataStruct
+{
+    char*  buffer;
+    size_t buffer_size;
+    TIFF*  tif_got_from_callback;
+    char   module[64];
+} MyErrorHandlerUserDataStruct;
+
+static int myErrorHandler(TIFF* tiff, void* user_data, const char* module, const char* fmt, va_list ap)
+{
+    MyErrorHandlerUserDataStruct* errorhandler_user_data = (MyErrorHandlerUserDataStruct*)user_data;
+    vsnprintf(errorhandler_user_data->buffer,
+              errorhandler_user_data->buffer_size,
+              fmt,
+              ap);
+    errorhandler_user_data->tif_got_from_callback = tiff;
+    snprintf(errorhandler_user_data->module, sizeof(errorhandler_user_data->module), "%s", module);
+    return 1;
+}
+
+int test_open_ext(int handlers_set_in_open)
+{
+    int ret = 0;
+    char error_buffer[ERROR_STRING_SIZE] = {0};
+    char warn_buffer[ERROR_STRING_SIZE] = {0};
+    MyErrorHandlerUserDataStruct errorhandler_user_data =
+    {
+        .buffer = error_buffer,
+        .buffer_size = ERROR_STRING_SIZE
+    };
+    MyErrorHandlerUserDataStruct warnhandler_user_data =
+    {
+        .buffer = warn_buffer,
+        .buffer_size = ERROR_STRING_SIZE
+    };
+    TIFF* tif;
+    if( handlers_set_in_open )
+    {
+        TIFFOpenExtStruct arguments = {
+            .version = 1,
+            .errorhandler = myErrorHandler,
+            .errorhandler_user_data = &errorhandler_user_data,
+            .warnhandler = myErrorHandler,
+            .warnhandler_user_data = &warnhandler_user_data
+        };
+        tif = TIFFOpenExt("test_error_handler.tif", "w", &arguments);
+    }
+    else
+    {
+        tif = TIFFOpen("test_error_handler.tif", "w");
+        TIFFSetErrorHandlerExtR(tif, myErrorHandler, &errorhandler_user_data);
+        TIFFSetWarningHandlerExtR(tif, myErrorHandler, &warnhandler_user_data);
+    }
+    if( tif == NULL )
+    {
+        fprintf(stderr, "Cannot create test_error_handler.tif");
+        exit(1);
+    }
+
+    // Simulate an error emitted by libtiff
+    TIFFErrorExtR(tif, "my_error_module", "%s", "some error message");
+    if( strcmp(error_buffer, "some error message") != 0 )
+    {
+        fprintf(stderr, "Did not get expected error message\n");
+        ret = 1;
+    }
+    if( strcmp(errorhandler_user_data.module, "my_error_module") != 0 )
+    {
+        fprintf(stderr, "Did not get expected error module\n");
+        ret = 1;
+    }
+    if( errorhandler_user_data.tif_got_from_callback != tif)
+    {
+        fprintf(stderr, "errorhandler_user_data.tif_got_from_callback != tif\n");
+        ret = 1;
+    }
+
+    // Simulate a warning emitted by libtiff
+    TIFFWarningExtR(tif, "my_warning_module", "%s", "some warning message");
+    if( strcmp(warn_buffer, "some warning message") != 0 )
+    {
+        fprintf(stderr, "Did not get expected warning message\n");
+        ret = 1;
+    }
+    if( strcmp(warnhandler_user_data.module, "my_warning_module") != 0 )
+    {
+        fprintf(stderr, "Did not get expected warning module\n");
+        ret = 1;
+    }
+    if( warnhandler_user_data.tif_got_from_callback != tif)
+    {
+        fprintf(stderr, "warnhandler_user_data.tif_got_from_callback != tif\n");
+        ret = 1;
+    }
+
+    TIFFClose(tif);
+    unlink("test_error_handler.tif");
+    return ret;
+}
+
+int main()
+{
+    int ret = 0;
+    ret += test_open_ext(1);
+    ret += test_open_ext(0);
+    return ret;
+}