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 <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 /* If ndest == 0, strlen in safe_strncat reads
80 * uninitialized data. To prevent that, set first character
81 * to zero */
82 if(ndest == 0)
83 *dest[0] = 0;
84 safe_strncat(*dest, source, outlen);
85 }
86
local_isprint(int c)87 static inline int local_isprint(int c)
88 {
89 if (c < 32) return 0;
90 if (c > 127) return 0;
91 return isprint(c);
92 }
93
hexdump(const char * filename,const FLAC__byte * buf,unsigned bytes,const char * indent)94 void hexdump(const char *filename, const FLAC__byte *buf, unsigned bytes, const char *indent)
95 {
96 unsigned i, left = bytes;
97 const FLAC__byte *b = buf;
98
99 for(i = 0; i < bytes; i += 16) {
100 flac_printf("%s%s", filename? filename:"", filename? ":":"");
101 printf("%s%08X: "
102 "%02X %02X %02X %02X %02X %02X %02X %02X "
103 "%02X %02X %02X %02X %02X %02X %02X %02X "
104 "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
105 indent, i,
106 left > 0? (unsigned char)b[ 0] : 0,
107 left > 1? (unsigned char)b[ 1] : 0,
108 left > 2? (unsigned char)b[ 2] : 0,
109 left > 3? (unsigned char)b[ 3] : 0,
110 left > 4? (unsigned char)b[ 4] : 0,
111 left > 5? (unsigned char)b[ 5] : 0,
112 left > 6? (unsigned char)b[ 6] : 0,
113 left > 7? (unsigned char)b[ 7] : 0,
114 left > 8? (unsigned char)b[ 8] : 0,
115 left > 9? (unsigned char)b[ 9] : 0,
116 left > 10? (unsigned char)b[10] : 0,
117 left > 11? (unsigned char)b[11] : 0,
118 left > 12? (unsigned char)b[12] : 0,
119 left > 13? (unsigned char)b[13] : 0,
120 left > 14? (unsigned char)b[14] : 0,
121 left > 15? (unsigned char)b[15] : 0,
122 (left > 0) ? (local_isprint(b[ 0]) ? b[ 0] : '.') : ' ',
123 (left > 1) ? (local_isprint(b[ 1]) ? b[ 1] : '.') : ' ',
124 (left > 2) ? (local_isprint(b[ 2]) ? b[ 2] : '.') : ' ',
125 (left > 3) ? (local_isprint(b[ 3]) ? b[ 3] : '.') : ' ',
126 (left > 4) ? (local_isprint(b[ 4]) ? b[ 4] : '.') : ' ',
127 (left > 5) ? (local_isprint(b[ 5]) ? b[ 5] : '.') : ' ',
128 (left > 6) ? (local_isprint(b[ 6]) ? b[ 6] : '.') : ' ',
129 (left > 7) ? (local_isprint(b[ 7]) ? b[ 7] : '.') : ' ',
130 (left > 8) ? (local_isprint(b[ 8]) ? b[ 8] : '.') : ' ',
131 (left > 9) ? (local_isprint(b[ 9]) ? b[ 9] : '.') : ' ',
132 (left > 10) ? (local_isprint(b[10]) ? b[10] : '.') : ' ',
133 (left > 11) ? (local_isprint(b[11]) ? b[11] : '.') : ' ',
134 (left > 12) ? (local_isprint(b[12]) ? b[12] : '.') : ' ',
135 (left > 13) ? (local_isprint(b[13]) ? b[13] : '.') : ' ',
136 (left > 14) ? (local_isprint(b[14]) ? b[14] : '.') : ' ',
137 (left > 15) ? (local_isprint(b[15]) ? b[15] : '.') : ' '
138 );
139 left -= 16;
140 b += 16;
141 }
142 }
143
print_error_with_chain_status(FLAC__Metadata_Chain * chain,const char * format,...)144 void print_error_with_chain_status(FLAC__Metadata_Chain *chain, const char *format, ...)
145 {
146 const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain);
147 va_list args;
148
149 FLAC__ASSERT(0 != format);
150
151 va_start(args, format);
152
153 (void) flac_vfprintf(stderr, format, args);
154
155 va_end(args);
156
157 flac_fprintf(stderr, ", status = \"%s\"\n", FLAC__Metadata_ChainStatusString[status]);
158
159 if(status == FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
160 flac_fprintf(stderr, "\n"
161 "The FLAC file could not be opened. Most likely the file does not exist\n"
162 "or is not readable.\n"
163 );
164 }
165 else if(status == FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE) {
166 flac_fprintf(stderr, "\n"
167 "The file does not appear to be a FLAC file.\n"
168 );
169 }
170 else if(status == FLAC__METADATA_CHAIN_STATUS_NOT_WRITABLE) {
171 flac_fprintf(stderr, "\n"
172 "The FLAC file does not have write permissions.\n"
173 );
174 }
175 else if(status == FLAC__METADATA_CHAIN_STATUS_BAD_METADATA) {
176 flac_fprintf(stderr, "\n"
177 "The metadata to be written does not conform to the FLAC metadata\n"
178 "specifications.\n"
179 );
180 }
181 else if(status == FLAC__METADATA_CHAIN_STATUS_READ_ERROR) {
182 flac_fprintf(stderr, "\n"
183 "There was an error while reading the FLAC file.\n"
184 );
185 }
186 else if(status == FLAC__METADATA_CHAIN_STATUS_WRITE_ERROR) {
187 flac_fprintf(stderr, "\n"
188 "There was an error while writing FLAC file; most probably the disk is\n"
189 "full.\n"
190 );
191 }
192 else if(status == FLAC__METADATA_CHAIN_STATUS_UNLINK_ERROR) {
193 flac_fprintf(stderr, "\n"
194 "There was an error removing the temporary FLAC file.\n"
195 );
196 }
197 }
198
parse_vorbis_comment_field(const char * field_ref,char ** field,char ** name,char ** value,unsigned * length,const char ** violation)199 FLAC__bool parse_vorbis_comment_field(const char *field_ref, char **field, char **name, char **value, unsigned *length, const char **violation)
200 {
201 static const char * const violations[] = {
202 "field name contains invalid character",
203 "field contains no '=' character"
204 };
205
206 char *p, *q, *s;
207
208 if(0 != field)
209 *field = local_strdup(field_ref);
210
211 s = local_strdup(field_ref);
212
213 if(0 == (p = strchr(s, '='))) {
214 free(s);
215 *violation = violations[1];
216 return false;
217 }
218 *p++ = '\0';
219
220 for(q = s; *q; q++) {
221 if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
222 free(s);
223 *violation = violations[0];
224 return false;
225 }
226 }
227
228 *name = local_strdup(s);
229 *value = local_strdup(p);
230 *length = strlen(p);
231
232 free(s);
233 return true;
234 }
235
write_vc_field(const char * filename,const FLAC__StreamMetadata_VorbisComment_Entry * entry,FLAC__bool raw,FILE * f)236 void write_vc_field(const char *filename, const FLAC__StreamMetadata_VorbisComment_Entry *entry, FLAC__bool raw, FILE *f)
237 {
238 if(0 != entry->entry) {
239 if(filename)
240 flac_fprintf(f, "%s:", filename);
241
242 if(!raw) {
243 /*
244 * WATCHOUT: comments that contain an embedded null will
245 * be truncated by utf_decode().
246 */
247 #ifdef _WIN32 /* if we are outputting to console, we need to use proper print functions to show unicode characters */
248 if (f == stdout || f == stderr) {
249 flac_fprintf(f, "%s", entry->entry);
250 } else {
251 #endif
252 char *converted;
253
254 if(utf8_decode((const char *)entry->entry, &converted) >= 0) {
255 (void) local_fwrite(converted, 1, strlen(converted), f);
256 free(converted);
257 }
258 else {
259 (void) local_fwrite(entry->entry, 1, entry->length, f);
260 }
261 #ifdef _WIN32
262 }
263 #endif
264 }
265 else {
266 (void) local_fwrite(entry->entry, 1, entry->length, f);
267 }
268 }
269
270 putc('\n', f);
271 }
272
write_vc_fields(const char * filename,const char * field_name,const FLAC__StreamMetadata_VorbisComment_Entry entry[],unsigned num_entries,FLAC__bool raw,FILE * f)273 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)
274 {
275 unsigned i;
276 const unsigned field_name_length = (0 != field_name)? strlen(field_name) : 0;
277
278 for(i = 0; i < num_entries; i++) {
279 if(0 == field_name || FLAC__metadata_object_vorbiscomment_entry_matches(entry[i], field_name, field_name_length))
280 write_vc_field(filename, entry + i, raw, f);
281 }
282 }
283