1/* 2** Copyright (C) 2008-2019 Erik de Castro Lopo <erikd@mega-nerd.com> 3** Copyright (C) 2018 Arthur Taylor <art@ified.ca> 4** 5** This program is free software ; you can redistribute it and/or modify 6** it under the terms of the GNU Lesser General Public License as published by 7** the Free Software Foundation ; either version 2.1 of the License, or 8** (at your option) any later version. 9** 10** This program is distributed in the hope that it will be useful, 11** but WITHOUT ANY WARRANTY ; without even the implied warranty of 12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13** GNU Lesser General Public License for more details. 14** 15** You should have received a copy of the GNU Lesser General Public License 16** along with this program ; if not, write to the Free Software 17** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18*/ 19 20#include "sfconfig.h" 21 22#include <stdio.h> 23#include <string.h> 24#include <ctype.h> 25 26#include "sndfile.h" 27#include "sfendian.h" 28#include "common.h" 29 30#if HAVE_EXTERNAL_XIPH_LIBS 31 32#include <ogg/ogg.h> 33 34#include "ogg_vcomment.h" 35 36typedef struct 37{ int id ; 38 const char *name ; 39} STR_PAIR ; 40 41/* See https://xiph.org/vorbis/doc/v-comment.html */ 42static STR_PAIR vorbiscomment_mapping [] = 43{ { SF_STR_TITLE, "TITLE" }, 44 { SF_STR_COPYRIGHT, "COPYRIGHT", }, 45 { SF_STR_SOFTWARE, "ENCODER", }, 46 { SF_STR_ARTIST, "ARTIST" }, 47 { SF_STR_COMMENT, "COMMENT" }, 48 { SF_STR_DATE, "DATE", }, 49 { SF_STR_ALBUM, "ALBUM" }, 50 { SF_STR_LICENSE, "LICENSE", }, 51 { SF_STR_TRACKNUMBER, "TRACKNUMBER", }, 52 { SF_STR_GENRE, "GENRE", }, 53 { 0, NULL, }, 54} ; 55 56/*----------------------------------------------------------------------------------------------- 57** Private function prototypes. 58*/ 59 60static int vorbiscomment_lookup_id (const char *name) ; 61static const char * vorbiscomment_lookup_name (int id) ; 62 63static inline size_t read_32bit_size_t (const unsigned char * ptr) 64{ /* Read a 32 bit positive value from the provided pointer. */ 65 return LE2H_32_PTR (ptr) & 0x7fffffff ; 66} /* read_32bit_size_t */ 67 68/*----------------------------------------------------------------------------------------------- 69** Exported functions. 70*/ 71 72int 73vorbiscomment_read_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident) 74{ unsigned char *p, *ep ; 75 char *tag, *c ; 76 size_t tag_size, tag_len = 0 ; 77 unsigned int ntags, i = 0 ; 78 int id, ret = 0 ; 79 80 /* 81 ** The smallest possible header is the ident string length plus two 4-byte 82 ** integers, (vender string length, tags count.) 83 */ 84 if (packet->bytes < (ident ? ident->length : 0) + 4 + 4) 85 return SFE_MALFORMED_FILE ; 86 87 /* Our working pointer. */ 88 p = packet->packet ; 89 /* Our end pointer for bound checking. */ 90 ep = p + packet->bytes ; 91 92 if (ident) 93 { if (memcmp (p, ident->ident, ident->length) != 0) 94 { psf_log_printf (psf, "Expected comment packet identifier missing.\n") ; 95 return SFE_MALFORMED_FILE ; 96 } ; 97 p += ident->length ; 98 } ; 99 100 tag_size = 1024 ; 101 tag = malloc (tag_size) ; 102 /* Unlikely */ 103 if (!tag) 104 return SFE_MALLOC_FAILED ; 105 106 psf_log_printf (psf, "VorbisComment Metadata\n") ; 107 108 /* 109 ** Vendor tag, manditory, no field name. 110 */ 111 tag_len = read_32bit_size_t (p) ; 112 p += 4 ; 113 if (tag_len > 0) 114 { /* Bound checking. 4 bytes for remaining manditory fields. */ 115 if (p + tag_len + 4 > ep) 116 { ret = SFE_MALFORMED_FILE ; 117 goto free_tag_out ; 118 } ; 119 if (tag_len > tag_size - 1) 120 { free (tag) ; 121 tag_size = tag_len + 1 ; 122 tag = malloc (tag_size) ; 123 /* Unlikely */ 124 if (!tag) 125 return SFE_MALLOC_FAILED ; 126 } ; 127 memcpy (tag, p, tag_len) ; p += tag_len ; 128 tag [tag_len] = '\0' ; 129 psf_log_printf (psf, " Vendor: %s\n", tag) ; 130 } ; 131 132 /* 133 ** List of tags of the form NAME=value 134 ** Allowable characters for NAME are the same as shell variable names. 135 */ 136 ntags = read_32bit_size_t (p) ; 137 p += 4 ; 138 for (i = 0 ; i < ntags ; i++) 139 { if (p + 4 > ep) 140 { ret = SFE_MALFORMED_FILE ; 141 goto free_tag_out ; 142 } ; 143 tag_len = read_32bit_size_t (p) ; 144 p += 4 ; 145 if (p + tag_len > ep) 146 { ret = SFE_MALFORMED_FILE ; 147 goto free_tag_out ; 148 } ; 149 if (tag_len > tag_size - 1) 150 { free (tag) ; 151 tag_size = tag_len + 1 ; 152 tag = malloc (tag_size) ; 153 /* Unlikely */ 154 if (!tag) 155 return SFE_MALLOC_FAILED ; 156 } ; 157 memcpy (tag, p, tag_len) ; p += tag_len ; 158 tag [tag_len] = '\0' ; 159 psf_log_printf (psf, " %s\n", tag) ; 160 for (c = tag ; *c ; c++) 161 { if (*c == '=') 162 break ; 163 *c = toupper (*c) ; 164 } ; 165 if (!c) 166 { psf_log_printf (psf, "Malformed Vorbis comment, no '=' found.\n") ; 167 continue ; 168 } ; 169 *c = '\0' ; 170 if ((id = vorbiscomment_lookup_id (tag)) != 0) 171 psf_store_string (psf, id, c + 1) ; 172 } ; 173 174free_tag_out: 175 if (tag != NULL) 176 free (tag) ; 177 return ret ; 178} /* vorbiscomment_read_tags */ 179 180int 181vorbiscomment_write_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident, const char *vendor, int targetsize) 182{ int i, ntags ; 183 int tags_start ; 184 const char *tag_name ; 185 int tag_name_len, tag_body_len ; 186 187 psf->header.ptr [0] = 0 ; 188 psf->header.indx = 0 ; 189 190 /* Packet identifier */ 191 if (ident) 192 psf_binheader_writef (psf, "eb", BHWv (ident->ident), BHWz (ident->length)) ; 193 194 /* Manditory Vendor Tag */ 195 tag_name_len = vendor ? strlen (vendor) : 0 ; 196 psf_binheader_writef (psf, "e4b", BHW4 (tag_name_len), BHWv (vendor), BHWz (tag_name_len)) ; 197 198 /* Tags Count. Skip for now, write after. */ 199 tags_start = psf->header.indx ; 200 psf_binheader_writef (psf, "j", BHWj (4)) ; 201 202 ntags = 0 ; 203 /* Write each tag */ 204 for (i = 0 ; i < SF_MAX_STRINGS ; i++) 205 { if (psf->strings.data [i].type == 0) 206 continue ; 207 208 tag_name = vorbiscomment_lookup_name (psf->strings.data [i].type) ; 209 if (tag_name == NULL) 210 continue ; 211 212 tag_name_len = strlen (tag_name) ; 213 tag_body_len = strlen (psf->strings.storage + psf->strings.data [i].offset) ; 214 if (targetsize > 0 && tag_name_len + tag_body_len + psf->header.indx > targetsize) 215 { /* If we are out of space, stop now. */ 216 return SFE_STR_MAX_DATA ; 217 } 218 psf_binheader_writef (psf, "e4b1b", 219 BHW4 (tag_name_len + 1 + tag_body_len), 220 BHWv (tag_name), BHWz (tag_name_len), 221 BHW1 ('='), 222 BHWv (psf->strings.storage + psf->strings.data [i].offset), BHWz (tag_body_len)) ; 223 ntags++ ; 224 } ; 225 226 if (targetsize < 0) 227 { /* 228 ** Padding. 229 ** 230 ** Pad to a minimum of -targetsize, but make sure length % 255 231 ** = 254 so that we get the most out of the ogg segment lacing. 232 */ 233 psf_binheader_writef (psf, "z", BHWz ((psf->header.indx + -targetsize + 255) / 255 * 255 - 1)) ; 234 } 235 else if (targetsize > 0) 236 psf_binheader_writef (psf, "z", BHWz (targetsize - psf->header.indx)) ; 237 238 packet->packet = psf->header.ptr ; 239 packet->bytes = psf->header.indx ; 240 packet->b_o_s = 0 ; 241 packet->e_o_s = 0 ; 242 243 /* Seek back and write the tag count. */ 244 psf_binheader_writef (psf, "eo4", BHWo (tags_start), BHW4 (ntags)) ; 245 246 return 0 ; 247} /* vorbiscomment_write_tags */ 248 249/*============================================================================== 250** Private functions. 251*/ 252 253static int 254vorbiscomment_lookup_id (const char * name) 255{ STR_PAIR *p ; 256 257 for (p = vorbiscomment_mapping ; p->id ; p++) 258 { if (!strcmp (name, p->name)) 259 return p->id ; 260 } ; 261 262 return 0 ; 263} /* vorbiscomment_lookup_id */ 264 265static const char * 266vorbiscomment_lookup_name (int id) 267{ STR_PAIR *p ; 268 269 for (p = vorbiscomment_mapping ; p->id ; p++) 270 { if (p->id == id) 271 return p->name ; 272 } ; 273 274 return NULL ; 275} /* vorbiscomment_lookup_name */ 276 277#endif /* HAVE_EXTERNAL_XIPH_LIBS */ 278