1 /* metaflac - Command-line FLAC metadata editor
2 * Copyright (C) 2001-2009 Josh Coalson
3 * Copyright (C) 2011-2023 Xiph.Org Foundation
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include "options.h"
25 #include "utils.h"
26 #include "FLAC/assert.h"
27 #include "share/grabbag.h" /* for grabbag__file_get_filesize() */
28 #include "share/utf8.h"
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include "operations_shorthand.h"
33 #include "share/compat.h"
34
35 static FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write);
36 static FLAC__bool remove_vc_all_except(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
37 static FLAC__bool remove_vc_field(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
38 static FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
39 static FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw);
40 static FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw);
41 static FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw);
42
do_shorthand_operation__vorbis_comment(const char * filename,FLAC__bool prefix_with_filename,FLAC__Metadata_Chain * chain,const Operation * operation,FLAC__bool * needs_write,FLAC__bool raw)43 FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__bool prefix_with_filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw)
44 {
45 FLAC__bool ok = true, found_vc_block = false;
46 FLAC__StreamMetadata *block = 0;
47 FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
48
49 if(0 == iterator)
50 die("out of memory allocating iterator");
51
52 FLAC__metadata_iterator_init(iterator, chain);
53
54 do {
55 block = FLAC__metadata_iterator_get_block(iterator);
56 if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
57 found_vc_block = true;
58 } while(!found_vc_block && FLAC__metadata_iterator_next(iterator));
59
60 if(!found_vc_block) {
61 /* create a new block if necessary */
62 if(operation->type == OP__SET_VC_FIELD || operation->type == OP__IMPORT_VC_FROM) {
63 block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
64 if(0 == block)
65 die("out of memory allocating VORBIS_COMMENT block");
66 while(FLAC__metadata_iterator_next(iterator))
67 ;
68 if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) {
69 print_error_with_chain_status(chain, "%s: ERROR: adding new VORBIS_COMMENT block to metadata", filename);
70 return false;
71 }
72 /* iterator is left pointing to new block */
73 FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block);
74 }
75 else {
76 FLAC__metadata_iterator_delete(iterator);
77 return ok;
78 }
79 }
80
81 FLAC__ASSERT(0 != block);
82 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
83
84 switch(operation->type) {
85 case OP__SHOW_VC_VENDOR:
86 write_vc_field(prefix_with_filename? filename : 0, &block->data.vorbis_comment.vendor_string, raw, stdout);
87 break;
88 case OP__SHOW_VC_FIELD:
89 write_vc_fields(prefix_with_filename? filename : 0, operation->argument.vc_field_name.value, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw, stdout);
90 break;
91 case OP__REMOVE_VC_ALL:
92 ok = remove_vc_all(filename, block, needs_write);
93 break;
94 case OP__REMOVE_VC_ALL_EXCEPT:
95 ok = remove_vc_all_except(filename, block, operation->argument.vc_field_name.value, needs_write);
96 break;
97 case OP__REMOVE_VC_FIELD:
98 ok = remove_vc_field(filename, block, operation->argument.vc_field_name.value, needs_write);
99 break;
100 case OP__REMOVE_VC_FIRSTFIELD:
101 ok = remove_vc_firstfield(filename, block, operation->argument.vc_field_name.value, needs_write);
102 break;
103 case OP__SET_VC_FIELD:
104 #ifdef _WIN32 /* do not convert anything or things will break */
105 ok = set_vc_field(filename, block, &operation->argument.vc_field, needs_write, true);
106 #else
107 ok = set_vc_field(filename, block, &operation->argument.vc_field, needs_write, raw);
108 #endif
109 break;
110 case OP__IMPORT_VC_FROM:
111 ok = import_vc_from(filename, block, &operation->argument.filename, needs_write, raw);
112 break;
113 case OP__EXPORT_VC_TO:
114 ok = export_vc_to(filename, block, &operation->argument.filename, raw);
115 break;
116 default:
117 ok = false;
118 FLAC__ASSERT(0);
119 break;
120 };
121
122 FLAC__metadata_iterator_delete(iterator);
123 return ok;
124 }
125
126 /*
127 * local routines
128 */
129
remove_vc_all(const char * filename,FLAC__StreamMetadata * block,FLAC__bool * needs_write)130 FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write)
131 {
132 FLAC__ASSERT(0 != block);
133 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
134 FLAC__ASSERT(0 != needs_write);
135
136 if(0 != block->data.vorbis_comment.comments) {
137 FLAC__ASSERT(block->data.vorbis_comment.num_comments > 0);
138 if(!FLAC__metadata_object_vorbiscomment_resize_comments(block, 0)) {
139 flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
140 return false;
141 }
142 *needs_write = true;
143 }
144 else {
145 FLAC__ASSERT(block->data.vorbis_comment.num_comments == 0);
146 }
147
148 return true;
149 }
150
remove_vc_all_except(const char * filename,FLAC__StreamMetadata * block,const char * field_name,FLAC__bool * needs_write)151 FLAC__bool remove_vc_all_except(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write)
152 {
153 char * field_names[200];
154 uint32_t field_name_length, i;
155 int j, num_field_names;
156
157 FLAC__ASSERT(0 != block);
158 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
159 FLAC__ASSERT(0 != needs_write);
160
161 field_name_length = strlen(field_name);
162 field_names[0] = (void *)field_name;
163
164 for(num_field_names = 1; num_field_names < 200; num_field_names++) {
165 char * separator = strchr(field_names[num_field_names-1], '=');
166 if(separator == 0 || separator >= field_name + field_name_length)
167 break;
168 field_names[num_field_names] = separator+1;
169 }
170
171 if(num_field_names > 200) {
172 flac_fprintf(stderr, "%s: ERROR: too many field names\n", filename);
173 return false;
174 }
175
176 for(i = 0; i < block->data.vorbis_comment.num_comments; ) {
177 int field_name_found = false;
178 for(j = 0; j < num_field_names; j++) {
179 const uint32_t length = (j == (num_field_names - 1))?(uint32_t)strlen(field_names[j]):(uint32_t)(strchr(field_names[j],'=')-field_names[j]);
180 if(FLAC__metadata_object_vorbiscomment_entry_matches(block->data.vorbis_comment.comments[i], field_names[j], length)) {
181 field_name_found = true;
182 break;
183 }
184 }
185 if(!field_name_found) {
186 FLAC__metadata_object_vorbiscomment_delete_comment(block, i);
187 *needs_write = true;
188 }
189 else
190 i++;
191 }
192
193 return true;
194 }
195
remove_vc_field(const char * filename,FLAC__StreamMetadata * block,const char * field_name,FLAC__bool * needs_write)196 FLAC__bool remove_vc_field(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write)
197 {
198 int n;
199
200 FLAC__ASSERT(0 != needs_write);
201
202 n = FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, field_name);
203
204 if(n < 0) {
205 flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
206 return false;
207 }
208 else if(n > 0)
209 *needs_write = true;
210
211 return true;
212 }
213
remove_vc_firstfield(const char * filename,FLAC__StreamMetadata * block,const char * field_name,FLAC__bool * needs_write)214 FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write)
215 {
216 int n;
217
218 FLAC__ASSERT(0 != needs_write);
219
220 n = FLAC__metadata_object_vorbiscomment_remove_entry_matching(block, field_name);
221
222 if(n < 0) {
223 flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
224 return false;
225 }
226 else if(n > 0)
227 *needs_write = true;
228
229 return true;
230 }
231
set_vc_field(const char * filename,FLAC__StreamMetadata * block,const Argument_VcField * field,FLAC__bool * needs_write,FLAC__bool raw)232 FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw)
233 {
234 FLAC__StreamMetadata_VorbisComment_Entry entry;
235 char *converted;
236
237 FLAC__ASSERT(0 != block);
238 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
239 FLAC__ASSERT(0 != field);
240 FLAC__ASSERT(0 != needs_write);
241
242 if(field->field_value_from_file) {
243 /* read the file into 'data' */
244 FILE *f = 0;
245 char *data = 0;
246 const FLAC__off_t size = grabbag__file_get_filesize(field->field_value);
247 if(size < 0) {
248 flac_fprintf(stderr, "%s: ERROR: can't open file '%s' for '%s' tag value\n", filename, field->field_value, field->field_name);
249 return false;
250 }
251 if(size >= 0x100000) { /* magic arbitrary limit, actual format limit is near 16MB */
252 flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is too large\n", filename, field->field_value, field->field_name);
253 return false;
254 }
255 if(0 == (data = malloc(size+1)))
256 die("out of memory allocating tag value");
257 data[size] = '\0';
258 if(0 == (f = flac_fopen(field->field_value, "rb")) || fread(data, 1, size, f) != (size_t)size) {
259 flac_fprintf(stderr, "%s: ERROR: while reading file '%s' for '%s' tag value: %s\n", filename, field->field_value, field->field_name, strerror(errno));
260 free(data);
261 if(f)
262 fclose(f);
263 return false;
264 }
265 fclose(f);
266 if(strlen(data) != (size_t)size) {
267 free(data);
268 flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value has embedded NULs\n", filename, field->field_value, field->field_name);
269 return false;
270 }
271
272 /* move 'data' into 'converted', converting to UTF-8 if necessary */
273 if(raw) {
274 converted = data;
275 }
276 else if(utf8_encode(data, &converted) >= 0) {
277 free(data);
278 }
279 else {
280 free(data);
281 flac_fprintf(stderr, "%s: ERROR: converting file '%s' contents to UTF-8 for tag value\n", filename, field->field_value);
282 return false;
283 }
284
285 /* create and entry and append it */
286 if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field->field_name, converted)) {
287 free(converted);
288 flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is not valid UTF-8\n", filename, field->field_value, field->field_name);
289 return false;
290 }
291 free(converted);
292 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/false)) {
293 flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
294 return false;
295 }
296
297 *needs_write = true;
298 return true;
299 }
300 else {
301 FLAC__bool needs_free = false;
302 entry.entry = (FLAC__byte *)field->field;
303 if(raw) {
304 entry.entry = (FLAC__byte *)field->field;
305 }
306 else if(utf8_encode(field->field, &converted) >= 0) {
307 entry.entry = (FLAC__byte *)converted;
308 needs_free = true;
309 }
310 else {
311 flac_fprintf(stderr, "%s: ERROR: converting comment '%s' to UTF-8\n", filename, field->field);
312 return false;
313 }
314 entry.length = strlen((const char *)entry.entry);
315 if(!FLAC__format_vorbiscomment_entry_is_legal(entry.entry, entry.length)) {
316 if(needs_free)
317 free(converted);
318 /*
319 * our previous parsing has already established that the field
320 * name is OK, so it must be the field value
321 */
322 flac_fprintf(stderr, "%s: ERROR: tag value for '%s' is not valid UTF-8\n", filename, field->field_name);
323 return false;
324 }
325
326 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) {
327 if(needs_free)
328 free(converted);
329 flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
330 return false;
331 }
332
333 *needs_write = true;
334 if(needs_free)
335 free(converted);
336 return true;
337 }
338 }
339
import_vc_from(const char * filename,FLAC__StreamMetadata * block,const Argument_String * vc_filename,FLAC__bool * needs_write,FLAC__bool raw)340 FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw)
341 {
342 FILE *f;
343 char line[65536];
344 FLAC__bool ret;
345
346 if(0 == vc_filename->value || strlen(vc_filename->value) == 0) {
347 flac_fprintf(stderr, "%s: ERROR: empty import file name\n", filename);
348 return false;
349 }
350 if(0 == strcmp(vc_filename->value, "-"))
351 f = stdin;
352 else
353 f = flac_fopen(vc_filename->value, "r");
354
355 if(0 == f) {
356 flac_fprintf(stderr, "%s: ERROR: can't open import file %s: %s\n", filename, vc_filename->value, strerror(errno));
357 return false;
358 }
359
360 ret = true;
361 while(ret && !feof(f) && fgets(line, sizeof(line), f) != NULL) {
362 if(!feof(f)) {
363 char *p = strchr(line, '\n');
364 if(0 == p) {
365 flac_fprintf(stderr, "%s: ERROR: line too long, aborting\n", vc_filename->value);
366 ret = false;
367 }
368 else {
369 const char *violation;
370 Argument_VcField field;
371 *p = '\0';
372 memset(&field, 0, sizeof(Argument_VcField));
373 field.field_value_from_file = false;
374 if(!parse_vorbis_comment_field(line, &field.field, &field.field_name, &field.field_value, &field.field_value_length, &violation)) {
375 FLAC__ASSERT(0 != violation);
376 flac_fprintf(stderr, "%s: ERROR: malformed vorbis comment field \"%s\",\n %s\n", vc_filename->value, line, violation);
377 ret = false;
378 }
379 else {
380 ret = set_vc_field(filename, block, &field, needs_write, raw);
381 }
382 if(0 != field.field)
383 free(field.field);
384 if(0 != field.field_name)
385 free(field.field_name);
386 if(0 != field.field_value)
387 free(field.field_value);
388 }
389 }
390 };
391
392 if(f != stdin)
393 fclose(f);
394 return ret;
395 }
396
export_vc_to(const char * filename,FLAC__StreamMetadata * block,const Argument_String * vc_filename,FLAC__bool raw)397 FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw)
398 {
399 FILE *f;
400 FLAC__bool ret;
401
402 if(0 == vc_filename->value || strlen(vc_filename->value) == 0) {
403 flac_fprintf(stderr, "%s: ERROR: empty export file name\n", filename);
404 return false;
405 }
406 if(0 == strcmp(vc_filename->value, "-"))
407 f = stdout;
408 else
409 f = flac_fopen(vc_filename->value, "w");
410
411 if(0 == f) {
412 flac_fprintf(stderr, "%s: ERROR: can't open export file %s: %s\n", filename, vc_filename->value, strerror(errno));
413 return false;
414 }
415
416 ret = true;
417
418 write_vc_fields(0, 0, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw, f);
419
420 if(f != stdout)
421 fclose(f);
422
423 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
424 /* Delete output file when fuzzing */
425 if(f != stdout)
426 flac_unlink(vc_filename->value);
427 #endif
428
429 return ret;
430 }
431