Coverage Report

Created: 2026-10-28 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebp/imageio/pngdec.c
Line
Count
Source
1
// Copyright 2012 Google Inc. All Rights Reserved.
2
//
3
// Use of this source code is governed by a BSD-style license
4
// that can be found in the COPYING file in the root of the source
5
// tree. An additional intellectual property rights grant can be found
6
// in the file PATENTS. All contributing project authors may
7
// be found in the AUTHORS file in the root of the source tree.
8
// -----------------------------------------------------------------------------
9
//
10
// PNG decode.
11
12
#include "./pngdec.h"
13
14
#include <limits.h>
15
16
#ifdef HAVE_CONFIG_H
17
#include "webp/config.h"
18
#endif
19
20
#include <stdio.h>
21
22
#ifdef WEBP_HAVE_PNG
23
#ifndef PNG_USER_MEM_SUPPORTED
24
#define PNG_USER_MEM_SUPPORTED  // for png_create_read_struct_2
25
#endif
26
#include <assert.h>
27
#include <png.h>
28
#include <setjmp.h>  // note: this must be included *after* png.h
29
#include <stdlib.h>
30
#include <string.h>
31
32
#include "./imageio_util.h"
33
#include "./metadata.h"
34
#include "webp/encode.h"
35
#include "webp/types.h"
36
37
#define LOCAL_PNG_VERSION ((PNG_LIBPNG_VER_MAJOR << 8) | PNG_LIBPNG_VER_MINOR)
38
#define LOCAL_PNG_PREREQ(maj, min) (LOCAL_PNG_VERSION >= (((maj) << 8) | (min)))
39
40
41
static void PNGAPI error_function(png_structp png, png_const_charp error) {
41
41
  if (error != NULL) fprintf(stderr, "libpng error: %s\n", error);
42
41
  longjmp(png_jmpbuf(png), 1);
43
41
}
44
45
#if LOCAL_PNG_PREREQ(1, 4)
46
typedef png_alloc_size_t LocalPngAllocSize;
47
#else
48
typedef png_size_t LocalPngAllocSize;
49
#endif
50
51
309
static png_voidp MallocFunc(png_structp png_ptr, LocalPngAllocSize size) {
52
309
  (void)png_ptr;
53
309
  if (size != (size_t)size) return NULL;
54
309
  if (!ImgIoUtilCheckSizeArgumentsOverflow(size, 1)) return NULL;
55
309
  return (png_voidp)malloc((size_t)size);
56
309
}
57
58
300
static void FreeFunc(png_structp png_ptr, png_voidp ptr) {
59
300
  (void)png_ptr;
60
300
  free(ptr);
61
300
}
62
63
// Converts the NULL terminated 'hexstring' which contains 2-byte character
64
// representations of hex values to raw data.
65
// 'hexstring' may contain values consisting of [A-F][a-f][0-9] in pairs,
66
// e.g., 7af2..., separated by any number of newlines.
67
// 'expected_length' is the anticipated processed size.
68
// On success the raw buffer is returned with its length equivalent to
69
// 'expected_length'. NULL is returned if the processed length is less than
70
// 'expected_length' or any character aside from those above is encountered.
71
// The returned buffer must be freed by the caller.
72
static uint8_t* HexStringToBytes(const char* hexstring,
73
0
                                 size_t expected_length) {
74
0
  const char* src = hexstring;
75
0
  size_t actual_length = 0;
76
0
  uint8_t* const raw_data = (uint8_t*)malloc(expected_length);
77
0
  uint8_t* dst;
78
79
0
  if (raw_data == NULL) return NULL;
80
81
0
  for (dst = raw_data; actual_length < expected_length && *src != '\0'; ++src) {
82
0
    char* end;
83
0
    char val[3];
84
0
    if (*src == '\n') continue;
85
0
    val[0] = *src++;
86
0
    val[1] = *src;
87
0
    val[2] = '\0';
88
0
    *dst++ = (uint8_t)strtol(val, &end, 16);
89
0
    if (end != val + 2) break;
90
0
    ++actual_length;
91
0
  }
92
93
0
  if (actual_length != expected_length) {
94
0
    free(raw_data);
95
0
    return NULL;
96
0
  }
97
0
  return raw_data;
98
0
}
99
100
static int ProcessRawProfile(const char* profile, size_t profile_len,
101
0
                             MetadataPayload* const payload) {
102
0
  const char* src = profile;
103
0
  char* end;
104
0
  long expected_length;
105
0
  size_t name_and_length_size, payload_size;
106
107
0
  if (profile == NULL || profile_len == 0) return 0;
108
109
  // ImageMagick formats 'raw profiles' as
110
  // '\n<name>\n<length>(%8lu)\n<hex payload>\n'.
111
0
  if (*src != '\n') {
112
0
    fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n",
113
0
            *src);
114
0
    return 0;
115
0
  }
