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