| 1 | /* grabbag - Convenience lib for various routines common to several tools |
| 2 | * Copyright (C) 2006-2009 Josh Coalson |
| 3 | * Copyright (C) 2011-2016 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 | } |