pngunknown.c revision b50c217251b086440efcdb273c22f86a06c80cba
1 2/* pngunknown.c - test the read side unknown chunk handling 3 * 4 * Last changed in libpng 1.6.0 [February 14, 2013] 5 * Copyright (c) 2013 Glenn Randers-Pehrson 6 * Written by John Cunningham Bowler 7 * 8 * This code is released under the libpng license. 9 * For conditions of distribution and use, see the disclaimer 10 * and license in png.h 11 * 12 * NOTES: 13 * This is a C program that is intended to be linked against libpng. It 14 * allows the libpng unknown handling code to be tested by interpreting 15 * arguments to save or discard combinations of chunks. The program is 16 * currently just a minimal validation for the built-in libpng facilities. 17 */ 18 19#include <stdlib.h> 20#include <string.h> 21#include <stdio.h> 22#include <setjmp.h> 23 24/* Define the following to use this test against your installed libpng, rather 25 * than the one being built here: 26 */ 27#ifdef PNG_FREESTANDING_TESTS 28# include <png.h> 29#else 30# include "../../png.h" 31#endif 32 33#ifdef PNG_READ_SUPPORTED 34 35#if PNG_LIBPNG_VER < 10500 36/* This deliberately lacks the PNG_CONST. */ 37typedef png_byte *png_const_bytep; 38 39/* This is copied from 1.5.1 png.h: */ 40#define PNG_INTERLACE_ADAM7_PASSES 7 41#define PNG_PASS_START_ROW(pass) (((1U&~(pass))<<(3-((pass)>>1)))&7) 42#define PNG_PASS_START_COL(pass) (((1U& (pass))<<(3-(((pass)+1)>>1)))&7) 43#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3) 44#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3) 45#define PNG_PASS_ROWS(height, pass) (((height)+(((1<<PNG_PASS_ROW_SHIFT(pass))\ 46 -1)-PNG_PASS_START_ROW(pass)))>>PNG_PASS_ROW_SHIFT(pass)) 47#define PNG_PASS_COLS(width, pass) (((width)+(((1<<PNG_PASS_COL_SHIFT(pass))\ 48 -1)-PNG_PASS_START_COL(pass)))>>PNG_PASS_COL_SHIFT(pass)) 49#define PNG_ROW_FROM_PASS_ROW(yIn, pass) \ 50 (((yIn)<<PNG_PASS_ROW_SHIFT(pass))+PNG_PASS_START_ROW(pass)) 51#define PNG_COL_FROM_PASS_COL(xIn, pass) \ 52 (((xIn)<<PNG_PASS_COL_SHIFT(pass))+PNG_PASS_START_COL(pass)) 53#define PNG_PASS_MASK(pass,off) ( \ 54 ((0x110145AFU>>(((7-(off))-(pass))<<2)) & 0xFU) | \ 55 ((0x01145AF0U>>(((7-(off))-(pass))<<2)) & 0xF0U)) 56#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \ 57 ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1) 58#define PNG_COL_IN_INTERLACE_PASS(x, pass) \ 59 ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) 60 61/* These are needed too for the default build: */ 62#define PNG_WRITE_16BIT_SUPPORTED 63#define PNG_READ_16BIT_SUPPORTED 64 65/* This comes from pnglibconf.h afer 1.5: */ 66#define PNG_FP_1 100000 67#define PNG_GAMMA_THRESHOLD_FIXED\ 68 ((png_fixed_point)(PNG_GAMMA_THRESHOLD * PNG_FP_1)) 69#endif 70 71#if PNG_LIBPNG_VER < 10600 72 /* 1.6.0 constifies many APIs. The following exists to allow pngvalid to be 73 * compiled against earlier versions. 74 */ 75# define png_const_structp png_structp 76#endif 77 78 79/* Copied from pngpriv.h */ 80#define PNG_32b(b,s) ((png_uint_32)(b) << (s)) 81#define PNG_CHUNK(b1,b2,b3,b4) \ 82 (PNG_32b(b1,24) | PNG_32b(b2,16) | PNG_32b(b3,8) | PNG_32b(b4,0)) 83 84#define png_IHDR PNG_CHUNK( 73, 72, 68, 82) 85#define png_IDAT PNG_CHUNK( 73, 68, 65, 84) 86#define png_IEND PNG_CHUNK( 73, 69, 78, 68) 87#define png_PLTE PNG_CHUNK( 80, 76, 84, 69) 88#define png_bKGD PNG_CHUNK( 98, 75, 71, 68) 89#define png_cHRM PNG_CHUNK( 99, 72, 82, 77) 90#define png_gAMA PNG_CHUNK(103, 65, 77, 65) 91#define png_hIST PNG_CHUNK(104, 73, 83, 84) 92#define png_iCCP PNG_CHUNK(105, 67, 67, 80) 93#define png_iTXt PNG_CHUNK(105, 84, 88, 116) 94#define png_oFFs PNG_CHUNK(111, 70, 70, 115) 95#define png_pCAL PNG_CHUNK(112, 67, 65, 76) 96#define png_sCAL PNG_CHUNK(115, 67, 65, 76) 97#define png_pHYs PNG_CHUNK(112, 72, 89, 115) 98#define png_sBIT PNG_CHUNK(115, 66, 73, 84) 99#define png_sPLT PNG_CHUNK(115, 80, 76, 84) 100#define png_sRGB PNG_CHUNK(115, 82, 71, 66) 101#define png_sTER PNG_CHUNK(115, 84, 69, 82) 102#define png_tEXt PNG_CHUNK(116, 69, 88, 116) 103#define png_tIME PNG_CHUNK(116, 73, 77, 69) 104#define png_tRNS PNG_CHUNK(116, 82, 78, 83) 105#define png_zTXt PNG_CHUNK(122, 84, 88, 116) 106#define png_vpAg PNG_CHUNK('v', 'p', 'A', 'g') 107 108/* Test on flag values as defined in the spec (section 5.4): */ 109#define PNG_CHUNK_ANCILLARY(c ) (1 & ((c) >> 29)) 110#define PNG_CHUNK_CRITICAL(c) (!PNG_CHUNK_ANCILLARY(c)) 111#define PNG_CHUNK_PRIVATE(c) (1 & ((c) >> 21)) 112#define PNG_CHUNK_RESERVED(c) (1 & ((c) >> 13)) 113#define PNG_CHUNK_SAFE_TO_COPY(c) (1 & ((c) >> 5)) 114 115/* Chunk information */ 116#define PNG_INFO_tEXt 0x10000000U 117#define PNG_INFO_iTXt 0x20000000U 118#define PNG_INFO_zTXt 0x40000000U 119 120#define PNG_INFO_sTER 0x01000000U 121#define PNG_INFO_vpAg 0x02000000U 122 123#define ABSENT 0 124#define START 1 125#define END 2 126 127static struct 128{ 129 char name[5]; 130 png_uint_32 flag; 131 png_uint_32 tag; 132 int unknown; /* Chunk not known to libpng */ 133 int all; /* Chunk set by the '-1' option */ 134 int position; /* position in pngtest.png */ 135 int keep; /* unknown handling setting */ 136} chunk_info[] = { 137 /* Critical chunks */ 138 { "IDAT", PNG_INFO_IDAT, png_IDAT, 0, 0, START, 0 }, /* must be [0] */ 139 { "PLTE", PNG_INFO_PLTE, png_PLTE, 0, 0, ABSENT, 0 }, 140 141 /* Non-critical chunks that libpng handles */ 142 /* This is a mess but it seems to be the only way to do it - there is no way to 143 * check for definition outside a #if. 144 */ 145 { "bKGD", PNG_INFO_bKGD, png_bKGD, 146# ifdef PNG_READ_bKGD_SUPPORTED 147 0, 148# else 149 1, 150# endif 151 1, START, 0 }, 152 { "cHRM", PNG_INFO_cHRM, png_cHRM, 153# ifdef PNG_READ_cHRM_SUPPORTED 154 0, 155# else 156 1, 157# endif 158 1, START, 0 }, 159 { "gAMA", PNG_INFO_gAMA, png_gAMA, 160# ifdef PNG_READ_gAMA_SUPPORTED 161 0, 162# else 163 1, 164# endif 165 1, START, 0 }, 166 { "hIST", PNG_INFO_hIST, png_hIST, 167# ifdef PNG_READ_hIST_SUPPORTED 168 0, 169# else 170 1, 171# endif 172 1, ABSENT, 0 }, 173 { "iCCP", PNG_INFO_iCCP, png_iCCP, 174# ifdef PNG_READ_iCCP_SUPPORTED 175 0, 176# else 177 1, 178# endif 179 1, ABSENT, 0 }, 180 { "iTXt", PNG_INFO_iTXt, png_iTXt, 181# ifdef PNG_READ_iTXt_SUPPORTED 182 0, 183# else 184 1, 185# endif 186 1, ABSENT, 0 }, 187 { "oFFs", PNG_INFO_oFFs, png_oFFs, 188# ifdef PNG_READ_oFFs_SUPPORTED 189 0, 190# else 191 1, 192# endif 193 1, START, 0 }, 194 { "pCAL", PNG_INFO_pCAL, png_pCAL, 195# ifdef PNG_READ_pCAL_SUPPORTED 196 0, 197# else 198 1, 199# endif 200 1, START, 0 }, 201 { "pHYs", PNG_INFO_pHYs, png_pHYs, 202# ifdef PNG_READ_pHYs_SUPPORTED 203 0, 204# else 205 1, 206# endif 207 1, START, 0 }, 208 { "sBIT", PNG_INFO_sBIT, png_sBIT, 209# ifdef PNG_READ_sBIT_SUPPORTED 210 0, 211# else 212 1, 213# endif 214 1, START, 0 }, 215 { "sCAL", PNG_INFO_sCAL, png_sCAL, 216# ifdef PNG_READ_sCAL_SUPPORTED 217 0, 218# else 219 1, 220# endif 221 1, START, 0 }, 222 { "sPLT", PNG_INFO_sPLT, png_sPLT, 223# ifdef PNG_READ_sPLT_SUPPORTED 224 0, 225# else 226 1, 227# endif 228 1, ABSENT, 0 }, 229 { "sRGB", PNG_INFO_sRGB, png_sRGB, 230# ifdef PNG_READ_sRGB_SUPPORTED 231 0, 232# else 233 1, 234# endif 235 1, START, 0 }, 236 { "tEXt", PNG_INFO_tEXt, png_tEXt, 237# ifdef PNG_READ_tEXt_SUPPORTED 238 0, 239# else 240 1, 241# endif 242 1, START, 0 }, 243 { "tIME", PNG_INFO_tIME, png_tIME, 244# ifdef PNG_READ_tIME_SUPPORTED 245 0, 246# else 247 1, 248# endif 249 1, START, 0 }, 250 { "tRNS", PNG_INFO_tRNS, png_tRNS, 251# ifdef PNG_READ_tRNS_SUPPORTED 252 0, 253# else 254 1, 255# endif 256 0, ABSENT, 0 }, 257 { "zTXt", PNG_INFO_zTXt, png_zTXt, 258# ifdef PNG_READ_zTXt_SUPPORTED 259 0, 260# else 261 1, 262# endif 263 1, END, 0 }, 264 265 /* No libpng handling */ 266 { "sTER", PNG_INFO_sTER, png_sTER, 1, 1, START, 0 }, 267 { "vpAg", PNG_INFO_vpAg, png_vpAg, 1, 0, START, 0 }, 268}; 269 270#define NINFO ((int)((sizeof chunk_info)/(sizeof chunk_info[0]))) 271 272static void 273clear_keep(void) 274{ 275 int i = NINFO; 276 while (--i >= 0) 277 chunk_info[i].keep = 0; 278} 279 280static int 281find(const char *name) 282{ 283 int i = NINFO; 284 while (--i >= 0) 285 { 286 if (memcmp(chunk_info[i].name, name, 4) == 0) 287 break; 288 } 289 290 return i; 291} 292 293static int 294findb(const png_byte *name) 295{ 296 int i = NINFO; 297 while (--i >= 0) 298 { 299 if (memcmp(chunk_info[i].name, name, 4) == 0) 300 break; 301 } 302 303 return i; 304} 305 306static int 307find_by_flag(png_uint_32 flag) 308{ 309 int i = NINFO; 310 311 while (--i >= 0) if (chunk_info[i].flag == flag) return i; 312 313 fprintf(stderr, "pngunknown: internal error\n"); 314 exit(4); 315} 316 317static int 318ancillary(const char *name) 319{ 320 return PNG_CHUNK_ANCILLARY(PNG_CHUNK(name[0], name[1], name[2], name[3])); 321} 322 323static int 324ancillaryb(const png_byte *name) 325{ 326 return PNG_CHUNK_ANCILLARY(PNG_CHUNK(name[0], name[1], name[2], name[3])); 327} 328 329/* Type of an error_ptr */ 330typedef struct 331{ 332 jmp_buf error_return; 333 png_structp png_ptr; 334 png_infop info_ptr, end_ptr; 335 int error_count; 336 int warning_count; 337 const char *program; 338 const char *file; 339 const char *test; 340} display; 341 342static const char init[] = "initialization"; 343static const char cmd[] = "command line"; 344 345static void 346init_display(display *d, const char *program) 347{ 348 memset(d, 0, sizeof *d); 349 d->png_ptr = NULL; 350 d->info_ptr = d->end_ptr = NULL; 351 d->error_count = d->warning_count = 0; 352 d->program = program; 353 d->file = program; 354 d->test = init; 355} 356 357static void 358clean_display(display *d) 359{ 360 png_destroy_read_struct(&d->png_ptr, &d->info_ptr, &d->end_ptr); 361 362 /* This must not happen - it might cause an app crash */ 363 if (d->png_ptr != NULL || d->info_ptr != NULL || d->end_ptr != NULL) 364 { 365 fprintf(stderr, "%s(%s): png_destroy_read_struct error\n", d->file, 366 d->test); 367 exit(1); 368 } 369 370 /* Invalidate the test */ 371 d->test = init; 372} 373 374PNG_FUNCTION(void, display_exit, (display *d), static PNG_NORETURN) 375{ 376 ++(d->error_count); 377 378 if (d->png_ptr != NULL) 379 clean_display(d); 380 381 /* During initialization and if this is a single command line argument set 382 * exit now - there is only one test, otherwise longjmp to do the next test. 383 */ 384 if (d->test == init || d->test == cmd) 385 exit(1); 386 387 longjmp(d->error_return, 1); 388} 389 390static int 391display_rc(const display *d, int strict) 392{ 393 return d->error_count + (strict ? d->warning_count : 0); 394} 395 396/* libpng error and warning callbacks */ 397PNG_FUNCTION(void, error, (png_structp png_ptr, const char *message), 398 static PNG_NORETURN) 399{ 400 display *d = (display*)png_get_error_ptr(png_ptr); 401 402 fprintf(stderr, "%s(%s): libpng error: %s\n", d->file, d->test, message); 403 display_exit(d); 404} 405 406static void 407warning(png_structp png_ptr, const char *message) 408{ 409 display *d = (display*)png_get_error_ptr(png_ptr); 410 411 fprintf(stderr, "%s(%s): libpng warning: %s\n", d->file, d->test, message); 412 ++(d->warning_count); 413} 414 415static png_uint_32 416get_valid(display *d, png_infop info_ptr) 417{ 418 png_uint_32 flags = png_get_valid(d->png_ptr, info_ptr, (png_uint_32)~0); 419 420 /* Map the text chunks back into the flags */ 421 { 422 png_textp text; 423 png_uint_32 ntext = png_get_text(d->png_ptr, info_ptr, &text, NULL); 424 425 while (ntext-- > 0) switch (text[ntext].compression) 426 { 427 case -1: 428 flags |= PNG_INFO_tEXt; 429 break; 430 case 0: 431 flags |= PNG_INFO_zTXt; 432 break; 433 case 1: 434 case 2: 435 flags |= PNG_INFO_iTXt; 436 break; 437 default: 438 fprintf(stderr, "%s(%s): unknown text compression %d\n", d->file, 439 d->test, text[ntext].compression); 440 display_exit(d); 441 } 442 } 443 444 return flags; 445} 446 447static png_uint_32 448get_unknown(display *d, int def, png_infop info_ptr) 449{ 450 /* Create corresponding 'unknown' flags */ 451 png_uint_32 flags = 0; 452 { 453 png_unknown_chunkp unknown; 454 int num_unknown = png_get_unknown_chunks(d->png_ptr, info_ptr, &unknown); 455 456 while (--num_unknown >= 0) 457 { 458 int chunk = findb(unknown[num_unknown].name); 459 460 /* Chunks not known to pngunknown must be validated here; since they 461 * must also be unknown to libpng the 'def' behavior should have been 462 * used. 463 */ 464 if (chunk < 0) switch (def) 465 { 466 default: /* impossible */ 467 case PNG_HANDLE_CHUNK_AS_DEFAULT: 468 case PNG_HANDLE_CHUNK_NEVER: 469 fprintf(stderr, "%s(%s): %s: %s: unknown chunk saved\n", 470 d->file, d->test, def ? "discard" : "default", 471 unknown[num_unknown].name); 472 ++(d->error_count); 473 break; 474 475 case PNG_HANDLE_CHUNK_IF_SAFE: 476 if (!ancillaryb(unknown[num_unknown].name)) 477 { 478 fprintf(stderr, 479 "%s(%s): if-safe: %s: unknown critical chunk saved\n", 480 d->file, d->test, unknown[num_unknown].name); 481 ++(d->error_count); 482 break; 483 } 484 /* FALL THROUGH (safe) */ 485 case PNG_HANDLE_CHUNK_ALWAYS: 486 break; 487 } 488 489 else 490 flags |= chunk_info[chunk].flag; 491 } 492 } 493 494 return flags; 495} 496 497static int 498check(FILE *fp, int argc, const char **argv, png_uint_32p flags/*out*/, 499 display *d) 500{ 501 int i, def = PNG_HANDLE_CHUNK_AS_DEFAULT, npasses, ipass; 502 png_uint_32 height; 503 504 /* Some of these errors are permanently fatal and cause an exit here, others 505 * are per-test and cause an error return. 506 */ 507 d->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, d, error, 508 warning); 509 if (d->png_ptr == NULL) 510 { 511 fprintf(stderr, "%s(%s): could not allocate png struct\n", d->file, 512 d->test); 513 /* Terminate here, this error is not test specific. */ 514 exit(1); 515 } 516 517 d->info_ptr = png_create_info_struct(d->png_ptr); 518 d->end_ptr = png_create_info_struct(d->png_ptr); 519 if (d->info_ptr == NULL || d->end_ptr == NULL) 520 { 521 fprintf(stderr, "%s(%s): could not allocate png info\n", d->file, 522 d->test); 523 clean_display(d); 524 exit(1); 525 } 526 527 png_init_io(d->png_ptr, fp); 528 529 /* Handle each argument in turn; multiple settings are possible for the same 530 * chunk and multiple calls will occur (the last one should override all 531 * preceding ones). 532 */ 533 for (i=0; i<argc; ++i) 534 { 535 const char *equals = strchr(argv[i], '='); 536 537 if (equals != NULL) 538 { 539 int chunk, option; 540 541 if (strcmp(equals+1, "default") == 0) 542 option = PNG_HANDLE_CHUNK_AS_DEFAULT; 543 else if (strcmp(equals+1, "discard") == 0) 544 option = PNG_HANDLE_CHUNK_NEVER; 545 else if (strcmp(equals+1, "if-safe") == 0) 546 option = PNG_HANDLE_CHUNK_IF_SAFE; 547 else if (strcmp(equals+1, "save") == 0) 548 option = PNG_HANDLE_CHUNK_ALWAYS; 549 else 550 { 551 fprintf(stderr, "%s(%s): %s: unrecognized chunk option\n", d->file, 552 d->test, argv[i]); 553 display_exit(d); 554 } 555 556 switch (equals - argv[i]) 557 { 558 case 4: /* chunk name */ 559 chunk = find(argv[i]); 560 561 if (chunk >= 0) 562 { 563 /* These #if tests have the effect of skipping the arguments 564 * if SAVE support is unavailable - we can't do a useful test 565 * in this case, so we just check the arguments! This could 566 * be improved in the future by using the read callback. 567 */ 568# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED 569 png_byte name[5]; 570 571 memcpy(name, chunk_info[chunk].name, 5); 572 png_set_keep_unknown_chunks(d->png_ptr, option, name, 1); 573 chunk_info[chunk].keep = option; 574# endif 575 continue; 576 } 577 578 break; 579 580 case 7: /* default */ 581 if (memcmp(argv[i], "default", 7) == 0) 582 { 583# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED 584 png_set_keep_unknown_chunks(d->png_ptr, option, NULL, 0); 585# endif 586 def = option; 587 continue; 588 } 589 590 break; 591 592 case 3: /* all */ 593 if (memcmp(argv[i], "all", 3) == 0) 594 { 595# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED 596 png_set_keep_unknown_chunks(d->png_ptr, option, NULL, -1); 597 def = option; 598 599 for (chunk = 0; chunk < NINFO; ++chunk) 600 if (chunk_info[chunk].all) 601 chunk_info[chunk].keep = option; 602# endif 603 continue; 604 } 605 606 break; 607 608 default: /* some misplaced = */ 609 610 break; 611 } 612 } 613 614 fprintf(stderr, "%s(%s): %s: unrecognized chunk argument\n", d->file, 615 d->test, argv[i]); 616 display_exit(d); 617 } 618 619 png_read_info(d->png_ptr, d->info_ptr); 620 621 switch (png_get_interlace_type(d->png_ptr, d->info_ptr)) 622 { 623 case PNG_INTERLACE_NONE: 624 npasses = 1; 625 break; 626 627 case PNG_INTERLACE_ADAM7: 628 npasses = PNG_INTERLACE_ADAM7_PASSES; 629 break; 630 631 default: 632 /* Hard error because it is not test specific */ 633 fprintf(stderr, "%s(%s): invalid interlace type\n", d->file, d->test); 634 clean_display(d); 635 exit(1); 636 } 637 638 /* Skip the image data, if IDAT is not being handled then don't do this 639 * because it will cause a CRC error. 640 */ 641 if (chunk_info[0/*IDAT*/].keep == PNG_HANDLE_CHUNK_AS_DEFAULT) 642 { 643 png_start_read_image(d->png_ptr); 644 height = png_get_image_height(d->png_ptr, d->info_ptr); 645 646 if (npasses > 1) 647 { 648 png_uint_32 width = png_get_image_width(d->png_ptr, d->info_ptr); 649 650 for (ipass=0; ipass<npasses; ++ipass) 651 { 652 png_uint_32 wPass = PNG_PASS_COLS(width, ipass); 653 654 if (wPass > 0) 655 { 656 png_uint_32 y; 657 658 for (y=0; y<height; ++y) if (PNG_ROW_IN_INTERLACE_PASS(y, ipass)) 659 png_read_row(d->png_ptr, NULL, NULL); 660 } 661 } 662 } /* interlaced */ 663 664 else /* not interlaced */ 665 { 666 png_uint_32 y; 667 668 for (y=0; y<height; ++y) 669 png_read_row(d->png_ptr, NULL, NULL); 670 } 671 } 672 673 png_read_end(d->png_ptr, d->end_ptr); 674 675 flags[0] = get_valid(d, d->info_ptr); 676 flags[1] = get_unknown(d, def, d->info_ptr); 677 678 /* Only png_read_png sets PNG_INFO_IDAT! */ 679 flags[chunk_info[0/*IDAT*/].keep != PNG_HANDLE_CHUNK_AS_DEFAULT] |= 680 PNG_INFO_IDAT; 681 682 flags[2] = get_valid(d, d->end_ptr); 683 flags[3] = get_unknown(d, def, d->end_ptr); 684 685 clean_display(d); 686 687 return def; 688} 689 690static void 691check_error(display *d, png_uint_32 flags, const char *message) 692{ 693 while (flags) 694 { 695 png_uint_32 flag = flags & -(png_int_32)flags; 696 int i = find_by_flag(flag); 697 698 fprintf(stderr, "%s(%s): chunk %s: %s\n", d->file, d->test, 699 chunk_info[i].name, message); 700 ++(d->error_count); 701 702 flags &= ~flag; 703 } 704} 705 706static void 707check_handling(display *d, int def, png_uint_32 chunks, png_uint_32 known, 708 png_uint_32 unknown, const char *position) 709{ 710 while (chunks) 711 { 712 png_uint_32 flag = chunks & -(png_int_32)chunks; 713 int i = find_by_flag(flag); 714 int keep = chunk_info[i].keep; 715 const char *type; 716 const char *errorx = NULL; 717 718 if (chunk_info[i].unknown) 719 { 720 if (keep == PNG_HANDLE_CHUNK_AS_DEFAULT) 721 { 722 type = "UNKNOWN (default)"; 723 keep = def; 724 } 725 726 else 727 type = "UNKNOWN (specified)"; 728 729 if (flag & known) 730 errorx = "chunk processed"; 731 732 else switch (keep) 733 { 734 case PNG_HANDLE_CHUNK_AS_DEFAULT: 735 if (flag & unknown) 736 errorx = "DEFAULT: unknown chunk saved"; 737 break; 738 739 case PNG_HANDLE_CHUNK_NEVER: 740 if (flag & unknown) 741 errorx = "DISCARD: unknown chunk saved"; 742 break; 743 744 case PNG_HANDLE_CHUNK_IF_SAFE: 745 if (ancillary(chunk_info[i].name)) 746 { 747 if (!(flag & unknown)) 748 errorx = "IF-SAFE: unknown ancillary chunk lost"; 749 } 750 751 else if (flag & unknown) 752 errorx = "IF-SAFE: unknown critical chunk saved"; 753 break; 754 755 case PNG_HANDLE_CHUNK_ALWAYS: 756 if (!(flag & unknown)) 757 errorx = "SAVE: unknown chunk lost"; 758 break; 759 760 default: 761 errorx = "internal error: bad keep"; 762 break; 763 } 764 } /* unknown chunk */ 765 766 else /* known chunk */ 767 { 768 type = "KNOWN"; 769 770 if (flag & known) 771 { 772 /* chunk was processed, it won't have been saved because that is 773 * caught below when checking for inconsistent processing. 774 */ 775 if (keep != PNG_HANDLE_CHUNK_AS_DEFAULT) 776 errorx = "!DEFAULT: known chunk processed"; 777 } 778 779 else /* not processed */ switch (keep) 780 { 781 case PNG_HANDLE_CHUNK_AS_DEFAULT: 782 errorx = "DEFAULT: known chunk not processed"; 783 break; 784 785 case PNG_HANDLE_CHUNK_NEVER: 786 if (flag & unknown) 787 errorx = "DISCARD: known chunk saved"; 788 break; 789 790 case PNG_HANDLE_CHUNK_IF_SAFE: 791 if (ancillary(chunk_info[i].name)) 792 { 793 if (!(flag & unknown)) 794 errorx = "IF-SAFE: known ancillary chunk lost"; 795 } 796 797 else if (flag & unknown) 798 errorx = "IF-SAFE: known critical chunk saved"; 799 break; 800 801 case PNG_HANDLE_CHUNK_ALWAYS: 802 if (!(flag & unknown)) 803 errorx = "SAVE: known chunk lost"; 804 break; 805 806 default: 807 errorx = "internal error: bad keep (2)"; 808 break; 809 } 810 } 811 812 if (errorx != NULL) 813 { 814 ++(d->error_count); 815 fprintf(stderr, "%s(%s): %s %s %s: %s\n", 816 d->file, d->test, type, chunk_info[i].name, position, errorx); 817 } 818 819 chunks &= ~flag; 820 } 821} 822 823static void 824perform_one_test(FILE *fp, int argc, const char **argv, 825 png_uint_32 *default_flags, display *d) 826{ 827 int def; 828 png_uint_32 flags[2][4]; 829 830 rewind(fp); 831 clear_keep(); 832 memcpy(flags[0], default_flags, sizeof flags[0]); 833 834 def = check(fp, argc, argv, flags[1], d); 835 836 /* Chunks should either be known or unknown, never both and this should apply 837 * whether the chunk is before or after the IDAT (actually, the app can 838 * probably change this by swapping the handling after the image, but this 839 * test does not do that.) 840 */ 841 check_error(d, (flags[0][0]|flags[0][2]) & (flags[0][1]|flags[0][3]), 842 "chunk handled inconsistently in count tests"); 843 check_error(d, (flags[1][0]|flags[1][2]) & (flags[1][1]|flags[1][3]), 844 "chunk handled inconsistently in option tests"); 845 846 /* Now find out what happened to each chunk before and after the IDAT and 847 * determine if the behavior was correct. First some basic sanity checks, 848 * any known chunk should be known in the original count, any unknown chunk 849 * should be either known or unknown in the original. 850 */ 851 { 852 png_uint_32 test; 853 854 test = flags[1][0] & ~flags[0][0]; 855 check_error(d, test, "new known chunk before IDAT"); 856 test = flags[1][1] & ~(flags[0][0] | flags[0][1]); 857 check_error(d, test, "new unknown chunk before IDAT"); 858 test = flags[1][2] & ~flags[0][2]; 859 check_error(d, test, "new known chunk after IDAT"); 860 test = flags[1][3] & ~(flags[0][2] | flags[0][3]); 861 check_error(d, test, "new unknown chunk after IDAT"); 862 } 863 864 /* Now each chunk in the original list should have been handled according to 865 * the options set for that chunk, regardless of whether libpng knows about 866 * it or not. 867 */ 868 check_handling(d, def, flags[0][0] | flags[0][1], flags[1][0], flags[1][1], 869 "before IDAT"); 870 check_handling(d, def, flags[0][2] | flags[0][3], flags[1][2], flags[1][3], 871 "after IDAT"); 872} 873 874static void 875perform_one_test_safe(FILE *fp, int argc, const char **argv, 876 png_uint_32 *default_flags, display *d, const char *test) 877{ 878 if (setjmp(d->error_return) == 0) 879 { 880 d->test = test; /* allow use of d->error_return */ 881 perform_one_test(fp, argc, argv, default_flags, d); 882 d->test = init; /* prevent use of d->error_return */ 883 } 884} 885 886static const char *standard_tests[] = 887{ 888 "discard", "default=discard", 0, 889 "save", "default=save", 0, 890 "if-safe", "default=if-safe", 0, 891 "vpAg", "vpAg=if-safe", 0, 892 "sTER", "sTER=if-safe", 0, 893 "IDAT", "default=discard", "IDAT=save", 0, 894 "sAPI", "bKGD=save", "cHRM=save", "gAMA=save", "all=discard", "iCCP=save", 895 "sBIT=save", "sRGB=save", 0, 896 0/*end*/ 897}; 898 899static PNG_NORETURN void 900usage(const char *program, const char *reason) 901{ 902 fprintf(stderr, "pngunknown: %s: usage:\n %s [--strict] " 903 "--default|{(CHNK|default|all)=(default|discard|if-safe|save)} " 904 "testfile.png\n", reason, program); 905 exit(2); 906} 907 908int 909main(int argc, const char **argv) 910{ 911 FILE *fp; 912 png_uint_32 default_flags[4/*valid,unknown{before,after}*/]; 913 int strict = 0, default_tests = 0; 914 const char *count_argv = "default=save"; 915 const char *touch_file = NULL; 916 display d; 917 918 init_display(&d, argv[0]); 919 920 while (++argv, --argc > 0) 921 { 922 if (strcmp(*argv, "--strict") == 0) 923 strict = 1; 924 925 else if (strcmp(*argv, "--default") == 0) 926 default_tests = 1; 927 928 else if (strcmp(*argv, "--touch") == 0) 929 { 930 if (argc > 1) 931 touch_file = *++argv, --argc; 932 933 else 934 usage(d.program, "--touch: missing file name"); 935 } 936 937 else 938 break; 939 } 940 941 /* A file name is required, but there should be no other arguments if 942 * --default was specified. 943 */ 944 if (argc <= 0) 945 usage(d.program, "missing test file"); 946 947 /* GCC BUG: if (default_tests && argc != 1) triggers some weird GCC argc 948 * optimization which causes warnings with -Wstrict-overflow! 949 */ 950 else if (default_tests) if (argc != 1) 951 usage(d.program, "extra arguments"); 952 953# ifndef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED 954 fprintf(stderr, "%s: warning: no 'save' support so arguments ignored\n", 955 d.program); 956# endif 957 958 /* The name of the test file is the last argument; remove it. */ 959 d.file = argv[--argc]; 960 961 fp = fopen(d.file, "rb"); 962 if (fp == NULL) 963 { 964 perror(d.file); 965 exit(2); 966 } 967 968 /* First find all the chunks, known and unknown, in the test file, a failure 969 * here aborts the whole test. 970 */ 971 if (check(fp, 1, &count_argv, default_flags, &d) != 972 PNG_HANDLE_CHUNK_ALWAYS) 973 { 974 fprintf(stderr, "%s: %s: internal error\n", d.program, d.file); 975 exit(3); 976 } 977 978 /* Now find what the various supplied options cause to change: */ 979 if (!default_tests) 980 { 981 d.test = cmd; /* acts as a flag to say exit, do not longjmp */ 982 perform_one_test(fp, argc, argv, default_flags, &d); 983 d.test = init; 984 } 985 986 else 987 { 988 const char **test = standard_tests; 989 990 /* Set the exit_test pointer here so we can continue after a libpng error. 991 * NOTE: this leaks memory because the png_struct data from the failing 992 * test is never freed. 993 */ 994 while (*test) 995 { 996 const char *this_test = *test++; 997 const char **next = test; 998 int count = display_rc(&d, strict), new_count; 999 const char *result; 1000 int arg_count = 0; 1001 1002 while (*next) ++next, ++arg_count; 1003 1004 perform_one_test_safe(fp, arg_count, test, default_flags, &d, 1005 this_test); 1006 1007 new_count = display_rc(&d, strict); 1008 1009 if (new_count == count) 1010 result = "PASS"; 1011 1012 else 1013 result = "FAIL"; 1014 1015 printf("%s: %s %s\n", result, d.program, this_test); 1016 1017 test = next+1; 1018 } 1019 } 1020 1021 fclose(fp); 1022 1023 if (display_rc(&d, strict) == 0) 1024 { 1025 /* Success, touch the success file if appropriate */ 1026 if (touch_file != NULL) 1027 { 1028 FILE *fsuccess = fopen(touch_file, "wt"); 1029 1030 if (fsuccess != NULL) 1031 { 1032 int err = 0; 1033 fprintf(fsuccess, "PNG unknown tests succeeded\n"); 1034 fflush(fsuccess); 1035 err = ferror(fsuccess); 1036 1037 if (fclose(fsuccess) || err) 1038 { 1039 fprintf(stderr, "%s: write failed\n", touch_file); 1040 exit(1); 1041 } 1042 } 1043 1044 else 1045 { 1046 fprintf(stderr, "%s: open failed\n", touch_file); 1047 exit(1); 1048 } 1049 } 1050 1051 return 0; 1052 } 1053 1054 return 1; 1055} 1056 1057#else 1058int 1059main(void) 1060{ 1061 fprintf(stderr, 1062 " test ignored because libpng was not built with unknown chunk support\n"); 1063 /* So the test is skipped: */ 1064 return 77; 1065} 1066#endif 1067