116
0
  ++src;
117
  // skip the profile name and extract the length.
118
0
  while (*src != '\0' && *src++ != '\n') {
119
0
  }
120
  // libpng guarantees 'profile' ends with '\0' so the loop above and strtol()
121
  // below cannot read outside of the buffer.
122
0
  expected_length = strtol(src, &end, 10);
123
0
  if (*end != '\n') {
124
0
    fprintf(stderr, "Malformed raw profile, expected '\\n' got '\\x%.2X'\n",
125
0
            *end);
126
0
    return 0;
127
0
  }
128
0
  ++end;
129
0
  if (expected_length <= 0 || expected_length > INT_MAX ||
130
0
      (size_t)expected_length > profile_len / 2) {
131
0
    fprintf(stderr, "Malformed raw profile, invalid length %ld\n",
132
0
            expected_length);
133
0
    return 0;
134
0
  }
135
136
  // 'end' now points to the profile payload.
137
0
  name_and_length_size = (size_t)(end - profile);
138
0
  assert(name_and_length_size <= profile_len);
139
0
  payload_size = profile_len - name_and_length_size;
140
0
  if ((size_t)expected_length * 2 - 1 > payload_size) {
141
0
    fprintf(stderr,
142
0
            "Truncated raw profile, expected at least %zu hex chars, got %zu\n",
143
0
            (size_t)expected_length * 2 - 1, payload_size);
144
0
    return 0;
145
0
  }
146
0
  payload->bytes = HexStringToBytes(end, expected_length);
147
0
  if (payload->bytes == NULL) return 0;
148
0
  payload->size = expected_length;
149
0
  return 1;
