1/* flac - Command-line FLAC encoder/decoder 2 * Copyright (C) 2002,2003,2004,2005,2006,2007 Josh Coalson 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program 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 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 */ 18 19#if HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include "vorbiscomment.h" 24#include "FLAC/assert.h" 25#include "FLAC/metadata.h" 26#include "share/grabbag.h" /* for grabbag__file_get_filesize() */ 27#include "share/utf8.h" 28#include <ctype.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32 33 34/* 35 * This struct and the following 4 static functions are copied from 36 * ../metaflac/. Maybe someday there will be a convenience 37 * library for Vorbis comment parsing. 38 */ 39typedef struct { 40 char *field; /* the whole field as passed on the command line, i.e. "NAME=VALUE" */ 41 char *field_name; 42 /* according to the vorbis spec, field values can contain \0 so simple C strings are not enough here */ 43 unsigned field_value_length; 44 char *field_value; 45 FLAC__bool field_value_from_file; /* true if field_value holds a filename for the value, false for plain value */ 46} Argument_VcField; 47 48static void die(const char *message) 49{ 50 FLAC__ASSERT(0 != message); 51 fprintf(stderr, "ERROR: %s\n", message); 52 exit(1); 53} 54 55static char *local_strdup(const char *source) 56{ 57 char *ret; 58 FLAC__ASSERT(0 != source); 59 if(0 == (ret = strdup(source))) 60 die("out of memory during strdup()"); 61 return ret; 62} 63 64static FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation) 65{ 66 static const char * const violations[] = { 67 "field name contains invalid character", 68 "field contains no '=' character" 69 }; 70 71 char *p, *q, *s; 72 73 if(0 != field) 74 *field = local_strdup(field_ref); 75 76 s = local_strdup(field_ref); 77 78 if(0 == (p = strchr(s, '='))) { 79 free(s); 80 *violation = violations[1]; 81 return false; 82 } 83 *p++ = '\0'; 84 85 for(q = s; *q; q++) { 86 if(*q < 0x20 || *q > 0x7d || *q == 0x3d) { 87 free(s); 88 *violation = violations[0]; 89 return false; 90 } 91 } 92 93 *name = local_strdup(s); 94 *value = local_strdup(p); 95 *length = strlen(p); 96 97 free(s); 98 return true; 99} 100 101/* slight modification: no 'filename' arg, and errors are passed back in 'violation' instead of printed to stderr */ 102static FLAC__bool set_vc_field(FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw, const char **violation) 103{ 104 FLAC__StreamMetadata_VorbisComment_Entry entry; 105 char *converted; 106 107 FLAC__ASSERT(0 != block); 108 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 109 FLAC__ASSERT(0 != field); 110 FLAC__ASSERT(0 != needs_write); 111 112 if(field->field_value_from_file) { 113 /* read the file into 'data' */ 114 FILE *f = 0; 115 char *data = 0; 116 const off_t size = grabbag__file_get_filesize(field->field_value); 117 if(size < 0) { 118 *violation = "can't open file for tag value"; 119 return false; 120 } 121 if(size >= 0x100000) { /* magic arbitrary limit, actual format limit is near 16MB */ 122 *violation = "file for tag value is too large"; 123 return false; 124 } 125 if(0 == (data = malloc(size+1))) 126 die("out of memory allocating tag value"); 127 data[size] = '\0'; 128 if(0 == (f = fopen(field->field_value, "rb")) || fread(data, 1, size, f) != (size_t)size) { 129 free(data); 130 if(f) 131 fclose(f); 132 *violation = "error while reading file for tag value"; 133 return false; 134 } 135 fclose(f); 136 if(strlen(data) != (size_t)size) { 137 free(data); 138 *violation = "file for tag value has embedded NULs"; 139 return false; 140 } 141 142 /* move 'data' into 'converted', converting to UTF-8 if necessary */ 143 if(raw) { 144 converted = data; 145 } 146 else if(utf8_encode(data, &converted) >= 0) { 147 free(data); 148 } 149 else { 150 free(data); 151 *violation = "error converting file contents to UTF-8 for tag value"; 152 return false; 153 } 154 155 /* create and entry and append it */ 156 if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field->field_name, converted)) { 157 free(converted); 158 *violation = "file for tag value is not valid UTF-8"; 159 return false; 160 } 161 free(converted); 162 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/false)) { 163 *violation = "memory allocation failure"; 164 return false; 165 } 166 167 *needs_write = true; 168 return true; 169 } 170 else { 171 FLAC__bool needs_free = false; 172 if(raw) { 173 entry.entry = (FLAC__byte *)field->field; 174 } 175 else if(utf8_encode(field->field, &converted) >= 0) { 176 entry.entry = (FLAC__byte *)converted; 177 needs_free = true; 178 } 179 else { 180 *violation = "error converting comment to UTF-8"; 181 return false; 182 } 183 entry.length = strlen((const char *)entry.entry); 184 if(!FLAC__format_vorbiscomment_entry_is_legal(entry.entry, entry.length)) { 185 if(needs_free) 186 free(converted); 187 /* 188 * our previous parsing has already established that the field 189 * name is OK, so it must be the field value 190 */ 191 *violation = "tag value for is not valid UTF-8"; 192 return false; 193 } 194 195 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) { 196 if(needs_free) 197 free(converted); 198 *violation = "memory allocation failure"; 199 return false; 200 } 201 202 *needs_write = true; 203 if(needs_free) 204 free(converted); 205 return true; 206 } 207} 208 209/* 210 * The rest of the code is novel 211 */ 212 213static void free_field(Argument_VcField *obj) 214{ 215 if(0 != obj->field) 216 free(obj->field); 217 if(0 != obj->field_name) 218 free(obj->field_name); 219 if(0 != obj->field_value) 220 free(obj->field_value); 221} 222 223FLAC__bool flac__vorbiscomment_add(FLAC__StreamMetadata *block, const char *comment, FLAC__bool value_from_file, FLAC__bool raw, const char **violation) 224{ 225 Argument_VcField parsed; 226 FLAC__bool dummy; 227 228 FLAC__ASSERT(0 != block); 229 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 230 FLAC__ASSERT(0 != comment); 231 232 memset(&parsed, 0, sizeof(parsed)); 233 234 parsed.field_value_from_file = value_from_file; 235 if(!parse_vorbis_comment_field(comment, &(parsed.field), &(parsed.field_name), &(parsed.field_value), &(parsed.field_value_length), violation)) { 236 free_field(&parsed); 237 return false; 238 } 239 240 if(!set_vc_field(block, &parsed, &dummy, raw, violation)) { 241 free_field(&parsed); 242 return false; 243 } 244 else { 245 free_field(&parsed); 246 return true; 247 } 248} 249