• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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