1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006-2009 Josh Coalson
3 * Copyright (C) 2011-2016 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 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
146 uint32_t color_type = data[17];
147 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
148 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
149 if(color_type == 3) {
150 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
151 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
152 * sample depth is always 8
153 */
154 picture->depth = 8 * 3u;
155 need_palette = true;
156 data += 12 + clen;
157 len -= 12 + clen;
158 }
159 else {
160 if(color_type == 0) /* greyscale, 1 sample per pixel */
161 picture->depth = (FLAC__uint32)data[16];
162 if(color_type == 2) /* truecolor, 3 samples per pixel */
163 picture->depth = (FLAC__uint32)data[16] * 3u;
164 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
165 picture->depth = (FLAC__uint32)data[16] * 2u;
166 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
167 picture->depth = (FLAC__uint32)data[16] * 4u;
168 picture->colors = 0;
169 return true;
170 }
171 }
172 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
173 picture->colors = clen / 3u;
174 return true;
175 }
176 else if(clen + 12 > len)
177 return false;
178 else {
179 data += 12 + clen;
180 len -= 12 + clen;
181 }
182 }
183 }
184 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
185 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
186 if(len < 2 || memcmp(data, "\xff\xd8", 2))
187 return false;
188 data += 2;
189 len -= 2;
190 while(1) {
191 /* look for sync FF byte */
192 for( ; len > 0; data++, len--) {
193 if(*data == 0xff)
194 break;
195 }
196 if(len == 0)
197 return false;
198 /* eat any extra pad FF bytes before marker */
199 for( ; len > 0; data++, len--) {
200 if(*data != 0xff)
201 break;
202 }
203 if(len == 0)
204 return false;
205 /* if we hit SOS or EOI, bail */
206 if(*data == 0xda || *data == 0xd9)
207 return false;
208 /* looking for some SOFn */
209 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
210 data++; len--; /* skip marker byte */
211 if(len < 2)
212 return false;
213 else {
214 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
215 if(clen < 8 || len < clen)
216 return false;
217 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
218 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
219 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
220 picture->colors = 0;
221 return true;
222 }
223 }
224 /* else skip it */
225 else {
226 data++; len--; /* skip marker byte */
227 if(len < 2)
228 return false;
229 else {
230 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
231 if(clen < 2 || len < clen)
232 return false;
233 data += clen;
234 len -= clen;
235 }
236 }
237 }
238 }
239 else if(0 == strcmp(picture->mime_type, "image/gif")) {
240 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
241 if(len < 14)
242 return false;
243 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
244 return false;
245 #if 0
246 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
247 if(data[10] & 0x80 == 0)
248 return false;
249 #endif
250 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
251 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
252 #if 0
253 /* this value doesn't seem to be reliable... */
254 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
255 #else
256 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
257 picture->depth = 8u * 3u;
258 #endif
259 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
260 return true;
261 }
262 return false;
263 }
264
265 static const char *error_messages[] = {
266 "memory allocation error",
267 "invalid picture specification",
268 "invalid picture specification: can't parse resolution/color part",
269 "unable to extract resolution and color info from URL, user must set explicitly",
270 "unable to extract resolution and color info from file, user must set explicitly",
271 "error opening picture file",
272 "error reading picture file",
273 "invalid picture type",
274 "unable to guess MIME type from file, user must set explicitly",
275 "type 1 icon must be a 32x32 pixel PNG",
276 "file not found", /* currently unused */
277 "file is too large"
278 };
279
read_file(const char * filepath,FLAC__StreamMetadata * obj)280 static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj)
281 {
282 const FLAC__off_t size = grabbag__file_get_filesize(filepath);
283 FLAC__byte *buffer;
284 FILE *file;
285 const char *error_message=NULL;
286
287 if (size < 0)
288 return error_messages[5];
289
290 if (size >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN)) /* actual limit is less because of other fields in the PICTURE metadata block */
291 return error_messages[11];
292
293 if ((buffer = safe_malloc_(size)) == NULL)
294 return error_messages[0];
295
296 if ((file = flac_fopen(filepath, "rb")) == NULL) {
297 free(buffer);
298 return error_messages[5];
299 }
300
301 if (fread(buffer, 1, size, file) != (size_t) size) {
302 fclose(file);
303 free(buffer);
304 return error_messages[6];
305 }
306 fclose(file);
307
308 if (!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
309 error_message = error_messages[6];
310 /* try to extract MIME type if user left it blank */
311 else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
312 error_message = error_messages[8];
313 /* try to extract resolution/color info if user left it blank */
314 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))
315 error_message = error_messages[4];
316 /* check metadata block size */
317 else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN))
318 error_message = error_messages[11];
319
320 return error_message;
321 }
322
grabbag__picture_parse_specification(const char * spec,const char ** error_message)323 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
324 {
325 FLAC__StreamMetadata *obj;
326 int state = 0;
327
328 FLAC__ASSERT(0 != spec);
329 FLAC__ASSERT(0 != error_message);
330
331 /* double protection */
332 if(0 == spec)
333 return 0;
334 if(0 == error_message)
335 return 0;
336
337 *error_message = 0;
338
339 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
340 *error_message = error_messages[0];
341 return obj;
342 }
343
344 if(strchr(spec, '|')) { /* full format */
345 const char *p;
346 char *q;
347 for(p = spec; *error_message==0 && *p; ) {
348 if(*p == '|') {
349 switch(state) {
350 case 0: /* type */
351 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
352 *error_message = error_messages[7];
353 break;
354 case 1: /* mime type */
355 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
356 if(0 == (q = local__strndup_(spec, p-spec)))
357 *error_message = error_messages[0];
358 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
359 *error_message = error_messages[0];
360 }
361 break;
362 case 2: /* description */
363 if(0 == (q = local__strndup_(spec, p-spec)))
364 *error_message = error_messages[0];
365 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
366 *error_message = error_messages[0];
367 break;
368 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
369 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
370 *error_message = error_messages[2];
371 break;
372 default:
373 *error_message = error_messages[1];
374 break;
375 }
376 p++;
377 spec = p;
378 state++;
379 }
380 else
381 p++;
382 }
383 }
384 else { /* simple format, filename only, everything else guessed */
385 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
386 *error_message = error_messages[7];
387 /* leave MIME type to be filled in later */
388 /* leave description empty */
389 /* leave the rest to be filled in later: */
390 else if(!local__parse_resolution_("", 0, &obj->data.picture))
391 *error_message = error_messages[2];
392 else
393 state = 4;
394 }
395
396 /* parse filename, read file, try to extract resolution/color info if needed */
397 if(*error_message == 0) {
398 if(state != 4)
399 *error_message = error_messages[1];
400 else { /* 'spec' points to filename/URL */
401 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
402 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
403 *error_message = error_messages[0];
404 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
405 *error_message = error_messages[3];
406 }
407 else { /* regular picture file */
408 *error_message = read_file (spec, obj);
409 }
410 }
411 }
412
413 if(*error_message == 0) {
414 if(
415 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
416 (
417 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
418 obj->data.picture.width != 32 ||
419 obj->data.picture.height != 32
420 )
421 )
422 *error_message = error_messages[9];
423 }
424
425 if(*error_message && obj) {
426 FLAC__metadata_object_delete(obj);
427 obj = 0;
428 }
429
430 return obj;
431 }
432
grabbag__picture_from_specification(int type,const char * mime_type_in,const char * description,const PictureResolution * res,const char * filepath,const char ** error_message)433 FLAC__StreamMetadata *grabbag__picture_from_specification(int type, const char *mime_type_in, const char * description,
434 const PictureResolution * res, const char * filepath, const char **error_message)
435 {
436
437 FLAC__StreamMetadata *obj;
438 char mime_type [64] ;
439
440 if (error_message == 0)
441 return 0;
442
443 safe_strncpy(mime_type, mime_type_in, sizeof (mime_type));
444
445 *error_message = 0;
446
447 if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) {
448 *error_message = error_messages[0];
449 return obj;
450 }
451
452 /* Picture type if known. */
453 obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
454
455 /* Mime type if known. */
456 if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, /*copy=*/true)) {
457 *error_message = error_messages[0];
458 return obj;
459 }
460
461 /* Description if present. */
462 if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, /*copy=*/true)) {
463 *error_message = error_messages[0];
464 return obj;
465 }
466
467 if (res == NULL) {
468 obj->data.picture.width = 0;
469 obj->data.picture.height = 0;
470 obj->data.picture.depth = 0;
471 obj->data.picture.colors = 0;
472 }
473 else {
474 obj->data.picture.width = res->width;
475 obj->data.picture.height = res->height;
476 obj->data.picture.depth = res->depth;
477 obj->data.picture.colors = res->colors;
478 }
479
480 if (strcmp(obj->data.picture.mime_type, "-->") == 0) { /* magic MIME type means URL */
481 if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), /*copy=*/true))
482 *error_message = error_messages[0];
483 else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
484 *error_message = error_messages[3];
485 }
486 else {
487 *error_message = read_file (filepath, obj);
488 }
489
490 if (*error_message == NULL) {
491 if (
492 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
493 (
494 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
495 obj->data.picture.width != 32 ||
496 obj->data.picture.height != 32
497 )
498 )
499 *error_message = error_messages[9];
500 }
501
502 if (*error_message && obj) {
503 FLAC__metadata_object_delete(obj);
504 obj = 0;
505 }
506
507 return obj;
508 }
509