#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "share/alloc.h"
#include "share/grabbag.h"
#include "FLAC/assert.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "share/compat.h"
#include "share/safe_str.h"
static char *local__strndup_(const char *s, size_t size)
{
char *x = safe_malloc_add_2op_(size, 1);
if(x) {
memcpy(x, s, size);
x[size] = '\0';
}
return x;
}
static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
{
size_t i;
FLAC__uint32 val = 0;
picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
if(len == 0)
return true;
for(i = 0; i < len; i++) {
if(s[i] >= '0' && s[i] <= '9')
val = 10*val + (FLAC__uint32)(s[i] - '0');
else
return false;
}
if(i == len)
picture->type = val;
else
return false;
return true;
}
static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
{
int state = 0;
size_t i;
FLAC__uint32 val = 0;
picture->width = picture->height = picture->depth = picture->colors = 0;
if(len == 0)
return true;
for(i = 0; i < len; i++) {
if(s[i] == 'x') {
if(state == 0)
picture->width = val;
else if(state == 1)
picture->height = val;
else
return false;
state++;
val = 0;
}
else if(s[i] == '/') {
if(state == 2)
picture->depth = val;
else
return false;
state++;
val = 0;
}
else if(s[i] >= '0' && s[i] <= '9')
val = 10*val + (FLAC__uint32)(s[i] - '0');
else
return false;
}
if(state < 2)
return false;
else if(state == 2)
picture->depth = val;
else if(state == 3)
picture->colors = val;
else
return false;
if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
return false;
return true;
}
static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
{
if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", true);
else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", true);
else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", true);
return false;
}
static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
{
const FLAC__byte *data = picture->data;
FLAC__uint32 len = picture->data_length;
if(0 == strcmp(picture->mime_type, "image/png")) {
FLAC__bool need_palette = false;
if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
return false;
data += 8;
len -= 8;
while(len > 12) {
const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
if(clen + 12 <= clen || clen + 12 > len)
return false;
else if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
uint32_t color_type = data[17];
picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
if(color_type == 3) {
picture->depth = 8 * 3u;
need_palette = true;
data += 12 + clen;
len -= 12 + clen;
}
else {
if(color_type == 0)
picture->depth = (FLAC__uint32)data[16];
if(color_type == 2)
picture->depth = (FLAC__uint32)data[16] * 3u;
if(color_type == 4)
picture->depth = (FLAC__uint32)data[16] * 2u;
if(color_type == 6)
picture->depth = (FLAC__uint32)data[16] * 4u;
picture->colors = 0;
return true;
}
}
else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
picture->colors = clen / 3u;
return true;
}
else {
data += 12 + clen;
len -= 12 + clen;
}
}
}
else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
if(len < 2 || memcmp(data, "\xff\xd8", 2))
return false;
data += 2;
len -= 2;
while(1) {
for( ; len > 0; data++, len--) {
if(*data == 0xff)
break;
}
if(len == 0)
return false;
for( ; len > 0; data++, len--) {
if(*data != 0xff)
break;
}
if(len == 0)
return false;
if(*data == 0xda || *data == 0xd9)
return false;
else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
data++; len--;
if(len < 2)
return false;
else {
const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
if(clen < 8 || len < clen)
return false;
picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
picture->colors = 0;
return true;
}
}
else {
data++; len--;
if(len < 2)
return false;
else {
const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
if(clen < 2 || len < clen)
return false;
data += clen;
len -= clen;
}
}
}
}
else if(0 == strcmp(picture->mime_type, "image/gif")) {
if(len < 14)
return false;
if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
return false;
#if 0#endif
picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
#if 0#else
picture->depth = 8u * 3u;
#endif
picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
return true;
}
return false;
}
static const char *error_messages[] = {
"memory allocation error",
"invalid picture specification",
"invalid picture specification: can't parse resolution/color part",
"unable to extract resolution and color info from URL, user must set explicitly",
"unable to extract resolution and color info from file, user must set explicitly",
"error opening picture file",
"error reading picture file",
"invalid picture type",
"unable to guess MIME type from file, user must set explicitly",
"type 1 icon must be a 32x32 pixel PNG",
"file not found",
"file is too large",
"empty file"
};
static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj)
{
const FLAC__off_t size = grabbag__file_get_filesize(filepath);
FLAC__byte *buffer;
FILE *file;
const char *error_message=NULL;
if (size < 0)
return error_messages[5];
if (size == 0)
return error_messages[12];
if (size >= (FLAC__off_t)(1u << FLAC__STREAM_METADATA_LENGTH_LEN))
return error_messages[11];
if ((buffer = safe_malloc_(size)) == NULL)
return error_messages[0];
if ((file = flac_fopen(filepath, "rb")) == NULL) {
free(buffer);
return error_messages[5];
}
if (fread(buffer, 1, size, file) != (size_t) size) {
fclose(file);
free(buffer);
return error_messages[6];
}
fclose(file);
if (!FLAC__metadata_object_picture_set_data(obj, buffer, (FLAC__uint32)size, false))
error_message = error_messages[6];
else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
error_message = error_messages[8];
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))
error_message = error_messages[4];
else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN))
error_message = error_messages[11];
return error_message;
}
FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
{
FLAC__StreamMetadata *obj;
int state = 0;
FLAC__ASSERT(0 != spec);
FLAC__ASSERT(0 != error_message);
if(0 == spec)
return 0;
if(0 == error_message)
return 0;
*error_message = 0;
if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
*error_message = error_messages[0];
return obj;
}
if(strchr(spec, '|')) {
const char *p;
char *q;
for(p = spec; *error_message==0 && *p; ) {
if(*p == '|') {
switch(state) {
case 0:
if(!local__parse_type_(spec, p-spec, &obj->data.picture))
*error_message = error_messages[7];
break;
case 1:
if(p-spec) {
if(0 == (q = local__strndup_(spec, p-spec)))
*error_message = error_messages[0];
else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, false))
*error_message = error_messages[0];
}
break;
case 2:
if(0 == (q = local__strndup_(spec, p-spec)))
*error_message = error_messages[0];
else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, false))
*error_message = error_messages[0];
break;
case 3:
if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
*error_message = error_messages[2];
break;
default:
*error_message = error_messages[1];
break;
}
p++;
spec = p;
state++;
}
else
p++;
}
}
else {
if(!local__parse_type_("", 0, &obj->data.picture))
*error_message = error_messages[7];
else if(!local__parse_resolution_("", 0, &obj->data.picture))
*error_message = error_messages[2];
else
state = 4;
}
if(*error_message == 0) {
if(state != 4)
*error_message = error_messages[1];
else {
if(0 == strcmp(obj->data.picture.mime_type, "-->")) {
if(strlen(spec) == 0)
*error_message = error_messages[1];
else if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), true))
*error_message = error_messages[0];
else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
*error_message = error_messages[3];
}
else {
*error_message = read_file (spec, obj);
}
}
}
if(*error_message == 0) {
if(
obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
(
(strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
obj->data.picture.width != 32 ||
obj->data.picture.height != 32
)
)
*error_message = error_messages[9];
}
if(*error_message && obj) {
FLAC__metadata_object_delete(obj);
obj = 0;
}
return obj;
}
FLAC__StreamMetadata *grabbag__picture_from_specification(int type, const char *mime_type_in, const char * description,
const PictureResolution * res, const char * filepath, const char **error_message)
{
FLAC__StreamMetadata *obj;
char mime_type [64] ;
if (error_message == 0)
return 0;
safe_strncpy(mime_type, mime_type_in, sizeof (mime_type));
*error_message = 0;
if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) {
*error_message = error_messages[0];
return obj;
}
obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, true)) {
*error_message = error_messages[0];
return obj;
}
if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, true)) {
*error_message = error_messages[0];
return obj;
}
if (res == NULL) {
obj->data.picture.width = 0;
obj->data.picture.height = 0;
obj->data.picture.depth = 0;
obj->data.picture.colors = 0;
}
else {
obj->data.picture.width = res->width;
obj->data.picture.height = res->height;
obj->data.picture.depth = res->depth;
obj->data.picture.colors = res->colors;
}
if (strcmp(obj->data.picture.mime_type, "-->") == 0) {
if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), true))
*error_message = error_messages[0];
else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
*error_message = error_messages[3];
}
else {
*error_message = read_file (filepath, obj);
}
if (*error_message == NULL) {
if (
obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
(
(strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
obj->data.picture.width != 32 ||
obj->data.picture.height != 32
)
)
*error_message = error_messages[9];
}
if (*error_message && obj) {
FLAC__metadata_object_delete(obj);
obj = 0;
}
return obj;
}