xref: /third_party/libsnd/src/ogg_vcomment.c (revision b815c7f3)
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