6d0578c14f1cc69280f24886c49183c15c274330
[flac.git] / src / share / grabbag / picture.c
1 /* grabbag - Convenience lib for various routines common to several tools
2  * Copyright (C) 2006-2009  Josh Coalson
3  * Copyright (C) 2011-2014  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 }