150
0
}
151
152
static const struct {
153
  const char* name;
154
  int (*process)(const char* profile, size_t profile_len,
155
                 MetadataPayload* const payload);
156
  size_t storage_offset;
157
} kPNGMetadataMap[] = {
158
    // https://exiftool.org/TagNames/PNG.html#TextualData
159
    // See also: ExifTool on CPAN.
160
    {"Raw profile type exif", ProcessRawProfile, METADATA_OFFSET(exif)},
161
    {"Raw profile type xmp", ProcessRawProfile, METADATA_OFFSET(xmp)},
162
    // Exiftool puts exif data in APP1 chunk, too.
163
    {"Raw profile type APP1", ProcessRawProfile, METADATA_OFFSET(exif)},
164
    // ImageMagick uses lowercase app1.
165
    {"Raw profile type app1", ProcessRawProfile, METADATA_OFFSET(exif)},
166
    // XMP Specification Part 3, Section 3 #PNG
167
    {"XML:com.adobe.xmp", MetadataCopy, METADATA_OFFSET(xmp)},
168
    {NULL, NULL, 0},
169
};
170
171
// Looks for metadata at both the beginning and end of the PNG file, giving
172
// preference to the head.
173
// Returns true on success. The caller must use MetadataFree() on 'metadata' in
174
// all cases.
175
static int ExtractMetadataFromPNG(png_structp png, png_infop const head_info,
176
                                  png_infop const end_info,
177
0
                                  Metadata* const metadata) {
178
0
  int p;
179
180
0
  for (p = 0; p < 2; ++p) {
181
0
    png_infop const info = (p == 0) ? head_info : end_info;
182
0
    png_textp text = NULL;
183
0
    const png_uint_32 num = png_get_text(png, info, &text, NULL);
184
0
    png_uint_32 i;
185
186
0
#ifdef PNG_eXIf_SUPPORTED
187
    // Look for an 'eXIf' tag. Preference is given to this tag as it's newer
188
    // than the TextualData tags.
189
0
    {
190
0
      png_bytep exif;
191
0
      png_uint_32 len;
192
193
0
      if (png_get_eXIf_1(png, info, &len, &exif) == PNG_INFO_eXIf) {
194
0
        if (metadata->exif.bytes != NULL) {
195
0
          fprintf(stderr, "Ignoring additional eXIf\n");
196
0
        } else if (!MetadataCopy((const char*)exif, len, &metadata->exif)) {
197
0
          return 0;
198
0
        }
199
0
      }
200
0
    }
201
0
#endif  // PNG_eXIf_SUPPORTED
202
203
    // Look for EXIF / XMP metadata.
204
0
    for (i = 0; i < num; ++i, ++text) {
205
0
      int j;
206
0
      for (j = 0; kPNGMetadataMap[j].name != NULL; ++j) {
207
0
        if (!strcmp(text->key, kPNGMetadataMap[j].name)) {
208
0
          MetadataPayload* const payload =
209
0
              (MetadataPayload*)((uint8_t*)metadata +
210
0
                                 kPNGMetadataMap[j].storage_offset);
211
0
          png_size_t text_length;
212
0
          switch (text->compression) {
213
0
#ifdef PNG_iTXt_SUPPORTED
214
0
            case PNG_ITXT_COMPRESSION_NONE:
215
0
            case PNG_ITXT_COMPRESSION_zTXt:
216
0
              text_length = text->itxt_length;
217
0
              break;
218
0
#endif
219
0
            case PNG_TEXT_COMPRESSION_NONE:
220
0
            case PNG_TEXT_COMPRESSION_zTXt:
221
0
            default:
222
0
              text_length = text->text_length;
223
0
              break;
224
0
          }
225
0
          if (payload->bytes != NULL) {
226
0
            fprintf(stderr, "Ignoring additional '%s'\n", text->key);
227
0
          } else if (!kPNGMetadataMap[j].process(text->text, text_length,
228
0
                                                 payload)) {
229
0
            fprintf(stderr, "Failed to process: '%s'\n", text->key);
230
0
            return 0;
231
0
          }
232
0
          break;
233
0
        }
234
0
      }
235
0
    }
236
0
#ifdef PNG_iCCP_SUPPORTED
237
    // Look for an ICC profile.
238
0
    {
239
0
      png_charp name;
240
0
      int comp_type;
241
0
#if LOCAL_PNG_PREREQ(1, 5)
242
0
      png_bytep profile;
243
#else
244
      png_charp profile;
245
#endif
246
0
      png_uint_32 len;
247
248
0
      if (png_get_iCCP(png, info, &name, &comp_type, &profile, &len) ==
249
0
          PNG_INFO_iCCP) {
250
0
        if (metadata->iccp.bytes != NULL) {
251
0
          fprintf(stderr, "Ignoring additional iCCP\n");
252
0
        } else if (!MetadataCopy((const char*)profile, len, &metadata->iccp)) {
253
0
          return 0;
254
0
        }
255
0
      }
256
0
    }
257
0
#endif  // PNG_iCCP_SUPPORTED
258
0
  }
259
0
  return 1;
260
0
}
261
262
typedef struct {
263
  const uint8_t* data;
264
  size_t data_size;
265
  png_size_t offset;
266
} PNGReadContext;
267
268
3.35k
static void ReadFunc(png_structp png_ptr, png_bytep data, png_size_t length) {
269
3.35k
  PNGReadContext* const ctx = (PNGReadContext*)png_get_io_ptr(png_ptr);
270
3.35k
  if (ctx->data_size - ctx->offset < length) {
271
87
    png_error(png_ptr, "ReadFunc: invalid read length (overflow)!");
272
87
  }
273
3.26k
  memcpy(data, ctx->data + ctx->offset, length);
274
3.26k
  ctx->offset += length;
275
3.26k
}
276
277
static void PNGAPI error_function_silent(png_structp png,
278
84
                                         png_const_charp error) {
279
84
  (void)error;
280
84
  longjmp(png_jmpbuf(png), 1);
281
84
}
282
283
static void PNGAPI warning_function_silent(png_structp png,
284
2
                                           png_const_charp warning) {
285
2
  (void)png;
286
2
  (void)warning;
287
2
}
288
289
static int ReadPNGInternal(const uint8_t* const data, size_t data_size,
290
                           struct WebPPicture* const pic, int keep_alpha,
291
                           struct Metadata* const metadata,
292
9.31k
                           int* const width_out, int* const height_out) {
293
9.31k
  volatile png_structp png = NULL;
294
9.31k
  volatile png_infop info = NULL;
295
9.31k
  volatile png_infop end_info = NULL;
296
9.31k
  PNGReadContext context = {NULL, 0, 0};
297
9.31k
  int color_type, bit_depth, interlaced;
298
9.31k
  int num_channels;
299
9.31k
  int num_passes;
300
9.31k
  int p;
301
9.31k
  volatile int ok = 0;
302
9.31k
  png_uint_32 width, height, y;
303
9.31k
  int64_t stride;
304
9.31k
  uint8_t* volatile rgb = NULL;
305
306
9.31k
  if (data == NULL || data_size < 8 ||
307
9.18k
      png_sig_cmp((png_bytep)data, 0, 8) != 0) {
308
9.18k
    return 0;
309
9.18k
  }
310
134
  if (pic == NULL && (width_out == NULL || height_out == NULL)) return 0;
311
312
134
  context.data = data;
313
134
  context.data_size = data_size;
314
315
134
  png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL,
316
134
                                 MallocFunc, FreeFunc);
