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