Coverage Report

Created: 2026-09-06 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/flac/src/share/grabbag/picture.c
Line
Count
Source
1
/* grabbag - Convenience lib for various routines common to several tools
2
 * Copyright (C) 2006-2009  Josh Coalson
3
 * Copyright (C) 2011-2025  Xiph.Org Foundation
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
 */
19
20
#ifdef HAVE_CONFIG_H
21
#  include <config.h>
22
#endif
23
24
#include "share/alloc.h"
25
#include "share/grabbag.h"
26
#include "FLAC/assert.h"
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
#include "share/compat.h"
31
#include "share/safe_str.h"
32
33
/* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
34
static char *local__strndup_(const char *s, size_t size)
35
8.09k
{
36
8.09k
  char *x = safe_malloc_add_2op_(size, /*+*/1);
37
8.09k
  if(x) {
38
7.87k
    memcpy(x, s, size);
39
7.87k
    x[size] = '\0';
40
7.87k
  }
41
8.09k
  return x;
42
8.09k
}
43
44
static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
45
5.94k
{
46
5.94k
  size_t i;
47
5.94k
  FLAC__uint32 val = 0;
48
49
5.94k
  picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
50
51
5.94k
  if(len == 0)
52
3.97k
    return true; /* empty string implies default to 'front cover' */
53
54
6.90k
  for(i = 0; i < len; i++) {
55
5.15k
    if(s[i] >= '0' && s[i] <= '9')
56
4.93k
      val = 10*val + (FLAC__uint32)(s[i] - '0');
57
218
    else
58
218
      return false;
59
5.15k
  }
60
61
1.75k
  if(i == len)
62
1.75k
    picture->type = val;
63
0
  else
64
0
    return false;
65
66
1.75k
  return true;
67
1.75k
}
68
69
static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
70
4.93k
{
71
4.93k
  int state = 0;
72
4.93k
  size_t i;
73
4.93k
  FLAC__uint32 val = 0;
74
75
4.93k
  picture->width = picture->height = picture->depth = picture->colors = 0;
76
77
4.93k
  if(len == 0)
78
1.70k
    return true; /* empty string implies client wants to get info from the file itself */
79
80
28.6k
  for(i = 0; i < len; i++) {
81
25.7k
    if(s[i] == 'x') {
82
5.94k
      if(state == 0)
83
2.97k
        picture->width = val;
84
2.96k
      else if(state == 1)
85
2.89k
        picture->height = val;
86
70
      else
87
70
        return false;
88
5.87k
      state++;
89
5.87k
      val = 0;
90
5.87k
    }
91
19.8k
    else if(s[i] == '/') {
92
347
      if(state == 2)
93
246
        picture->depth = val;
94
101
      else
95
101
        return false;
96
246
      state++;
97
246
      val = 0;
98
246
    }
99
19.4k
    else if(s[i] >= '0' && s[i] <= '9')
100
19.2k
      val = 10*val + (FLAC__uint32)(s[i] - '0');
101
198
    else
102
198
      return false;
103
25.7k
  }
104
105
2.86k
  if(state < 2)
106
89
    return false;
107
2.77k
  else if(state == 2)
108
2.56k
    picture->depth = val;
109
206
  else if(state == 3)
110
206
    picture->colors = val;
111
0
  else
112
0
    return false;
113
2.77k
  if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
114
143
    return false;
115
116
2.62k
  return true;
117
2.77k
}
118
119
static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
120
402
{
121
402
  if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
122
109
    return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
123
293
  else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
124
7
    return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
125
286
  else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
126
134
    return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
127
152
  return false;
128
402
}
129
130
static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
131
874
{
132
874
  const FLAC__byte *data = picture->data;
133
874
  FLAC__uint32 len = picture->data_length;
134
135
874
  if(0 == strcmp(picture->mime_type, "image/png")) {
136
    /* c.f. http://www.w3.org/TR/PNG/ */
137
146
    FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
138
146
    if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
139
37
      return false;
140
    /* try to find IHDR chunk */
141
109
    data += 8;
142
109
    len -= 8;
143
618
    while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
144
584
      const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
145
      /* First check whether clen makes sense or causes overflow in this bit of code */
146
584
      if(clen + 12 <= clen || clen + 12 > len)
147
63
        return false;
148
521
      else if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
149
86
        uint32_t color_type = data[17];
150
86
        picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
151
86
        picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
152
86
        if(color_type == 3) {
153
          /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
154
           * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
155
           * sample depth is always 8
156
           */
157
75
          picture->depth = 8 * 3u;
158
75
          need_palette = true;
159
75
          data += 12 + clen;
160
75
          len -= 12 + clen;
161
75
        }
162
11
        else {
163
11
          if(color_type == 0) /* greyscale, 1 sample per pixel */
164
1
            picture->depth = (FLAC__uint32)data[16];
165
11
          if(color_type == 2) /* truecolor, 3 samples per pixel */
166
1
            picture->depth = (FLAC__uint32)data[16] * 3u;
167
11
          if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
168
1
            picture->depth = (FLAC__uint32)data[16] * 2u;
169
11
          if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
170
1
            picture->depth = (FLAC__uint32)data[16] * 4u;
171
11
          picture->colors = 0;
172
11
          return true;
173
11
        }
174
86
      }
175
435
      else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
176
1
        picture->colors = clen / 3u;
177
1
        return true;
178
1
      }