317
134
  if (png == NULL) goto End;
318
319
130
  if (pic == NULL) {
320
84
    png_set_error_fn(png, 0, error_function_silent, warning_function_silent);
321
84
  } else {
322
46
    png_set_error_fn(png, 0, error_function, NULL);
323
46
  }
324
130
  if (setjmp(png_jmpbuf(png))) {
325
130
  Error:
326
130
    MetadataFree(metadata);
327
130
    goto End;
328
125
  }
329
330
5
#if LOCAL_PNG_PREREQ(1, 5) || \
331
5
    (LOCAL_PNG_PREREQ(1, 4) && PNG_LIBPNG_VER_RELEASE >= 1)
332
  // If it looks like the bitstream is going to need more memory than libpng's
333
  // internal limit (default: 8M), try to (reasonably) raise it.
334
46
  if (pic != NULL && data_size > png_get_chunk_malloc_max(png) &&
335
0
      data_size < (1u << 24)) {
336
0
    png_set_chunk_malloc_max(png, data_size);
337
0
  }
338
5
#endif
339
340
5
  info = png_create_info_struct(png);
341
5
  if (info == NULL) goto Error;
342
45
  if (pic != NULL) {
343
45
    end_info = png_create_info_struct(png);
344
45
    if (end_info == NULL) goto Error;
345
45
  }
346
347
0
  png_set_read_fn(png, &context, ReadFunc);
348
0
  png_read_info(png, info);
349
0
  if (!png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type,
350
0
                    &interlaced, NULL, NULL))
351
0
    goto Error;
352
353
0
  if (width_out != NULL) *width_out = (int)width;
354
0
  if (height_out != NULL) *height_out = (int)height;
355
0
  if (pic == NULL) {
356
0
    ok = 1;
357
0
    goto End;
358
0
  }
359
360
0
  png_set_strip_16(png);
361
0
  png_set_packing(png);
362
0
  if (color_type == PNG_COLOR_TYPE_PALETTE) {
363
0
    png_set_palette_to_rgb(png);
364
0
  }
365
0
  if (color_type == PNG_COLOR_TYPE_GRAY ||
366
0
      color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
367
0
    if (bit_depth < 8) {
368
0
      png_set_expand_gray_1_2_4_to_8(png);
369
0
    }
370
0
    png_set_gray_to_rgb(png);
371
0
  }
