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