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