Coverage Report

Created: 2025-10-10 06:21

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