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