1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006-2009 Josh Coalson
3 * Copyright (C) 2011-2023 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. */
local__strndup_(const char * s,size_t size)34 static char *local__strndup_(const char *s, size_t size)
35 {
36 char *x = safe_malloc_add_2op_(size, /*+*/1);
37 if(x) {
38 memcpy(x, s, size);
39 x[size] = '\0';
40 }
41 return x;
42 }
43
local__parse_type_(const char * s,size_t len,FLAC__StreamMetadata_Picture * picture)44 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
45 {
46 size_t i;
47 FLAC__uint32 val = 0;
48
49 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
50
51 if(len == 0)
52 return true; /* empty string implies default to 'front cover' */
53
54 for(i = 0; i < len; i++) {
55 if(s[i] >= '0' && s[i] <= '9')
56 val = 10*val + (FLAC__uint32)(s[i] - '0');
57 else
58 return false;
59 }
60
61 if(i == len)
62 picture->type = val;
63 else
64 return false;
65
66 return true;
67 }
68
local__parse_resolution_(const char * s,size_t len,FLAC__StreamMetadata_Picture * picture)69 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
70 {
71 int state = 0;
72 size_t i;
73 FLAC__uint32 val = 0;
74
75 picture->width = picture->height = picture->depth = picture->colors = 0;
76
77 if(len == 0)
78 return true; /* empty string implies client wants to get info from the file itself */
79
80 for(i = 0; i < len; i++) {
81 if(s[i] == 'x') {
82 if(state == 0)
83 picture->width = val;
84 else if(state == 1)
85 picture->height = val;
86 else
87 return false;
88 state++;
89 val = 0;
90 }
91 else if(s[i] == '/') {
92 if(state == 2)
93 picture->depth = val;
94 else
95 return false;
96 state++;
97 val = 0;
98 }
99 else if(s[i] >= '0' && s[i] <= '9')
100 val = 10*val + (FLAC__uint32)(s[i] - '0');
101 else
102 return false;
103 }
104
105 if(state < 2)
106 return false;
107 else if(state == 2)
108 picture->depth = val;
109 else if(state == 3)
110 picture->colors = val;
111 else
112 return false;
113 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
114 return false;
115
116 return true;
117 }
118
local__extract_mime_type_(FLAC__StreamMetadata * obj)119 static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
120 {
121 if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
122 return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
123 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 return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
125 else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
126 return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
127 return false;
128 }
129
local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture * picture)130 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
131 {
132 const FLAC__byte *data = picture->data;
133 FLAC__uint32 len = picture->data_length;
134
135 if(0 == strcmp(picture->mime_type, "image/png")) {
136 /* c.f. http://www.w3.org/TR/PNG/ */
137 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
138 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
139 return false;
140 /* try to find IHDR chunk */
141 data += 8;
142 len -= 8;
143 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
144 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 if(clen + 12 <= clen || clen + 12 > len)
147 return false;
148 else if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
149 uint32_t color_type = data[17];
150 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
151 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
152 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 picture->depth = 8 * 3u;
158 need_palette = true;
159 data += 12 + clen;
160 len -= 12 + clen;
161 }
162 else {
163 if(color_type == 0) /* greyscale, 1 sample per pixel */
164 picture->depth = (FLAC__uint32)data[16];
165 if(color_type == 2) /* truecolor, 3 samples per pixel */
166 picture->depth = (FLAC__uint32)data[16] * 3u;
167 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
168 picture->depth = (FLAC__uint32)data[16] * 2u;
169 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
170 picture->depth = (FLAC__uint32)data[16] * 4u;
171 picture->colors = 0;
172 return true;
173 }
174 }
175 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
176 picture->colors = clen / 3u;
177 return true;
178 }
179 else {
180 data += 12 + clen;
181 len -= 12 + clen;
182 }
183 }
184 }
185 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 if(len < 2 || memcmp(data, "\xff\xd8", 2))
188 return false;
189 data += 2;
190 len -= 2;
191 while(1) {
192 /* look for sync FF byte */
193 for( ; len > 0; data++, len--) {
194 if(*data == 0xff)
195 break;
196 }
197 if(len == 0)
198 return false;
199 /* eat any extra pad FF bytes before marker */
200 for( ; len > 0; data++, len--) {
201 if(*data != 0xff)
202 break;
203 }
204 if(len == 0)
205 return false;
206 /* if we hit SOS or EOI, bail */
207 if(*data == 0xda || *data == 0xd9)
208 return false;
209 /* looking for some SOFn */
210 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
211 data++; len--; /* skip marker byte */
212 if(len < 2)
213 return false;
214 else {
215 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
216 if(clen < 8 || len < clen)
217 return false;
218 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
219 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
220 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
221 picture->colors = 0;
222 return true;
223 }
224 }
225 /* else skip it */
226 else {
227 data++; len--; /* skip marker byte */
228 if(len < 2)
229 return false;
230 else {
231 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
232 if(clen < 2 || len < clen)
233 return false;
234 data += clen;
235 len -= clen;
236 }
237 }
238 }
239 }
240 else if(0 == strcmp(picture->mime_type, "image/gif")) {
241 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
242 if(len < 14)
243 return false;
244 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
245 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 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
252 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 picture->depth = 8u * 3u;
259 #endif
260 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
261 return true;
262 }
263 return false;
264 }
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
read_file(const char * filepath,FLAC__StreamMetadata * obj)282 static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj)
283 {
284 const FLAC__off_t size = grabbag__file_get_filesize(filepath);
285 FLAC__byte *buffer;
286 FILE *file;
287 const char *error_message=NULL;
288
289 if (size < 0)
290 return error_messages[5];
291
292 if (size == 0)
293 return error_messages[12];
294
295 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 return error_messages[11];
297
298 if ((buffer = safe_malloc_(size)) == NULL)
299 return error_messages[0];
300
301 if ((file = flac_fopen(filepath, "rb")) == NULL) {
302 free(buffer);
303 return error_messages[5];
304 }
305
306 if (fread(buffer, 1, size, file) != (size_t) size) {
307 fclose(file);
308 free(buffer);
309 return error_messages[6];
310 }
311 fclose(file);
312
313 if (!FLAC__metadata_object_picture_set_data(obj, buffer, (FLAC__uint32)size, /*copy=*/false))
314 error_message = error_messages[6];
315 /* try to extract MIME type if user left it blank */
316 else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
317 error_message = error_messages[8];
318 /* try to extract resolution/color info if user left it blank */
319 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 error_message = error_messages[4];
321 /* check metadata block size */
322 else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN))
323 error_message = error_messages[11];
324
325 return error_message;
326 }
327
grabbag__picture_parse_specification(const char * spec,const char ** error_message)328 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
329 {
330 FLAC__StreamMetadata *obj;
331 int state = 0;
332
333 FLAC__ASSERT(0 != spec);
334 FLAC__ASSERT(0 != error_message);
335
336 /* double protection */
337 if(0 == spec)
338 return 0;
339 if(0 == error_message)
340 return 0;
341
342 *error_message = 0;
343
344 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
345 *error_message = error_messages[0];
346 return obj;
347 }
348
349 if(strchr(spec, '|')) { /* full format */
350 const char *p;
351 char *q;
352 for(p = spec; *error_message==0 && *p; ) {
353 if(*p == '|') {
354 switch(state) {
355 case 0: /* type */
356 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
357 *error_message = error_messages[7];
358 break;
359 case 1: /* mime type */
360 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
361 if(0 == (q = local__strndup_(spec, p-spec)))
362 *error_message = error_messages[0];
363 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
364 *error_message = error_messages[0];
365 }
366 break;
367 case 2: /* description */
368 if(0 == (q = local__strndup_(spec, p-spec)))
369 *error_message = error_messages[0];
370 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
371 *error_message = error_messages[0];
372 break;
373 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
374 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
375 *error_message = error_messages[2];
376 break;
377 default:
378 *error_message = error_messages[1];
379 break;
380 }
381 p++;
382 spec = p;
383 state++;
384 }
385 else
386 p++;
387 }
388 }
389 else { /* simple format, filename only, everything else guessed */
390 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
391 *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 else if(!local__parse_resolution_("", 0, &obj->data.picture))
396 *error_message = error_messages[2];
397 else
398 state = 4;
399 }
400
401 /* parse filename, read file, try to extract resolution/color info if needed */
402 if(*error_message == 0) {
403 if(state != 4)
404 *error_message = error_messages[1];
405 else { /* 'spec' points to filename/URL */
406 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
407 if(strlen(spec) == 0)
408 *error_message = error_messages[1];
409 else if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
410 *error_message = error_messages[0];
411 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
412 *error_message = error_messages[3];
413 }
414 else { /* regular picture file */
415 *error_message = read_file (spec, obj);
416 }
417 }
418 }
419
420 if(*error_message == 0) {
421 if(
422 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
423 (
424 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
425 obj->data.picture.width != 32 ||
426 obj->data.picture.height != 32
427 )
428 )
429 *error_message = error_messages[9];
430 }
431
432 if(*error_message && obj) {
433 FLAC__metadata_object_delete(obj);
434 obj = 0;
435 }
436
437 return obj;
438 }
439
grabbag__picture_from_specification(int type,const char * mime_type_in,const char * description,const PictureResolution * res,const char * filepath,const char ** error_message)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 {
443
444 FLAC__StreamMetadata *obj;
445 char mime_type [64] ;
446
447 if (error_message == 0)
448 return 0;
449
450 safe_strncpy(mime_type, mime_type_in, sizeof (mime_type));
451
452 *error_message = 0;
453
454 if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) {
455 *error_message = error_messages[0];
456 return obj;
457 }
458
459 /* Picture type if known. */
460 obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
461
462 /* Mime type if known. */
463 if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, /*copy=*/true)) {
464 *error_message = error_messages[0];
465 return obj;
466 }
467
468 /* Description if present. */
469 if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, /*copy=*/true)) {
470 *error_message = error_messages[0];
471 return obj;
472 }
473
474 if (res == NULL) {
475 obj->data.picture.width = 0;
476 obj->data.picture.height = 0;
477 obj->data.picture.depth = 0;
478 obj->data.picture.colors = 0;
479 }
480 else {
481 obj->data.picture.width = res->width;
482 obj->data.picture.height = res->height;
483 obj->data.picture.depth = res->depth;
484 obj->data.picture.colors = res->colors;
485 }
486
487 if (strcmp(obj->data.picture.mime_type, "-->") == 0) { /* magic MIME type means URL */
488 if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), /*copy=*/true))
489 *error_message = error_messages[0];
490 else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
491 *error_message = error_messages[3];
492 }
493 else {
494 *error_message = read_file (filepath, obj);
495 }
496
497 if (*error_message == NULL) {
498 if (
499 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
500 (
501 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
502 obj->data.picture.width != 32 ||
503 obj->data.picture.height != 32
504 )
505 )
506 *error_message = error_messages[9];
507 }
508
509 if (*error_message && obj) {
510 FLAC__metadata_object_delete(obj);
511 obj = 0;
512 }
513
514 return obj;
515 }
516