1/* plugin_common - Routines common to several plugins
2 * Copyright (C) 2002,2003,2004,2005,2006,2007  Josh Coalson
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19#if HAVE_CONFIG_H
20#  include <config.h>
21#endif
22
23#include <stdio.h>
24#include <string.h>
25#include <stdlib.h>
26
27#include "tags.h"
28#include "FLAC/assert.h"
29#include "FLAC/metadata.h"
30#include "share/alloc.h"
31
32#ifndef FLaC__INLINE
33#define FLaC__INLINE
34#endif
35
36
37static FLaC__INLINE size_t local__wide_strlen(const FLAC__uint16 *s)
38{
39	size_t n = 0;
40	while(*s++)
41		n++;
42	return n;
43}
44
45/*
46 * also disallows non-shortest-form encodings, c.f.
47 *   http://www.unicode.org/versions/corrigendum1.html
48 * and a more clear explanation at the end of this section:
49 *   http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
50 */
51static FLaC__INLINE size_t local__utf8len(const FLAC__byte *utf8)
52{
53	FLAC__ASSERT(0 != utf8);
54	if ((utf8[0] & 0x80) == 0) {
55		return 1;
56	}
57	else if ((utf8[0] & 0xE0) == 0xC0 && (utf8[1] & 0xC0) == 0x80) {
58		if ((utf8[0] & 0xFE) == 0xC0) /* overlong sequence check */
59			return 0;
60		return 2;
61	}
62	else if ((utf8[0] & 0xF0) == 0xE0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80) {
63		if (utf8[0] == 0xE0 && (utf8[1] & 0xE0) == 0x80) /* overlong sequence check */
64			return 0;
65		/* illegal surrogates check (U+D800...U+DFFF and U+FFFE...U+FFFF) */
66		if (utf8[0] == 0xED && (utf8[1] & 0xE0) == 0xA0) /* D800-DFFF */
67			return 0;
68		if (utf8[0] == 0xEF && utf8[1] == 0xBF && (utf8[2] & 0xFE) == 0xBE) /* FFFE-FFFF */
69			return 0;
70		return 3;
71	}
72	else if ((utf8[0] & 0xF8) == 0xF0 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80) {
73		if (utf8[0] == 0xF0 && (utf8[1] & 0xF0) == 0x80) /* overlong sequence check */
74			return 0;
75		return 4;
76	}
77	else if ((utf8[0] & 0xFC) == 0xF8 && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80) {
78		if (utf8[0] == 0xF8 && (utf8[1] & 0xF8) == 0x80) /* overlong sequence check */
79			return 0;
80		return 5;
81	}
82	else if ((utf8[0] & 0xFE) == 0xFC && (utf8[1] & 0xC0) == 0x80 && (utf8[2] & 0xC0) == 0x80 && (utf8[3] & 0xC0) == 0x80 && (utf8[4] & 0xC0) == 0x80 && (utf8[5] & 0xC0) == 0x80) {
83		if (utf8[0] == 0xFC && (utf8[1] & 0xFC) == 0x80) /* overlong sequence check */
84			return 0;
85		return 6;
86	}
87	else {
88		return 0;
89	}
90}
91
92
93static FLaC__INLINE size_t local__utf8_to_ucs2(const FLAC__byte *utf8, FLAC__uint16 *ucs2)
94{
95	const size_t len = local__utf8len(utf8);
96
97	FLAC__ASSERT(0 != ucs2);
98
99	if (len == 1)
100		*ucs2 = *utf8;
101	else if (len == 2)
102		*ucs2 = (*utf8 & 0x3F)<<6 | (*(utf8+1) & 0x3F);
103	else if (len == 3)
104		*ucs2 = (*utf8 & 0x1F)<<12 | (*(utf8+1) & 0x3F)<<6 | (*(utf8+2) & 0x3F);
105	else
106		*ucs2 = '?';
107
108	return len;
109}
110
111static FLAC__uint16 *local__convert_utf8_to_ucs2(const char *src, unsigned length)
112{
113	FLAC__uint16 *out;
114	size_t chars = 0;
115
116	FLAC__ASSERT(0 != src);
117
118	/* calculate length */
119	{
120		const unsigned char *s, *end;
121		for (s=(const unsigned char *)src, end=s+length; s<end; chars++) {
122			const unsigned n = local__utf8len(s);
123			if (n == 0)
124				return 0;
125			s += n;
126		}
127		FLAC__ASSERT(s == end);
128	}
129
130	/* allocate */
131	out = (FLAC__uint16*)safe_malloc_mul_2op_(chars, /*times*/sizeof(FLAC__uint16));
132	if (0 == out) {
133		FLAC__ASSERT(0);
134		return 0;
135	}
136
137	/* convert */
138	{
139		const unsigned char *s = (const unsigned char *)src;
140		FLAC__uint16 *u = out;
141		for ( ; chars; chars--)
142			s += local__utf8_to_ucs2(s, u++);
143	}
144
145	return out;
146}
147
148static FLaC__INLINE size_t local__ucs2len(FLAC__uint16 ucs2)
149{
150	if (ucs2 < 0x0080)
151		return 1;
152	else if (ucs2 < 0x0800)
153		return 2;
154	else
155		return 3;
156}
157
158static FLaC__INLINE size_t local__ucs2_to_utf8(FLAC__uint16 ucs2, FLAC__byte *utf8)
159{
160	if (ucs2 < 0x080) {
161		utf8[0] = (FLAC__byte)ucs2;
162		return 1;
163	}
164	else if (ucs2 < 0x800) {
165		utf8[0] = 0xc0 | (ucs2 >> 6);
166		utf8[1] = 0x80 | (ucs2 & 0x3f);
167		return 2;
168	}
169	else {
170		utf8[0] = 0xe0 | (ucs2 >> 12);
171		utf8[1] = 0x80 | ((ucs2 >> 6) & 0x3f);
172		utf8[2] = 0x80 | (ucs2 & 0x3f);
173		return 3;
174	}
175}
176
177static char *local__convert_ucs2_to_utf8(const FLAC__uint16 *src, unsigned length)
178{
179	char *out;
180	size_t len = 0, n;
181
182	FLAC__ASSERT(0 != src);
183
184	/* calculate length */
185	{
186		unsigned i;
187		for (i = 0; i < length; i++) {
188			n = local__ucs2len(src[i]);
189			if(len + n < len) /* overflow check */
190				return 0;
191			len += n;
192		}
193	}
194
195	/* allocate */
196	out = (char*)safe_malloc_mul_2op_(len, /*times*/sizeof(char));
197	if (0 == out)
198		return 0;
199
200	/* convert */
201	{
202		unsigned char *u = (unsigned char *)out;
203		for ( ; *src; src++)
204			u += local__ucs2_to_utf8(*src, u);
205		local__ucs2_to_utf8(*src, u);
206	}
207
208	return out;
209}
210
211
212FLAC__bool FLAC_plugin__tags_get(const char *filename, FLAC__StreamMetadata **tags)
213{
214	if(!FLAC__metadata_get_tags(filename, tags))
215		if(0 == (*tags = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)))
216			return false;
217	return true;
218}
219
220FLAC__bool FLAC_plugin__tags_set(const char *filename, const FLAC__StreamMetadata *tags)
221{
222	FLAC__Metadata_Chain *chain;
223	FLAC__Metadata_Iterator *iterator;
224	FLAC__StreamMetadata *block;
225	FLAC__bool got_vorbis_comments = false;
226	FLAC__bool ok;
227
228	if(0 == (chain = FLAC__metadata_chain_new()))
229		return false;
230
231	if(!FLAC__metadata_chain_read(chain, filename)) {
232		FLAC__metadata_chain_delete(chain);
233		return false;
234	}
235
236	if(0 == (iterator = FLAC__metadata_iterator_new())) {
237		FLAC__metadata_chain_delete(chain);
238		return false;
239	}
240
241	FLAC__metadata_iterator_init(iterator, chain);
242
243	do {
244		if(FLAC__metadata_iterator_get_block_type(iterator) == FLAC__METADATA_TYPE_VORBIS_COMMENT)
245			got_vorbis_comments = true;
246	} while(!got_vorbis_comments && FLAC__metadata_iterator_next(iterator));
247
248	if(0 == (block = FLAC__metadata_object_clone(tags))) {
249		FLAC__metadata_chain_delete(chain);
250		FLAC__metadata_iterator_delete(iterator);
251		return false;
252	}
253
254	if(got_vorbis_comments)
255		ok = FLAC__metadata_iterator_set_block(iterator, block);
256	else
257		ok = FLAC__metadata_iterator_insert_block_after(iterator, block);
258
259	FLAC__metadata_iterator_delete(iterator);
260
261	if(ok) {
262		FLAC__metadata_chain_sort_padding(chain);
263		ok = FLAC__metadata_chain_write(chain, /*use_padding=*/true, /*preserve_file_stats=*/true);
264	}
265
266	FLAC__metadata_chain_delete(chain);
267
268	return ok;
269}
270
271void FLAC_plugin__tags_destroy(FLAC__StreamMetadata **tags)
272{
273	FLAC__metadata_object_delete(*tags);
274	*tags = 0;
275}
276
277const char *FLAC_plugin__tags_get_tag_utf8(const FLAC__StreamMetadata *tags, const char *name)
278{
279	const int i = FLAC__metadata_object_vorbiscomment_find_entry_from(tags, /*offset=*/0, name);
280	return (i < 0? 0 : strchr((const char *)tags->data.vorbis_comment.comments[i].entry, '=')+1);
281}
282
283FLAC__uint16 *FLAC_plugin__tags_get_tag_ucs2(const FLAC__StreamMetadata *tags, const char *name)
284{
285	const char *utf8 = FLAC_plugin__tags_get_tag_utf8(tags, name);
286	if(0 == utf8)
287		return 0;
288	return local__convert_utf8_to_ucs2(utf8, strlen(utf8)+1); /* +1 for terminating null */
289}
290
291int FLAC_plugin__tags_delete_tag(FLAC__StreamMetadata *tags, const char *name)
292{
293	return FLAC__metadata_object_vorbiscomment_remove_entries_matching(tags, name);
294}
295
296int FLAC_plugin__tags_delete_all(FLAC__StreamMetadata *tags)
297{
298	int n = (int)tags->data.vorbis_comment.num_comments;
299	if(n > 0) {
300		if(!FLAC__metadata_object_vorbiscomment_resize_comments(tags, 0))
301			n = -1;
302	}
303	return n;
304}
305
306FLAC__bool FLAC_plugin__tags_add_tag_utf8(FLAC__StreamMetadata *tags, const char *name, const char *value, const char *separator)
307{
308	int i;
309
310	FLAC__ASSERT(0 != tags);
311	FLAC__ASSERT(0 != name);
312	FLAC__ASSERT(0 != value);
313
314	if(separator && (i = FLAC__metadata_object_vorbiscomment_find_entry_from(tags, /*offset=*/0, name)) >= 0) {
315		FLAC__StreamMetadata_VorbisComment_Entry *entry = tags->data.vorbis_comment.comments+i;
316		const size_t value_len = strlen(value);
317		const size_t separator_len = strlen(separator);
318		FLAC__byte *new_entry;
319		if(0 == (new_entry = (FLAC__byte*)safe_realloc_add_4op_(entry->entry, entry->length, /*+*/value_len, /*+*/separator_len, /*+*/1)))
320			return false;
321		memcpy(new_entry+entry->length, separator, separator_len);
322		entry->length += separator_len;
323		memcpy(new_entry+entry->length, value, value_len);
324		entry->length += value_len;
325		new_entry[entry->length] = '\0';
326		entry->entry = new_entry;
327	}
328	else {
329		FLAC__StreamMetadata_VorbisComment_Entry entry;
330		if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, value))
331			return false;
332		FLAC__metadata_object_vorbiscomment_append_comment(tags, entry, /*copy=*/false);
333	}
334	return true;
335}
336
337FLAC__bool FLAC_plugin__tags_set_tag_ucs2(FLAC__StreamMetadata *tags, const char *name, const FLAC__uint16 *value, FLAC__bool replace_all)
338{
339	FLAC__StreamMetadata_VorbisComment_Entry entry;
340
341	FLAC__ASSERT(0 != tags);
342	FLAC__ASSERT(0 != name);
343	FLAC__ASSERT(0 != value);
344
345	{
346		char *utf8 = local__convert_ucs2_to_utf8(value, local__wide_strlen(value)+1); /* +1 for the terminating null */
347		if(0 == utf8)
348			return false;
349		if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, name, utf8)) {
350			free(utf8);
351			return false;
352		}
353		free(utf8);
354	}
355	if(!FLAC__metadata_object_vorbiscomment_replace_comment(tags, entry, replace_all, /*copy=*/false))
356		return false;
357	return true;
358}
359