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