372
0
  if (png_get_valid(png, info, PNG_INFO_tRNS)) {
373
0
    png_set_tRNS_to_alpha(png);
374
0
  }
375
376
  // Apply gamma correction if needed.
377
0
  {
378
0
    double image_gamma = 1 / 2.2, screen_gamma = 2.2;
379
0
    int srgb_intent;
380
0
    if (png_get_sRGB(png, info, &srgb_intent) ||
381
0
        png_get_gAMA(png, info, &image_gamma)) {
382
0
      png_set_gamma(png, screen_gamma, image_gamma);
383
0
    }
384
0
  }
385
386
0
  if (!keep_alpha) {
387
0
    png_set_strip_alpha(png);
388
0
  }
389
390
0
  num_passes = png_set_interlace_handling(png);
391
0
  png_read_update_info(png, info);
392
393
0
  num_channels = png_get_channels(png, info);
394
0
  if (num_channels != 3 && num_channels != 4) {
395
0
    goto Error;
396
0
  }
397
0
  stride = (int64_t)num_channels * width * sizeof(*rgb);
398
0
  if (stride != (int)stride ||
399
0
      !ImgIoUtilCheckSizeArgumentsOverflow(stride, height)) {
400
0
    goto Error;
401
0
  }
402
403
0
  rgb = (uint8_t*)malloc((size_t)stride * height);
404
0
  if (rgb == NULL) goto Error;
405
0
  for (p = 0; p < num_passes; ++p) {
406
0
    png_bytep row = rgb;
407
0
    for (y = 0; y < height; ++y) {
408
0
      png_read_rows(png, &row, NULL, 1);
409
0
      row += stride;
410
0
    }
411
0
  }
412
0
  png_read_end(png, end_info);
413
414
0
  if (metadata != NULL &&
415
0
      !ExtractMetadataFromPNG(png, info, end_info, metadata)) {
416
0
    fprintf(stderr, "Error extracting PNG metadata!\n");
417
0
    goto Error;
418
0
  }
419
420
0
  pic->width = (int)width;
421
0
  pic->height = (int)height;
422
0
  ok = (num_channels == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
423
0
                           : WebPPictureImportRGB(pic, rgb, (int)stride);
424
425
0
  if (!ok) {
426
0
    goto Error;
427
0
  }
428
429
134
End:
430
134
  if (png != NULL) {
431
130
    png_destroy_read_struct((png_structpp)&png, (png_infopp)&info,
432
130
                            (png_infopp)&end_info);
433
130
  }
434
134
  free(rgb);
435
134
  return ok;
436
0
}
437
438
int ReadPNG(const uint8_t* const data, size_t data_size,
439
            struct WebPPicture* const pic, int keep_alpha,
440
377
            struct Metadata* const metadata) {
441
377
  if (pic == NULL) return 0;
442
377
  return ReadPNGInternal(data, data_size, pic, keep_alpha, metadata, NULL,
443
377
                         NULL);
444
377
}
445
446
int ReadPngDimensions(const uint8_t* const data, size_t data_size,
447
8.94k
                      int* const width, int* const height) {
448
8.94k
  return ReadPNGInternal(data, data_size, NULL, 0, NULL, width, height);
449
8.94k
}
450
#else   // !WEBP_HAVE_PNG
451
int ReadPNG(const uint8_t* const data, size_t data_size,
452
            struct WebPPicture* const pic, int keep_alpha,
453
            struct Metadata* const metadata) {
454
  (void)data;
455
  (void)data_size;
456
  (void)pic;
457
  (void)keep_alpha;
458
  (void)metadata;
459
  fprintf(stderr,
460
          "PNG support not compiled. Please install the libpng "
461
          "development package before building.\n");
462
  return 0;
463
}
464
465
int ReadPngDimensions(const uint8_t* const data, size_t data_size,
466
                      int* const width, int* const height) {
467
  (void)data;
468
  (void)data_size;
469
  (void)width;
470
  (void)height;
471
  return 0;
472
}
473
#endif  // WEBP_HAVE_PNG
474
475
// -----------------------------------------------------------------------------