/* * 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 #include #ifdef HAVE_ASSERT_H # include #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((char*)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((char*)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((char*)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 = (id3_byte_t*) 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 = (id3_byte_t*) 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((char*) 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((char*) 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((char*) id, data, end - data); goto done; } /* * generate the internal frame structure */ frame = id3_frame_new((char*) 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; }