1/* metaflac - Command-line FLAC metadata editor
2 * Copyright (C) 2001,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 "utils.h"
24#include "FLAC/assert.h"
25#include "share/alloc.h"
26#include "share/utf8.h"
27#include <ctype.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33void die(const char *message)
34{
35	FLAC__ASSERT(0 != message);
36	fprintf(stderr, "ERROR: %s\n", message);
37	exit(1);
38}
39
40#ifdef FLAC__VALGRIND_TESTING
41size_t local_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
42{
43	size_t ret = fwrite(ptr, size, nmemb, stream);
44	if(!ferror(stream))
45		fflush(stream);
46	return ret;
47}
48#endif
49
50char *local_strdup(const char *source)
51{
52	char *ret;
53	FLAC__ASSERT(0 != source);
54	if(0 == (ret = strdup(source)))
55		die("out of memory during strdup()");
56	return ret;
57}
58
59void local_strcat(char **dest, const char *source)
60{
61	size_t ndest, nsource;
62
63	FLAC__ASSERT(0 != dest);
64	FLAC__ASSERT(0 != source);
65
66	ndest = *dest? strlen(*dest) : 0;
67	nsource = strlen(source);
68
69	if(nsource == 0)
70		return;
71
72	*dest = (char*)safe_realloc_add_3op_(*dest, ndest, /*+*/nsource, /*+*/1);
73	if(0 == *dest)
74		die("out of memory growing string");
75	strcpy((*dest)+ndest, source);
76}
77
78void hexdump(const char *filename, const FLAC__byte *buf, unsigned bytes, const char *indent)
79{
80	unsigned i, left = bytes;
81	const FLAC__byte *b = buf;
82
83	for(i = 0; i < bytes; i += 16) {
84		printf("%s%s%s%08X: "
85			"%02X %02X %02X %02X %02X %02X %02X %02X "
86			"%02X %02X %02X %02X %02X %02X %02X %02X "
87			"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
88			filename? filename:"", filename? ":":"",
89			indent, i,
90			left >  0? (unsigned char)b[ 0] : 0,
91			left >  1? (unsigned char)b[ 1] : 0,
92			left >  2? (unsigned char)b[ 2] : 0,
93			left >  3? (unsigned char)b[ 3] : 0,
94			left >  4? (unsigned char)b[ 4] : 0,
95			left >  5? (unsigned char)b[ 5] : 0,
96			left >  6? (unsigned char)b[ 6] : 0,
97			left >  7? (unsigned char)b[ 7] : 0,
98			left >  8? (unsigned char)b[ 8] : 0,
99			left >  9? (unsigned char)b[ 9] : 0,
100			left > 10? (unsigned char)b[10] : 0,
101			left > 11? (unsigned char)b[11] : 0,
102			left > 12? (unsigned char)b[12] : 0,
103			left > 13? (unsigned char)b[13] : 0,
104			left > 14? (unsigned char)b[14] : 0,
105			left > 15? (unsigned char)b[15] : 0,
106			(left >  0) ? (isprint(b[ 0]) ? b[ 0] : '.') : ' ',
107			(left >  1) ? (isprint(b[ 1]) ? b[ 1] : '.') : ' ',
108			(left >  2) ? (isprint(b[ 2]) ? b[ 2] : '.') : ' ',
109			(left >  3) ? (isprint(b[ 3]) ? b[ 3] : '.') : ' ',
110			(left >  4) ? (isprint(b[ 4]) ? b[ 4] : '.') : ' ',
111			(left >  5) ? (isprint(b[ 5]) ? b[ 5] : '.') : ' ',
112			(left >  6) ? (isprint(b[ 6]) ? b[ 6] : '.') : ' ',
113			(left >  7) ? (isprint(b[ 7]) ? b[ 7] : '.') : ' ',
114			(left >  8) ? (isprint(b[ 8]) ? b[ 8] : '.') : ' ',
115			(left >  9) ? (isprint(b[ 9]) ? b[ 9] : '.') : ' ',
116			(left > 10) ? (isprint(b[10]) ? b[10] : '.') : ' ',
117			(left > 11) ? (isprint(b[11]) ? b[11] : '.') : ' ',
118			(left > 12) ? (isprint(b[12]) ? b[12] : '.') : ' ',
119			(left > 13) ? (isprint(b[13]) ? b[13] : '.') : ' ',
120			(left > 14) ? (isprint(b[14]) ? b[14] : '.') : ' ',
121			(left > 15) ? (isprint(b[15]) ? b[15] : '.') : ' '
122		);
123		left -= 16;
124		b += 16;
125   }
126}
127
128void print_error_with_chain_status(FLAC__Metadata_Chain *chain, const char *format, ...)
129{
130	const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain);
131	va_list args;
132
133	FLAC__ASSERT(0 != format);
134
135	va_start(args, format);
136
137	(void) vfprintf(stderr, format, args);
138
139	va_end(args);
140
141	fprintf(stderr, ", status = \"%s\"\n", FLAC__Metadata_ChainStatusString[status]);
142
143	if(status == FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
144		fprintf(stderr, "\n"
145			"The FLAC file could not be opened.  Most likely the file does not exist\n"
146			"or is not readable.\n"
147		);
148	}
149	else if(status == FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE) {
150		fprintf(stderr, "\n"
151			"The file does not appear to be a FLAC file.\n"
152		);
153	}
154	else if(status == FLAC__METADATA_CHAIN_STATUS_NOT_WRITABLE) {
155		fprintf(stderr, "\n"
156			"The FLAC file does not have write permissions.\n"
157		);
158	}
159	else if(status == FLAC__METADATA_CHAIN_STATUS_BAD_METADATA) {
160		fprintf(stderr, "\n"
161			"The metadata to be writted does not conform to the FLAC metadata\n"
162			"specifications.\n"
163		);
164	}
165	else if(status == FLAC__METADATA_CHAIN_STATUS_READ_ERROR) {
166		fprintf(stderr, "\n"
167			"There was an error while reading the FLAC file.\n"
168		);
169	}
170	else if(status == FLAC__METADATA_CHAIN_STATUS_WRITE_ERROR) {
171		fprintf(stderr, "\n"
172			"There was an error while writing FLAC file; most probably the disk is\n"
173			"full.\n"
174		);
175	}
176	else if(status == FLAC__METADATA_CHAIN_STATUS_UNLINK_ERROR) {
177		fprintf(stderr, "\n"
178			"There was an error removing the temporary FLAC file.\n"
179		);
180	}
181}
182
183FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation)
184{
185	static const char * const violations[] = {
186		"field name contains invalid character",
187		"field contains no '=' character"
188	};
189
190	char *p, *q, *s;
191
192	if(0 != field)
193		*field = local_strdup(field_ref);
194
195	s = local_strdup(field_ref);
196
197	if(0 == (p = strchr(s, '='))) {
198		free(s);
199		*violation = violations[1];
200		return false;
201	}
202	*p++ = '\0';
203
204	for(q = s; *q; q++) {
205		if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
206			free(s);
207			*violation = violations[0];
208			return false;
209		}
210	}
211
212	*name = local_strdup(s);
213	*value = local_strdup(p);
214	*length = strlen(p);
215
216	free(s);
217	return true;
218}
219
220void write_vc_field(const char *filename, const FLAC__StreamMetadata_VorbisComment_Entry *entry, FLAC__bool raw, FILE *f)
221{
222	if(0 != entry->entry) {
223		if(filename)
224			fprintf(f, "%s:", filename);
225
226		if(!raw) {
227			/*
228			 * WATCHOUT: comments that contain an embedded null will
229			 * be truncated by utf_decode().
230			 */
231			char *converted;
232
233			if(utf8_decode((const char *)entry->entry, &converted) >= 0) {
234				(void) local_fwrite(converted, 1, strlen(converted), f);
235				free(converted);
236			}
237			else {
238				(void) local_fwrite(entry->entry, 1, entry->length, f);
239			}
240		}
241		else {
242			(void) local_fwrite(entry->entry, 1, entry->length, f);
243		}
244	}
245
246	putc('\n', f);
247}
248
249void write_vc_fields(const char *filename, const char *field_name, const FLAC__StreamMetadata_VorbisComment_Entry entry[], unsigned num_entries, FLAC__bool raw, FILE *f)
250{
251	unsigned i;
252	const unsigned field_name_length = (0 != field_name)? strlen(field_name) : 0;
253
254	for(i = 0; i < num_entries; i++) {
255		if(0 == field_name || FLAC__metadata_object_vorbiscomment_entry_matches(entry[i], field_name, field_name_length))
256			write_vc_field(filename, entry + i, raw, f);
257	}
258}
259