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