/* png-fix-too-far-back.c * * Copyright (c) 2013 John Cunningham Bowler * * Last changed in libpng 1.6.3 [(PENDING RELEASE)] * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h * * Tool to check and fix the zlib inflate 'too far back' problem, see the usage * message for more information. */ #include #include #include #include #define PROGRAM_NAME "png-fix-too-far-back" /* Define the following to use this program against your installed libpng, * rather than the one being built here: */ #ifdef PNG_FREESTANDING_TESTS # include #else # include "../../png.h" #endif #if PNG_LIBPNG_VER < 10603 /* 1.6.3 */ # error "png-fix-too-far-back will not work with libpng prior to 1.6.3" #endif #ifdef PNG_READ_SUPPORTED #include #ifndef PNG_MAXIMUM_INFLATE_WINDOW # error "png-fix-too-far-back not supported in this libpng version" #endif #if PNG_ZLIB_VERNUM >= 0x1240 /* Copied from pngpriv.h */ #ifdef __cplusplus # define png_voidcast(type, value) static_cast(value) # define png_constcast(type, value) const_cast(value) # define png_aligncast(type, value) \ static_cast(static_cast(value)) # define png_aligncastconst(type, value) \ static_cast(static_cast(value)) #else # define png_voidcast(type, value) (value) # define png_constcast(type, value) ((type)(value)) # define png_aligncast(type, value) ((void*)(value)) # define png_aligncastconst(type, value) ((const void*)(value)) #endif /* __cplusplus */ static int idat_error = 0; static int verbose = 0; static int errors = 0; static int warnings = 0; #ifdef PNG_MAXIMUM_INFLATE_WINDOW static int set_option = 0; #endif static const char *name = "stdin"; static uLong crc_IDAT_head; /* CRC32 of "IDAT" */ static uLong crc_IEND; static z_stream z_idat; /* Control structure for the temporary file */ typedef struct { size_t image_size; off_t file_size; fpos_t header_pos; fpos_t crc_pos; uLong crc_tail; /* CRC of bytes after header */ png_uint_32 len_tail; /* Count thereof */ png_byte header[2]; /* Image info */ png_uint_32 width; png_uint_32 height; png_byte bit_depth; png_byte color_type; png_byte compression_method; png_byte filter_method; png_byte interlace_method; } IDAT_info; static png_uint_32 mult(png_uint_32 n, png_uint_32 m) { if ((n + (m-1)) / m > 0xffffffff/m) { fprintf(stderr, "%s: overflow (%lu, %u)\n", name, (unsigned long)n, m); exit(2); } return n * m; } static size_t image_size(const IDAT_info *info) { unsigned int pd = info->bit_depth; size_t cb; switch (info->color_type) { case 0: case 3: break; case 2: /* rgb */ pd *= 3; break; case 4: /* ga */ pd *= 2; break; case 6: /* rgba */ pd *= 4; break; default: fprintf(stderr, "%s: invalid color type (%d)\n", name, info->color_type); exit(2); } switch (info->interlace_method) { case PNG_INTERLACE_ADAM7: /* Interlacing makes the image larger because of the replication of * both the filter byte and the padding to a byte boundary. */ { int pass; for (cb=0, pass=0; pass<=6; ++pass) { png_uint_32 pw = PNG_PASS_COLS(info->width, pass); if (pw > 0) cb += mult(((mult(pd, pw)+7) >> 3)+1, PNG_PASS_ROWS(info->height, pass)); } } break; case PNG_INTERLACE_NONE: cb = mult(info->height, 1+((mult(info->width, pd) + 7) >> 3)); break; default: fprintf(stderr, "%s: invalid interlace type %d\n", name, info->interlace_method); exit(2); } return cb; } static int image_windowBits(const IDAT_info *info) { size_t cb = image_size(info); if (cb > 16384) return 15; if (cb > 8192) return 14; if (cb > 4096) return 13; if (cb > 2048) return 12; if (cb > 1024) return 11; if (cb > 512) return 10; if (cb > 256) return 9; return 8; } static void error_handler(png_structp png_ptr, png_const_charp message) { if (strcmp(message, "IDAT: invalid distance too far back") == 0) idat_error = 1; else if (errors || verbose) fprintf(stderr, "%s: %s\n", name, message); png_longjmp(png_ptr, 1); } static void warning_handler(png_structp png_ptr, png_const_charp message) { if (warnings || verbose) fprintf(stderr, "%s: %s\n", name, message); (void)png_ptr; } static int read_png(FILE *fp) { png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0, error_handler, warning_handler); png_infop info_ptr = NULL; png_bytep row = NULL, display = NULL; if (png_ptr == NULL) return 0; if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (row != NULL) free(row); if (display != NULL) free(display); return 0; } # ifdef PNG_MAXIMUM_INFLATE_WINDOW png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, set_option != 0); # endif png_init_io(png_ptr, fp); info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) png_error(png_ptr, "OOM allocating info structure"); png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); png_read_info(png_ptr, info_ptr); /* Limit the decompression buffer size to 1 - this ensures that overlong * length codes are always detected. */ png_set_compression_buffer_size(png_ptr, 1); { png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr); row = png_voidcast(png_byte*, malloc(rowbytes)); display = png_voidcast(png_byte*, malloc(rowbytes)); if (row == NULL || display == NULL) png_error(png_ptr, "OOM allocating row buffers"); { png_uint_32 height = png_get_image_height(png_ptr, info_ptr); int passes = png_set_interlace_handling(png_ptr); int pass; png_start_read_image(png_ptr); for (pass = 0; pass < passes; ++pass) { png_uint_32 y = height; /* NOTE: this trashes the row each time; interlace handling won't * work, but this avoids memory thrashing for speed testing. */ while (y-- > 0) png_read_row(png_ptr, row, display); } } } /* Make sure to read to the end of the file: */ png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); free(row); free(display); return 1; } /* Chunk tags (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) static void rx(FILE *fp, png_bytep buf, off_t cb) { if (fread(buf,cb,1,fp) != 1) { fprintf(stderr, "%s: failed to read %lu bytes\n", name, (unsigned long)cb); exit(2); } } static png_uint_32 r32(FILE *fp) { png_byte buf[4]; rx(fp, buf, 4); return ((((((png_uint_32)buf[0] << 8)+buf[1]) << 8)+buf[2]) << 8) + buf[3]; } static void wx(FILE *fp, png_const_bytep buf, off_t cb) { if (fwrite(buf,cb,1,fp) != 1) { fprintf(stderr, "%s: failed to write %lu bytes\n", name, (unsigned long)cb); exit(3); } } static void w32(FILE *fp, png_uint_32 val) { png_byte buf[4]; buf[0] = (png_byte)(val >> 24); buf[1] = (png_byte)(val >> 16); buf[2] = (png_byte)(val >> 8); buf[3] = (png_byte)(val); wx(fp, buf, 4); } static void wcrc(FILE *fp, uLong crc) { /* Safe cast because a CRC is 32 bits */ w32(fp, (png_uint_32)crc); } static void copy(FILE *fp, FILE *fpIn, off_t cb) { png_byte buffer[1024]; while (cb >= 1024) { rx(fpIn, buffer, 1024); wx(fp, buffer, 1024); cb -= 1024; } if (cb > 0) { rx(fpIn, buffer, cb); wx(fp, buffer, cb); } } static void skip_bytes(FILE *fpIn, png_uint_32 cb) { png_byte buffer[1024]; while (cb >= 1024) { rx(fpIn, buffer, 1024); cb -= 1024; } if (cb > 0) rx(fpIn, buffer, cb); } static void safe_getpos(FILE *fp, fpos_t *pos) { if (fgetpos(fp, pos)) { perror("tmpfile"); fprintf(stderr, "%s: tmpfile fgetpos failed\n", name); exit(3); } } static void safe_setpos(FILE *fp, fpos_t *pos) { if (fflush(fp)) { perror("tmpfile"); fprintf(stderr, "%s: tmpfile fflush failed\n", name); exit(3); } if (fsetpos(fp, pos)) { perror("tmpfile"); fprintf(stderr, "%s: tmpfile fsetpos failed\n", name); exit(3); } } static void idat_update(FILE *fp, IDAT_info *info) { uLong crc; safe_setpos(fp, &info->header_pos); wx(fp, info->header, 2); crc = crc32(crc_IDAT_head, info->header, 2); crc = crc32_combine(crc, info->crc_tail, info->len_tail); safe_setpos(fp, &info->crc_pos); wcrc(fp, crc); } static void set_bits(const char *file, FILE *fp, IDAT_info *info, int bits) { int byte1 = (info->header[0] & 0xf) + ((bits-8) << 4); int byte2 = info->header[1] & 0xe0; /* The checksum calculation: */ byte2 += 0x1f - ((byte1 << 8) + byte2) % 0x1f; info->header[0] = (png_byte)byte1; info->header[1] = (png_byte)byte2; if (verbose) fprintf(stderr, "%s: trying windowBits %d (Z_CMF = 0x%x)\n", file, bits, byte1); idat_update(fp, info); } static void ptagchar(png_uint_32 ch) { ch &= 0xff; if (isprint(ch)) putc(ch, stderr); else fprintf(stderr, "[%02x]", ch); } static void ptag(png_uint_32 tag) { if (tag != 0) { ptag(tag >> 8); ptagchar(tag); } } static int fix_one(FILE *fp, FILE *fpIn, IDAT_info *info, png_uint_32 max_IDAT, int strip) { int state = 0; /* 0: at beginning, before first IDAT * 1: read first CMF header byte * 2: read second byte, in first IDAT * 3: after first IDAT * +4: saw deflate stream end. */ int truncated_idat = 0; /* Count of spurious IDAT bytes */ uLong crc_idat = 0; /* Running CRC of current IDAT */ png_uint_32 len_IDAT = 0; /* Length of current IDAT */ fpos_t pos_IDAT_length; /* fpos_t of length field in current IDAT */ /* The signature: */ { png_byte buf[8]; rx(fpIn, buf, 8); wx(fp, buf, 8); } info->file_size = 45; /* signature + IHDR + IEND */ for (;;) /* Chunk for loop */ { png_uint_32 len = r32(fpIn); png_uint_32 tag = r32(fpIn); if (tag == png_IHDR) { /* Need width, height, color type, bit depth and interlace for the * file. */ info->width = r32(fpIn); info->height = r32(fpIn); rx(fpIn, &info->bit_depth, 1); rx(fpIn, &info->color_type, 1); rx(fpIn, &info->compression_method, 1); rx(fpIn, &info->filter_method, 1); rx(fpIn, &info->interlace_method, 1); /* And write the information. */ w32(fp, len); w32(fp, tag); w32(fp, info->width); w32(fp, info->height); wx(fp, &info->bit_depth, 1); wx(fp, &info->color_type, 1); wx(fp, &info->compression_method, 1); wx(fp, &info->filter_method, 1); wx(fp, &info->interlace_method, 1); /* Copy the CRC */ copy(fp, fpIn, 4); } else if (tag == png_IEND) { /* Ok, write an IEND chunk and finish. */ w32(fp, 0); w32(fp, png_IEND); wcrc(fp, crc_IEND); break; } else if (tag == png_IDAT && len > 0) { /* Write the chunk header now if it hasn't been written yet */ if (len_IDAT == 0) { /* The length is set at the end: */ safe_getpos(fp, &pos_IDAT_length); w32(fp, max_IDAT); /* length, not yet written */ w32(fp, png_IDAT); if (state == 0) /* Start of first IDAT */ { safe_getpos(fp, &info->header_pos); /* This will become info->crc_tail: */ crc_idat = crc32(0L, Z_NULL, 0); } else crc_idat = crc_IDAT_head; } /* Do the zlib 2-byte header, it gets written out but not added * to the CRC (yet): */ while (len > 0 && state < 2) { rx(fpIn, info->header + state, 1); wx(fp, info->header + state, 1); ++len_IDAT; --len; if (state++ == 1) { /* The zlib stream is used to validate the compressed IDAT * data in the most relaxed way possible. */ png_byte bdummy; int ret; z_idat.next_in = info->header; z_idat.avail_in = 2; z_idat.next_out = &bdummy; /* Else Z_STREAM_ERROR! */ z_idat.avail_out = 0; ret = inflate(&z_idat, Z_NO_FLUSH); if (ret != Z_OK || z_idat.avail_in != 0) { fprintf(stderr, "%s: unexpected/invalid inflate result %d \"%s\"\n", name, ret, z_idat.msg); return 1; } } } /* while in zlib header */ /* Process further bytes in the IDAT chunk */ while (len > 0 && state < 4) { png_byte b; rx(fpIn, &b, 1); --len; /* Do this 1 byte at a time to guarantee * detecting errors (in particular zlib can skip the * 'too-far-back' error if the output buffer is bigger than * the window size.) */ z_idat.next_in = &b; z_idat.avail_in = 1; do { int ret; png_byte bout; z_idat.next_out = &bout; z_idat.avail_out = 1; ret = inflate(&z_idat, Z_SYNC_FLUSH); if (z_idat.avail_out == 0) ++info->image_size; switch (ret) { case Z_OK: /* Just keep going */ break; case Z_BUF_ERROR: if (z_idat.avail_in > 0) { fprintf(stderr, "%s: unexpected buffer error \"%s\"\n", name, z_idat.msg); return 1; } goto end_loop; case Z_STREAM_END: /* End of stream */ state |= 4; goto end_loop; default: fprintf(stderr, "%s: bad zlib stream %d, \"%s\"\n", name, ret, z_idat.msg); return 1; } } while (z_idat.avail_in > 0 || z_idat.avail_out == 0); /* The byte need not be consumed, if, for example, there is a * spurious byte after the end of the zlib data. */ end_loop: if (z_idat.avail_in == 0) { /* Write it and update the length information and running * CRC. */ wx(fp, &b, 1); crc_idat = crc32(crc_idat, &b, 1); ++len_IDAT; } else ++truncated_idat; if (len_IDAT >= max_IDAT || state >= 4) { /* Either the IDAT chunk is full or we've seen the end of * the deflate stream, or both. Flush the chunk and handle * the details of the first chunk. */ fpos_t save; if ((state & 3) < 3) /* First IDAT */ { safe_getpos(fp, &info->crc_pos); info->crc_tail = crc_idat; info->len_tail = len_IDAT-2; } /* This is not the correct value for the first IDAT! */ wcrc(fp, crc_idat); state |= 3; /* Update the length if it is not max_IDAT: */ if (len_IDAT != max_IDAT) { safe_getpos(fp, &save); safe_setpos(fp, &pos_IDAT_length); w32(fp, len_IDAT); safe_setpos(fp, &save); } /* Add this IDAT to the file size: */ info->file_size += 12 + len_IDAT; } } /* while len > 0 && state < 4 */ /* The above loop only exits on 0 bytes left or end of stream. If * the stream ended with bytes left, discard them: */ if (len > 0) { truncated_idat += len; /* Skip those bytes and the CRC */ skip_bytes(fpIn, len+4); } else skip_bytes(fpIn, 4); /* The CRC */ } /* IDAT and len > 0 */ else { int skip = 0; if (tag == png_IDAT) skip = 1; else if (state == 0) { /* Chunk before IDAT */ if (!skip) switch (strip) { case 0: /* Don't strip */ break; case 1: /* Keep gAMA, sRGB */ if (tag == png_gAMA || tag == png_sRGB) break; /* Fall through */ default: /* Keep only IHDR, PLTE, tRNS */ if (tag == png_IHDR || tag == png_PLTE || tag == png_tRNS) break; skip = 1; break; } } else if (state >= 4) { /* Keep nothing after IDAT if stripping: */ skip = strip; } else { /* This is either an unterminated deflate stream or a spurious * non-IDAT chunk in the list of IDAT chunks. Both are fatal * errors. */ fprintf(stderr, "%s: tag '", name); ptag(tag); fprintf(stderr, "' after unterminated IDAT\n"); break; } /* Skip or send? */ if (skip) { if (tag != png_IDAT && (tag & 0x20000000) == 0) { fprintf(stderr, "%s: unknown critical chunk '", name); ptag(tag); fprintf(stderr, "'\n"); return 1; } /* Skip this tag */ if (fseek(fpIn, len+4, SEEK_CUR)) { perror(name); fprintf(stderr, "%s: seek failed\n", name); return 1; } } else /* Keep this tag */ { w32(fp, len); w32(fp, tag); copy(fp, fpIn, len+4); info->file_size += 12+len; } } /* Not IDAT or len == 0 */ } /* Chunk for loop */ /* Break out of the loop on error or end */ if (state >= 4) { if (truncated_idat) fprintf(stderr, "%s: removed %d bytes from end of IDAT\n", name, truncated_idat); return 0; /* success */ } /* This is somewhat generic but it works: */ fprintf(stderr, "%s: unterminated/truncated PNG (%d)\n", name, state); return 1; } static FILE *fpIn; static int fix_file(FILE *fp, const char *file, png_uint_32 max_IDAT, int inplace, int strip, int optimize, const char *output) { IDAT_info info; int imageBits, oldBits, bestBits, lowBits, newBits, ok_read; memset(&info, 0, sizeof info); name = file; idat_error = 0; /* fpIn is closed by the caller if necessary */ fpIn = fopen(file, "rb"); if (fpIn == NULL) { perror(file); fprintf(stderr, "%s: open failed\n", file); return 1; } /* With no arguments just check this file */ if (optimize == 0 && strip == 0 && inplace == 0 && output == NULL) return !read_png(fpIn); /* Otherwise, maybe, fix it */ if (fix_one(fp, fpIn, &info, max_IDAT, strip)) return 1; /* oldBits may be invalid, imageBits is always OK, newBits always records the * actual window bits of the temporary file (fp). */ bestBits = imageBits = image_windowBits(&info); newBits = oldBits = 8+(info.header[0] >> 4); ok_read = 0; /* records a successful read */ /* Find the optimal (lowest) newBits */ if (optimize) for (lowBits=8; lowBits < bestBits;) { /* This will always set 'newBits' to a value lower than 'bestBits' because * 'lowBits' is less than 'bestBits': */ newBits = (bestBits + lowBits) >> 1; set_bits(file, fp, &info, newBits); rewind(fp); idat_error = 0; if (!read_png(fp)) { /* If idat_error is *not* set this is some other problem */ if (!idat_error) return 1; /* This is the hypothetical case where the IDAT has too much data *and* * the window size is wrong. In fact this should never happen because * of the way libpng handles a deflate stream that produces extra data. */ if (newBits >= imageBits) { fprintf(stderr, "%s: imageBits(%d) too low (%d)\n", file, imageBits, newBits); return 1; } if (lowBits <= newBits) lowBits = newBits+1; } else { bestBits = newBits; ok_read = 1; } } else if (bestBits > oldBits) { /* See if the original value is ok */ rewind(fp); idat_error = 0; if (read_png(fp)) { ok_read = 1; bestBits = oldBits; } else if (!idat_error) return 1; /* Otherwise there is an IDAT error and no optimization is being done, so * just use imageBits (which is already set in bestBits). */ } if (newBits != bestBits) { /* Update the header to the required value */ newBits = bestBits; set_bits(file, fp, &info, newBits); } if (!ok_read) { /* bestBits has not been checked */ idat_error = 0; rewind(fp); ok_read = read_png(fp); if (idat_error) { /* This should never happen */ fprintf(stderr, "%s: imageBits(%d) too low [%d]\n", file, imageBits, newBits); return 1; } /* This means that the PNG has some other error */ if (!ok_read) return 1; } /* Have a valid newBits */ if (optimize) printf("%2d %2d %2d %s %s %d %s\n", newBits, oldBits, imageBits, newBits < imageBits ? "<" : "=", newBits < oldBits ? "reduce " : (newBits > oldBits ? "INCREASE" : "ok "), newBits - oldBits, name); # ifdef PNG_MAXIMUM_INFLATE_WINDOW /* Because setting libpng to use the maximum window bits breaks the * read_png test above. */ if (set_option) return 0; # endif if (output != NULL || (inplace && (bestBits != oldBits || strip))) { FILE *fpOut; if (output != NULL) fpOut = fopen(output, "wb"); else { fpOut = freopen(file, "wb", fpIn); fpIn = NULL; } if (fpOut == NULL) { perror(output); fprintf(stderr, "%s: %s: open failed\n", file, output); exit(3); } rewind(fp); copy(fpOut, fp, info.file_size); if (fflush(fpOut) || ferror(fpOut) || fclose(fpOut)) { perror(output != NULL ? output : file); fprintf(stderr, "%s: %s: close failed\n", file, output); if (output != NULL) remove(output); exit(3); } } return 0; } static void usage(const char *prog, int rc) { /* ANSI C-90 limits strings to 509 characters, so use a string array: */ size_t i; static const char *usage_string[] = { " Tests, optimizes and optionally fixes the zlib header in PNG files.\n", " Optionally, when fixing, strips ancilliary chunks from the file.\n", "\n", "OPTIONS\n", " OPERATION\n", " By default files are just checked for readability.\n", " --optimize (-o):\n", " Find the smallest deflate window size for the file, also outputs\n", " a summary of the result for each file.\n", " --strip (-s):\n", " Remove chunks except for IHDR, PLTE, IEND, tRNS, gAMA, sRGB. If\n", " given twice remove gAMA and sRGB as well.\n", " --max=:\n", " Use IDAT chunks sized . If not given the the IDAT\n", " chunks will be the maximum size permitted; 2^31-1 bytes.\n", " MESSAGES\n", " By default the program is silent.\n", " --errors (-e):\n", " Output errors from libpng (except too-far-back).\n", " --warnings (-w):\n", " Output warnings from libpng.\n", " --verbose (-v):\n", " Describe program progress (refer to the source).\n", " OUTPUT\n", " By default nothing is written.\n", " --out=:\n", " Write the optimized/corrected version of the next PNG to\n", " . This overrides the following two options\n", " --suffix=:\n", " Set --out= for all following files, unless\n", " overridden on a per-file basis by explicit --out. If no\n", " --suffix= value is given behaves as --inplace.\n", " --inplace (-i):\n", " Modify the file in place. THIS IS DANGEROUS - please keep a\n", " backup of the file because a program interrupt or bug will\n", " result in a corrupted file.\n", #ifdef PNG_MAXIMUM_INFLATE_WINDOW " INTERNAL OPTIONS\n", " --test:\n", " Test the PNG_MAXIMUM_INFLATE_WINDOW option. Setting this\n", " disables output as this would produce a broken file.\n", #endif "\n", "EXIT CODES\n", " 0: Success, all files pass the test, any output written ok.\n", " 1: At least one file had a read error, all files checked.\n", " 2: A file had an unrecoverable error (integer overflow, bad format),\n", " the program exited immediately, without processing further files.\n", " 3: An IO or out of memory error, or a file could not be opened.h\n", "\n", "DESCRIPTION\n", " " PROGRAM_NAME " checks each PNG file on the command line\n", " for errors. By default it is silent and just returns an exit code\n", " (as above). Options allow the zlib error:\n", "\n", " \"invalid distance too far back\"\n", "\n", " to be fixed during the read and therefore test the file for other\n", " errors that may prevent reading.\n", "\n", " Setting one of the \"OUTPUT\" options causes the possibly modified\n", " file to be written to a new file or, with --inplace, to the existing\n", " file.\n", "\n", " IMPORTANT: --inplace will overwrite the original file, if you use it\n", " be sure to keep a backup of the original file, this program can fail\n", " during the write and has been known to have serious bugs! A failure\n", " during write will certainly damage the original file.\n", "\n", " Notice that some PNG files with the zlib header problem can still be\n", " read by libpng under some circumstances. This program will still\n", " detect and, if requested, correct the error.\n", "\n", " The output produced with --optimize is as follows:\n", "\n", " opt-bits curr-bits image-bits opt-flag opt-type change file\n", "\n", " opt-bits: The minimum window bits (8-15) that works, if the file\n", " is written this is the value that will be stored.\n", " curr-bits: The value currently stored in the file.\n", " image-bits: The window bits value corresponding to the size of the\n", " uncompressed PNG image data. When --optimize is not\n", " given but --strip is, this value will be used if lower\n", " than the current value.\n", " opt-flag: < if the optimized bit value is less than that implied by\n", " the PNG image size (opt-bits < image-bits)\n", " = if optimization is not possible (opt-bits = image-bits)\n", " opt-type: reduce if opts-bits < curr-bits\n", " ok if opt-bits = curr-bits (no change required)\n", " INCREASE if opt-bits > curr-bits (the file has the bug)\n", " change: opt-bits - curr-bits, so negative if optimization is\n", " possible, 0 if no change is required, positive if the\n", " bug is present.\n", " file: The file name.\n", }; fprintf(stderr, "Usage: %s {[options] png-file}\n", prog); for (i=0; i < (sizeof usage_string)/(sizeof usage_string[0]); ++i) fputs(usage_string[i], stderr); exit(rc); } int main(int argc, const char **argv) { int err, strip = 0, optimize = 0, inplace = 0, done = 0; png_uint_32 max_IDAT = 0x7fffffff; FILE *fp; const char *outfile = NULL; const char *suffix = NULL; const char *prog = *argv; static const png_byte idat_bytes[4] = { 73, 68, 65, 84 }; static const png_byte iend_bytes[4] = { 73, 69, 78, 68 }; /* Initialize this first, could be stored as a constant: */ crc_IEND = crc_IDAT_head = crc32(0L, Z_NULL, 0); crc_IDAT_head = crc32(crc_IDAT_head, idat_bytes, 4); crc_IEND = crc32(crc_IEND, iend_bytes, 4); z_idat.next_in = Z_NULL; z_idat.avail_in = 0; z_idat.zalloc = Z_NULL; z_idat.zfree = Z_NULL; z_idat.opaque = Z_NULL; err = inflateInit(&z_idat); if (err != Z_OK) { fprintf(stderr, "inflateInit failed %d \"%s\"\n", err, z_idat.msg); inflateEnd(&z_idat); return 3; } fp = tmpfile(); if (fp == NULL) { perror("tmpfile"); fprintf(stderr, "could not open a temporary file\n"); return 3; } err = 0; while (--argc > 0) { ++argv; if (strcmp(*argv, "--inplace") == 0 || strcmp(*argv, "-i") == 0) ++inplace; else if (strncmp(*argv, "--max=", 6) == 0) max_IDAT = (png_uint_32)atol(6+*argv); else if (strcmp(*argv, "--optimize") == 0 || strcmp(*argv, "-o") == 0) ++optimize; else if (strncmp(*argv, "--out=", 6) == 0) outfile = 6+*argv; else if (strncmp(*argv, "--suffix=", 9) == 0) suffix = 9+*argv; else if (strcmp(*argv, "--strip") == 0 || strcmp(*argv, "-s") == 0) ++strip; else if (strcmp(*argv, "--errors") == 0 || strcmp(*argv, "-e") == 0) ++errors; else if (strcmp(*argv, "--warnings") == 0 || strcmp(*argv, "-w") == 0) ++warnings; else if (strcmp(*argv, "--verbose") == 0 || strcmp(*argv, "-v") == 0) ++verbose; # ifdef PNG_MAXIMUM_INFLATE_WINDOW else if (strcmp(*argv, "--test") == 0) ++set_option; # endif else if ((*argv)[0] == '-') usage(prog, 3); else { int ret, overwrite; if (outfile != NULL) overwrite = 0; else if (suffix != NULL) { if (*suffix == 0) overwrite = 1; else { static char temp_name[FILENAME_MAX]; size_t filelen = strlen(*argv); size_t suffixlen = strlen(suffix); if (filelen + suffixlen >= FILENAME_MAX) { fprintf(stderr, "%s: output file name too long: %s%s\n", prog, *argv, suffix); exit(3); } memcpy(temp_name, *argv, filelen); memcpy(temp_name+filelen, suffix, suffixlen); temp_name[filelen+suffixlen] = 0; outfile = temp_name; overwrite = 0; } } else overwrite = inplace; err += fix_file(fp, *argv, max_IDAT, overwrite, strip, optimize, outfile); if (fpIn != NULL) { fclose(fpIn); fpIn = NULL; } z_idat.next_in = z_idat.next_out = Z_NULL; z_idat.avail_in = z_idat.avail_out = 0; ret = inflateReset(&z_idat); if (ret != Z_OK) { fprintf(stderr, "inflateReset failed %d \"%s\"\n", ret, z_idat.msg); inflateEnd(&z_idat); return 3; } rewind(fp); outfile = NULL; ++done; } } inflateEnd(&z_idat); if (!done) usage(prog, 0); return err != 0; } #else /* PNG_ZLIB_VERNUM < 0x1240 */ int main(void) { fprintf(stderr, "png-fix-too-far-back needs libpng with a zlib >=1.2.4 (not 0x%x)\n", PNG_ZLIB_VERNUM); return 77; } #endif /* PNG_ZLIB_VERNUM */ #else /* No read support */ int main(void) { fprintf(stderr, "png-fix-too-far-back does not work without read support\n"); return 77; } #endif /* PNG_READ_SUPPORTED */