/* flac - Command-line FLAC encoder/decoder * Copyright (C) 2002-2009 Josh Coalson * Copyright (C) 2011-2016 Xiph.Org Foundation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H # include #endif #include "vorbiscomment.h" #include "FLAC/assert.h" #include "FLAC/metadata.h" #include "share/grabbag.h" /* for grabbag__file_get_filesize() */ #include "share/utf8.h" #include #include #include #include #include "share/compat.h" /* * This struct and the following 4 static functions are copied from * ../metaflac/. Maybe someday there will be a convenience * library for Vorbis comment parsing. */ typedef struct { char *field; /* the whole field as passed on the command line, i.e. "NAME=VALUE" */ char *field_name; /* according to the vorbis spec, field values can contain \0 so simple C strings are not enough here */ uint32_t field_value_length; char *field_value; FLAC__bool field_value_from_file; /* true if field_value holds a filename for the value, false for plain value */ } Argument_VcField; static void die(const char *message) { FLAC__ASSERT(0 != message); fprintf(stderr, "ERROR: %s\n", message); exit(1); } static char *local_strdup(const char *source) { char *ret; FLAC__ASSERT(0 != source); if(0 == (ret = strdup(source))) die("out of memory during strdup()"); return ret; } static FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, uint32_t *length, const char **violation) { static const char * const violations[] = { "field name contains invalid character", "field contains no '=' character" }; char *p, *q, *s; if(0 != field) *field = local_strdup(field_ref); s = local_strdup(field_ref); if(0 == (p = strchr(s, '='))) { free(s); *violation = violations[1]; return false; } *p++ = '\0'; for(q = s; *q; q++) { if(*q < 0x20 || *q > 0x7d || *q == 0x3d) { free(s); *violation = violations[0]; return false; } } *name = local_strdup(s); *value = local_strdup(p); *length = strlen(p); free(s); return true; } /* slight modification: no 'filename' arg, and errors are passed back in 'violation' instead of printed to stderr */ static FLAC__bool set_vc_field(FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw, const char **violation) { FLAC__StreamMetadata_VorbisComment_Entry entry; char *converted = NULL; FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__ASSERT(0 != field); FLAC__ASSERT(0 != needs_write); if(field->field_value_from_file) { /* read the file into 'data' */ FILE *f = 0; char *data = 0; const FLAC__off_t size = grabbag__file_get_filesize(field->field_value); if(size < 0) { *violation = "can't open file for tag value"; return false; } if(size >= 0x100000) { /* magic arbitrary limit, actual format limit is near 16MB */ *violation = "file for tag value is too large"; return false; } if(0 == (data = malloc(size+1))) die("out of memory allocating tag value"); data[size] = '\0'; if(0 == (f = flac_fopen(field->field_value, "rb")) || fread(data, 1, size, f) != (size_t)size) { free(data); if(f) fclose(f); *violation = "error while reading file for tag value"; return false; } fclose(f); if(strlen(data) != (size_t)size) { free(data); *violation = "file for tag value has embedded NULs"; return false; } /* move 'data' into 'converted', converting to UTF-8 if necessary */ if(raw) { converted = data; } else if(utf8_encode(data, &converted) >= 0) { free(data); } else { free(data); *violation = "error converting file contents to UTF-8 for tag value"; return false; } /* create and entry and append it */ if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field->field_name, converted)) { free(converted); *violation = "file for tag value is not valid UTF-8"; return false; } free(converted); if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/false)) { *violation = "memory allocation failure"; return false; } *needs_write = true; return true; } else { FLAC__bool needs_free = false; #ifdef _WIN32 /* everything in UTF-8 already. Must not alter */ entry.entry = (FLAC__byte *)field->field; #else if(raw) { entry.entry = (FLAC__byte *)field->field; } else if(utf8_encode(field->field, &converted) >= 0) { entry.entry = (FLAC__byte *)converted; needs_free = true; } else { *violation = "error converting comment to UTF-8"; return false; } #endif entry.length = strlen((const char *)entry.entry); if(!FLAC__format_vorbiscomment_entry_is_legal(entry.entry, entry.length)) { if(needs_free) free(converted); /* * our previous parsing has already established that the field * name is OK, so it must be the field value */ *violation = "tag value is not valid UTF-8"; return false; } if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) { if(needs_free) free(converted); *violation = "memory allocation failure"; return false; } *needs_write = true; if(needs_free) free(converted); return true; } } /* * The rest of the code is novel */ static void free_field(Argument_VcField *obj) { if(0 != obj->field) free(obj->field); if(0 != obj->field_name) free(obj->field_name); if(0 != obj->field_value) free(obj->field_value); } FLAC__bool flac__vorbiscomment_add(FLAC__StreamMetadata *block, const char *comment, FLAC__bool value_from_file, FLAC__bool raw, const char **violation) { Argument_VcField parsed; FLAC__bool dummy; FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__ASSERT(0 != comment); memset(&parsed, 0, sizeof(parsed)); parsed.field_value_from_file = value_from_file; if(!parse_vorbis_comment_field(comment, &(parsed.field), &(parsed.field_name), &(parsed.field_value), &(parsed.field_value_length), violation)) { free_field(&parsed); return false; } if(parsed.field_value_length > 0 && !set_vc_field(block, &parsed, &dummy, raw, violation)) { free_field(&parsed); return false; } else { free_field(&parsed); return true; } }