Coverage Report

Created: 2025-07-12 06:31

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