diff --git a/ANNOUNCE b/ANNOUNCE index 954cfdf4f..b5ee27256 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -1,5 +1,5 @@ -Libpng 1.6.0beta28 - August 11, 2012 +Libpng 1.6.0beta28 - August 16, 2012 This is not intended to be a public release. It will be replaced within a few weeks by a public version or by another test version. @@ -442,7 +442,14 @@ Version 1.6.0beta27 [August 11, 2012] Work around gcc 3.x and Microsoft Visual Studio 2010 complaints. Both object to the split initialization of num_chunks. -Version 1.6.0beta28 [August 11, 2012] +Version 1.6.0beta28 [August 16, 2012] + Unknown handling fixes and clean up. This adds more correct option + control of the unknown handling, corrects the pre-existing bug where + the per-chunk 'keep' setting is ignored and makes it possible to skip + IDAT chunks in the sequential reader (broken in earlier 1.6 versions). + There is a new test program, test-unknown.c, which is a work in progress + (not currently part of the test suite). Comments in the header files now + explain how the unknown handling works. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/CHANGES b/CHANGES index a07787d6a..fab5c9dd2 100644 --- a/CHANGES +++ b/CHANGES @@ -4193,7 +4193,14 @@ Version 1.6.0beta27 [August 11, 2012] Work around gcc 3.x and Microsoft Visual Studio 2010 complaints. Both object to the split initialization of num_chunks. -Version 1.6.0beta28 [August 11, 2012] +Version 1.6.0beta28 [August 16, 2012] + Unknown handling fixes and clean up. This adds more correct option + control of the unknown handling, corrects the pre-existing bug where + the per-chunk 'keep' setting is ignored and makes it possible to skip + IDAT chunks in the sequential reader (broken in earlier 1.6 versions). + There is a new test program, test-unknown.c, which is a work in progress + (not currently part of the test suite). Comments in the header files now + explain how the unknown handling works. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/contrib/libtests/test-unknown.c b/contrib/libtests/test-unknown.c new file mode 100644 index 000000000..b55904f61 --- /dev/null +++ b/contrib/libtests/test-unknown.c @@ -0,0 +1,683 @@ + +/* test-unknown.c - test the read side unknown chunk handling + * + * Last changed in libpng 1.6.0 [(PENDING RELEASE)] + * Copyright (c) 2012 Glenn Randers-Pehrson + * Written by John Cunningham Bowler + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * NOTES: + * This is a C program that is intended to be linked against libpng. It + * generates bitmaps internally, stores them as PNG files (using the + * sequential write code) then reads them back (using the sequential + * read code) and validates that the result has the correct data. + * + * The program can be modified and extended to test the correctness of + * transformations performed by libpng. + */ + +#include +#include +#include + +#include + +#if PNG_LIBPNG_VER < 10500 +/* This deliberately lacks the PNG_CONST. */ +typedef png_byte *png_const_bytep; + +/* This is copied from 1.5.1 png.h: */ +#define PNG_INTERLACE_ADAM7_PASSES 7 +#define PNG_PASS_START_ROW(pass) (((1U&~(pass))<<(3-((pass)>>1)))&7) +#define PNG_PASS_START_COL(pass) (((1U& (pass))<<(3-(((pass)+1)>>1)))&7) +#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3) +#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3) +#define PNG_PASS_ROWS(height, pass) (((height)+(((1<>PNG_PASS_ROW_SHIFT(pass)) +#define PNG_PASS_COLS(width, pass) (((width)+(((1<>PNG_PASS_COL_SHIFT(pass)) +#define PNG_ROW_FROM_PASS_ROW(yIn, pass) \ + (((yIn)<>(((7-(off))-(pass))<<2)) & 0xFU) | \ + ((0x01145AF0U>>(((7-(off))-(pass))<<2)) & 0xF0U)) +#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \ + ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1) +#define PNG_COL_IN_INTERLACE_PASS(x, pass) \ + ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) + +/* These are needed too for the default build: */ +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED + +/* This comes from pnglibconf.h afer 1.5: */ +#define PNG_FP_1 100000 +#define PNG_GAMMA_THRESHOLD_FIXED\ + ((png_fixed_point)(PNG_GAMMA_THRESHOLD * PNG_FP_1)) +#endif + +#if PNG_LIBPNG_VER < 10600 + /* 1.6.0 constifies many APIs, the following exists to allow pngvalid to be + * compiled against earlier versions. + */ +# define png_const_structp png_structp +#endif + + +/* Copied from pngpriv.h */ +#define PNG_32b(b,s) ((png_uint_32)(b) << (s)) +#define PNG_CHUNK(b1,b2,b3,b4) \ + (PNG_32b(b1,24) | PNG_32b(b2,16) | PNG_32b(b3,8) | PNG_32b(b4,0)) + +#define png_IHDR PNG_CHUNK( 73, 72, 68, 82) +#define png_IDAT PNG_CHUNK( 73, 68, 65, 84) +#define png_IEND PNG_CHUNK( 73, 69, 78, 68) +#define png_PLTE PNG_CHUNK( 80, 76, 84, 69) +#define png_bKGD PNG_CHUNK( 98, 75, 71, 68) +#define png_cHRM PNG_CHUNK( 99, 72, 82, 77) +#define png_gAMA PNG_CHUNK(103, 65, 77, 65) +#define png_hIST PNG_CHUNK(104, 73, 83, 84) +#define png_iCCP PNG_CHUNK(105, 67, 67, 80) +#define png_iTXt PNG_CHUNK(105, 84, 88, 116) +#define png_oFFs PNG_CHUNK(111, 70, 70, 115) +#define png_pCAL PNG_CHUNK(112, 67, 65, 76) +#define png_sCAL PNG_CHUNK(115, 67, 65, 76) +#define png_pHYs PNG_CHUNK(112, 72, 89, 115) +#define png_sBIT PNG_CHUNK(115, 66, 73, 84) +#define png_sPLT PNG_CHUNK(115, 80, 76, 84) +#define png_sRGB PNG_CHUNK(115, 82, 71, 66) +#define png_sTER PNG_CHUNK(115, 84, 69, 82) +#define png_tEXt PNG_CHUNK(116, 69, 88, 116) +#define png_tIME PNG_CHUNK(116, 73, 77, 69) +#define png_tRNS PNG_CHUNK(116, 82, 78, 83) +#define png_zTXt PNG_CHUNK(122, 84, 88, 116) +#define png_vpAg PNG_CHUNK('v', 'p', 'A', 'g') + +/* Test on flag values as defined in the spec (section 5.4): */ +#define PNG_CHUNK_ANCILLIARY(c) (1 & ((c) >> 29)) +#define PNG_CHUNK_CRITICAL(c) (!PNG_CHUNK_ANCILLIARY(c)) +#define PNG_CHUNK_PRIVATE(c) (1 & ((c) >> 21)) +#define PNG_CHUNK_RESERVED(c) (1 & ((c) >> 13)) +#define PNG_CHUNK_SAFE_TO_COPY(c) (1 & ((c) >> 5)) + +/* Chunk information */ +#define PNG_INFO_tEXt 0x10000000U +#define PNG_INFO_iTXt 0x20000000U +#define PNG_INFO_zTXt 0x40000000U + +#define PNG_INFO_sTER 0x01000000U +#define PNG_INFO_vpAg 0x02000000U + +#define ABSENT 0 +#define START 1 +#define END 2 + +static struct +{ + char name[5]; + png_uint_32 flag; + png_uint_32 tag; + int unknown; /* Chunk not known to libpng */ + int all; /* Chunk set by the '-1' option */ + int position; /* position in pngtest.png */ + int keep; /* unknown handling setting */ +} chunk_info[] = { + /* Critical chunks */ + { "IDAT", PNG_INFO_IDAT, png_IDAT, 0, 0, START, 0 }, /* must be [0] */ + { "PLTE", PNG_INFO_PLTE, png_PLTE, 0, 0, ABSENT, 0 }, + + /* Non-critical chunks that libpng handles */ + { "bKGD", PNG_INFO_bKGD, png_bKGD, 0, 1, START, 0 }, + { "cHRM", PNG_INFO_cHRM, png_cHRM, 0, 1, START, 0 }, + { "gAMA", PNG_INFO_gAMA, png_gAMA, 0, 1, START, 0 }, + { "hIST", PNG_INFO_hIST, png_hIST, 0, 1, ABSENT, 0 }, + { "iCCP", PNG_INFO_iCCP, png_iCCP, 0, 1, ABSENT, 0 }, + { "iTXt", PNG_INFO_iTXt, png_iTXt, 0, 1, ABSENT, 0 }, + { "oFFs", PNG_INFO_oFFs, png_oFFs, 0, 1, START, 0 }, + { "pCAL", PNG_INFO_pCAL, png_pCAL, 0, 1, START, 0 }, + { "pHYs", PNG_INFO_pHYs, png_pHYs, 0, 1, START, 0 }, + { "sBIT", PNG_INFO_sBIT, png_sBIT, 0, 1, START, 0 }, + { "sCAL", PNG_INFO_sCAL, png_sCAL, 0, 1, START, 0 }, + { "sPLT", PNG_INFO_sPLT, png_sPLT, 0, 1, ABSENT, 0 }, + { "sRGB", PNG_INFO_sRGB, png_sRGB, 0, 1, START, 0 }, + { "tEXt", PNG_INFO_tEXt, png_tEXt, 0, 1, START, 0 }, + { "tIME", PNG_INFO_tIME, png_tIME, 0, 1, START, 0 }, + { "tRNS", PNG_INFO_tRNS, png_tRNS, 0, 0, ABSENT, 0 }, + { "zTXt", PNG_INFO_zTXt, png_zTXt, 0, 1, END, 0 }, + + /* No libpng handling */ + { "sTER", PNG_INFO_sTER, png_sTER, 1, 1, START, 0 }, + { "vpAg", PNG_INFO_vpAg, png_vpAg, 1, 0, START, 0 }, +}; + +#define NINFO ((int)((sizeof chunk_info)/(sizeof chunk_info[0]))) + +static int +find(const char *name) +{ + int i = NINFO; + while (--i >= 0) + { + if (memcmp(chunk_info[i].name, name, 4) == 0) + break; + } + + return i; +} + +static int +findb(const png_byte *name) +{ + int i = NINFO; + while (--i >= 0) + { + if (memcmp(chunk_info[i].name, name, 4) == 0) + break; + } + + return i; +} + +static int +find_by_flag(png_uint_32 flag) +{ + int i = NINFO; + + while (--i >= 0) if (chunk_info[i].flag == flag) return i; + + fprintf(stderr, "test-unknown: internal error\n"); + exit(4); +} + +static int +ancilliary(const char *name) +{ + return PNG_CHUNK_ANCILLIARY(PNG_CHUNK(name[0], name[1], name[2], name[3])); +} + +static int +ancilliaryb(const png_byte *name) +{ + return PNG_CHUNK_ANCILLIARY(PNG_CHUNK(name[0], name[1], name[2], name[3])); +} + +static int error_count = 0; +static int warning_count = 0; + +static void +error(png_structp png_ptr, const char *message) +{ + fprintf(stderr, "libpng error: %s\n", message); + exit(1); + (void)png_ptr; +} + +static void +warning(png_structp png_ptr, const char *message) +{ + ++warning_count; + fprintf(stderr, "libpng warning: %s\n", message); + (void)png_ptr; +} + +static png_uint_32 +get_valid(const char *file, png_const_structp png_ptr, png_const_infop info_ptr) +{ + png_uint_32 flags = png_get_valid(png_ptr, info_ptr, (png_uint_32)~0); + + /* Map the text chunks back into the flags */ + { + png_textp text; + png_uint_32 ntext = png_get_text(png_ptr, info_ptr, &text, NULL); + + while (ntext-- > 0) switch (text[ntext].compression) + { + case -1: + flags |= PNG_INFO_tEXt; + break; + case 0: + flags |= PNG_INFO_zTXt; + break; + case 1: + case 2: + flags |= PNG_INFO_iTXt; + break; + default: + fprintf(stderr, "%s: unknown text compression %d\n", file, + text[ntext].compression); + exit(1); + } + } + + return flags; +} + +static png_uint_32 +get_unknown(const char *file, int def, png_const_structp png_ptr, + png_const_infop info_ptr) +{ + /* Create corresponding 'unknown' flags */ + png_uint_32 flags = 0; + { + png_unknown_chunkp unknown; + int num_unknown = png_get_unknown_chunks(png_ptr, info_ptr, &unknown); + + while (--num_unknown >= 0) + { + int chunk = findb(unknown[num_unknown].name); + + /* Chunks not know to test-unknown must be validated here; since they + * must also be unknown to libpng the 'def' behavior should have been + * used. + */ + if (chunk < 0) switch (def) + { + default: /* impossible */ + case PNG_HANDLE_CHUNK_AS_DEFAULT: + case PNG_HANDLE_CHUNK_NEVER: + fprintf(stderr, "%s: %s: %s: unknown chunk saved\n", + file, def ? "discard" : "default", unknown[num_unknown].name); + ++error_count; + break; + + case PNG_HANDLE_CHUNK_IF_SAFE: + if (!ancilliaryb(unknown[num_unknown].name)) + { + fprintf(stderr, + "%s: if-safe: %s: unknown critical chunk saved\n", + file, unknown[num_unknown].name); + ++error_count; + break; + } + /* FALL THROUGH (safe) */ + case PNG_HANDLE_CHUNK_ALWAYS: + break; + } + + else + flags |= chunk_info[chunk].flag; + } + } + + return flags; +} + +static int +check(FILE *fp, int argc, const char **argv, png_uint_32p flags/*out*/) +{ + png_structp png_ptr; + png_infop info_ptr, end_ptr; + int i, def = PNG_HANDLE_CHUNK_AS_DEFAULT, npasses, ipass; + png_uint_32 height; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, error, warning); + if (png_ptr == NULL) + { + fprintf(stderr, "%s: could not allocate png struct\n", argv[argc]); + exit(1); + } + + info_ptr = png_create_info_struct(png_ptr); + end_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL || end_ptr == NULL) + { + fprintf(stderr, "%s: could not allocate png info\n", argv[argc]); + exit(1); + } + + png_init_io(png_ptr, fp); + + /* Handle each argument in turn; multiple settings are possible for the same + * chunk and multiple calls will occur (the last one should override all + * preceding ones). + */ + for (i=1; i 1) + { + png_uint_32 width = png_get_image_width(png_ptr, info_ptr); + + for (ipass=0; ipass 0) + { + png_uint_32 y; + + for (y=0; y 1 && strcmp(argv[1], "--strict") == 0) + { + strict = 1; + --argc; + ++argv; + } + + if (--argc < 1) + { + fprintf(stderr, "test-unknown: usage:\n" + " %s {(CHNK|default|all)=(default|discard|if-safe|save)} testfile.png\n", + program); + exit(2); + } + + fp = fopen(argv[argc], "rb"); + if (fp == NULL) + { + perror(argv[argc]); + exit(2); + } + + /* First find all the chunks, known and unknown, in the test file: */ + count_argv[0] = program; + count_argv[1] = "default=save"; + count_argv[2] = argv[argc]; + (void)check(fp, 2, count_argv, flags[0]); + + /* Now find what the various supplied options cause to change: */ + rewind(fp); + def = check(fp, argc, argv, flags[1]); + fclose(fp); + + /* Chunks should either be known or unknown, never both and this should apply + * whether the chunk is before or after the IDAT (actually, the app can + * probably change this by swapping the handling after the image, but this + * test does not to that.) + */ + check_error(argc[argv], + (flags[0][0]|flags[0][2]) & (flags[0][1]|flags[0][3]), + "chunk handled inconsistently in count tests"); + check_error(argc[argv], + (flags[1][0]|flags[1][2]) & (flags[1][1]|flags[1][3]), + "chunk handled inconsistently in option tests"); + + /* Now find out what happened to each chunk before and after the IDAT and + * determine if the behavior was correct. First some basic sanity checks, + * any known chunk should be known in the original count, any unknown chunk + * should be either known or unknown in the original. + */ + { + png_uint_32 test; + + test = flags[1][0] & ~flags[0][0]; + check_error(argv[argc], test, "new known chunk before IDAT"); + test = flags[1][1] & ~(flags[0][0] | flags[0][1]); + check_error(argv[argc], test, "new unknown chunk before IDAT"); + test = flags[1][2] & ~flags[0][2]; + check_error(argv[argc], test, "new known chunk after IDAT"); + test = flags[1][3] & ~(flags[0][2] | flags[0][3]); + check_error(argv[argc], test, "new unknown chunk after IDAT"); + } + + /* Now each chunk in the original list should have been handled according to + * the options set for that chunk, regardless of whether libpng knows about + * it or not. + */ + check_handling(argv[argc], def, flags[0][0] | flags[0][1], flags[1][0], + flags[1][1], "before IDAT"); + check_handling(argv[argc], def, flags[0][2] | flags[0][3], flags[1][2], + flags[1][3], "after IDAT"); + + return error_count + (strict ? warning_count : 0); +} diff --git a/png.c b/png.c index eebf6ebcd..e4007868a 100644 --- a/png.c +++ b/png.c @@ -556,7 +556,7 @@ png_free_data(png_const_structrp png_ptr, png_inforp info_ptr, png_uint_32 mask, } #endif -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED if ((mask & PNG_FREE_UNKN) & info_ptr->free_me) { if (num != -1) @@ -570,7 +570,7 @@ png_free_data(png_const_structrp png_ptr, png_inforp info_ptr, png_uint_32 mask, else { - int i; + unsigned int i; if (info_ptr->unknown_chunks_num) { @@ -749,13 +749,13 @@ png_get_copyright(png_const_structrp png_ptr) #else # ifdef __STDC__ return PNG_STRING_NEWLINE \ - "libpng version 1.6.0beta28 - August 11, 2012" PNG_STRING_NEWLINE \ + "libpng version 1.6.0beta28 - August 16, 2012" PNG_STRING_NEWLINE \ "Copyright (c) 1998-2012 Glenn Randers-Pehrson" PNG_STRING_NEWLINE \ "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \ "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \ PNG_STRING_NEWLINE; # else - return "libpng version 1.6.0beta28 - August 11, 2012\ + return "libpng version 1.6.0beta28 - August 16, 2012\ Copyright (c) 1998-2012 Glenn Randers-Pehrson\ Copyright (c) 1996-1997 Andreas Dilger\ Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc."; @@ -802,9 +802,9 @@ png_get_header_version(png_const_structrp png_ptr) #endif } -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED int PNGAPI -png_handle_as_unknown(png_structrp png_ptr, png_const_bytep chunk_name) +png_handle_as_unknown(png_const_structrp png_ptr, png_const_bytep chunk_name) { /* Check chunk_name and return "keep" value if it's on the list, else 0 */ png_const_bytep p, p_end; @@ -816,29 +816,37 @@ png_handle_as_unknown(png_structrp png_ptr, png_const_bytep chunk_name) p = p_end + png_ptr->num_chunk_list*5; /* beyond end */ /* The code is the fifth byte after each four byte string. Historically this - * code was always searched from the end of the list, so it should continue - * to do so in case there are duplicated entries. + * code was always searched from the end of the list, this is no longer + * necessary because the 'set' routine handles duplicate entries correcty. */ do /* num_chunk_list > 0, so at least one */ { p -= 5; + if (!memcmp(chunk_name, p, 4)) return p[4]; } while (p > p_end); + /* This means that known chunks should be processed and unknown chunks should + * be handled according to the value of png_ptr->unknown_default; this can be + * confusing because, as a result, there are two levels of defaulting for + * unknown chunks. + */ return PNG_HANDLE_CHUNK_AS_DEFAULT; } +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED int /* PRIVATE */ -png_chunk_unknown_handling(png_structrp png_ptr, png_uint_32 chunk_name) +png_chunk_unknown_handling(png_const_structrp png_ptr, png_uint_32 chunk_name) { png_byte chunk_string[5]; PNG_CSTRING_FROM_CHUNK(chunk_string, chunk_name); return png_handle_as_unknown(png_ptr, chunk_string); } -#endif +#endif /* READ_UNKNOWN_CHUNKS */ +#endif /* SET_UNKNOWN_CHUNKS */ #ifdef PNG_READ_SUPPORTED /* This function, added to libpng-1.0.6g, is untested. */ diff --git a/png.h b/png.h index 6af7c5c07..3baffcf45 100644 --- a/png.h +++ b/png.h @@ -1,7 +1,7 @@ /* png.h - header file for PNG reference library * - * libpng version 1.6.0beta28 - August 11, 2012 + * libpng version 1.6.0beta28 - August 16, 2012 * Copyright (c) 1998-2012 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) @@ -11,7 +11,7 @@ * Authors and maintainers: * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat * libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger - * libpng versions 0.97, January 1998, through 1.6.0beta28 - August 11, 2012: Glenn + * libpng versions 0.97, January 1998, through 1.6.0beta28 - August 16, 2012: Glenn * See also "Contributing Authors", below. * * Note about libpng version numbers: @@ -198,7 +198,7 @@ * * This code is released under the libpng license. * - * libpng versions 1.2.6, August 15, 2004, through 1.6.0beta28, August 11, 2012, are + * libpng versions 1.2.6, August 15, 2004, through 1.6.0beta28, August 16, 2012, are * Copyright (c) 2004, 2006-2012 Glenn Randers-Pehrson, and are * distributed according to the same disclaimer and license as libpng-1.2.5 * with the following individual added to the list of Contributing Authors: @@ -310,7 +310,7 @@ * Y2K compliance in libpng: * ========================= * - * August 11, 2012 + * August 16, 2012 * * Since the PNG Development group is an ad-hoc body, we can't make * an official declaration. @@ -378,7 +378,7 @@ /* Version information for png.h - this should match the version in png.c */ #define PNG_LIBPNG_VER_STRING "1.6.0beta28" #define PNG_HEADER_VERSION_STRING \ - " libpng version 1.6.0beta28 - August 11, 2012\n" + " libpng version 1.6.0beta28 - August 16, 2012\n" #define PNG_LIBPNG_VER_SONUM 16 #define PNG_LIBPNG_VER_DLLNUM 16 @@ -704,20 +704,26 @@ typedef png_time * png_timep; typedef const png_time * png_const_timep; typedef png_time * * png_timepp; -#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED) || \ - defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED) +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED /* png_unknown_chunk is a structure to hold queued chunks for which there is * no specific support. The idea is that we can use this to queue * up private chunks for output even though the library doesn't actually * know about their semantics. + * + * The data in the structure is set by libpng on read and used on write. */ typedef struct png_unknown_chunk_t { - png_byte name[5]; - png_byte *data; + png_byte name[5]; /* Textual chunk name with '\0' terminator */ + png_byte *data; /* Data, should not be modified on read! */ png_size_t size; - /* libpng-using applications should NOT directly modify this byte. */ + /* On write 'locaation' must be set using the flag values listed below. + * Notice that on read it is set by libpng however the values stored have + * more bits set than are listed below. Always treat the value as a + * bitmask. On write set only one bit - setting multiple bits may cause the + * chunk to be written in multiple places. + */ png_byte location; /* mode of operation at read time */ } png_unknown_chunk; @@ -727,8 +733,7 @@ typedef const png_unknown_chunk * png_const_unknown_chunkp; typedef png_unknown_chunk * * png_unknown_chunkpp; #endif -/* Values for the unknown chunk location byte */ - +/* Flag values for the unknown chunk location byte. */ #define PNG_HAVE_IHDR 0x01 #define PNG_HAVE_PLTE 0x02 #define PNG_AFTER_IDAT 0x08 @@ -896,7 +901,8 @@ typedef PNG_CALLBACK(int, *png_user_chunk_ptr, (png_structp, png_unknown_chunkp)); #endif #ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED -typedef PNG_CALLBACK(void, *png_unknown_chunk_ptr, (png_structp)); +/* not used anywhere */ +/* typedef PNG_CALLBACK(void, *png_unknown_chunk_ptr, (png_structp)); */ #endif #ifdef PNG_SETJMP_SUPPORTED @@ -1826,9 +1832,31 @@ PNG_EXPORT(217, png_uint_32, png_get_current_row_number, (png_const_structrp)); PNG_EXPORT(218, png_byte, png_get_current_pass_number, (png_const_structrp)); #endif -#ifdef PNG_USER_CHUNKS_SUPPORTED +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +/* This callback is called only for *unknown* chunks, if + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED is set then it is possible to set known + * chunks to be treated as unknown, however in this case the callback must do + * any processing required by the chunk (e.g. by calling the appropriate + * png_set_ APIs.) + * + * There is no write support - on write, by default, all the chunks in the + * 'unknown' list are written in the specified position. + * + * The integer return from the callback function is interpreted thus: + * + * negative: An error occured, png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be discarded unless + * png_set_keep_unknown_chunks has been used to set a 'keep' behavior + * for this particular chunk, in which case that will be used. A + * critical chunk will cause an error at this point unless it is to be + * saved. + * positive: The chunk was handled, libpng will ignore/discard it. + */ PNG_EXPORT(88, void, png_set_read_user_chunk_fn, (png_structrp png_ptr, png_voidp user_chunk_ptr, png_user_chunk_ptr read_user_chunk_fn)); +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED PNG_EXPORT(89, png_voidp, png_get_user_chunk_ptr, (png_const_structrp png_ptr)); #endif @@ -1913,8 +1941,10 @@ PNG_EXPORTA(99, void, png_data_freer, (png_const_structrp png_ptr, #define PNG_FREE_ROWS 0x0040 #define PNG_FREE_PCAL 0x0080 #define PNG_FREE_SCAL 0x0100 -#define PNG_FREE_UNKN 0x0200 -#define PNG_FREE_LIST 0x0400 +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +# define PNG_FREE_UNKN 0x0200 +#endif +/* PNG_FREE_LIST 0x0400 removed in 1.6.0 because it is ignored */ #define PNG_FREE_PLTE 0x1000 #define PNG_FREE_TRNS 0x2000 #define PNG_FREE_TEXT 0x4000 @@ -2321,40 +2351,123 @@ PNG_EXPORT(171, void, png_set_sCAL_s, (png_const_structrp png_ptr, png_const_charp swidth, png_const_charp sheight)); #endif /* PNG_sCAL_SUPPORTED */ -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED -/* Provide a list of chunks and how they are to be handled, if the built-in - * handling or default unknown chunk handling is not desired. Any chunks not - * listed will be handled in the default manner. The IHDR and IEND chunks - * must not be listed. Because this turns off the default handling for chunks - * that would otherwise be recognized the behavior of libpng transformations may - * well become incorrect! - * keep = 0: PNG_HANDLE_CHUNK_AS_DEFAULT: follow default behavior - * = 1: PNG_HANDLE_CHUNK_NEVER: do not keep - * = 2: PNG_HANDLE_CHUNK_IF_SAFE: keep only if safe-to-copy - * = 3: PNG_HANDLE_CHUNK_ALWAYS: keep even if unsafe-to-copy - * If num_chunks is 0, then the "keep" parameter specifies the default - * manner for handling unknown chunks. If num_chunks is positive, then - * the "keep" parameter specifies the manner for handling only those chunks - * appearing in the chunk_list array. If it is negative, then the "keep" - * parameter specifies the manner for handling all unknown chunks plus - * all chunks recognized by libpng except for the IHDR, PLTE, tRNS, IDAT, - * and IEND chunks. -*/ +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +/* Provide the default handling for all unknown chunks or, optionally, for + * specific unknown chunks. + * + * NOTE: prior to 1.6.0 the handling specified for particular chunks on read was + * ignored and the default was used, the per-chunk setting only had an effect on + * write. If you wish to have chunk-specific handling on read in code that must + * work on earlier versions you must use a user chunk callback to specify the + * desired handling (keep or discard.) + * + * The 'keep' parameter is a PNG_HANDLE_CHUNK_ value as listed below. The + * parameter is interpreted as follows: + * + * READ: + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Known chunks: do normal libpng processing, do not keep the chunk (but + * set the comments below about PNG_HANDLE_AS_UNKNOWN_SUPPORTED) + * Unknown chunks: for a specific chunk use the global default, when used + * as the default discard the chunk data. + * PNG_HANDLE_CHUNK_NEVER: + * Discard the chunk data. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Keep the chunk data if the chunk is not critial else raise a chunk + * error. + * PNG_HANDLE_CHUNK_ALWAYS: + * Keep the chunk data. + * + * If the chunk data is saved it can be retrieved using png_get_unknown_chunks, + * below. Notice that specifying "AS_DEFAULT" as a global default is equivalent + * to specifying "NEVER", however when "AS_DEFAULT" is used for specific chunks + * it simply resets the behavior to the libpng default. + * + * The per-chunk handling is always used when there is a png_user_chunk_ptr + * callback and the callback returns 0; the chunk is then always stored *unless* + * it is critical and the per-chunk setting is other than ALWAYS. Notice that + * the global default is *not* used in this case. (In effect the per-chunk + * value is incremented to at least IF_SAFE.) + * + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED: + * If this is *not* set known chunks will always be handled by libpng and + * will never be stored in the unknown chunk list. Known chunks listed to + * png_set_keep_unknown_chunks will have no effect. If it is set then known + * chunks listed with a keep other than AS_DEFAULT will *never* be processed + * by libpng, in addition critical chunks must either be processed by the + * callback or saved. + * + * The IHDR and IEND chunks must not be listed. Because this turns off the + * default handling for chunks that would otherwise be recognized the + * behavior of libpng transformations may well become incorrect! + * + * WRITE: + * When writing chunks the options only apply to the chunks specified by + * png_set_unknown_chunks (below), libpng will *always* write known chunks + * required by png_set_ calls and will always write the core critical chunks + * (as required for PLTE). + * + * Each chunk in the png_set_unknown_chunks list is looked up in the + * png_set_keep_unknown_chunks list to find the keep setting, this is then + * interpreted as follows: + * + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Write safe-to-copy chunks and write other chunks if the global + * default is set to _ALWAYS, otherwise don't write this chunk. + * PNG_HANDLE_CHUNK_NEVER: + * Do not write the chunk. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Write the chunk if it is safe-to-copy, otherwise do not write it. + * PNG_HANDLE_CHUNK_ALWAYS: + * Write the chunk. + * + * Note that the default behavior is effectively the opposite of the read case - + * in read unknown chunks are not stored by default, in write they are written + * by default. Also the behavior of PNG_HANDLE_CHUNK_IF_SAFE is very different + * - on write the safe-to-copy bit is checked, on read the critical bit is + * checked and on read if the chunk is critical an error will be raised. + * + * num_chunks: + * =========== + * If num_chunks is positive, then the "keep" parameter specifies the manner + * for handling only those chunks appearing in the chunk_list array, + * otherwise the chunk list array is ignored. + * + * If num_chunks is 0 the "keep" parameter specifies the default behavior for + * unknown chunks, as described above. + * + * If num_chunks is negative, then the "keep" parameter specifies the manner + * for handling all unknown chunks plus all chunks recognized by libpng + * except for the IHDR, PLTE, tRNS, IDAT, and IEND chunks (which continue to + * be processed by libpng. + */ PNG_EXPORT(172, void, png_set_keep_unknown_chunks, (png_structrp png_ptr, int keep, png_const_bytep chunk_list, int num_chunks)); -/* The handling code is returned; the result is therefore true (non-zero) if - * special handling is required, false for the default handling. +/* The "keep" PNG_HANDLE_CHUNK_ parameter for the specified chunk is returned; + * the result is therefore true (non-zero) if special handling is required, + * false for the default handling. */ -PNG_EXPORT(173, int, png_handle_as_unknown, (png_structrp png_ptr, +PNG_EXPORT(173, int, png_handle_as_unknown, (png_const_structrp png_ptr, png_const_bytep chunk_name)); #endif -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED PNG_EXPORT(174, void, png_set_unknown_chunks, (png_const_structrp png_ptr, png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns)); + /* NOTE: prior to 1.6.0 this routine set the 'location' field of the added + * unknowns to the location currently stored in the png_struct. This is + * invariably the wrong value on write. To fix this call the following API + * for each chunk in the list with the correct location. If you know your + * code won't be compiled on earlier versions you can rely on + * png_set_unknown_chunks(write-ptr, png_get_unknown_chunks(read-ptr)) doing + * the correct thing. + */ + PNG_EXPORT(175, void, png_set_unknown_chunk_location, (png_const_structrp png_ptr, png_inforp info_ptr, int chunk, int location)); + PNG_EXPORT(176, int, png_get_unknown_chunks, (png_const_structrp png_ptr, png_const_inforp info_ptr, png_unknown_chunkpp entries)); #endif @@ -2393,6 +2506,7 @@ PNG_EXPORT(184, png_uint_32, png_permit_mng_features, (png_structrp png_ptr, #define PNG_HANDLE_CHUNK_NEVER 1 #define PNG_HANDLE_CHUNK_IF_SAFE 2 #define PNG_HANDLE_CHUNK_ALWAYS 3 +#define PNG_HANDLE_CHUNK_LAST 4 /* Strip the prepended error numbers ("#nnn ") from error and warning * messages before passing them to the error or warning handler. diff --git a/pnginfo.h b/pnginfo.h index a1e7dbf3e..ee79beb8b 100644 --- a/pnginfo.h +++ b/pnginfo.h @@ -220,11 +220,10 @@ defined(PNG_READ_BACKGROUND_SUPPORTED) /* New members added in libpng-1.0.6 */ png_uint_32 free_me; /* flags items libpng is responsible for freeing */ -#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED) || \ - defined(PNG_HANDLE_AS_UNKNOWN_SUPPORTED) +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED /* Storage for unknown chunks that the library doesn't recognize. */ png_unknown_chunkp unknown_chunks; - int unknown_chunks_num; + unsigned int unknown_chunks_num; #endif #ifdef PNG_sPLT_SUPPORTED diff --git a/pngpread.c b/pngpread.c index 608eda10b..2da3dfe3c 100644 --- a/pngpread.c +++ b/pngpread.c @@ -185,6 +185,9 @@ void /* PRIVATE */ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) { png_uint_32 chunk_name; +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; /* unknown handling method */ +#endif /* First we make sure we have enough data for the 4 byte chunk name * and the 4 byte chunk length before proceeding with decoding the @@ -216,14 +219,28 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) if (chunk_name == png_IDAT) { - /* This is here above the if/else case statement below because if the - * unknown handling marks 'IDAT' as unknown then the IDAT handling case is - * completely skipped. - * - * TODO: there must be a better way of doing this. - */ if (png_ptr->mode & PNG_AFTER_IDAT) png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + + /* If we reach an IDAT chunk, this means we have read all of the + * header chunks, and we can start reading the image (or if this + * is called after the image has been read - we have an error). + */ + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + !(png_ptr->mode & PNG_HAVE_PLTE)) + png_error(png_ptr, "Missing PLTE before IDAT"); + + png_ptr->mode |= PNG_HAVE_IDAT; + + if (!(png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) + if (png_ptr->push_length == 0) + return; + + if (png_ptr->mode & PNG_AFTER_IDAT) + png_benign_error(png_ptr, "Too many IDATs found"); } if (chunk_name == png_IHDR) @@ -255,7 +272,7 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) } #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - else if (png_chunk_unknown_handling(png_ptr, chunk_name)) + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name))) { if (png_ptr->push_length + 4 > png_ptr->buffer_size) { @@ -263,23 +280,10 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) return; } - if (chunk_name == png_IDAT) - png_ptr->mode |= PNG_HAVE_IDAT; - - png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length); + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, keep); if (chunk_name == png_PLTE) png_ptr->mode |= PNG_HAVE_PLTE; - - else if (chunk_name == png_IDAT) - { - if (!(png_ptr->mode & PNG_HAVE_IHDR)) - png_error(png_ptr, "Missing IHDR before IDAT"); - - else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && - !(png_ptr->mode & PNG_HAVE_PLTE)) - png_error(png_ptr, "Missing PLTE before IDAT"); - } } #endif @@ -295,30 +299,7 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) else if (chunk_name == png_IDAT) { - /* If we reach an IDAT chunk, this means we have read all of the - * header chunks, and we can start reading the image (or if this - * is called after the image has been read - we have an error). - */ - - if (!(png_ptr->mode & PNG_HAVE_IHDR)) - png_error(png_ptr, "Missing IHDR before IDAT"); - - else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && - !(png_ptr->mode & PNG_HAVE_PLTE)) - png_error(png_ptr, "Missing PLTE before IDAT"); - - if (png_ptr->mode & PNG_HAVE_IDAT) - { - if (!(png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) - if (png_ptr->push_length == 0) - return; - - if (png_ptr->mode & PNG_AFTER_IDAT) - png_benign_error(png_ptr, "Too many IDATs found"); - } - png_ptr->idat_size = png_ptr->push_length; - png_ptr->mode |= PNG_HAVE_IDAT; png_ptr->process_mode = PNG_READ_IDAT_MODE; png_push_have_info(png_ptr, info_ptr); png_ptr->zstream.avail_out = @@ -556,7 +537,8 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) png_push_save_buffer(png_ptr); return; } - png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length); + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, + PNG_HANDLE_CHUNK_AS_DEFAULT); } png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; diff --git a/pngpriv.h b/pngpriv.h index 552e1f2ce..8a703b165 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -528,8 +528,8 @@ typedef const png_uint_16p * png_const_uint_16pp; #define PNG_FLAG_ASSUME_sRGB 0x1000 /* Added to libpng-1.5.4 */ #define PNG_FLAG_OPTIMIZE_ALPHA 0x2000 /* Added to libpng-1.5.4 */ #define PNG_FLAG_DETECT_UNINITIALIZED 0x4000 /* Added to libpng-1.5.4 */ -#define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000 -#define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000 +/* #define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000 */ +/* #define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000 */ #define PNG_FLAG_LIBRARY_MISMATCH 0x20000 #define PNG_FLAG_STRIP_ERROR_NUMBERS 0x40000 #define PNG_FLAG_STRIP_ERROR_TEXT 0x80000 @@ -1310,19 +1310,28 @@ PNG_INTERNAL_FUNCTION(void,png_handle_zTXt,(png_structrp png_ptr, png_inforp inf png_uint_32 length),PNG_EMPTY); #endif -PNG_INTERNAL_FUNCTION(void,png_handle_unknown,(png_structrp png_ptr, - png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); - PNG_INTERNAL_FUNCTION(void,png_check_chunk_name,(png_structrp png_ptr, png_uint_32 chunk_name),PNG_EMPTY); -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED -/* Exactly as png_handle_as_unknown() except that the argument is a 32-bit chunk - * name, not a string. - */ -PNG_INTERNAL_FUNCTION(int,png_chunk_unknown_handling,(png_structrp png_ptr, - png_uint_32 chunk_name),PNG_EMPTY); +#ifdef PNG_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_unknown,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length, int keep),PNG_EMPTY); + /* This is the function that gets called for unknown chunks. The 'keep' + * argument is either non-0 for a known chunk that has been set to be handled + * as unknown or 0 for an unknown chunk. By default the function just skips + * the chunk or errors out if it is critical. + */ + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_chunk_unknown_handling, + (png_const_structrp png_ptr, png_uint_32 chunk_name),PNG_EMPTY); + /* Exactly as the API png_handle_as_unknown() except that the argument is a + * 32-bit chunk name, not a string. + */ #endif +#endif /* PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED */ /* Handle the transformations for reading and writing */ #ifdef PNG_READ_TRANSFORMS_SUPPORTED diff --git a/pngread.c b/pngread.c index feb92b097..27fa7fcdc 100644 --- a/pngread.c +++ b/pngread.c @@ -91,6 +91,10 @@ png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, void PNGAPI png_read_info(png_structrp png_ptr, png_inforp info_ptr) { +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + png_debug(1, "in png_read_info"); if (png_ptr == NULL || info_ptr == NULL) @@ -104,13 +108,30 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) png_uint_32 length = png_read_chunk_header(png_ptr); png_uint_32 chunk_name = png_ptr->chunk_name; + /* IDAT logic needs to happen here to simplify getting the two flags + * right. + */ + if (chunk_name == png_IDAT) + { + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + !(png_ptr->mode & PNG_HAVE_PLTE)) + png_chunk_error(png_ptr, "Missing PLTE before IDAT"); + + else if (png_ptr->mode & PNG_AFTER_IDAT) + png_chunk_benign_error(png_ptr, "Too many IDATs found"); + + png_ptr->mode |= PNG_HAVE_IDAT; + } + + else if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + /* This should be a binary subdivision search or a hash for * matching the chunk name rather than a linear search. */ - if (chunk_name == png_IDAT) - if (png_ptr->mode & PNG_AFTER_IDAT) - png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; - if (chunk_name == png_IHDR) png_handle_IHDR(png_ptr, info_ptr, length); @@ -118,26 +139,16 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) png_handle_IEND(png_ptr, info_ptr, length); #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - else if (png_chunk_unknown_handling(png_ptr, chunk_name) != - PNG_HANDLE_CHUNK_AS_DEFAULT) + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name))) { - if (chunk_name == png_IDAT) - png_ptr->mode |= PNG_HAVE_IDAT; - - png_handle_unknown(png_ptr, info_ptr, length); + png_handle_unknown(png_ptr, info_ptr, length, keep); if (chunk_name == png_PLTE) png_ptr->mode |= PNG_HAVE_PLTE; else if (chunk_name == png_IDAT) { - if (!(png_ptr->mode & PNG_HAVE_IHDR)) - png_error(png_ptr, "Missing IHDR before IDAT"); - - else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && - !(png_ptr->mode & PNG_HAVE_PLTE)) - png_error(png_ptr, "Missing PLTE before IDAT"); - + png_ptr->idat_size = 0; /* It has been consumed */ break; } } @@ -147,15 +158,7 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) else if (chunk_name == png_IDAT) { - if (!(png_ptr->mode & PNG_HAVE_IHDR)) - png_error(png_ptr, "Missing IHDR before IDAT"); - - else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && - !(png_ptr->mode & PNG_HAVE_PLTE)) - png_error(png_ptr, "Missing PLTE before IDAT"); - png_ptr->idat_size = length; - png_ptr->mode |= PNG_HAVE_IDAT; break; } @@ -245,7 +248,8 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) #endif else - png_handle_unknown(png_ptr, info_ptr, length); + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); } } #endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ @@ -683,6 +687,10 @@ png_read_image(png_structrp png_ptr, png_bytepp image) void PNGAPI png_read_end(png_structrp png_ptr, png_inforp info_ptr) { +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + png_debug(1, "in png_read_end"); if (png_ptr == NULL) @@ -691,7 +699,10 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) /* If png_read_end is called in the middle of reading the rows there may * still be pending IDAT data and an owned zstream. Deal with this here. */ - png_read_finish_IDAT(png_ptr); +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + if (!png_chunk_unknown_handling(png_ptr, png_IDAT)) +#endif + png_read_finish_IDAT(png_ptr); #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED /* Report invalid palette index; added at libng-1.5.10 */ @@ -712,15 +723,14 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) png_handle_IEND(png_ptr, info_ptr, length); #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - else if (png_chunk_unknown_handling(png_ptr, chunk_name) != - PNG_HANDLE_CHUNK_AS_DEFAULT) + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name))) { if (chunk_name == png_IDAT) { if ((length > 0) || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) png_benign_error(png_ptr, "Too many IDATs found"); } - png_handle_unknown(png_ptr, info_ptr, length); + png_handle_unknown(png_ptr, info_ptr, length, keep); if (chunk_name == png_PLTE) png_ptr->mode |= PNG_HAVE_PLTE; } @@ -825,7 +835,8 @@ png_read_end(png_structrp png_ptr, png_inforp info_ptr) #endif else - png_handle_unknown(png_ptr, info_ptr, length); + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); } while (!(png_ptr->mode & PNG_HAVE_IEND)); } #endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ @@ -866,11 +877,12 @@ png_read_destroy(png_structrp png_ptr) png_free(png_ptr, png_ptr->save_buffer); #endif -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +#if (defined PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) &&\ + (defined PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) png_free(png_ptr, png_ptr->unknown_chunk.data); #endif -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED png_free(png_ptr, png_ptr->chunk_list); #endif @@ -1476,11 +1488,11 @@ png_image_skip_unused_chunks(png_structrp png_ptr) /* Ignore unknown chunks and all other chunks except for the * IHDR, PLTE, tRNS, IDAT, and IEND chunks. */ - png_set_keep_unknown_chunks(png_ptr, 1 /* PNG_HANDLE_CHUNK_NEVER */, + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, -1); /* But do not ignore image data handling chunks */ - png_set_keep_unknown_chunks(png_ptr, 0 /* PNG_HANDLE_CHUNK_AS_DEFAULT */, + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_AS_DEFAULT, chunks_to_process, (sizeof chunks_to_process)/5); } } diff --git a/pngrutil.c b/pngrutil.c index cb476e6b4..3982c80a7 100644 --- a/pngrutil.c +++ b/pngrutil.c @@ -2732,136 +2732,247 @@ png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } #endif -/* This function is called when we haven't found a handler for a - * chunk. If there isn't a problem with the chunk itself (ie bad - * chunk name, CRC, or a critical chunk), the chunk is silently ignored - * -- unless the PNG_FLAG_UNKNOWN_CHUNKS_SUPPORTED flag is on in which - * case it will be saved away to be written out later. - */ -void /* PRIVATE */ -png_handle_unknown(png_structrp png_ptr, png_inforp info_ptr, - png_uint_32 length) -{ - png_uint_32 skip = 0; - - png_debug(1, "in png_handle_unknown"); - -#ifdef PNG_USER_LIMITS_SUPPORTED - if (png_ptr->user_chunk_cache_max != 0) - { - if (png_ptr->user_chunk_cache_max == 1) - { - png_crc_finish(png_ptr, length); - return; - } - - if (--png_ptr->user_chunk_cache_max == 1) - { - png_crc_finish(png_ptr, length); - png_chunk_benign_error(png_ptr, "no space in chunk cache"); - return; - } - } -#endif - - if (png_ptr->mode & PNG_HAVE_IDAT) - { - if (png_ptr->chunk_name != png_IDAT) - png_ptr->mode |= PNG_AFTER_IDAT; - } - - if (PNG_CHUNK_CRITICAL(png_ptr->chunk_name)) - { -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - if (png_chunk_unknown_handling(png_ptr, png_ptr->chunk_name) != - PNG_HANDLE_CHUNK_ALWAYS -#ifdef PNG_READ_USER_CHUNKS_SUPPORTED - && png_ptr->read_user_chunk_fn == NULL -#endif - ) -#endif - png_chunk_error(png_ptr, "unknown critical chunk"); - } - #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED - if ((png_ptr->flags & PNG_FLAG_KEEP_UNKNOWN_CHUNKS) -#ifdef PNG_READ_USER_CHUNKS_SUPPORTED - || (png_ptr->read_user_chunk_fn != NULL) -#endif - ) - { -#ifdef PNG_MAX_MALLOC_64K - if (length > 65535) - { - png_crc_finish(png_ptr, length); - png_chunk_benign_error(png_ptr, - "unknown chunk too large to fit in memory"); - return; - } -#endif +/* Utility function for png_handle_unknown; set up png_ptr::unknown_chunk */ +static int +png_cache_unknown_chunk(png_structrp png_ptr, png_uint_32 length) +{ + png_alloc_size_t limit = PNG_SIZE_MAX; - /* TODO: this code is very close to the unknown handling in pngpread.c, - * maybe it can be put into a common utility routine? - * png_struct::unknown_chunk is just used as a temporary variable, along - * with the data into which the chunk is read. These can be eliminated. - */ + if (png_ptr->unknown_chunk.data != NULL) + { + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + } + +# ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; + +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + + if (length <= limit) + { PNG_CSTRING_FROM_CHUNK(png_ptr->unknown_chunk.name, png_ptr->chunk_name); - png_ptr->unknown_chunk.size = (png_size_t)length; + /* The following is safe because of the PNG_SIZE_MAX init above */ + png_ptr->unknown_chunk.size = (png_size_t)length/*SAFE*/; + /* 'mode' is a flag array, only the bottom four bits matter here */ + png_ptr->unknown_chunk.location = (png_byte)png_ptr->mode/*SAFE*/; if (length == 0) png_ptr->unknown_chunk.data = NULL; else { - png_ptr->unknown_chunk.data = (png_bytep)png_malloc(png_ptr, length); - png_crc_read(png_ptr, png_ptr->unknown_chunk.data, length); + /* Do a 'warn' here - it is handled below. */ + png_ptr->unknown_chunk.data = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, length)); } + } -#ifdef PNG_READ_USER_CHUNKS_SUPPORTED - if (png_ptr->read_user_chunk_fn != NULL) - { - /* Callback to user unknown chunk handler */ - int ret; - - ret = (*(png_ptr->read_user_chunk_fn)) - (png_ptr, &png_ptr->unknown_chunk); - - if (ret < 0) - png_chunk_error(png_ptr, "error in user chunk"); - - if (ret == 0) - { - if (PNG_CHUNK_CRITICAL(png_ptr->chunk_name)) - { -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - if (png_chunk_unknown_handling(png_ptr, png_ptr->chunk_name) != - PNG_HANDLE_CHUNK_ALWAYS) -#endif - png_chunk_error(png_ptr, "unknown critical chunk"); - } - - png_set_unknown_chunks(png_ptr, info_ptr, - &png_ptr->unknown_chunk, 1); - } - } - - else -#endif - png_set_unknown_chunks(png_ptr, info_ptr, &png_ptr->unknown_chunk, 1); - - png_free(png_ptr, png_ptr->unknown_chunk.data); - png_ptr->unknown_chunk.data = NULL; + if (png_ptr->unknown_chunk.data == NULL && length > 0) + { + /* This is benign because we clean up correctly */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "unknown chunk exceeds memory limits"); + return 0; } else -#endif - skip = length; + { + if (length > 0) + png_crc_read(png_ptr, png_ptr->unknown_chunk.data, length); + png_crc_finish(png_ptr, 0); + return 1; + } +} +#endif /* PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ - png_crc_finish(png_ptr, skip); +/* Handle an unknown, or known but disabled, chunk */ +void /* PRIVATE */ +png_handle_unknown(png_structrp png_ptr, png_inforp info_ptr, + png_uint_32 length, int keep) +{ + int handled = 0; /* the chunk was handled */ -#ifndef PNG_READ_USER_CHUNKS_SUPPORTED - PNG_UNUSED(info_ptr) /* Quiet compiler warnings about unused info_ptr */ -#endif + png_debug(1, "in png_handle_unknown"); + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* NOTE: this code is based on the code in libpng-1.4.12 except for fixing + * the bug which meant that setting a non-default behavior for a specific + * chunk would be ignored (the default was always used unless a user + * callback was installed). + * + * 'keep' is the value from the png_chunk_unknown_handling, the setting for + * this specific chunk_name, if PNG_HANDLE_AS_UNKNOWN_SUPPORTED, if not it + * will always be PNG_HANDLE_CHUNK_AS_DEFAULT and it needs to be set here. + * This is just an optimization to avoid multiple calls to the lookup + * function. + */ +# ifndef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + keep = png_chunk_unknown_handling(png_ptr, png_ptr->chunk_name); +# endif + + /* One of the following methods will read the chunk or skip it (at least one + * of these is always defined because this is the only way to switch on + * PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) + */ +# ifdef PNG_READ_USER_CHUNKS_SUPPORTED + /* The user callback takes precedence over the chunk keep value, but the + * keep value is still required to validate a save of a critical chunk. + */ + if (png_ptr->read_user_chunk_fn != NULL) + { + if (png_cache_unknown_chunk(png_ptr, length)) + { + /* Callback to user unknown chunk handler */ + int ret = (*(png_ptr->read_user_chunk_fn))(png_ptr, + &png_ptr->unknown_chunk); + + /* ret is: + * negative: An error occured, png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be discarded + * unless png_set_keep_unknown_chunks has been used to set + * a 'keep' behavior for this particular chunk, in which + * case that will be used. A critical chunk will cause an + * error at this point unless it is to be saved. + * positive: The chunk was handled, libpng will ignore/discard it. + */ + if (ret < 0) + png_chunk_error(png_ptr, "error in user chunk"); + + else if (ret == 0) + { + /* If the keep value is 'default' or 'never' override it, but + * still error out on critical chunks unless the keep value is + * 'always' While this is weird it is the behavior in 1.4.12. A + * possible improvement would be to obey the value set for the + * chunk, but this would be an API change that would probably + * damage some applications. + * + * The png_app_warning below catches the case that matters, where + * the application has neither set specific save for this chunk + * or global save. + */ + if (keep < PNG_HANDLE_CHUNK_IF_SAFE) + { +# ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + if (png_ptr->unknown_default < PNG_HANDLE_CHUNK_IF_SAFE) + png_app_warning(png_ptr, + "forcing save of an unhandled chunk; please call png_set_keep_unknown_chunks"); +# endif + keep = PNG_HANDLE_CHUNK_IF_SAFE; + } + } + + else /* chunk was handled */ + { + handled = 1; + /* Critical chunks can be safely discarded at this point. */ + keep = PNG_HANDLE_CHUNK_NEVER; + } + } + + else + keep = PNG_HANDLE_CHUNK_NEVER; /* insufficient memory */ + } + +# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + else +# endif +# endif /* PNG_READ_USER_CHUNKS_SUPPORTED */ + +# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + { + /* keep is currently just the per-chunk setting, if there was no + * setting change it to the global default now (not that this may + * still be AS_DEFAULT) then obtain the cache of the chunk if required, + * if not simply skip the chunk. + */ + if (keep == PNG_HANDLE_CHUNK_AS_DEFAULT) + keep = png_ptr->unknown_default; + + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name))) + { + if (!png_cache_unknown_chunk(png_ptr, length)) + keep = PNG_HANDLE_CHUNK_NEVER; + } + + else + png_crc_finish(png_ptr, length); + } +# else +# ifndef PNG_READ_USER_CHUNKS_SUPPORTED +# error no method to support READ_UNKNOWN_CHUNKS +# endif +# endif + +# ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + /* Now store the chunk in the chunk list if appropriate, and if the limits + * permit it. + */ + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name))) + { +# ifdef PNG_USER_LIMITS_SUPPORTED + switch (png_ptr->user_chunk_cache_max) + { + case 2: + png_ptr->user_chunk_cache_max = 1; + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + /* FALL THROUGH */ + case 1: + /* NOTE: prior to 1.6.0 this case resulted in an unknown critical + * chunk being skipped, now there will be a hard error below. + */ + break; + + default: /* not at limit */ + --(png_ptr->user_chunk_cache_max); + /* FALL THROUGH */ + case 0: /* no limit */ +# endif /* PNG_USER_LIMITS_SUPPORTED */ + /* Here when the limit isn't reached or when limits are compiled + * out; store the chunk. + */ + png_set_unknown_chunks(png_ptr, info_ptr, + &png_ptr->unknown_chunk, 1); + handled = 1; +# ifdef PNG_USER_LIMITS_SUPPORTED + break; + } +# endif + } +# else /* no store support! */ + PNG_UNUSED(info_ptr) +# error untested code (reading unknown chunks with no store support) +# endif + + /* Regardless of the error handling below the cached data (if any) can be + * freed now. Notice that the data is not freed if there is a png_error, but + * it will be freed by destroy_read_struct. + */ + if (png_ptr->unknown_chunk.data != NULL) + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + +#else /* !PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + /* There is no support to read an unknown chunk, so just skip it. */ + png_crc_finish(png_ptr, length); + PNG_UNUSED(info_ptr) + PNG_UNUSED(keep) +#endif /* !PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + + /* Check for unhandled critical chunks */ + if (!handled && PNG_CHUNK_CRITICAL(png_ptr->chunk_name)) + png_chunk_error(png_ptr, "unhandled critical chunk"); } /* This function is called to verify that a chunk name is valid. diff --git a/pngset.c b/pngset.c index 40c7d392a..7cd23ff39 100644 --- a/pngset.c +++ b/pngset.c @@ -1067,79 +1067,121 @@ png_set_sPLT(png_const_structrp png_ptr, } #endif /* PNG_sPLT_SUPPORTED */ -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +static png_byte +check_location(png_const_structrp png_ptr, unsigned int location) +{ + location &= (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT); + + /* New in 1.6.0; copy the location and check it. This is an API + * change, previously the app had to use the + * png_set_unknown_chunk_location API below for each chunk. + */ + if (location == 0 && !(png_ptr->mode & PNG_IS_READ_STRUCT)) + { + /* Write struct, so unknown chunks come from the app */ + png_app_warning(png_ptr, + "png_set_unknown_chunks now expects a valid location"); + /* Use the old behavior */ + location = png_ptr->mode & + (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT); + } + + if (location == 0) + png_error(png_ptr, "invalid location in png_set_unknown_chunks"); + + /* Now reduce the location to the top-most set bit by removing each least + * significant bit in turn. + */ + while (location != (location & -location)) + location &= (png_byte)~(location & -location); + + /* The cast is safe because 'location' is a bit mask and only the low four + * bits are significant. + */ + return (png_byte)location; +} + void PNGAPI png_set_unknown_chunks(png_const_structrp png_ptr, png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns) { png_unknown_chunkp np; - int i; - if (png_ptr == NULL || info_ptr == NULL || num_unknowns == 0) + if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0) return; - np = png_voidcast(png_unknown_chunkp, png_malloc_warn(png_ptr, - (png_size_t)(info_ptr->unknown_chunks_num + num_unknowns) * + /* Prior to 1.6.0 this code used png_malloc_warn, however this meant that + * unknown critical chunks could be lost with just a warning resulting in + * undefined behavior. Changing to png_malloc fixes this by producing a + * png_error. The (png_size_t) cast was also removed as it hides a potential + * overflow. + * + * TODO: fix the potential overflow in the multiply + */ + np = png_voidcast(png_unknown_chunkp, png_malloc(png_ptr, + (info_ptr->unknown_chunks_num + (unsigned int)num_unknowns) * (sizeof (png_unknown_chunk)))); - if (np == NULL) - { - png_warning(png_ptr, - "Out of memory while processing unknown chunk"); - return; - } - memcpy(np, info_ptr->unknown_chunks, - (png_size_t)info_ptr->unknown_chunks_num * - (sizeof (png_unknown_chunk))); + info_ptr->unknown_chunks_num * (sizeof (png_unknown_chunk))); png_free(png_ptr, info_ptr->unknown_chunks); - info_ptr->unknown_chunks = NULL; + info_ptr->unknown_chunks = np; /* safe because it is initialized */ + info_ptr->free_me |= PNG_FREE_UNKN; - for (i = 0; i < num_unknowns; i++) + np += info_ptr->unknown_chunks_num; + + /* Increment unknown_chunks_num each time round the loop to protect the + * just-allocated chunk data. + */ + for (; --num_unknowns >= 0; + ++np, ++unknowns, ++(info_ptr->unknown_chunks_num)) { - png_unknown_chunkp to = np + info_ptr->unknown_chunks_num + i; - png_const_unknown_chunkp from = unknowns + i; + memcpy(np->name, unknowns->name, (sizeof unknowns->name)); + np->name[(sizeof np->name)-1] = '\0'; + np->size = unknowns->size; + np->location = check_location(png_ptr, unknowns->location); - memcpy(to->name, from->name, (sizeof from->name)); - to->name[(sizeof to->name)-1] = '\0'; - to->size = from->size; - - /* Note our location in the read or write sequence */ - to->location = (png_byte)png_ptr->mode; - - if (from->size == 0) - to->data=NULL; + if (unknowns->size == 0) + np->data = NULL; else { - to->data = (png_bytep)png_malloc_warn(png_ptr, - (png_size_t)from->size); - - if (to->data == NULL) - { - png_warning(png_ptr, - "Out of memory while processing unknown chunk"); - to->size = 0; - } - - else - memcpy(to->data, from->data, from->size); + /* png_error is safe here because the list is stored in png_ptr */ + np->data = png_voidcast(png_bytep, + png_malloc(png_ptr, unknowns->size)); + memcpy(np->data, unknowns->data, unknowns->size); } } - - info_ptr->unknown_chunks = np; - info_ptr->unknown_chunks_num += num_unknowns; - info_ptr->free_me |= PNG_FREE_UNKN; } void PNGAPI png_set_unknown_chunk_location(png_const_structrp png_ptr, png_inforp info_ptr, int chunk, int location) { - if (png_ptr != NULL && info_ptr != NULL && chunk >= 0 && chunk < - info_ptr->unknown_chunks_num) - info_ptr->unknown_chunks[chunk].location = (png_byte)location; + /* This API is pretty pointless in 1.6.0 because the location can be set + * before the call to png_set_unknown_chunks. + * + * TODO: add a png_app_warning in 1.7 + */ + if (png_ptr != NULL && info_ptr != NULL && chunk >= 0 && + (unsigned int)chunk < info_ptr->unknown_chunks_num) + { + if ((location & (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT)) == 0) + { + png_app_error(png_ptr, "invalid unknown chunk location"); + /* Fake out the pre 1.6.0 behavior: */ + if ((location & PNG_HAVE_IDAT)) /* undocumented! */ + location = PNG_AFTER_IDAT; + + else + location = PNG_HAVE_IHDR; /* also undocumented */ + } + + info_ptr->unknown_chunks[chunk].location = + check_location(png_ptr, (png_byte)location); + } } #endif @@ -1160,54 +1202,51 @@ png_permit_mng_features (png_structrp png_ptr, png_uint_32 mng_features) #endif #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static unsigned int +add_one_chunk(png_bytep list, unsigned int count, png_const_bytep add, int keep) +{ + unsigned int i; + + /* Utility function: update the 'keep' state of a chunk if it is already in + * the list, otherwise add it to the list. + */ + for (i=0; i= PNG_HANDLE_CHUNK_LAST) { - case PNG_HANDLE_CHUNK_AS_DEFAULT: - keep = PNG_HANDLE_CHUNK_AS_DEFAULT; - break; - - case PNG_HANDLE_CHUNK_NEVER: - keep = PNG_HANDLE_CHUNK_NEVER; - break; - - case PNG_HANDLE_CHUNK_IF_SAFE: - keep = PNG_HANDLE_CHUNK_IF_SAFE; - break; - - case PNG_HANDLE_CHUNK_ALWAYS: - keep = PNG_HANDLE_CHUNK_ALWAYS; - break; - - - default: - png_app_error(png_ptr, "png_set_keep_unknown_chunks: invalid keep"); - return; + png_app_error(png_ptr, "png_set_keep_unknown_chunks: invalid keep"); + return; } if (num_chunksIn <= 0) { - if (keep == PNG_HANDLE_CHUNK_ALWAYS || keep == PNG_HANDLE_CHUNK_IF_SAFE) - png_ptr->flags |= PNG_FLAG_KEEP_UNKNOWN_CHUNKS; - - else - png_ptr->flags &= ~PNG_FLAG_KEEP_UNKNOWN_CHUNKS; - - if (keep == PNG_HANDLE_CHUNK_ALWAYS) - png_ptr->flags |= PNG_FLAG_KEEP_UNSAFE_CHUNKS; - - else - png_ptr->flags &= ~PNG_FLAG_KEEP_UNSAFE_CHUNKS; + png_ptr->unknown_default = keep; + /* '0' means just set the flags, so stop here */ if (num_chunksIn == 0) return; } @@ -1244,12 +1283,20 @@ png_set_keep_unknown_chunks(png_structrp png_ptr, int keepIn, else /* num_chunksIn > 0 */ { if (chunk_list == NULL) + { + /* Prior to 1.6.0 this was silently ignored, now it is an app_error + * which can be switched off. + */ + png_app_error(png_ptr, "png_set_keep_unknown_chunks: no chunk list"); return; + } num_chunks = num_chunksIn; } old_num_chunks = png_ptr->num_chunk_list; + if (png_ptr->chunk_list == NULL) + old_num_chunks = 0; /* Since num_chunks is always restricted to UINT_MAX/5 this can't overflow. */ @@ -1259,24 +1306,73 @@ png_set_keep_unknown_chunks(png_structrp png_ptr, int keepIn, return; } - new_list = png_voidcast(png_bytep, png_malloc(png_ptr, - 5 * (num_chunks + old_num_chunks))); - - if (png_ptr->chunk_list != NULL) + /* If these chunks are being reset to the default then no more memory is + * required because add_one_chunk above doesn't extend the list if the 'keep' + * parameter is the default. + */ + if (keep) { - memcpy(new_list, png_ptr->chunk_list, 5*old_num_chunks); - png_free(png_ptr, png_ptr->chunk_list); - png_ptr->chunk_list=NULL; + new_list = png_voidcast(png_bytep, png_malloc(png_ptr, + 5 * (num_chunks + old_num_chunks))); + + if (old_num_chunks > 0) + memcpy(new_list, png_ptr->chunk_list, 5*old_num_chunks); } - memcpy(new_list + 5*old_num_chunks, chunk_list, 5*num_chunks); + else if (old_num_chunks > 0) + new_list = png_ptr->chunk_list; - for (p = new_list + 5*old_num_chunks + 4, i = 0; inum_chunk_list = old_num_chunks + num_chunks; - png_ptr->chunk_list = new_list; - png_ptr->free_me |= PNG_FREE_LIST; + /* Add the new chunks together with each one's handling code. If the chunk + * already exists the code is updated, otherwise the chunk is added to the + * end. (In libpng 1.6.0 order no longer matters because this code enforces + * the earlier convention that the last setting is the one that is used.) + */ + if (new_list != NULL) + { + png_const_bytep inlist; + png_bytep outlist; + unsigned int i; + + for (i=0; ichunk_list != new_list) + png_free(png_ptr, new_list); + + new_list = NULL; + } + } + + else + num_chunks = 0; + + png_ptr->num_chunk_list = num_chunks; + + if (png_ptr->chunk_list != new_list) + { + if (png_ptr->chunk_list != NULL) + png_free(png_ptr, png_ptr->chunk_list); + + png_ptr->chunk_list = new_list; + } } #endif diff --git a/pngstruct.h b/pngstruct.h index d1bcf322f..a3f8383fa 100644 --- a/pngstruct.h +++ b/pngstruct.h @@ -368,12 +368,16 @@ struct png_struct_def #ifdef PNG_USER_CHUNKS_SUPPORTED png_voidp user_chunk_ptr; +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED png_user_chunk_ptr read_user_chunk_fn; /* user read chunk handler */ #endif +#endif -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - unsigned int num_chunk_list; - png_bytep chunk_list; +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + int unknown_default; /* As PNG_HANDLE_* */ + unsigned int num_chunk_list; /* Number of entries in the list */ + png_bytep chunk_list; /* List of png_byte[5]; the textual chunk name + * followed by a PNG_HANDLE_* byte */ #endif /* New members added in libpng-1.0.3 */ @@ -438,14 +442,12 @@ struct png_struct_def #endif /* New member added in libpng-1.0.25 and 1.2.17 */ -#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED /* Temporary storage for unknown chunk that the library doesn't recognize, * used while reading the chunk. */ -#ifdef PNG_READ_SUPPORTED png_unknown_chunk unknown_chunk; #endif -#endif /* New member added in libpng-1.2.26 */ png_size_t old_big_row_buf_size; diff --git a/pngwrite.c b/pngwrite.c index 0c2bed57d..098a68230 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -18,6 +18,54 @@ #ifdef PNG_WRITE_SUPPORTED +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +/* Write out all the unknown chunks for the current given location */ +static void +write_unknown_chunks(png_structrp png_ptr, png_const_inforp info_ptr, + unsigned int where) +{ + if (info_ptr->unknown_chunks_num) + { + png_const_unknown_chunkp up; + + png_debug(5, "writing extra chunks"); + + for (up = info_ptr->unknown_chunks; + up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; + ++up) + if (up->location & where) + { + int keep = png_handle_as_unknown(png_ptr, up->name); + + /* NOTE: this code is radically different from the read side in the + * matter of handling an ancilliary unknown chunk. In the read side + * the default behavior is to discard it, in the code below the default + * behavior is to write it. Critical chunks are, however, only + * written if explicitly listed or if the default is set to write all + * unknown chunks. + * + * The default handling is also slightly weird - it is not possible to + * stop the writing of all unsafe-to-copy chunks! + * + * TODO: REVIEW: this would seem to be a bug. + */ + if (keep != PNG_HANDLE_CHUNK_NEVER && + ((up->name[3] & 0x20) /* safe-to-copy overrides everything */ || + keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_AS_DEFAULT && + png_ptr->unknown_default == PNG_HANDLE_CHUNK_ALWAYS))) + { + /* TODO: review, what is wrong with a zero length unknown chunk? */ + if (up->size == 0) + png_warning(png_ptr, "Writing zero-length unknown chunk"); + + png_write_chunk(png_ptr, up->name, up->data, up->size); + } + } + } +} +#endif /* PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED */ + /* Writes all the PNG information. This is the suggested way to use the * library. If you have a new chunk to add, make a function to write it, * and put it in the correct location here. If you want the chunk written @@ -54,10 +102,12 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) info_ptr->bit_depth, info_ptr->color_type, info_ptr->compression_type, info_ptr->filter_type, #ifdef PNG_WRITE_INTERLACING_SUPPORTED - info_ptr->interlace_type); + info_ptr->interlace_type #else - 0); + 0 #endif + ); + /* The rest of these check to see if the valid field has the appropriate * flag set, and if it does, writes the chunk. * @@ -125,34 +175,9 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) #endif #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED - if (info_ptr->unknown_chunks_num) - { - png_unknown_chunk *up; - - png_debug(5, "writing extra chunks"); - - for (up = info_ptr->unknown_chunks; - up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; - up++) - { - int keep = png_handle_as_unknown(png_ptr, up->name); - - if (keep != PNG_HANDLE_CHUNK_NEVER && - up->location && - !(up->location & PNG_HAVE_PLTE) && - !(up->location & PNG_HAVE_IDAT) && - !(up->location & PNG_AFTER_IDAT) && - ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS || - (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS))) - { - if (up->size == 0) - png_warning(png_ptr, "Writing zero-length unknown chunk"); - - png_write_chunk(png_ptr, up->name, up->data, up->size); - } - } - } + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_IHDR); #endif + png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE; } } @@ -302,29 +327,7 @@ png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) #endif /* tEXt */ #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED - if (info_ptr->unknown_chunks_num) - { - png_unknown_chunk *up; - - png_debug(5, "writing extra chunks"); - - for (up = info_ptr->unknown_chunks; - up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; - up++) - { - int keep = png_handle_as_unknown(png_ptr, up->name); - if (keep != PNG_HANDLE_CHUNK_NEVER && - up->location && - (up->location & PNG_HAVE_PLTE) && - !(up->location & PNG_HAVE_IDAT) && - !(up->location & PNG_AFTER_IDAT) && - ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS || - (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS))) - { - png_write_chunk(png_ptr, up->name, up->data, up->size); - } - } - } + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_PLTE); #endif } @@ -416,27 +419,7 @@ png_write_end(png_structrp png_ptr, png_inforp info_ptr) } #endif #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED - if (info_ptr->unknown_chunks_num) - { - png_unknown_chunk *up; - - png_debug(5, "writing extra chunks"); - - for (up = info_ptr->unknown_chunks; - up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; - up++) - { - int keep = png_handle_as_unknown(png_ptr, up->name); - if (keep != PNG_HANDLE_CHUNK_NEVER && - up->location && - (up->location & PNG_AFTER_IDAT) && - ((up->name[3] & 0x20) || keep == PNG_HANDLE_CHUNK_ALWAYS || - (png_ptr->flags & PNG_FLAG_KEEP_UNSAFE_CHUNKS))) - { - png_write_chunk(png_ptr, up->name, up->data, up->size); - } - } - } + write_unknown_chunks(png_ptr, info_ptr, PNG_AFTER_IDAT); #endif } @@ -889,7 +872,7 @@ png_write_destroy(png_structrp png_ptr) png_free(png_ptr, png_ptr->inv_filter_costs); #endif -#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED png_free(png_ptr, png_ptr->chunk_list); #endif diff --git a/scripts/pnglibconf.dfa b/scripts/pnglibconf.dfa index 083ec20a6..014f04b73 100644 --- a/scripts/pnglibconf.dfa +++ b/scripts/pnglibconf.dfa @@ -635,9 +635,52 @@ chunk zTXt option READ_OPT_PLTE requires READ_ANCILLARY_CHUNKS -option READ_UNKNOWN_CHUNKS requires READ -option READ_UNKNOWN_CHUNKS enables UNKNOWN_CHUNKS READ_USER_CHUNKS -option READ_USER_CHUNKS requires READ enables USER_CHUNKS +# Unknown chunk handling +# +# 'UNKNOWN_CHUNKS' is a global option to disable all unknown chunk handling on +# read or write; everything else below requires it (directly or indirectly). +option UNKNOWN_CHUNKS + +# There are three main options to control the ability to read and write unknown +# chunks. If either read option is turned on then unknown chunks will be read, +# otherwise they are skipped. If the write option is turned on unknown chunks +# set by png_set_unknown_chunks will be written otherwise it is an error to call +# that API on a write struct. +option WRITE_UNKNOWN_CHUNKS requires WRITE requires UNKNOWN_CHUNKS +option WRITE_UNKNOWN_CHUNKS enables STORE_UNKNOWN_CHUNKS SET_UNKNOWN_CHUNKS + +# The first way to read user chunks is to have libpng save them for a later call +# to png_get_unknown_chunks, the application must call +# png_set_keep_unknown_chunks to cause this to actually happen (see png.h) +option SAVE_UNKNOWN_CHUNKS requires READ requires UNKNOWN_CHUNKS +option SAVE_UNKNOWN_CHUNKS enables READ_UNKNOWN_CHUNKS + +# The second approach is to use an application provided callback to process the +# chunks, the callback can either handle the chunk entirely itself or request +# that libpng store the chunk for later retrieval via png_get_unknown_chunks. +# +# Note that there is no 'WRITE_USER_CHUNKS' so the USER_CHUNKS option is always +# the same as READ_USER_CHUNKS at present +option READ_USER_CHUNKS requires READ requires UNKNOWN_CHUNKS +option READ_USER_CHUNKS enables READ_UNKNOWN_CHUNKS USER_CHUNKS + +# A further option allows known chunks to be handled using the unknown code by +# providing the relevant chunks to png_set_keep_unknown_chunks. This is turned +# on by the following option. In 1.6.0 turning this option *off* no longer +# disables png_set_keep_unknown_chunks, a behavior which effectively prevented +# unknown chunk saving by the first mechanism on read. +# +# Notice that this option no longer affects the write code either. It can be +# safely disabled and will prevent applications stopping libpng reading known +# chunks. +option HANDLE_AS_UNKNOWN requires SET_UNKNOWN_CHUNKS + +# The following options are derived from the above and should not be turned on +# explicitly. +option READ_UNKNOWN_CHUNKS requires UNKNOWN_CHUNKS disabled +option READ_UNKNOWN_CHUNKS enables STORE_UNKNOWN_CHUNKS SET_UNKNOWN_CHUNKS +option STORE_UNKNOWN_CHUNKS requires UNKNOWN_CHUNKS disabled +option SET_UNKNOWN_CHUNKS requires UNKNOWN_CHUNKS disabled option CONVERT_tIME requires WRITE_ANCILLARY_CHUNKS # The "tm" structure is not supported on WindowsCE @@ -648,10 +691,6 @@ option CONVERT_tIME requires WRITE_ANCILLARY_CHUNKS option WRITE_FILTER requires WRITE -option WRITE_UNKNOWN_CHUNKS requires WRITE - -option HANDLE_AS_UNKNOWN - option SAVE_INT_32 requires WRITE # png_save_int_32 is required by the ancillary chunks oFFs and pCAL diff --git a/scripts/pnglibconf.h.prebuilt b/scripts/pnglibconf.h.prebuilt index 367647f64..b777595ae 100644 --- a/scripts/pnglibconf.h.prebuilt +++ b/scripts/pnglibconf.h.prebuilt @@ -3,7 +3,7 @@ /* pnglibconf.h - library build configuration */ -/* Libpng 1.6.0beta28 - August 11, 2012 */ +/* Libpng 1.6.0beta28 - August 16, 2012 */ /* Copyright (c) 1998-2012 Glenn Randers-Pehrson */ @@ -126,12 +126,14 @@ #define PNG_READ_zTXt_SUPPORTED /*#undef PNG_SAFE_LIMITS_SUPPORTED*/ #define PNG_SAVE_INT_32_SUPPORTED +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED #define PNG_sBIT_SUPPORTED #define PNG_sCAL_SUPPORTED #define PNG_SEQUENTIAL_READ_SUPPORTED #define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED #define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED #define PNG_SETJMP_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED #define PNG_SET_USER_LIMITS_SUPPORTED #define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED #define PNG_SIMPLIFIED_READ_BGR_SUPPORTED @@ -142,6 +144,7 @@ #define PNG_sPLT_SUPPORTED #define PNG_sRGB_SUPPORTED #define PNG_STDIO_SUPPORTED +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED #define PNG_tEXt_SUPPORTED #define PNG_TEXT_SUPPORTED #define PNG_TIME_RFC1123_SUPPORTED