for grabbag__file_are_same() on windows use strcmp() on filenames temporarily since...
[flac.git] / src / share / grabbag / picture.c
1 /* grabbag - Convenience lib for various routines common to several tools
2  * Copyright (C) 2006  Josh Coalson
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  */
18
19 #if HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "share/grabbag.h"
24 #include "FLAC/assert.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
30 static char *local__strndup_(const char *s, size_t size)
31 {
32         char *x = (char*)malloc(size+1);
33         if(x) {
34                 memcpy(x, s, size);
35                 x[size] = '\0';
36         }
37         return x;
38 }
39
40 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
41 {
42         size_t i;
43         FLAC__uint32 val = 0;
44
45         picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;;
46
47         if(len == 0)
48                 return true; /* empty string implies default to 'front cover' */
49
50         for(i = 0; i < len; i++) {
51                 if(s[i] >= '0' && s[i] <= '9')
52                         val = 10*val + (FLAC__uint32)(s[i] - '0');
53                 else
54                         return false;
55         }
56
57         if(i == len)
58                 picture->type = val;
59         else
60                 return false;
61
62         return true;
63 }
64
65 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
66 {
67         int state = 0;
68         size_t i;
69         FLAC__uint32 val = 0;
70
71         picture->width = picture->height = picture->depth = picture->colors = 0;
72
73         if(len == 0)
74                 return true; /* empty string implies client wants to get info from the file itself */
75
76         for(i = 0; i < len; i++) {
77                 if(s[i] == 'x') {
78                         if(state == 0)
79                                 picture->width = val;
80                         else if(state == 1)
81                                 picture->height = val;
82                         else
83                                 return false;
84                         state++;
85                         val = 0;
86                 }
87                 else if(s[i] == '/') {
88                         if(state == 2)
89                                 picture->depth = val;
90                         else
91                                 return false;
92                         state++;
93                         val = 0;
94                 }
95                 else if(s[i] >= '0' && s[i] <= '9')
96                         val = 10*val + (FLAC__uint32)(s[i] - '0');
97                 else
98                         return false;
99         }
100
101         if(state < 2)
102                 return false;
103         else if(state == 2)
104                 picture->depth = val;
105         else if(state == 3)
106                 picture->colors = val;
107         else
108                 return false;
109         if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
110                 return false;
111
112         return true;
113 }
114
115 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
116 {
117         const FLAC__byte *data = picture->data;
118         FLAC__uint32 len = picture->data_length;
119
120         if(0 == strcmp(picture->mime_type, "image/png")) {
121                 /* c.f. http://www.w3.org/TR/PNG/ */
122                 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
123                 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
124                         return false;
125                 /* try to find IHDR chunk */
126                 data += 8;
127                 len -= 8;
128                 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
129                         const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
130                         if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
131                                 unsigned color_type = data[17];
132                                 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
133                                 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
134                                 if(color_type == 3) {
135                                         /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
136                                          * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
137                                          * sample depth is always 8
138                                          */
139                                         picture->depth = 8 * 3u;
140                                         need_palette = true;
141                                         data += 12 + clen;
142                                         len -= 12 + clen;
143                                 }
144                                 else {
145                                         if(color_type == 0) /* greyscale, 1 sample per pixel */
146                                                 picture->depth = (FLAC__uint32)data[16];
147                                         if(color_type == 2) /* truecolor, 3 samples per pixel */
148                                                 picture->depth = (FLAC__uint32)data[16] * 3u;
149                                         if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
150                                                 picture->depth = (FLAC__uint32)data[16] * 2u;
151                                         if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
152                                                 picture->depth = (FLAC__uint32)data[16] * 4u;
153                                         picture->colors = 0;
154                                         return true;
155                                 }
156                         }
157                         else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
158                                 picture->colors = clen / 3u;
159                                 return true;
160                         }
161                         else if(clen + 12 > len)
162                                 return false;
163                         else {
164                                 data += 12 + clen;
165                                 len -= 12 + clen;
166                         }
167                 }
168         }
169         else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
170                 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
171                 if(len < 2 || memcmp(data, "\xff\xd8", 2))
172                         return false;
173                 data += 2;
174                 len -= 2;
175                 while(1) {
176                         /* look for sync FF byte */
177                         for( ; len > 0; data++, len--) {
178                                 if(*data == 0xff)
179                                         break;
180                         }
181                         if(len == 0)
182                                 return false;
183                         /* eat any extra pad FF bytes before marker */
184                         for( ; len > 0; data++, len--) {
185                                 if(*data != 0xff)
186                                         break;
187                         }
188                         if(len == 0)
189                                 return false;
190                         /* if we hit SOS or EOI, bail */
191                         if(*data == 0xda || *data == 0xd9)
192                                 return false;
193                         /* looking for some SOFn */
194                         else if(strchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data)) {
195                                 data++; len--; /* skip marker byte */
196                                 if(len < 2)
197                                         return false;
198                                 else {
199                                         const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
200                                         if(clen < 8 || len < clen)
201                                                 return false;
202                                         picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
203                                         picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
204                                         picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
205                                         picture->colors = 0;
206                                         return true;
207                                 }
208                         }
209                         /* else skip it */
210                         else {
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 < 2 || len < clen)
217                                                 return false;
218                                         data += clen;
219                                         len -= clen;
220                                 }
221                         }
222                 }
223         }
224         else if(0 == strcmp(picture->mime_type, "image/gif")) {
225                 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
226                 if(len < 14)
227                         return false;
228                 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
229                         return false;
230 #if 0
231                 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
232                 if(data[10] & 0x80 == 0)
233                         return false;
234 #endif
235                 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
236                 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
237 #if 0
238                 /* this value doesn't seem to be reliable... */
239                 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
240 #else
241                 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
242                 picture->depth = 8u * 3u;
243 #endif
244                 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
245                 return true;
246         }
247         return false;
248 }
249
250 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
251 {
252         FLAC__StreamMetadata *obj;
253         int state = 0;
254         const char *p;
255         char *q;
256         static const char *error_messages[] = {
257                 "memory allocation error",
258                 "invalid picture specification",
259                 "invalid picture specification: can't parse resolution/color part",
260                 "unable to extract resolution and color info from URL, user must set explicitly",
261                 "unable to extract resolution and color info from file, user must set explicitly",
262                 "error opening picture file",
263                 "error reading picture file",
264                 "invalid picture type",
265                 "invalid MIME type",
266                 "type 1 icon must be a 32x32 pixel PNG"
267         };
268
269         FLAC__ASSERT(0 != spec);
270         FLAC__ASSERT(0 != error_message);
271
272         /* double protection */
273         if(0 == spec)
274                 return 0;
275         if(0 == error_message)
276                 return 0;
277
278         *error_message = 0;
279
280         if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)))
281                 *error_message = error_messages[0];
282
283         for(p = spec; *error_message==0 && *p; ) {
284                 if(*p == '|') {
285                         switch(state) {
286                                 case 0: /* type */
287                                         if(!local__parse_type_(spec, p-spec, &obj->data.picture))
288                                                 *error_message = error_messages[7];
289                                         break;
290                                 case 1: /* mime type */
291                                         if(p-spec == 0)
292                                                 *error_message = error_messages[8];
293                                         else if(0 == (q = local__strndup_(spec, p-spec)))
294                                                 *error_message = error_messages[0];
295                                         else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
296                                                 *error_message = error_messages[0];
297                                         break;
298                                 case 2: /* description */
299                                         if(0 == (q = local__strndup_(spec, p-spec)))
300                                                 *error_message = error_messages[0];
301                                         else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
302                                                 *error_message = error_messages[0];
303                                         break;
304                                 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
305                                         if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
306                                                 *error_message = error_messages[2];
307                                         break;
308                                 default:
309                                         *error_message = error_messages[1];
310                                         break;
311                         }
312                         p++;
313                         spec = p;
314                         state++;
315                 }
316                 else
317                         p++;
318         }
319         /* parse filename, read file, try to extract resolution/color info if needed */
320         if(*error_message == 0) {
321                 if(state != 4)
322                         *error_message = error_messages[1];
323                 else { /* 'spec' points to filename/URL */
324                         if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
325                                 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
326                                         *error_message = error_messages[0];
327                                 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
328                                         *error_message = error_messages[3];
329                         }
330                         else { /* regular picture file */
331                                 const off_t size = grabbag__file_get_filesize(spec);
332                                 if(size < 0)
333                                         *error_message = error_messages[5];
334                                 else {
335                                         FLAC__byte *buffer = (FLAC__byte*)malloc(size);
336                                         if(0 == buffer)
337                                                 *error_message = error_messages[0];
338                                         else {
339                                                 FILE *f = fopen(spec, "rb");
340                                                 if(0 == f)
341                                                         *error_message = error_messages[5];
342                                                 else {
343                                                         if(fread(buffer, 1, size, f) != (size_t)size)
344                                                                 *error_message = error_messages[6];
345                                                         fclose(f);
346                                                         if(0 == *error_message) {
347                                                                 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
348                                                                         *error_message = error_messages[6];
349                                                                 /* try to extract resolution/color info if user left it blank */
350                                                                 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) {
351                                                                         if(!local__extract_resolution_color_info_(&obj->data.picture))
352                                                                                 *error_message = error_messages[4];
353                                                                 }
354                                                         }
355                                                 }
356                                         }
357                                 }
358                         }
359                 }
360         }
361
362         if(*error_message == 0) {
363                 if(
364                         obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD && 
365                         (
366                                 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
367                                 obj->data.picture.width != 32 ||
368                                 obj->data.picture.height != 32
369                         )
370                 )
371                         *error_message = error_messages[9];
372         }
373
374         if(*error_message && obj) {
375                 FLAC__metadata_object_delete(obj);
376                 obj = 0;
377         }
378
379         return obj;
380 }