/* metaflac - Command-line FLAC metadata editor
- * Copyright (C) 2001,2002,2003,2004,2005 Josh Coalson
+ * Copyright (C) 2001,2002,2003,2004,2005,2006,2007,2008,2009 Josh Coalson
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 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.
*/
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
#include "options.h"
#include "utils.h"
#include "FLAC/assert.h"
+#include "share/grabbag.h" /* for grabbag__file_get_filesize() */
#include "share/utf8.h"
+#include <errno.h>
#include <stdlib.h>
#include <string.h>
+#include "operations_shorthand.h"
+#include "share/compat.h"
static FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write);
static FLAC__bool remove_vc_field(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
static FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write);
static FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw);
-static FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_Filename *vc_filename, FLAC__bool *needs_write, FLAC__bool raw);
-static FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_Filename *vc_filename, FLAC__bool raw);
+static FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw);
+static FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw);
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)
{
if(0 != block->data.vorbis_comment.comments) {
FLAC__ASSERT(block->data.vorbis_comment.num_comments > 0);
if(!FLAC__metadata_object_vorbiscomment_resize_comments(block, 0)) {
- fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
+ flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
return false;
}
*needs_write = true;
n = FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, field_name);
if(n < 0) {
- fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
+ flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
return false;
}
else if(n > 0)
n = FLAC__metadata_object_vorbiscomment_remove_entry_matching(block, field_name);
if(n < 0) {
- fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
+ flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
return false;
}
else if(n > 0)
{
FLAC__StreamMetadata_VorbisComment_Entry entry;
char *converted;
- FLAC__bool needs_free = false;
FLAC__ASSERT(0 != block);
FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__ASSERT(0 != field);
FLAC__ASSERT(0 != needs_write);
- 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 {
- fprintf(stderr, "%s: ERROR: couldn't convert comment to UTF-8\n", filename);
- return false;
- }
+ 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) {
+ flac_fprintf(stderr, "%s: ERROR: can't open file '%s' for '%s' tag value\n", filename, field->field_value, field->field_name);
+ return false;
+ }
+ if(size >= 0x100000) { /* magic arbitrary limit, actual format limit is near 16MB */
+ flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is too large\n", filename, field->field_value, field->field_name);
+ 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) {
+ flac_fprintf(stderr, "%s: ERROR: while reading file '%s' for '%s' tag value: %s\n", filename, field->field_value, field->field_name, strerror(errno));
+ free(data);
+ if(f)
+ fclose(f);
+ return false;
+ }
+ fclose(f);
+ if(strlen(data) != (size_t)size) {
+ free(data);
+ flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value has embedded NULs\n", filename, field->field_value, field->field_name);
+ return false;
+ }
- entry.length = strlen((const char *)entry.entry);
+ /* 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);
+ flac_fprintf(stderr, "%s: ERROR: converting file '%s' contents to UTF-8 for tag value\n", filename, field->field_value);
+ return false;
+ }
- if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) {
- if(needs_free)
+ /* create and entry and append it */
+ if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field->field_name, converted)) {
free(converted);
- fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
- return false;
+ flac_fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is not valid UTF-8\n", filename, field->field_value, field->field_name);
+ return false;
+ }
+ free(converted);
+ if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/false)) {
+ flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
+ return false;
+ }
+
+ *needs_write = true;
+ return true;
}
else {
+ FLAC__bool needs_free = false;
+#ifdef FLAC__STRINGS_IN_UTF8 /* do not convert anything or things will break */
+ 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 {
+ flac_fprintf(stderr, "%s: ERROR: converting comment '%s' to UTF-8\n", filename, field->field);
+ 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
+ */
+ flac_fprintf(stderr, "%s: ERROR: tag value for '%s' is not valid UTF-8\n", filename, field->field_name);
+ return false;
+ }
+
+ if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) {
+ if(needs_free)
+ free(converted);
+ flac_fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename);
+ return false;
+ }
+
*needs_write = true;
if(needs_free)
free(converted);
}
}
-FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_Filename *vc_filename, FLAC__bool *needs_write, FLAC__bool raw)
+FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw)
{
FILE *f;
char line[65536];
FLAC__bool ret;
if(0 == vc_filename->value || strlen(vc_filename->value) == 0) {
- fprintf(stderr, "%s: ERROR: empty import file name\n", filename);
+ flac_fprintf(stderr, "%s: ERROR: empty import file name\n", filename);
return false;
}
if(0 == strcmp(vc_filename->value, "-"))
f = stdin;
else
- f = fopen(vc_filename->value, "r");
+ f = flac_fopen(vc_filename->value, "r");
if(0 == f) {
- fprintf(stderr, "%s: ERROR: can't open import file %s\n", filename, vc_filename->value);
+ flac_fprintf(stderr, "%s: ERROR: can't open import file %s: %s\n", filename, vc_filename->value, strerror(errno));
return false;
}
ret = true;
- while(ret && !feof(f)) {
- fgets(line, sizeof(line), f);
+ while(ret && !feof(f) && fgets(line, sizeof(line), f) != NULL) {
if(!feof(f)) {
char *p = strchr(line, '\n');
if(0 == p) {
- fprintf(stderr, "%s: ERROR: line too long, aborting\n", vc_filename->value);
+ flac_fprintf(stderr, "%s: ERROR: line too long, aborting\n", vc_filename->value);
ret = false;
}
else {
Argument_VcField field;
*p = '\0';
memset(&field, 0, sizeof(Argument_VcField));
+ field.field_value_from_file = false;
if(!parse_vorbis_comment_field(line, &field.field, &field.field_name, &field.field_value, &field.field_value_length, &violation)) {
FLAC__ASSERT(0 != violation);
- fprintf(stderr, "%s: ERROR: malformed vorbis comment field \"%s\",\n %s\n", vc_filename->value, line, violation);
+ flac_fprintf(stderr, "%s: ERROR: malformed vorbis comment field \"%s\",\n %s\n", vc_filename->value, line, violation);
ret = false;
}
else {
return ret;
}
-FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_Filename *vc_filename, FLAC__bool raw)
+FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw)
{
FILE *f;
FLAC__bool ret;
if(0 == vc_filename->value || strlen(vc_filename->value) == 0) {
- fprintf(stderr, "%s: ERROR: empty export file name\n", filename);
+ flac_fprintf(stderr, "%s: ERROR: empty export file name\n", filename);
return false;
}
if(0 == strcmp(vc_filename->value, "-"))
f = stdout;
else
- f = fopen(vc_filename->value, "w");
+ f = flac_fopen(vc_filename->value, "w");
if(0 == f) {
- fprintf(stderr, "%s: ERROR: can't open export file %s\n", filename, vc_filename->value);
+ flac_fprintf(stderr, "%s: ERROR: can't open export file %s: %s\n", filename, vc_filename->value, strerror(errno));
return false;
}