e2d2baece9
git-svn-id: https://svn.musicpd.org/mpd/trunk@196 09075e82-0dd4-0310-85a5-a0d7c8717e4f
627 lines
13 KiB
C
627 lines
13 KiB
C
/*
|
|
* libid3tag - ID3 tag manipulation library
|
|
* Copyright (C) 2000-2004 Underbit Technologies, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* $Id: frame.c,v 1.15 2004/01/23 09:41:32 rob Exp $
|
|
*/
|
|
|
|
# ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
# endif
|
|
|
|
# include "global.h"
|
|
|
|
# include <stdlib.h>
|
|
# include <string.h>
|
|
|
|
# ifdef HAVE_ASSERT_H
|
|
# include <assert.h>
|
|
# endif
|
|
|
|
# include "id3tag.h"
|
|
# include "frame.h"
|
|
# include "frametype.h"
|
|
# include "compat.h"
|
|
# include "field.h"
|
|
# include "render.h"
|
|
# include "parse.h"
|
|
# include "util.h"
|
|
|
|
static
|
|
int valid_idchar(char c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->validid()
|
|
* DESCRIPTION: return true if the parameter string is a legal frame ID
|
|
*/
|
|
int id3_frame_validid(char const *id)
|
|
{
|
|
return id &&
|
|
valid_idchar(id[0]) &&
|
|
valid_idchar(id[1]) &&
|
|
valid_idchar(id[2]) &&
|
|
valid_idchar(id[3]);
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->new()
|
|
* DESCRIPTION: allocate and return a new frame
|
|
*/
|
|
struct id3_frame *id3_frame_new(char const *id)
|
|
{
|
|
struct id3_frametype const *frametype;
|
|
struct id3_frame *frame;
|
|
unsigned int i;
|
|
|
|
if (!id3_frame_validid(id))
|
|
return 0;
|
|
|
|
frametype = id3_frametype_lookup(id, 4);
|
|
if (frametype == 0) {
|
|
switch (id[0]) {
|
|
case 'T':
|
|
frametype = &id3_frametype_text;
|
|
break;
|
|
|
|
case 'W':
|
|
frametype = &id3_frametype_url;
|
|
break;
|
|
|
|
case 'X':
|
|
case 'Y':
|
|
case 'Z':
|
|
frametype = &id3_frametype_experimental;
|
|
break;
|
|
|
|
default:
|
|
frametype = &id3_frametype_unknown;
|
|
if (id3_compat_lookup(id, 4))
|
|
frametype = &id3_frametype_obsolete;
|
|
break;
|
|
}
|
|
}
|
|
|
|
frame = malloc(sizeof(*frame) + frametype->nfields * sizeof(*frame->fields));
|
|
if (frame) {
|
|
frame->id[0] = id[0];
|
|
frame->id[1] = id[1];
|
|
frame->id[2] = id[2];
|
|
frame->id[3] = id[3];
|
|
frame->id[4] = 0;
|
|
|
|
frame->description = frametype->description;
|
|
frame->refcount = 0;
|
|
frame->flags = frametype->defaultflags;
|
|
frame->group_id = 0;
|
|
frame->encryption_method = 0;
|
|
frame->encoded = 0;
|
|
frame->encoded_length = 0;
|
|
frame->decoded_length = 0;
|
|
frame->nfields = frametype->nfields;
|
|
frame->fields = (union id3_field *) &frame[1];
|
|
|
|
for (i = 0; i < frame->nfields; ++i)
|
|
id3_field_init(&frame->fields[i], frametype->fields[i]);
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
void id3_frame_delete(struct id3_frame *frame)
|
|
{
|
|
assert(frame);
|
|
|
|
if (frame->refcount == 0) {
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < frame->nfields; ++i)
|
|
id3_field_finish(&frame->fields[i]);
|
|
|
|
if (frame->encoded)
|
|
free(frame->encoded);
|
|
|
|
free(frame);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->addref()
|
|
* DESCRIPTION: add an external reference to a frame
|
|
*/
|
|
void id3_frame_addref(struct id3_frame *frame)
|
|
{
|
|
assert(frame);
|
|
|
|
++frame->refcount;
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->delref()
|
|
* DESCRIPTION: remove an external reference to a frame
|
|
*/
|
|
void id3_frame_delref(struct id3_frame *frame)
|
|
{
|
|
assert(frame && frame->refcount > 0);
|
|
|
|
--frame->refcount;
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->field()
|
|
* DESCRIPTION: return a pointer to a field in a frame
|
|
*/
|
|
union id3_field *id3_frame_field(struct id3_frame const *frame,
|
|
unsigned int index)
|
|
{
|
|
assert(frame);
|
|
|
|
return (index < frame->nfields) ? &frame->fields[index] : 0;
|
|
}
|
|
|
|
static
|
|
struct id3_frame *obsolete(char const *id, id3_byte_t const *data,
|
|
id3_length_t length)
|
|
{
|
|
struct id3_frame *frame;
|
|
|
|
frame = id3_frame_new(ID3_FRAME_OBSOLETE);
|
|
if (frame) {
|
|
if (id3_field_setframeid(&frame->fields[0], id) == -1 ||
|
|
id3_field_setbinarydata(&frame->fields[1], data, length) == -1)
|
|
goto fail;
|
|
}
|
|
|
|
if (0) {
|
|
fail:
|
|
if (frame) {
|
|
id3_frame_delete(frame);
|
|
frame = 0;
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
static
|
|
struct id3_frame *unparseable(char const *id, id3_byte_t const **ptr,
|
|
id3_length_t length, int flags,
|
|
int group_id, int encryption_method,
|
|
id3_length_t decoded_length)
|
|
{
|
|
struct id3_frame *frame = 0;
|
|
id3_byte_t *mem;
|
|
|
|
mem = malloc(length ? length : 1);
|
|
if (mem == 0)
|
|
goto fail;
|
|
|
|
frame = id3_frame_new(id);
|
|
if (frame == 0)
|
|
free(mem);
|
|
else {
|
|
memcpy(mem, *ptr, length);
|
|
|
|
frame->flags = flags;
|
|
frame->group_id = group_id;
|
|
frame->encryption_method = encryption_method;
|
|
frame->encoded = mem;
|
|
frame->encoded_length = length;
|
|
frame->decoded_length = decoded_length;
|
|
}
|
|
|
|
if (0) {
|
|
fail:
|
|
;
|
|
}
|
|
|
|
*ptr += length;
|
|
|
|
return frame;
|
|
}
|
|
|
|
static
|
|
int parse_data(struct id3_frame *frame,
|
|
id3_byte_t const *data, id3_length_t length)
|
|
{
|
|
enum id3_field_textencoding encoding;
|
|
id3_byte_t const *end;
|
|
unsigned int i;
|
|
|
|
encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;
|
|
|
|
end = data + length;
|
|
|
|
for (i = 0; i < frame->nfields; ++i) {
|
|
if (id3_field_parse(&frame->fields[i], &data, end - data, &encoding) == -1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->parse()
|
|
* DESCRIPTION: parse raw frame data according to the specified ID3 tag version
|
|
*/
|
|
struct id3_frame *id3_frame_parse(id3_byte_t const **ptr, id3_length_t length,
|
|
unsigned int version)
|
|
{
|
|
struct id3_frame *frame = 0;
|
|
id3_byte_t const *id, *end, *data;
|
|
id3_length_t size, decoded_length = 0;
|
|
int flags = 0, group_id = 0, encryption_method = 0;
|
|
struct id3_compat const *compat = 0;
|
|
id3_byte_t *mem = 0;
|
|
char xid[4];
|
|
|
|
id = *ptr;
|
|
end = *ptr + length;
|
|
|
|
if (ID3_TAG_VERSION_MAJOR(version) < 4) {
|
|
switch (ID3_TAG_VERSION_MAJOR(version)) {
|
|
case 2:
|
|
if (length < 6)
|
|
goto fail;
|
|
|
|
compat = id3_compat_lookup(id, 3);
|
|
|
|
*ptr += 3;
|
|
size = id3_parse_uint(ptr, 3);
|
|
|
|
if (size > end - *ptr)
|
|
goto fail;
|
|
|
|
end = *ptr + size;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
if (length < 10)
|
|
goto fail;
|
|
|
|
compat = id3_compat_lookup(id, 4);
|
|
|
|
*ptr += 4;
|
|
size = id3_parse_uint(ptr, 4);
|
|
flags = id3_parse_uint(ptr, 2);
|
|
|
|
if (size > end - *ptr)
|
|
goto fail;
|
|
|
|
end = *ptr + size;
|
|
|
|
if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~0x00e0)) {
|
|
frame = unparseable(id, ptr, end - *ptr, 0, 0, 0, 0);
|
|
goto done;
|
|
}
|
|
|
|
flags =
|
|
((flags >> 1) & ID3_FRAME_FLAG_STATUSFLAGS) |
|
|
((flags >> 4) & (ID3_FRAME_FLAG_COMPRESSION |
|
|
ID3_FRAME_FLAG_ENCRYPTION)) |
|
|
((flags << 1) & ID3_FRAME_FLAG_GROUPINGIDENTITY);
|
|
|
|
if (flags & ID3_FRAME_FLAG_COMPRESSION) {
|
|
if (end - *ptr < 4)
|
|
goto fail;
|
|
|
|
decoded_length = id3_parse_uint(ptr, 4);
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
|
|
if (end - *ptr < 1)
|
|
goto fail;
|
|
|
|
encryption_method = id3_parse_uint(ptr, 1);
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY) {
|
|
if (end - *ptr < 1)
|
|
goto fail;
|
|
|
|
group_id = id3_parse_uint(ptr, 1);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
|
|
/* canonicalize frame ID for ID3v2.4 */
|
|
|
|
if (compat && compat->equiv)
|
|
id = compat->equiv;
|
|
else if (ID3_TAG_VERSION_MAJOR(version) == 2) {
|
|
xid[0] = 'Y';
|
|
xid[1] = id[0];
|
|
xid[2] = id[1];
|
|
xid[3] = id[2];
|
|
|
|
id = xid;
|
|
|
|
flags |=
|
|
ID3_FRAME_FLAG_TAGALTERPRESERVATION |
|
|
ID3_FRAME_FLAG_FILEALTERPRESERVATION;
|
|
}
|
|
}
|
|
else { /* ID3v2.4 */
|
|
if (length < 10)
|
|
goto fail;
|
|
|
|
*ptr += 4;
|
|
size = id3_parse_syncsafe(ptr, 4);
|
|
flags = id3_parse_uint(ptr, 2);
|
|
|
|
if (size > end - *ptr)
|
|
goto fail;
|
|
|
|
end = *ptr + size;
|
|
|
|
if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~ID3_FRAME_FLAG_KNOWNFLAGS)) {
|
|
frame = unparseable(id, ptr, end - *ptr, flags, 0, 0, 0);
|
|
goto done;
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY) {
|
|
if (end - *ptr < 1)
|
|
goto fail;
|
|
|
|
group_id = id3_parse_uint(ptr, 1);
|
|
}
|
|
|
|
if ((flags & ID3_FRAME_FLAG_COMPRESSION) &&
|
|
!(flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR))
|
|
goto fail;
|
|
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
|
|
if (end - *ptr < 1)
|
|
goto fail;
|
|
|
|
encryption_method = id3_parse_uint(ptr, 1);
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR) {
|
|
if (end - *ptr < 4)
|
|
goto fail;
|
|
|
|
decoded_length = id3_parse_syncsafe(ptr, 4);
|
|
}
|
|
}
|
|
|
|
data = *ptr;
|
|
*ptr = end;
|
|
|
|
/* undo frame encodings */
|
|
|
|
if ((flags & ID3_FRAME_FLAG_UNSYNCHRONISATION) && end - data > 0) {
|
|
mem = malloc(end - data);
|
|
if (mem == 0)
|
|
goto fail;
|
|
|
|
memcpy(mem, data, end - data);
|
|
|
|
end = mem + id3_util_deunsynchronise(mem, end - data);
|
|
data = mem;
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION) {
|
|
frame = unparseable(id, &data, end - data, flags,
|
|
group_id, encryption_method, decoded_length);
|
|
goto done;
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_COMPRESSION) {
|
|
id3_byte_t *decomp;
|
|
|
|
decomp = id3_util_decompress(data, end - data, decoded_length);
|
|
if (decomp == 0)
|
|
goto fail;
|
|
|
|
if (mem)
|
|
free(mem);
|
|
|
|
data = mem = decomp;
|
|
end = data + decoded_length;
|
|
}
|
|
|
|
/* check for obsolescence */
|
|
|
|
if (compat && !compat->equiv) {
|
|
frame = obsolete(id, data, end - data);
|
|
goto done;
|
|
}
|
|
|
|
/* generate the internal frame structure */
|
|
|
|
frame = id3_frame_new(id);
|
|
if (frame) {
|
|
frame->flags = flags;
|
|
frame->group_id = group_id;
|
|
|
|
if (compat && compat->translate) {
|
|
if (compat->translate(frame, compat->id, data, end - data) == -1)
|
|
goto fail;
|
|
}
|
|
else {
|
|
if (parse_data(frame, data, end - data) == -1)
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (0) {
|
|
fail:
|
|
if (frame) {
|
|
id3_frame_delete(frame);
|
|
frame = 0;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (mem)
|
|
free(mem);
|
|
|
|
return frame;
|
|
}
|
|
|
|
static
|
|
id3_length_t render_data(id3_byte_t **ptr,
|
|
union id3_field *fields, unsigned int length)
|
|
{
|
|
id3_length_t size = 0;
|
|
enum id3_field_textencoding encoding;
|
|
unsigned int i;
|
|
|
|
encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;
|
|
|
|
for (i = 0; i < length; ++i)
|
|
size += id3_field_render(&fields[i], ptr, &encoding, i < length - 1);
|
|
|
|
return size;
|
|
}
|
|
|
|
/*
|
|
* NAME: frame->render()
|
|
* DESCRIPTION: render a single, complete frame
|
|
*/
|
|
id3_length_t id3_frame_render(struct id3_frame const *frame,
|
|
id3_byte_t **ptr, int options)
|
|
{
|
|
id3_length_t size = 0, decoded_length, datalen;
|
|
id3_byte_t *size_ptr = 0, *flags_ptr = 0, *data = 0;
|
|
int flags;
|
|
|
|
assert(frame);
|
|
|
|
if ((frame->flags & ID3_FRAME_FLAG_TAGALTERPRESERVATION) ||
|
|
((options & ID3_TAG_OPTION_FILEALTERED) &&
|
|
(frame->flags & ID3_FRAME_FLAG_FILEALTERPRESERVATION)))
|
|
return 0;
|
|
|
|
/* a frame must be at least 1 byte big, excluding the header */
|
|
|
|
decoded_length = render_data(0, frame->fields, frame->nfields);
|
|
if (decoded_length == 0 && frame->encoded == 0)
|
|
return 0;
|
|
|
|
/* header */
|
|
|
|
size += id3_render_immediate(ptr, frame->id, 4);
|
|
|
|
if (ptr)
|
|
size_ptr = *ptr;
|
|
|
|
size += id3_render_syncsafe(ptr, 0, 4);
|
|
|
|
if (ptr)
|
|
flags_ptr = *ptr;
|
|
|
|
flags = frame->flags;
|
|
|
|
size += id3_render_int(ptr, flags, 2);
|
|
|
|
if (flags & (ID3_FRAME_FLAG_FORMATFLAGS & ~ID3_FRAME_FLAG_KNOWNFLAGS)) {
|
|
size += id3_render_binary(ptr, frame->encoded, frame->encoded_length);
|
|
if (size_ptr)
|
|
id3_render_syncsafe(&size_ptr, size - 10, 4);
|
|
|
|
return size;
|
|
}
|
|
|
|
flags &= ID3_FRAME_FLAG_KNOWNFLAGS;
|
|
|
|
flags &= ~ID3_FRAME_FLAG_UNSYNCHRONISATION;
|
|
if (options & ID3_TAG_OPTION_UNSYNCHRONISATION)
|
|
flags |= ID3_FRAME_FLAG_UNSYNCHRONISATION;
|
|
|
|
if (!(flags & ID3_FRAME_FLAG_ENCRYPTION)) {
|
|
flags &= ~ID3_FRAME_FLAG_COMPRESSION;
|
|
if (options & ID3_TAG_OPTION_COMPRESSION)
|
|
flags |= ID3_FRAME_FLAG_COMPRESSION | ID3_FRAME_FLAG_DATALENGTHINDICATOR;
|
|
}
|
|
|
|
if (flags & ID3_FRAME_FLAG_GROUPINGIDENTITY)
|
|
size += id3_render_int(ptr, frame->group_id, 1);
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION)
|
|
size += id3_render_int(ptr, frame->encryption_method, 1);
|
|
if (flags & ID3_FRAME_FLAG_DATALENGTHINDICATOR) {
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION)
|
|
decoded_length = frame->decoded_length;
|
|
size += id3_render_syncsafe(ptr, decoded_length, 4);
|
|
}
|
|
|
|
if (ptr)
|
|
data = *ptr;
|
|
|
|
if (flags & ID3_FRAME_FLAG_ENCRYPTION)
|
|
datalen = id3_render_binary(ptr, frame->encoded, frame->encoded_length);
|
|
else {
|
|
if (ptr == 0)
|
|
datalen = decoded_length;
|
|
else {
|
|
datalen = render_data(ptr, frame->fields, frame->nfields);
|
|
|
|
if (flags & ID3_FRAME_FLAG_COMPRESSION) {
|
|
id3_byte_t *comp;
|
|
id3_length_t complen;
|
|
|
|
comp = id3_util_compress(data, datalen, &complen);
|
|
if (comp == 0)
|
|
flags &= ~ID3_FRAME_FLAG_COMPRESSION;
|
|
else {
|
|
*ptr = data;
|
|
datalen = id3_render_binary(ptr, comp, complen);
|
|
|
|
free(comp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* unsynchronisation */
|
|
|
|
if (flags & ID3_FRAME_FLAG_UNSYNCHRONISATION) {
|
|
if (data == 0)
|
|
datalen *= 2;
|
|
else {
|
|
id3_length_t newlen;
|
|
|
|
newlen = id3_util_unsynchronise(data, datalen);
|
|
if (newlen == datalen)
|
|
flags &= ~ID3_FRAME_FLAG_UNSYNCHRONISATION;
|
|
else {
|
|
*ptr += newlen - datalen;
|
|
datalen = newlen;
|
|
}
|
|
}
|
|
}
|
|
|
|
size += datalen;
|
|
|
|
/* patch size and flags */
|
|
|
|
if (size_ptr)
|
|
id3_render_syncsafe(&size_ptr, size - 10, 4);
|
|
if (flags_ptr)
|
|
id3_render_int(&flags_ptr, flags, 2);
|
|
|
|
return size;
|
|
}
|