179
434
      else {
180
434
        data += 12 + clen;
181
434
        len -= 12 + clen;
182
434
      }
183
584
    }
184
109
  }
185
728
  else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
186
    /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
187
154
    if(len < 2 || memcmp(data, "\xff\xd8", 2))
188
21
      return false;
189
133
    data += 2;
190
133
    len -= 2;
191
1.77k
    while(1) {
192
      /* look for sync FF byte */
193
118k
      for( ; len > 0; data++, len--) {
194
118k
        if(*data == 0xff)
195
1.74k
          break;
196
118k
      }
197
1.77k
      if(len == 0)
198
30
        return false;
199
      /* eat any extra pad FF bytes before marker */
200
3.95k
      for( ; len > 0; data++, len--) {
201
3.95k
        if(*data != 0xff)
202
1.74k
          break;
203
3.95k
      }
204
1.74k
      if(len == 0)
205
4
        return false;
206
      /* if we hit SOS or EOI, bail */
207
1.74k
      if(*data == 0xda || *data == 0xd9)
208
3
        return false;
209
      /* looking for some SOFn */
210
1.73k
      else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
211
23
        data++; len--; /* skip marker byte */
212
23
        if(len < 2)
213
3
          return false;
214
20
        else {
215
20
          const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
216
20
          if(clen < 8 || len < clen)
217
16
            return false;
218
4
          picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
219
4
          picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
220
4
          picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
221
4
          picture->colors = 0;
222
4
          return true;
223
20
        }
224
23
      }
225
      /* else skip it */
226
1.71k
      else {
227
1.71k
        data++; len--; /* skip marker byte */
228
1.71k
        if(len < 2)
229
23
          return false;
230
1.69k
        else {
231
1.69k
          const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
232
1.69k
          if(clen < 2 || len < clen)
233
50
            return false;
234
1.64k
          data += clen;
235
1.64k
          len -= clen;
236
1.64k
        }
237
1.71k
      }
238
1.74k
    }
239
133
  }
240
574
  else if(0 == strcmp(picture->mime_type, "image/gif")) {
241
    /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
242
44
    if(len < 14)
243
3
      return false;
244
41
    if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
245
37
      return false;
246
#if 0
247
    /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
248
    if(data[10] & 0x80 == 0)
249
      return false;
250
#endif
251
4
    picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
252
4
    picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
253
#if 0
254
    /* this value doesn't seem to be reliable... */
255
    picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
256
#else
257
    /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
258
4
    picture->depth = 8u * 3u;
259
4
#endif
260
4
    picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
261
4
    return true;
262
41
  }
263
564
  return false;
