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