From 912c64517a1f32328b059c0fad2e59321f004b90 Mon Sep 17 00:00:00 2001 From: John Bowler Date: Sun, 13 Oct 2024 16:53:02 -0700 Subject: [PATCH] dev: Make the use of stdio FILE robust. ABI changes: The three API functions png_init_io, png_begin_read_from_stdio and png_image_write_to_stdio now require the host fread, fwrite and fflush functions where appropriate in addition to the host FILE. Internally libpng uses the provided functions for all operations on the FILE; it does not use the implementations available when libpng was built. API changes: The original APIs of the above three functions are now implemented as function-like macros which automatically pass the correct ISO-C functions. This ensures that there are no net API changes and that the correct arguments are passed. Build changes: The read stdio functionality is now dependent on SEQUENTIAL_READ rather than READ. This is done for clarity; the progressive read mechanism does not use FILE. Internal changes: png_struct::io_ptr has been supplemented with png_struct::stdio_ptr used when stdio is used directly by libpng for IO as opposed to caller-provided callbacks. Error checking has been added to detect mismatched use of the stdio (png_init_io etc) API with the callback (png_set_read_fn, png_set_write_fn APIs.) Changing from one API to the other mid-stream should work but has not been tested and is not currently a documented option. The changes address an issue which is frequently encountered on Microsoft Windows systems because Windows NT DLLs each have their own ISO-C hosted environment. This means that a FILE from one DLL cannot be used safely from a different DLL unless all access to the object is done using functionality from the creating DLL. The problem is more general; passing objects across DLL or other boundaries is frequently supported but direct access to those objects' internal structure in the receiving environment is not safe. Other such uses were address early on in the development of libpng, this addresses the main, almost certainly, sole remaining issue. The idea of adding additional function pointers png_init_io was suggested by github.com user pp383 in pull request #208. Signed-off-by: John Bowler --- png.c | 90 ++++++++++++++++++++++--- png.h | 27 ++++++-- pngpriv.h | 27 +++++--- pngread.c | 58 ++++++++-------- pngrio.c | 130 ++++++++++++++++++++---------------- pngstruct.h | 12 ++++ pngwio.c | 185 ++++++++++++++++++++++++++-------------------------- pngwrite.c | 52 +++++++++------ 8 files changed, 360 insertions(+), 221 deletions(-) diff --git a/png.c b/png.c index 621e4a4ad3..df7a662699 100644 --- a/png.c +++ b/png.c @@ -669,7 +669,7 @@ png_get_io_ptr(png_const_structrp png_ptr) return png_ptr->io_ptr; } -#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +#if defined(PNG_SEQUENTIAL_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) # ifdef PNG_STDIO_SUPPORTED /* Initialize the default input/output functions for the PNG file. If you * use your own read or write routines, you can call either png_set_read_fn() @@ -677,18 +677,91 @@ png_get_io_ptr(png_const_structrp png_ptr) * PNG_NO_STDIO or otherwise disabled PNG_STDIO_SUPPORTED, you must use a * function of your own because "FILE *" isn't necessarily available. */ -void PNGAPI -png_init_io(png_structrp png_ptr, png_FILE_p fp) +void PNGAPI ( +png_init_io)(png_structrp png_ptr, FILE *fp, + size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*), + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*), + int (*fflush)(FILE*)) { png_debug(1, "in png_init_io"); if (png_ptr == NULL) return; - png_ptr->io_ptr = (png_voidp)fp; -} + /* If SEQUENTIAL_READ is not supported and this IS a read png_struct + * png_init_io cannot be used (or, rather, it will not work). Detect this + * early. + */ +# ifndef PNG_SEQUENTIAL_READ_SUPPORTED + /* Read must be done using the progressive reader therefore: */ + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { + png_app_error(png_ptr, "API: IO cannot be set with progressive read"); + return; + } +# endif /* !SEQUENTIAL_READ */ + + /* First clear out any read/write functionality set by the caller. */ + png_ptr->io_ptr = NULL; +# ifdef PNG_READ_SUPPORTED + png_ptr->read_data_fn = NULL; +# endif /* READ */ +# ifdef PNG_WRITE_SUPPORTED + png_ptr->write_data_fn = NULL; +# endif /* WRITE */ +# ifdef PNG_WRITE_FLUSH_SUPPPORTED + png_ptr->output_flush_fn = NULL; # endif + /* Set up the stdio based read or write functions as appropriate. */ +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { + png_ptr->fread = fread; + png_ptr->read_data_fn = png_stdio_read; + } + else + { + png_ptr->fread = NULL; + } +# else /* !SEQUENTIAL_READ */ + PNG_UNUSED(fread) +# endif /* !SEQUENTIAL_READ */ + +# ifdef PNG_WRITE_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + png_ptr->fwrite = fwrite; + png_ptr->write_data_fn = png_stdio_write; + } + else + { + png_ptr->fwrite = NULL; + } +# else /* !WRITE */ + PNG_UNUSED(fwrite) +# endif /* !WRITE */ + +# ifdef PNG_WRITE_FLUSH_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + png_ptr->fflush = fflush; + png_ptr->output_flush_fn = png_stdio_flush; + } + else + { + png_ptr->fflush = NULL; + } +# else /* !WRITE_FLUSH */ + PNG_UNUSED(fflush) +# endif /* !WRITE_FLUSH */ + + png_ptr->stdio_ptr = fp; +} +# endif /* STDIO */ +#endif /* SEQUENTIAL_READ || WRITE */ + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) # ifdef PNG_SAVE_INT_32_SUPPORTED /* PNG signed integers are saved in 32-bit 2's complement format. ANSI C-90 * defines a cast of a signed integer to an unsigned integer either to preserve @@ -4556,15 +4629,14 @@ png_image_free_function(png_voidp argument) /* First free any data held in the control structure. */ # ifdef PNG_STDIO_SUPPORTED - if (cp->owned_file != 0) + if (cp->io_file != NULL) { - FILE *fp = png_voidcast(FILE*, cp->png_ptr->io_ptr); - cp->owned_file = 0; + FILE *fp = cp->io_file; /* Ignore errors here. */ if (fp != NULL) { - cp->png_ptr->io_ptr = NULL; + cp->io_file = NULL; (void)fclose(fp); } } diff --git a/png.h b/png.h index a30fb99e02..eedc1c25e6 100644 --- a/png.h +++ b/png.h @@ -1541,9 +1541,17 @@ PNG_REMOVED(209, void, png_set_filter_heuristics_fixed, */ #ifdef PNG_STDIO_SUPPORTED -/* Initialize the input/output for the PNG file to the default functions. */ -PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, png_FILE_p fp)); -#endif +# if defined(PNG_SEQUENTIAL_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Initialize the input/output for the PNG file to use stdio. */ +PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, FILE *fp, + size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*), + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*), + int (*fflush)(FILE*))); + +#define png_init_io(png_ptr, fp)\ + ((png_init_io)(png_ptr, fp, fread, fwrite, fflush)) +#endif /* SEQUENTIAL_READ || WRITE */ +#endif /* STDIO */ /* Replace the (error and abort), and warning functions with user * supplied functions. If no messages are to be printed you must still @@ -2977,7 +2985,10 @@ PNG_EXPORT(234, int, png_image_begin_read_from_file, (png_imagep image, */ PNG_EXPORT(235, int, png_image_begin_read_from_stdio, (png_imagep image, - FILE* file)); + FILE* file, size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*))); +#define png_image_begin_read_from_stdio(image, file)\ + ((png_image_begin_read_from_stdio)(image, file, fread)) + /* The PNG header is read from the stdio FILE object. */ #endif /* STDIO */ @@ -3051,8 +3062,14 @@ PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image, PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, int convert_to_8_bit, const void *buffer, png_int_32 row_stride, - const void *colormap)); + const void *colormap, + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*), + int (*fflush)(FILE*))); /* Write the image to the given (FILE*). */ + +#define png_image_write_to_stdio(png_ptr, fp, cmap, cvt_to_8, buffer, stride)\ + ((png_image_write_to_stdio)(png_ptr, fp, cmap, cvt_to_8, buffer, stride,\ + fwrite, fflush)) #endif /* SIMPLIFIED_WRITE_STDIO */ /* With all write APIs if image is in one of the linear formats with 16-bit diff --git a/pngpriv.h b/pngpriv.h index 102ccbae2c..cd4d50f4e3 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -849,23 +849,27 @@ PNG_INTERNAL_FUNCTION(void,png_zfree,(voidpf png_ptr, voidpf ptr),PNG_EMPTY); * PNGCBAPI at 1.5.0 */ -PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_read_data,(png_structp png_ptr, - png_bytep data, size_t length),PNG_EMPTY); - #ifdef PNG_PROGRESSIVE_READ_SUPPORTED PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_push_fill_buffer,(png_structp png_ptr, png_bytep buffer, size_t length),PNG_EMPTY); -#endif +#endif /* PROGRESSIVE_READ */ -PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_write_data,(png_structp png_ptr, +#ifdef PNG_STDIO_SUPPORTED +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_stdio_read,(png_structp png_ptr, + png_bytep data, size_t length),PNG_EMPTY); +# endif /* SEQUENTIAL_READ */ + +#ifdef PNG_WRITE_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_stdio_write,(png_structp png_ptr, png_bytep data, size_t length),PNG_EMPTY); +#endif /* WRITE */ #ifdef PNG_WRITE_FLUSH_SUPPORTED -# ifdef PNG_STDIO_SUPPORTED -PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_flush,(png_structp png_ptr), +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_stdio_flush,(png_structp png_ptr), PNG_EMPTY); -# endif -#endif +#endif /* WRITE_FLUSH */ +#endif /* STDIO */ /* Reset the CRC variable */ PNG_INTERNAL_FUNCTION(void,png_reset_crc,(png_structrp png_ptr),PNG_EMPTY); @@ -1815,8 +1819,11 @@ typedef struct png_control png_const_bytep memory; /* Memory buffer. */ size_t size; /* Size of the memory buffer. */ +# ifdef PNG_STDIO_SUPPORTED + FILE *io_file; /* FILE* opened by us, not user/app */ +# endif + unsigned int for_write :1; /* Otherwise it is a read structure */ - unsigned int owned_file :1; /* We own the file in io_ptr */ } png_control; /* Return the pointer to the jmp_buf from a png_control: necessary because C diff --git a/pngread.c b/pngread.c index 3ec5623a00..3bec07639b 100644 --- a/pngread.c +++ b/pngread.c @@ -75,12 +75,6 @@ png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, if (png_ptr->target_state != 0U) png_set_option(png_ptr, PNG_TARGET_SPECIFIC_CODE, 1); # endif - - /* TODO: delay this, it can be done in png_init_io (if the app doesn't - * do it itself) avoiding setting the default function if it is not - * required. - */ - png_set_read_fn(png_ptr, NULL, NULL); } return png_ptr; @@ -1486,21 +1480,38 @@ png_image_read_header(png_voidp argument) } #ifdef PNG_STDIO_SUPPORTED -int PNGAPI -png_image_begin_read_from_stdio(png_imagep image, FILE* file) +typedef struct +{ + png_structrp png_ptr; + FILE *file; + size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*); +} stdio_read_setup; + +static int +setup_stdio_for_read(png_voidp parm) +{ + stdio_read_setup *read = png_voidcast(stdio_read_setup*, parm); + (png_init_io)(read->png_ptr, read->file, read->fread, NULL, NULL); + return 1; +} + +int PNGAPI ( +png_image_begin_read_from_stdio)(png_imagep image, FILE* file, + size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*)) { if (image != NULL && image->version == PNG_IMAGE_VERSION) { if (file != NULL) { - if (png_image_read_init(image) != 0) + if (png_image_read_init(image)) { - /* This is slightly evil, but png_init_io doesn't do anything other - * than this and we haven't changed the standard IO functions so - * this saves a 'safe' function. - */ - image->opaque->png_ptr->io_ptr = file; - return png_safe_execute(image, png_image_read_header, image); + stdio_read_setup parm; + parm.png_ptr = image->opaque->png_ptr; + parm.file = file; + parm.fread = fread; + + return png_safe_execute(image, setup_stdio_for_read, &parm) && + png_safe_execute(image, png_image_read_header, image); } } @@ -1523,20 +1534,13 @@ png_image_begin_read_from_file(png_imagep image, const char *file_name) { if (file_name != NULL) { - FILE *fp = fopen(file_name, "rb"); + /* The file is stored in png_control::io_file and this means that it + * will passed to fclose on error: + */ + FILE* fp = image->opaque->io_file = fopen(file_name, "rb"); if (fp != NULL) - { - if (png_image_read_init(image) != 0) - { - image->opaque->png_ptr->io_ptr = fp; - image->opaque->owned_file = 1; - return png_safe_execute(image, png_image_read_header, image); - } - - /* Clean up: just the opened file. */ - (void)fclose(fp); - } + return png_image_begin_read_from_stdio(image, fp); else return png_image_error(image, strerror(errno)); diff --git a/pngrio.c b/pngrio.c index 3b137f275f..be7abac328 100644 --- a/pngrio.c +++ b/pngrio.c @@ -27,93 +27,107 @@ * buffering if you are using unbuffered reads. This should never be asked * to read more than 64K on a 16-bit machine. */ +static int +invalid(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return 1; + + if ((png_ptr->mode & PNG_IS_READ_STRUCT) == 0) + { + png_app_error(png_ptr, "API: invalid in write"); + return 1; + } + + return 0; +} + void /* PRIVATE */ png_read_data(png_structrp png_ptr, png_bytep data, size_t length) { - png_debug1(4, "reading %d bytes", (int)length); + if (invalid(png_ptr)) + return; if (png_ptr->read_data_fn != NULL) - (*(png_ptr->read_data_fn))(png_ptr, data, length); + png_ptr->read_data_fn(png_ptr, data, length); else - png_error(png_ptr, "Call to NULL read function"); + png_app_error(png_ptr, "API: no read function"); } -#ifdef PNG_STDIO_SUPPORTED +#if defined(PNG_STDIO_SUPPORTED) && defined (PNG_SEQUENTIAL_READ_SUPPORTED) /* This is the function that does the actual reading of data. If you are * not reading from a standard C stream, you should create a replacement * read_data function and use it at run time with png_set_read_fn(), rather * than changing the library. */ -void PNGCBAPI -png_default_read_data(png_structp png_ptr, png_bytep data, size_t length) +static int +invalid_stdio(png_const_structrp png_ptr) { - size_t check; + if (invalid(png_ptr)) + return 1; - if (png_ptr == NULL) - return; + if (png_ptr->stdio_ptr == NULL) + { + png_app_error(png_ptr, "API: C stdio: no (FILE*)"); + return 1; + } - /* fread() returns 0 on error, so it is OK to store this in a size_t - * instead of an int, which is what fread() actually returns. - */ - check = fread(data, 1, length, png_voidcast(png_FILE_p, png_ptr->io_ptr)); + return 0; +} - if (check != length) - png_error(png_ptr, "Read Error"); +void PNGCBAPI +png_stdio_read(png_structp png_ptr, png_bytep data, size_t length) +{ + if (!invalid_stdio(png_ptr)) + { + if (png_ptr->fread != NULL) + { + size_t read = + (png_ptr->fread)(data, 1U, length, png_ptr->stdio_ptr); + + if (read != length) + png_error(png_ptr, "C stdio: read error"); + } + else /* This should be impossible: */ + png_error(png_ptr, "API(internal): missing fread"); + } } -#endif +#endif /* STDIO && SEQUENTIAL_READ */ -/* This function allows the application to supply a new input function - * for libpng if standard C streams aren't being used. - * - * This function takes as its arguments: - * - * png_ptr - pointer to a png input data structure - * - * io_ptr - pointer to user supplied structure containing info about - * the input functions. May be NULL. - * - * read_data_fn - pointer to a new input function that takes as its - * arguments a pointer to a png_struct, a pointer to - * a location where input data can be stored, and a 32-bit - * unsigned int that is the number of bytes to be read. - * To exit and output any fatal error messages the new write - * function should call png_error(png_ptr, "Error msg"). - * May be NULL, in which case libpng's default function will - * be used. +/* This API is an alternative to png_init_io (see png.c) which must be used if + * the caller of libpng is using something other than FILE* as the input device + * when reading a PNG. */ void PNGAPI -png_set_read_fn(png_structrp png_ptr, png_voidp io_ptr, - png_rw_ptr read_data_fn) +png_set_read_fn(png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr read_data_fn) { - if (png_ptr == NULL) + if (invalid(png_ptr)) return; - png_ptr->io_ptr = io_ptr; +# ifdef PNG_STDIO_SUPPORTED + /* Ensure none of the stdio settings remain set: */ + png_ptr->stdio_ptr = NULL; -#ifdef PNG_STDIO_SUPPORTED - if (read_data_fn != NULL) - png_ptr->read_data_fn = read_data_fn; +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + png_ptr->fread = NULL; +# endif /* SEQUENTIAL_READ */ +# ifdef PNG_WRITE_SUPPORTED + png_ptr->fwrite = NULL; +# endif /* WRITE */ +# ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->fflush = NULL; +# endif /* WRITE_FLUSH */ +# endif /* STDIO */ - else - png_ptr->read_data_fn = png_default_read_data; -#else + png_ptr->io_ptr = io_ptr; png_ptr->read_data_fn = read_data_fn; -#endif -#ifdef PNG_WRITE_SUPPORTED - /* It is an error to write to a read device */ - if (png_ptr->write_data_fn != NULL) - { +# ifdef PNG_WRITE_SUPPORTED png_ptr->write_data_fn = NULL; - png_warning(png_ptr, - "Can't set both read_data_fn and write_data_fn in the" - " same structure"); - } -#endif - -#ifdef PNG_WRITE_FLUSH_SUPPORTED - png_ptr->output_flush_fn = NULL; -#endif +# endif /* WRITE */ +# ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->output_flush_fn = NULL; +# endif /* WRITE_FLUSH */ } #endif /* READ */ diff --git a/pngstruct.h b/pngstruct.h index e77d69bc2b..fe36f5ee5b 100644 --- a/pngstruct.h +++ b/pngstruct.h @@ -155,6 +155,18 @@ struct png_struct_def png_rw_ptr write_data_fn; /* function for writing output data */ png_rw_ptr read_data_fn; /* function for reading input data */ png_voidp io_ptr; /* ptr to application struct for I/O functions */ +#ifdef PNG_STDIO_SUPPORTED + FILE *stdio_ptr; +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + size_t (*fread)(void *ptr, size_t size, size_t nmemb, FILE*); +# endif /* SEQUENTIAL_READ */ +# ifdef PNG_WRITE_SUPPORTED + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*); +# ifdef PNG_WRITE_FLUSH_SUPPORTED + int (*fflush)(FILE*); +# endif /* WRITE_FLUSH */ +# endif /* WRITE */ +#endif /* STDIO */ #ifdef PNG_READ_USER_TRANSFORM_SUPPORTED png_user_transform_ptr read_user_transform_fn; /* user read transform */ diff --git a/pngwio.c b/pngwio.c index 38c9c006cb..c965df412c 100644 --- a/pngwio.c +++ b/pngwio.c @@ -27,141 +27,140 @@ * buffering if you are using unbuffered writes. This should never be asked * to write more than 64K on a 16-bit machine. */ +static int +invalid(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return 1; + + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0) + { + png_app_error(png_ptr, "API: invalid in read"); + return 1; + } + + return 0; +} void /* PRIVATE */ png_write_data(png_structrp png_ptr, png_const_bytep data, size_t length) { + if (invalid(png_ptr)) + return; + /* NOTE: write_data_fn must not change the buffer! */ if (png_ptr->write_data_fn != NULL ) - (*(png_ptr->write_data_fn))(png_ptr, png_constcast(png_bytep,data), - length); + png_ptr->write_data_fn(png_ptr, png_constcast(png_bytep, data), length); else - png_error(png_ptr, "Call to NULL write function"); + png_app_error(png_ptr, "API: no write function"); } #ifdef PNG_STDIO_SUPPORTED -/* This is the function that does the actual writing of data. If you are - * not writing to a standard C stream, you should create a replacement - * write_data function and use it at run time with png_set_write_fn(), rather - * than changing the library. - */ -void PNGCBAPI -png_default_write_data(png_structp png_ptr, png_bytep data, size_t length) +static int +invalid_stdio(png_const_structrp png_ptr) { - size_t check; + if (invalid(png_ptr)) + return 1; - if (png_ptr == NULL) - return; + if (png_ptr->stdio_ptr == NULL) + { + png_app_error(png_ptr, "API: C stdio: no (FILE*)"); + return 1; + } - check = fwrite(data, 1, length, (png_FILE_p)(png_ptr->io_ptr)); + return 0; +} - if (check != length) - png_error(png_ptr, "Write Error"); +/* A C stream (FILE*) implementation of png_write_data. */ +void PNGCBAPI +png_stdio_write(png_structp png_ptr, png_bytep data, size_t length) +{ + if (!invalid_stdio(png_ptr)) + { + if (png_ptr->fwrite != NULL) + { + size_t written = + (png_ptr->fwrite)(data, 1U, length, png_ptr->stdio_ptr); + + if (written != length) + png_error(png_ptr, "C stdio: write error"); + } + else /* This should be impossible: */ + png_error(png_ptr, "API(internal): missing fwrite"); + } } -#endif +#endif /* STDIO */ -/* This function is called to output any data pending writing (normally - * to disk). After png_flush is called, there should be no data pending - * writing in any buffers. - */ #ifdef PNG_WRITE_FLUSH_SUPPORTED +/* If 'output_flush_fn' has been set it is called here to flush any pending + * data. If it is not set this does nothing apart from validating that this is + * a write png_struct. + */ void /* PRIVATE */ png_flush(png_structrp png_ptr) { - if (png_ptr->output_flush_fn != NULL) + if (!invalid(png_ptr) && png_ptr->output_flush_fn != NULL) (*(png_ptr->output_flush_fn))(png_ptr); } # ifdef PNG_STDIO_SUPPORTED void PNGCBAPI -png_default_flush(png_structp png_ptr) +png_stdio_flush(png_structp png_ptr) { - png_FILE_p io_ptr; - - if (png_ptr == NULL) - return; - - io_ptr = png_voidcast(png_FILE_p, (png_ptr->io_ptr)); - fflush(io_ptr); + if (!invalid_stdio(png_ptr)) + { + if (png_ptr->fflush != NULL) + { + if ((png_ptr->fflush)(png_ptr->stdio_ptr)) + png_error(png_ptr, "C stdio: write error"); + } + else /* This should be impossible */ + png_error(png_ptr, "API(internal): missing fflush"); + } } -# endif -#endif +# endif /* STDIO */ +#endif /* WRITE_FLUSH */ -/* This function allows the application to supply new output functions for - * libpng if standard C streams aren't being used. +/* This API is an alternative to png_init_io (see png.c) which must be used if + * the caller of libpng is using something other than FILE* as the output device + * when writing a PNG. * - * This function takes as its arguments: - * png_ptr - pointer to a png output data structure - * io_ptr - pointer to user supplied structure containing info about - * the output functions. May be NULL. - * write_data_fn - pointer to a new output function that takes as its - * arguments a pointer to a png_struct, a pointer to - * data to be written, and a 32-bit unsigned int that is - * the number of bytes to be written. The new write - * function should call png_error(png_ptr, "Error msg") - * to exit and output any fatal error messages. May be - * NULL, in which case libpng's default function will - * be used. - * flush_data_fn - pointer to a new flush function that takes as its - * arguments a pointer to a png_struct. After a call to - * the flush function, there should be no data in any buffers - * or pending transmission. If the output method doesn't do - * any buffering of output, a function prototype must still be - * supplied although it doesn't have to do anything. If - * PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile - * time, output_flush_fn will be ignored, although it must be - * supplied for compatibility. May be NULL, in which case - * libpng's default function will be used, if - * PNG_WRITE_FLUSH_SUPPORTED is defined. This is not - * a good idea if io_ptr does not point to a standard - * *FILE structure. + * If 'output_flush_fn' is NULL and WRITE_FLUSH is supported no ffflush + * operations will be done. The caller must ensure any buffers used by the + * caller's 'write_data_fn' are flushed appropriately. */ void PNGAPI png_set_write_fn(png_structrp png_ptr, png_voidp io_ptr, png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn) { - if (png_ptr == NULL) + if (invalid(png_ptr)) return; - png_ptr->io_ptr = io_ptr; - -#ifdef PNG_STDIO_SUPPORTED - if (write_data_fn != NULL) - png_ptr->write_data_fn = write_data_fn; +# ifdef PNG_STDIO_SUPPORTED + /* Ensure none of the stdio settings remain set: */ + png_ptr->stdio_ptr = NULL; +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + png_ptr->fread = NULL; +# endif + png_ptr->fwrite = NULL; +# ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->fflush = NULL; +# endif +# endif /* STDIO */ + +# ifdef PNG_READ_SUPPORTED + png_ptr->read_data_fn = NULL; +# endif - else - png_ptr->write_data_fn = png_default_write_data; -#else png_ptr->write_data_fn = write_data_fn; -#endif -#ifdef PNG_WRITE_FLUSH_SUPPORTED -# ifdef PNG_STDIO_SUPPORTED - - if (output_flush_fn != NULL) +# ifdef PNG_WRITE_FLUSH_SUPPORTED png_ptr->output_flush_fn = output_flush_fn; - - else - png_ptr->output_flush_fn = png_default_flush; - # else - png_ptr->output_flush_fn = output_flush_fn; -# endif -#else - PNG_UNUSED(output_flush_fn) -#endif /* WRITE_FLUSH */ - -#ifdef PNG_READ_SUPPORTED - /* It is an error to read while writing a png file */ - if (png_ptr->read_data_fn != NULL) - { - png_ptr->read_data_fn = NULL; + PNG_UNUSED(output_flush_fn) +# endif /* WRITE_FLUSH */ - png_warning(png_ptr, - "Can't set both read_data_fn and write_data_fn in the" - " same structure"); - } -#endif + png_ptr->io_ptr = io_ptr; } #endif /* WRITE */ diff --git a/pngwrite.c b/pngwrite.c index 18b29e38a4..d660230fcd 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -582,12 +582,6 @@ png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, #if PNG_RELEASE_BUILD png_ptr->flags |= PNG_FLAG_APP_WARNINGS_WARN; #endif - - /* TODO: delay this, it can be done in png_init_io() (if the app doesn't - * do it itself) avoiding setting the default function if it is not - * required. - */ - png_set_write_fn(png_ptr, NULL, NULL, NULL); } return png_ptr; @@ -2276,9 +2270,28 @@ png_image_write_to_memory(png_imagep image, void *memory, } #ifdef PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED -int PNGAPI -png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, - const void *buffer, png_int_32 row_stride, const void *colormap) +typedef struct +{ + png_structrp png_ptr; + FILE *file; + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*); + int (*fflush)(FILE*); +} stdio_write_setup; + +static int +setup_stdio_for_write(png_voidp parm) +{ + stdio_write_setup *write = png_voidcast(stdio_write_setup*, parm); + (png_init_io)( + write->png_ptr, write->file, NULL, write->fwrite, write->fflush); + return 1; +} + +int PNGAPI ( +png_image_write_to_stdio)(png_imagep image, FILE *file, int convert_to_8bit, + const void *buffer, png_int_32 row_stride, const void *colormap, + size_t (*fwrite)(const void *ptr, size_t size, size_t nmemb, FILE*), + int (*fflush)(FILE*)) { /* Write the image to the given (FILE*). */ if (image != NULL && image->version == PNG_IMAGE_VERSION) @@ -2288,13 +2301,13 @@ png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, if (png_image_write_init(image) != 0) { png_image_write_control display; + stdio_write_setup write; int result; - /* This is slightly evil, but png_init_io doesn't do anything other - * than this and we haven't changed the standard IO functions so - * this saves a 'safe' function. - */ - image->opaque->png_ptr->io_ptr = file; + write.png_ptr = image->opaque->png_ptr; + write.file = file; + write.fwrite = fwrite; + write.fflush = fflush; memset(&display, 0, (sizeof display)); display.image = image; @@ -2303,7 +2316,8 @@ png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, display.colormap = colormap; display.convert_to_8bit = convert_to_8bit; - result = png_safe_execute(image, png_image_write_main, &display); + result = png_safe_execute(image, setup_stdio_for_write, &write) && + png_safe_execute(image, png_image_write_main, &display); png_image_free(image); return result; } @@ -2335,14 +2349,15 @@ png_image_write_to_file(png_imagep image, const char *file_name, { if (file_name != NULL && buffer != NULL) { - FILE *fp = fopen(file_name, "wb"); + FILE *fp = image->opaque->io_file = fopen(file_name, "wb"); if (fp != NULL) { if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, - row_stride, colormap) != 0) + row_stride, colormap)) { int error; /* from fflush/fclose */ + image->opaque->io_file = NULL; /* Make sure the file is flushed correctly. */ if (fflush(fp) == 0 && ferror(fp) == 0) @@ -2368,8 +2383,7 @@ png_image_write_to_file(png_imagep image, const char *file_name, else { - /* Clean up: just the opened file. */ - (void)fclose(fp); + /* Clean up: just remove the file that was opened. */ (void)remove(file_name); return 0; }