264
874
}
265
266
static const char *error_messages[] = {
267
  "memory allocation error",
268
  "invalid picture specification",
269
  "invalid picture specification: can't parse resolution/color part",
270
  "unable to extract resolution and color info from URL, user must set explicitly",
271
  "unable to extract resolution and color info from file, user must set explicitly",
272
  "error opening picture file",
273
  "error reading picture file",
274
  "invalid picture type",
275
  "unable to guess MIME type from file, user must set explicitly",
276
  "type 1 icon must be a 32x32 pixel PNG",
277
  "file not found", /* currently unused */
278
  "file is too large",
279
  "empty file"
280
};
281
282
static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj)
283
2.35k
{
284
2.35k
  const FLAC__off_t size = grabbag__file_get_filesize(filepath);
285
2.35k
  FLAC__byte *buffer;
286
2.35k
  FILE *file;
287
2.35k
  const char *error_message=NULL;
288
289
2.35k
  if (size < 0)
290
464
    return error_messages[5];
291
292
1.89k
  if (size == 0)
293
67
    return error_messages[12];
294
295
1.82k
  if (size >= (FLAC__off_t)(1u << FLAC__STREAM_METADATA_LENGTH_LEN)) /* actual limit is less because of other fields in the PICTURE metadata block */
296
0
    return error_messages[11];
297
298
1.82k
  if ((buffer = safe_malloc_(size)) == NULL)
299
84
    return error_messages[0];
300
301
1.73k
  if ((file = flac_fopen(filepath, "rb")) == NULL) {
302
0
    free(buffer);
303
0
    return error_messages[5];
304
0
  }
305
306
1.73k
  if (fread(buffer, 1, size, file) != (size_t) size) {
307
207
    fclose(file);
308
207
    free(buffer);
309
207
    return error_messages[6];
310
207
  }
311
1.53k
  fclose(file);
312
313
1.53k
  if (!FLAC__metadata_object_picture_set_data(obj, buffer, (FLAC__uint32)size, /*copy=*/false))
314
0
    error_message = error_messages[6];
315
  /* try to extract MIME type if user left it blank */
316
1.53k
  else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
317
153
    error_message = error_messages[8];
318
  /* try to extract resolution/color info if user left it blank */
319
1.37k
  else if ((obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) && !local__extract_resolution_color_info_(&obj->data.picture))
320
854
    error_message = error_messages[4];
321
  /* check metadata block size */
322
525
  else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN))
323
0
    error_message = error_messages[11];
324
325
1.53k
  return error_message;
326
1.73k
}
327
328
FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
329
5.94k
{
330
5.94k
  FLAC__StreamMetadata *obj;
331
5.94k
  int state = 0;
332
333
5.94k
  FLAC__ASSERT(0 != spec);
334
5.94k
  FLAC__ASSERT(0 != error_message);
335
336
  /* double protection */
337
5.94k
  if(0 == spec)
338
0
    return 0;
339
5.94k
  if(0 == error_message)
340
0
    return 0;
341
342
5.94k
  *error_message = 0;
343
344
5.94k
  if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
345
0
    *error_message = error_messages[0];
346
0
    return obj;
347
0
  }
348
349
5.94k
  if(strchr(spec, '|')) { /* full format */
350
5.10k
    const char *p;
351
5.10k
    char *q;
352
9.02M
    for(p = spec; *error_message==0 && *p; ) {
353
9.02M
      if(*p == '|') {
354
18.5k
        switch(state) {
355
5.10k
          case 0: /* type */
356
5.10k
            if(!local__parse_type_(spec, p-spec, &obj->data.picture))
357
218
              *error_message = error_messages[7];
358
5.10k
            break;
359
4.80k
          case 1: /* mime type */
360
4.80k
            if(p-spec) { /* if blank, we'll try to guess later from the picture data */
361
3.69k
              if(0 == (q = local__strndup_(spec, p-spec)))
362
123
                *error_message = error_messages[0];
363
3.57k
              else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
364
0
                *error_message = error_messages[0];
365
3.69k
            }
366
4.80k
            break;
367
4.39k
          case 2: /* description */
368
4.39k
            if(0 == (q = local__strndup_(spec, p-spec)))
369
99
              *error_message = error_messages[0];
370
4.29k
            else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
371
0
              *error_message = error_messages[0];
372
4.39k
            break;
373
4.09k
          case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
374
4.09k
            if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
375
601
              *error_message = error_messages[2];
376
4.09k
            break;
377
120
          default:
378
120
            *error_message = error_messages[1];
379
120
            break;
380
18.5k
        }
381
18.5k
        p++;
382
18.5k
        spec = p;
383
18.5k
        state++;
384
18.5k
      }
385
9.00M
      else
386
9.00M
        p++;
387
9.02M
    }
388
5.10k
  }
