/* ** Copyright (C) 2008-2019 Erik de Castro Lopo ** Copyright (C) 2018 Arthur Taylor ** ** This program is free software ; you can redistribute it and/or modify ** it under the terms of the GNU Lesser General Public License as published by ** the Free Software Foundation ; either version 2.1 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 Lesser General Public License for more details. ** ** You should have received a copy of the GNU Lesser 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. */ #include "sfconfig.h" #include #include #include #include "sndfile.h" #include "sfendian.h" #include "common.h" #if HAVE_EXTERNAL_XIPH_LIBS #include #include "ogg_vcomment.h" typedef struct { int id ; const char *name ; } STR_PAIR ; /* See https://xiph.org/vorbis/doc/v-comment.html */ static STR_PAIR vorbiscomment_mapping [] = { { SF_STR_TITLE, "TITLE" }, { SF_STR_COPYRIGHT, "COPYRIGHT", }, { SF_STR_SOFTWARE, "ENCODER", }, { SF_STR_ARTIST, "ARTIST" }, { SF_STR_COMMENT, "COMMENT" }, { SF_STR_DATE, "DATE", }, { SF_STR_ALBUM, "ALBUM" }, { SF_STR_LICENSE, "LICENSE", }, { SF_STR_TRACKNUMBER, "TRACKNUMBER", }, { SF_STR_GENRE, "GENRE", }, { 0, NULL, }, } ; /*----------------------------------------------------------------------------------------------- ** Private function prototypes. */ static int vorbiscomment_lookup_id (const char *name) ; static const char * vorbiscomment_lookup_name (int id) ; static inline size_t read_32bit_size_t (const unsigned char * ptr) { /* Read a 32 bit positive value from the provided pointer. */ return LE2H_32_PTR (ptr) & 0x7fffffff ; } /* read_32bit_size_t */ /*----------------------------------------------------------------------------------------------- ** Exported functions. */ int vorbiscomment_read_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident) { unsigned char *p, *ep ; char *tag, *c ; size_t tag_size, tag_len = 0 ; unsigned int ntags, i = 0 ; int id, ret = 0 ; /* ** The smallest possible header is the ident string length plus two 4-byte ** integers, (vender string length, tags count.) */ if (packet->bytes < (ident ? ident->length : 0) + 4 + 4) return SFE_MALFORMED_FILE ; /* Our working pointer. */ p = packet->packet ; /* Our end pointer for bound checking. */ ep = p + packet->bytes ; if (ident) { if (memcmp (p, ident->ident, ident->length) != 0) { psf_log_printf (psf, "Expected comment packet identifier missing.\n") ; return SFE_MALFORMED_FILE ; } ; p += ident->length ; } ; tag_size = 1024 ; tag = malloc (tag_size) ; /* Unlikely */ if (!tag) return SFE_MALLOC_FAILED ; psf_log_printf (psf, "VorbisComment Metadata\n") ; /* ** Vendor tag, manditory, no field name. */ tag_len = read_32bit_size_t (p) ; p += 4 ; if (tag_len > 0) { /* Bound checking. 4 bytes for remaining manditory fields. */ if (p + tag_len + 4 > ep) { ret = SFE_MALFORMED_FILE ; goto free_tag_out ; } ; if (tag_len > tag_size - 1) { free (tag) ; tag_size = tag_len + 1 ; tag = malloc (tag_size) ; /* Unlikely */ if (!tag) return SFE_MALLOC_FAILED ; } ; memcpy (tag, p, tag_len) ; p += tag_len ; tag [tag_len] = '\0' ; psf_log_printf (psf, " Vendor: %s\n", tag) ; } ; /* ** List of tags of the form NAME=value ** Allowable characters for NAME are the same as shell variable names. */ ntags = read_32bit_size_t (p) ; p += 4 ; for (i = 0 ; i < ntags ; i++) { if (p + 4 > ep) { ret = SFE_MALFORMED_FILE ; goto free_tag_out ; } ; tag_len = read_32bit_size_t (p) ; p += 4 ; if (p + tag_len > ep) { ret = SFE_MALFORMED_FILE ; goto free_tag_out ; } ; if (tag_len > tag_size - 1) { free (tag) ; tag_size = tag_len + 1 ; tag = malloc (tag_size) ; /* Unlikely */ if (!tag) return SFE_MALLOC_FAILED ; } ; memcpy (tag, p, tag_len) ; p += tag_len ; tag [tag_len] = '\0' ; psf_log_printf (psf, " %s\n", tag) ; for (c = tag ; *c ; c++) { if (*c == '=') break ; *c = toupper (*c) ; } ; if (!c) { psf_log_printf (psf, "Malformed Vorbis comment, no '=' found.\n") ; continue ; } ; *c = '\0' ; if ((id = vorbiscomment_lookup_id (tag)) != 0) psf_store_string (psf, id, c + 1) ; } ; free_tag_out: if (tag != NULL) free (tag) ; return ret ; } /* vorbiscomment_read_tags */ int vorbiscomment_write_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident, const char *vendor, int targetsize) { int i, ntags ; int tags_start ; const char *tag_name ; int tag_name_len, tag_body_len ; psf->header.ptr [0] = 0 ; psf->header.indx = 0 ; /* Packet identifier */ if (ident) psf_binheader_writef (psf, "eb", BHWv (ident->ident), BHWz (ident->length)) ; /* Manditory Vendor Tag */ tag_name_len = vendor ? strlen (vendor) : 0 ; psf_binheader_writef (psf, "e4b", BHW4 (tag_name_len), BHWv (vendor), BHWz (tag_name_len)) ; /* Tags Count. Skip for now, write after. */ tags_start = psf->header.indx ; psf_binheader_writef (psf, "j", BHWj (4)) ; ntags = 0 ; /* Write each tag */ for (i = 0 ; i < SF_MAX_STRINGS ; i++) { if (psf->strings.data [i].type == 0) continue ; tag_name = vorbiscomment_lookup_name (psf->strings.data [i].type) ; if (tag_name == NULL) continue ; tag_name_len = strlen (tag_name) ; tag_body_len = strlen (psf->strings.storage + psf->strings.data [i].offset) ; if (targetsize > 0 && tag_name_len + tag_body_len + psf->header.indx > targetsize) { /* If we are out of space, stop now. */ return SFE_STR_MAX_DATA ; } psf_binheader_writef (psf, "e4b1b", BHW4 (tag_name_len + 1 + tag_body_len), BHWv (tag_name), BHWz (tag_name_len), BHW1 ('='), BHWv (psf->strings.storage + psf->strings.data [i].offset), BHWz (tag_body_len)) ; ntags++ ; } ; if (targetsize < 0) { /* ** Padding. ** ** Pad to a minimum of -targetsize, but make sure length % 255 ** = 254 so that we get the most out of the ogg segment lacing. */ psf_binheader_writef (psf, "z", BHWz ((psf->header.indx + -targetsize + 255) / 255 * 255 - 1)) ; } else if (targetsize > 0) psf_binheader_writef (psf, "z", BHWz (targetsize - psf->header.indx)) ; packet->packet = psf->header.ptr ; packet->bytes = psf->header.indx ; packet->b_o_s = 0 ; packet->e_o_s = 0 ; /* Seek back and write the tag count. */ psf_binheader_writef (psf, "eo4", BHWo (tags_start), BHW4 (ntags)) ; return 0 ; } /* vorbiscomment_write_tags */ /*============================================================================== ** Private functions. */ static int vorbiscomment_lookup_id (const char * name) { STR_PAIR *p ; for (p = vorbiscomment_mapping ; p->id ; p++) { if (!strcmp (name, p->name)) return p->id ; } ; return 0 ; } /* vorbiscomment_lookup_id */ static const char * vorbiscomment_lookup_name (int id) { STR_PAIR *p ; for (p = vorbiscomment_mapping ; p->id ; p++) { if (p->id == id) return p->name ; } ; return NULL ; } /* vorbiscomment_lookup_name */ #endif /* HAVE_EXTERNAL_XIPH_LIBS */