ce188d4d |
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. */ |
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 | |
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 | |
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 | |
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 | |
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 | unsigned 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 | |
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 | |
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 | |
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 | } |