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
36 typedef struct
37 { int id ;
38 const char *name ;
39 } STR_PAIR ;
40
41 /* See https://xiph.org/vorbis/doc/v-comment.html */
42 static 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
60 static int vorbiscomment_lookup_id (const char *name) ;
61 static const char * vorbiscomment_lookup_name (int id) ;
62
read_32bit_size_t(const unsigned char * ptr)63 static 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
72 int
vorbiscomment_read_tags(SF_PRIVATE * psf,ogg_packet * packet,vorbiscomment_ident * ident)73 vorbiscomment_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
174 free_tag_out:
175 if (tag != NULL)
176 free (tag) ;
177 return ret ;
178 } /* vorbiscomment_read_tags */
179
180 int
vorbiscomment_write_tags(SF_PRIVATE * psf,ogg_packet * packet,vorbiscomment_ident * ident,const char * vendor,int targetsize)181 vorbiscomment_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
253 static int
vorbiscomment_lookup_id(const char * name)254 vorbiscomment_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
265 static const char *
vorbiscomment_lookup_name(int id)266 vorbiscomment_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