389
839
  else { /* simple format, filename only, everything else guessed */
390
839
    if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
391
0
      *error_message = error_messages[7];
392
    /* leave MIME type to be filled in later */
393
    /* leave description empty */
394
    /* leave the rest to be filled in later: */
395
839
    else if(!local__parse_resolution_("", 0, &obj->data.picture))
396
0
      *error_message = error_messages[2];
397
839
    else
398
839
      state = 4;
399
839
  }
400
401
  /* parse filename, read file, try to extract resolution/color info if needed */
402
5.94k
  if(*error_message == 0) {
403
4.78k
    if(state != 4)
404
568
      *error_message = error_messages[1];
405
4.21k
    else { /* 'spec' points to filename/URL */
406
4.21k
      if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
407
1.85k
        if(strlen(spec) == 0)
408
44
          *error_message = error_messages[1];
409
1.81k
        else if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
410
9
          *error_message = error_messages[0];
411
1.80k
        else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
412
512
          *error_message = error_messages[3];
413
1.85k
      }
414
2.35k
      else { /* regular picture file */
415
2.35k
        *error_message = read_file (spec, obj);
416
2.35k
      }
417
4.21k
    }
418
4.78k
  }
419
420
5.94k
  if(*error_message == 0) {
421
1.81k
    if(
422
1.81k
      obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
423
717
      (
424
717
        (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
425
427
        obj->data.picture.width != 32 ||
426
261
        obj->data.picture.height != 32
427
717
      )
428
1.81k
    )
429
575
      *error_message = error_messages[9];
430
1.81k
  }
431
432
5.94k
  if(*error_message && obj) {
433
4.69k
    FLAC__metadata_object_delete(obj);
434
4.69k
    obj = 0;
435
4.69k
  }
436
437
5.94k
  return obj;
438
5.94k
}
439
440
FLAC__StreamMetadata *grabbag__picture_from_specification(int type, const char *mime_type_in, const char * description,
441
    const PictureResolution * res, const char * filepath, const char **error_message)
442
0
{
443
444
0
  FLAC__StreamMetadata *obj;
445
0
  char mime_type [64] ;
446
447
0
  if (error_message == 0)
448
0
    return 0;
449
450
0
  safe_strncpy(mime_type, mime_type_in, sizeof (mime_type));
451
452
0
  *error_message = 0;
453
454
0
  if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) {
455
0
    *error_message = error_messages[0];
456
0
    return obj;
457
0
  }
458
459
  /* Picture type if known. */
460
0
  obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
461
462
  /* Mime type if known. */
463
0
  if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, /*copy=*/true)) {
464
0
    *error_message = error_messages[0];
465
0
    return obj;
466
0
  }
467
468
  /* Description if present. */
469
0
  if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, /*copy=*/true)) {
470
0
    *error_message = error_messages[0];
471
0
    return obj;
472
0
  }
473
474
0
  if (res == NULL) {
475
0
    obj->data.picture.width = 0;
476
0
    obj->data.picture.height = 0;
477
0
    obj->data.picture.depth = 0;
478
0
    obj->data.picture.colors = 0;
479
0
  }
480
0
  else {
481
0
    obj->data.picture.width = res->width;
482
0
    obj->data.picture.height = res->height;
483
0
    obj->data.picture.depth = res->depth;
484
0
    obj->data.picture.colors = res->colors;
485
0
  }
486
487
0
  if (strcmp(obj->data.picture.mime_type, "-->") == 0) { /* magic MIME type means URL */
488
0
    if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), /*copy=*/true))
489
0
      *error_message = error_messages[0];
490
0
    else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
491
0
      *error_message = error_messages[3];
492
0
  }
493
0
  else {
494
0
    *error_message = read_file (filepath, obj);
495
0
  }
496
497
0
  if (*error_message == NULL) {
498
0
    if (
499
0
      obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
500
0
      (
501
0
        (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
502
0
        obj->data.picture.width != 32 ||
503
0
        obj->data.picture.height != 32
504
0
      )
505
0
    )
506
0
      *error_message = error_messages[9];
507
0
  }
508
509
0
  if (*error_message && obj) {
510
0
    FLAC__metadata_object_delete(obj);
511
0
    obj = 0;
512
0
  }
513
514
0
  return obj;
515
0
}