Coverage Report

Created: 2026-09-01 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/flac/src/metaflac/options.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 "options.h"
25
#include "usage.h"
26
#include "utils.h"
27
#include "FLAC/assert.h"
28
#include "share/alloc.h"
29
#include "share/compat.h"
30
#include "share/grabbag/replaygain.h"
31
#include <ctype.h>
32
#include <stdio.h>
33
#include <stdlib.h>
34
#include <string.h>
35
36
/*
37
   share__getopt format struct; note we don't use short options so we just
38
   set the 'val' field to 0 everywhere to indicate a valid option.
39
*/
40
struct share__option long_options_[] = {
41
  /* global options */
42
  { "output-name", 1, 0, 'o' }, /* NOTE: if this option moves in this list, change the index in parse_options */
43
  { "preserve-modtime", 0, 0, 0 },
44
  { "with-filename", 0, 0, 0 },
45
  { "no-filename", 0, 0, 0 },
46
  { "no-utf8-convert", 0, 0, 0 },
47
  { "dont-use-padding", 0, 0, 0 },
48
  { "no-cued-seekpoints", 0, 0, 0 },
49
  /* shorthand operations */
50
  { "show-md5sum", 0, 0, 0 },
51
  { "show-min-blocksize", 0, 0, 0 },
52
  { "show-max-blocksize", 0, 0, 0 },
53
  { "show-min-framesize", 0, 0, 0 },
54
  { "show-max-framesize", 0, 0, 0 },
55
  { "show-sample-rate", 0, 0, 0 },
56
  { "show-channels", 0, 0, 0 },
57
  { "show-bps", 0, 0, 0 },
58
  { "show-total-samples", 0, 0, 0 },
59
  { "set-md5sum", 1, 0, 0 }, /* undocumented */
60
  { "set-min-blocksize", 1, 0, 0 }, /* undocumented */
61
  { "set-max-blocksize", 1, 0, 0 }, /* undocumented */
62
  { "set-min-framesize", 1, 0, 0 }, /* undocumented */
63
  { "set-max-framesize", 1, 0, 0 }, /* undocumented */
64
  { "set-sample-rate", 1, 0, 0 }, /* undocumented */
65
  { "set-channels", 1, 0, 0 }, /* undocumented */
66
  { "set-bps", 1, 0, 0 }, /* undocumented */
67
  { "set-total-samples", 1, 0, 0 }, /* undocumented */ /* WATCHOUT: used by test/test_flac.sh on windows */
68
  { "show-vendor-tag", 0, 0, 0 },
69
  { "show-all-tags", 0, 0, 0 },
70
  { "show-tag", 1, 0, 0 },
71
  { "remove-all-tags", 0, 0, 0 },
72
  { "remove-all-tags-except", 1, 0, 0 },
73
  { "remove-tag", 1, 0, 0 },
74
  { "remove-first-tag", 1, 0, 0 },
75
  { "set-tag", 1, 0, 0 },
76
  { "set-tag-from-file", 1, 0, 0 },
77
  { "import-tags-from", 1, 0, 0 },
78
  { "export-tags-to", 1, 0, 0 },
79
  { "import-cuesheet-from", 1, 0, 0 },
80
  { "export-cuesheet-to", 1, 0, 0 },
81
  { "import-picture-from", 1, 0, 0 },
82
  { "export-picture-to", 1, 0, 0 },
83
  { "add-seekpoint", 1, 0, 0 },
84
  { "add-replay-gain", 0, 0, 0 },
85
  { "scan-replay-gain", 0, 0, 0 },
86
  { "remove-replay-gain", 0, 0, 0 },
87
  { "add-padding", 1, 0, 0 },
88
  /* major operations */
89
  { "help", 0, 0, 0 },
90
  { "version", 0, 0, 0 },
91
  { "list", 0, 0, 0 },
92
  { "append", 0, 0, 0 },
93
  { "remove", 0, 0, 0 },
94
  { "remove-all", 0, 0, 0 },
95
  { "merge-padding", 0, 0, 0 },
96
  { "sort-padding", 0, 0, 0 },
97
  /* major operation arguments */
98
  { "block-number", 1, 0, 0 },
99
  { "block-type", 1, 0, 0 },
100
  { "except-block-type", 1, 0, 0 },
101
  { "data-format", 1, 0, 0 },
102
  { "application-data-format", 1, 0, 0 },
103
  { "from-file", 1, 0, 0 },
104
  {0, 0, 0, 0}
105
};
106
107
static FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options);
108
static void append_new_operation(CommandLineOptions *options, Operation operation);
109
static void append_new_argument(CommandLineOptions *options, Argument argument);
110
static Operation *append_major_operation(CommandLineOptions *options, OperationType type);
111
static Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type);
112
static Argument *find_argument(CommandLineOptions *options, ArgumentType type);
113
static Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type);
114
static Argument *append_argument(CommandLineOptions *options, ArgumentType type);
115
static FLAC__bool parse_md5(const char *src, FLAC__byte dest[16]);
116
static FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest);
117
static FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest);
118
static FLAC__bool parse_string(const char *src, char **dest);
119
static FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation);
120
static FLAC__bool parse_vorbis_comment_field_names(const char *field_ref, char **names, const char **violation);
121
static FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation);
122
static FLAC__bool parse_add_padding(const char *in, unsigned *out);
123
static FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out);
124
static FLAC__bool parse_block_type(const char *in, Argument_BlockType *out);
125
static FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out);
126
static FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out);
127
static void undocumented_warning(const char *opt);
128
129
130
void init_options(CommandLineOptions *options)
131
15.9k
{
132
15.9k
  options->preserve_modtime = false;
133
134
  /* '2' is a hack to mean "use default if not forced on command line" */
135
15.9k
  FLAC__ASSERT(true != 2);
136
15.9k
  options->prefix_with_filename = 2;
137
138
15.9k
  options->utf8_convert = true;
139
15.9k
  options->use_padding = true;
140
15.9k
  options->cued_seekpoints = true;
141
15.9k
  options->show_long_help = false;
142
15.9k
  options->show_version = false;
143
15.9k
  options->data_format_is_binary = false;
144
15.9k
  options->data_format_is_binary_headerless = false;
145
15.9k
  options->application_data_format_is_hexdump = false;
146
147
15.9k
  options->ops.operations = 0;
148
15.9k
  options->ops.num_operations = 0;
149
15.9k
  options->ops.capacity = 0;
150
151
15.9k
  options->args.arguments = 0;
152
15.9k
  options->args.num_arguments = 0;
153
15.9k
  options->args.capacity = 0;
154
155
15.9k
  options->args.checks.num_shorthand_ops = 0;
156
15.9k
  options->args.checks.num_major_ops = 0;
157
15.9k
  options->args.checks.has_block_type = false;
158
15.9k
  options->args.checks.has_except_block_type = false;
159
160
15.9k
  options->num_files = 0;
161
15.9k
  options->filenames = 0;
162
15.9k
  options->output_name = 0;
163
15.9k
}
164
165
FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
166
15.9k
{
167
15.9k
  int ret;
168
15.9k
  int option_index = 1;
169
15.9k
  FLAC__bool had_error = false;
170
171
46.8k
  while ((ret = share__getopt_long(argc, argv, "o:", long_options_, &option_index)) != -1) {
172
30.9k
    switch (ret) {
173
29.5k
      case 0:
174
29.5k
        had_error |= !parse_option(option_index, share__optarg, options);
175
29.5k
        break;
176
286
      case 'o':
177
286
        had_error |= !parse_option(0, share__optarg, options);
178
286
        break;
179
1.06k
      case '?':
180
1.06k
      case ':':
181
1.06k
        had_error = true;
182
1.06k
        break;
183
0
      default:
184
0
        FLAC__ASSERT(0);
185
0
        break;
186
30.9k
    }
187
30.9k
  }
188
189
15.9k
  if(options->prefix_with_filename == 2)
190
15.8k
    options->prefix_with_filename = (argc - share__optind > 1);
191
192
15.9k
  if(share__optind >= argc && !options->show_long_help && !options->show_version) {
193
79
    flac_fprintf(stderr,"ERROR: you must specify at least one FLAC file;\n");
194
79
    flac_fprintf(stderr,"       metaflac cannot be used as a pipe\n");
195
79
    had_error = true;
196
79
  }
197
198
15.9k
  options->num_files = argc - share__optind;
199
200
15.9k
  if(options->num_files > 0) {
201
15.8k
    unsigned i = 0;
202
15.8k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
203
15.8k
    extern char* allowed_filename;
204
15.8k
#endif
205
15.8k
    if(0 == (options->filenames = safe_malloc_mul_2op_(sizeof(char*), /*times*/options->num_files)))
206
0
      die("out of memory allocating space for file names list");
207
56.4k
    while(share__optind < argc)
208
40.5k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
209
40.5k
      if(strcmp(argv[share__optind],allowed_filename) == 0)
210
27.5k
#endif
211
27.5k
        options->filenames[i++] = local_strdup(argv[share__optind++]);
212
12.9k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
213
12.9k
      else {
214
12.9k
        if(options->num_files > 0)
215
12.9k
          options->num_files--;
216
12.9k
        share__optind++;
217
12.9k
      }
218
15.8k
#endif
219
15.8k
  }
220
221
15.9k
  if(options->args.checks.num_major_ops > 0) {
222
3.43k
    if(options->args.checks.num_major_ops > 1) {
223
37
      flac_fprintf(stderr, "ERROR: you may only specify one major operation at a time\n");
224
37
      had_error = true;
225
37
    }
226
3.39k
    else if(options->args.checks.num_shorthand_ops > 0) {
227
14
      flac_fprintf(stderr, "ERROR: you may not mix shorthand and major operations\n");
228
14
      had_error = true;
229
14
    }
230
3.43k
  }
231
232
15.9k
  if(!options->show_long_help && !options->show_version &&
233
15.9k
     (options->args.checks.num_major_ops == 0) && (options->args.checks.num_shorthand_ops == 0)) {
234
1.71k
    flac_fprintf(stderr, "ERROR: no operations specified\n");
235
1.71k
    had_error = true;
236
1.71k
  }
237
238
  /* check for only one FLAC file used with certain options */
239
15.9k
  if(!had_error && options->num_files > 1) {
240
9.87k
    if(0 != options->output_name) {
241
1
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC input file when specifying an output filename\n");
242
1
      had_error = true;
243
1
    }
244
9.87k
    if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
245
8
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-cuesheet-from'\n");
246
8
      had_error = true;
247
8
    }
248
9.87k
    if(0 != find_shorthand_operation(options, OP__EXPORT_CUESHEET_TO)) {
249
3
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-cuesheet-to'\n");
250
3
      had_error = true;
251
3
    }
252
9.87k
    if(0 != find_shorthand_operation(options, OP__EXPORT_PICTURE_TO)) {
253
8
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-picture-to'\n");
254
8
      had_error = true;
255
8
    }
256
9.87k
    if(
257
9.87k
      0 != find_shorthand_operation(options, OP__IMPORT_VC_FROM) &&
258
36
      0 == strcmp(find_shorthand_operation(options, OP__IMPORT_VC_FROM)->argument.filename.value, "-")
259
9.87k
    ) {
260
1
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-tags-from=-'\n");
261
1
      had_error = true;
262
1
    }
263
9.87k
  }
264
265
15.9k
  if(options->args.checks.has_block_type && options->args.checks.has_except_block_type) {
266
1
    flac_fprintf(stderr, "ERROR: you may not specify both '--block-type' and '--except-block-type'\n");
267
1
    had_error = true;
268
1
  }
269
270
15.9k
  if(had_error)
271
2.41k
    short_usage(0);
272
273
  /*
274
   * We need to create an OP__ADD_SEEKPOINT operation if there is
275
   * not one already, and --import-cuesheet-from was specified but
276
   * --no-cued-seekpoints was not:
277
   */
278
15.9k
  if(options->cued_seekpoints) {
279
15.9k
    Operation *op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
280
15.9k
    if(0 != op) {
281
2.18k
      Operation *op2 = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
282
2.18k
      if(0 == op2) {
283
2.18k
        op2 = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
284
        /* Need to re-find op, because the appending might have caused realloc */
285
2.18k
        op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
286
2.18k
      }
287
2.18k
      op->argument.import_cuesheet_from.add_seekpoint_link = &(op2->argument.add_seekpoint);
288
2.18k
    }
289
15.9k
  }
290
291
15.9k
  return had_error;
292
15.9k
}
293
294
void free_options(CommandLineOptions *options)
295
15.9k
{
296
15.9k
  unsigned i;
297
15.9k
  Operation *op;
298
15.9k
  Argument *arg;
299
300
15.9k
  FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
301
15.9k
  FLAC__ASSERT(0 == options->args.arguments || options->args.num_arguments > 0);
302
303
44.8k
  for(i = 0, op = options->ops.operations; i < options->ops.num_operations; i++, op++) {
304
28.9k
    switch(op->type) {
305
191
      case OP__SHOW_VC_FIELD:
306
1.81k
      case OP__REMOVE_VC_FIELD:
307
1.96k
      case OP__REMOVE_VC_FIRSTFIELD:
308
2.29k
      case OP__REMOVE_VC_ALL_EXCEPT:
309
2.29k
        if(0 != op->argument.vc_field_name.value)
310
2.16k
          free(op->argument.vc_field_name.value);
311
2.29k
        break;
312
1.18k
      case OP__SET_VC_FIELD:
313
1.18k
        if(0 != op->argument.vc_field.field)
314
1.18k
          free(op->argument.vc_field.field);
315
1.18k
        if(0 != op->argument.vc_field.field_name)
316
1.10k
          free(op->argument.vc_field.field_name);
317
1.18k
        if(0 != op->argument.vc_field.field_value)
318
1.10k
          free(op->argument.vc_field.field_value);
319
1.18k
        break;
320
173
      case OP__IMPORT_VC_FROM:
321
347
      case OP__EXPORT_VC_TO:
322
620
      case OP__EXPORT_CUESHEET_TO:
323
620
        if(0 != op->argument.filename.value)
324
560
          free(op->argument.filename.value);
325
620
        break;
326
2.24k
      case OP__IMPORT_CUESHEET_FROM:
327
2.24k
        if(0 != op->argument.import_cuesheet_from.filename)
328
2.20k
          free(op->argument.import_cuesheet_from.filename);
329
2.24k
        break;
330
1.14k
      case OP__IMPORT_PICTURE_FROM:
331
1.14k
        if(0 != op->argument.specification.value)
332
1.12k
          free(op->argument.specification.value);
333
1.14k
        break;
334
292
      case OP__EXPORT_PICTURE_TO:
335
292
        if(0 != op->argument.export_picture_to.filename)
336
242
          free(op->argument.export_picture_to.filename);
337
292
        break;
338
3.61k
      case OP__ADD_SEEKPOINT:
339
3.61k
        if(0 != op->argument.add_seekpoint.specification)
340
1.83k
          free(op->argument.add_seekpoint.specification);
341
3.61k
        break;
342
17.5k
      default:
343
17.5k
        break;
344
28.9k
    }
345
28.9k
  }
346
347
18.9k
  for(i = 0, arg = options->args.arguments; i < options->args.num_arguments; i++, arg++) {
348
2.97k
    switch(arg->type) {
349
732
      case ARG__BLOCK_NUMBER:
350
732
        if(0 != arg->value.block_number.entries)
351
704
          free(arg->value.block_number.entries);
352
732
        break;
353
53
      case ARG__BLOCK_TYPE:
354
1.10k
      case ARG__EXCEPT_BLOCK_TYPE:
355
1.10k
        if(0 != arg->value.block_type.entries)
356
1.07k
          free(arg->value.block_type.entries);
357
1.10k
        break;
358
1.13k
      default:
359
1.13k
        break;
360
2.97k
    }
361
2.97k
  }
362
363
15.9k
  if(0 != options->ops.operations)
364
14.2k
    free(options->ops.operations);
365
366
15.9k
  if(0 != options->args.arguments)
367
2.40k
    free(options->args.arguments);
368
369
15.9k
  if(0 != options->filenames) {
370
43.4k
    for(i = 0; i < options->num_files; i++) {
371
27.5k
      if(0 != options->filenames[i])
372
27.5k
        free(options->filenames[i]);
373
27.5k
    }
374
15.8k
    free(options->filenames);
375
15.8k
  }
376
15.9k
}
377
378
/*
379
 * local routines
380
 */
381
382
FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
383
29.8k
{
384
29.8k
  const char *opt = long_options_[option_index].name;
385
29.8k
  Operation *op;
386
29.8k
  Argument *arg;
387
29.8k
  FLAC__bool ok = true;
388
389
29.8k
  if(0 == strcmp(opt, "preserve-modtime")) {
390
78
    options->preserve_modtime = true;
391
78
  }
392
29.7k
  else if(0 == strcmp(opt, "with-filename")) {
393
54
    options->prefix_with_filename = true;
394
54
  }
395
29.7k
  else if(0 == strcmp(opt, "no-filename")) {
396
117
    options->prefix_with_filename = false;
397
117
  }
398
29.5k
  else if(0 == strcmp(opt, "no-utf8-convert")) {
399
122
    options->utf8_convert = false;
400
122
  }
401
29.4k
  else if(0 == strcmp(opt, "dont-use-padding")) {
402
71
    options->use_padding = false;
403
71
  }
404
29.3k
  else if(0 == strcmp(opt, "no-cued-seekpoints")) {
405
33
    options->cued_seekpoints = false;
406
33
  }
407
29.3k
  else if(0 == strcmp(opt, "output-name")) {
408
286
    options->output_name = option_argument;
409
286
  }
410
29.0k
  else if(0 == strcmp(opt, "show-md5sum")) {
411
41
    (void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
412
41
  }
413
29.0k
  else if(0 == strcmp(opt, "show-min-blocksize")) {
414
39
    (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
415
39
  }
416
28.9k
  else if(0 == strcmp(opt, "show-max-blocksize")) {
417
37
    (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
418
37
  }
419
28.9k
  else if(0 == strcmp(opt, "show-min-framesize")) {
420
37
    (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
421
37
  }
422
28.9k
  else if(0 == strcmp(opt, "show-max-framesize")) {
423
38
    (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
424
38
  }
425
28.8k
  else if(0 == strcmp(opt, "show-sample-rate")) {
426
37
    (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
427
37
  }
428
28.8k
  else if(0 == strcmp(opt, "show-channels")) {
429
37
    (void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
430
37
  }
431
28.8k
  else if(0 == strcmp(opt, "show-bps")) {
432
38
    (void) append_shorthand_operation(options, OP__SHOW_BPS);
433
38
  }
434
28.7k
  else if(0 == strcmp(opt, "show-total-samples")) {
435
37
    (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
436
37
  }
437
28.7k
  else if(0 == strcmp(opt, "set-md5sum")) {
438
132
    op = append_shorthand_operation(options, OP__SET_MD5SUM);
439
132
    FLAC__ASSERT(0 != option_argument);
440
132
    if(!parse_md5(option_argument, op->argument.streaminfo_md5.value)) {
441
107
      flac_fprintf(stderr, "ERROR (--%s): bad MD5 sum\n", opt);
442
107
      ok = false;
443
107
    }
444
25
    else
445
25
      undocumented_warning(opt);
446
132
  }
447
28.6k
  else if(0 == strcmp(opt, "set-min-blocksize")) {
448
129
    op = append_shorthand_operation(options, OP__SET_MIN_BLOCKSIZE);
449
129
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
450
83
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
451
83
      ok = false;
452
83
    }
453
46
    else
454
46
      undocumented_warning(opt);
455
129
  }
456
28.4k
  else if(0 == strcmp(opt, "set-max-blocksize")) {
457
164
    op = append_shorthand_operation(options, OP__SET_MAX_BLOCKSIZE);
458
164
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BLOCK_SIZE || op->argument.streaminfo_uint32.value > FLAC__MAX_BLOCK_SIZE) {
459
102
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
460
102
      ok = false;
461
102
    }
462
62
    else
463
62
      undocumented_warning(opt);
464
164
  }
465
28.3k
  else if(0 == strcmp(opt, "set-min-framesize")) {
466
50
    op = append_shorthand_operation(options, OP__SET_MIN_FRAMESIZE);
467
50
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN)) {
468
29
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN);
469
29
      ok = false;
470
29
    }
471
21
    else
472
21
      undocumented_warning(opt);
473
50
  }
474
28.2k
  else if(0 == strcmp(opt, "set-max-framesize")) {
475
79
    op = append_shorthand_operation(options, OP__SET_MAX_FRAMESIZE);
476
79
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value >= (1u<<FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN)) {
477
42
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN);
478
42
      ok = false;
479
42
    }
480
37
    else
481
37
      undocumented_warning(opt);
482
79
  }
483
28.1k
  else if(0 == strcmp(opt, "set-sample-rate")) {
484
116
    op = append_shorthand_operation(options, OP__SET_SAMPLE_RATE);
485
116
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || !FLAC__format_sample_rate_is_valid(op->argument.streaminfo_uint32.value)) {
486
55
      flac_fprintf(stderr, "ERROR (--%s): invalid sample rate\n", opt);
487
55
      ok = false;
488
55
    }
489
61
    else
490
61
      undocumented_warning(opt);
491
116
  }
492
28.0k
  else if(0 == strcmp(opt, "set-channels")) {
493
147
    op = append_shorthand_operation(options, OP__SET_CHANNELS);
494
147
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value > FLAC__MAX_CHANNELS) {
495
105
      flac_fprintf(stderr, "ERROR (--%s): value must be > 0 and <= %u\n", opt, FLAC__MAX_CHANNELS);
496
105
      ok = false;
497
105
    }
498
42
    else
499
42
      undocumented_warning(opt);
500
147
  }
501
27.9k
  else if(0 == strcmp(opt, "set-bps")) {
502
170
    op = append_shorthand_operation(options, OP__SET_BPS);
503
170
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value < FLAC__MIN_BITS_PER_SAMPLE || op->argument.streaminfo_uint32.value > FLAC__MAX_BITS_PER_SAMPLE) {
504
127
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BITS_PER_SAMPLE, FLAC__MAX_BITS_PER_SAMPLE);
505
127
      ok = false;
506
127
    }
507
43
    else
508
43
      undocumented_warning(opt);
509
170
  }
510
27.7k
  else if(0 == strcmp(opt, "set-total-samples")) {
511
135
    op = append_shorthand_operation(options, OP__SET_TOTAL_SAMPLES);
512
135
    if(!parse_uint64(option_argument, &(op->argument.streaminfo_uint64.value)) || op->argument.streaminfo_uint64.value >= (((FLAC__uint64)1)<<FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN)) {
513
97
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN);
514
97
      ok = false;
515
97
    }
516
38
    else
517
38
      undocumented_warning(opt);
518
135
  }
519
27.6k
  else if(0 == strcmp(opt, "show-vendor-tag")) {
520
77
    (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
521
77
  }
522
27.5k
  else if(0 == strcmp(opt, "show-tag")) {
523
191
    const char *violation;
524
191
    op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
525
191
    FLAC__ASSERT(0 != option_argument);
526
191
    if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
527
42
      FLAC__ASSERT(0 != violation);
528
42
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
529
42
      ok = false;
530
42
    }
531
191
  }
532
27.3k
  else if(0 == strcmp(opt, "show-all-tags")) {
533
103
    op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
534
103
    parse_string("-",&op->argument.filename.value);
535
103
  }
536
27.2k
  else if(0 == strcmp(opt, "remove-all-tags")) {
537
85
    (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
538
85
  }
539
27.1k
  else if(0 == strcmp(opt, "remove-all-tags-except")) {
540
323
    const char *violation;
541
323
    op = append_shorthand_operation(options, OP__REMOVE_VC_ALL_EXCEPT);
542
323
    FLAC__ASSERT(0 != option_argument);
543
323
    if(!parse_vorbis_comment_field_names(option_argument, &(op->argument.vc_field_name.value), &violation)) {
544
43
      FLAC__ASSERT(0 != violation);
545
43
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
546
43
      ok = false;
547
43
    }
548
323
  }
549
26.8k
  else if(0 == strcmp(opt, "remove-tag")) {
550
111
    const char *violation;
551
111
    op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
552
111
    FLAC__ASSERT(0 != option_argument);
553
111
    if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
554
18
      FLAC__ASSERT(0 != violation);
555
18
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
556
18
      ok = false;
557
18
    }
558
111
  }
559
26.7k
  else if(0 == strcmp(opt, "remove-first-tag")) {
560
155
    const char *violation;
561
155
    op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
562
155
    FLAC__ASSERT(0 != option_argument);
563
155
    if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
564
18
      FLAC__ASSERT(0 != violation);
565
18
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
566
18
      ok = false;
567
18
    }
568
155
  }
569
26.5k
  else if(0 == strcmp(opt, "set-tag")) {
570
1.00k
    const char *violation;
571
1.00k
    op = append_shorthand_operation(options, OP__SET_VC_FIELD);
572
1.00k
    FLAC__ASSERT(0 != option_argument);
573
1.00k
    op->argument.vc_field.field_value_from_file = false;
574
1.00k
    if(!parse_vorbis_comment_field(option_argument, &(op->argument.vc_field.field), &(op->argument.vc_field.field_name), &(op->argument.vc_field.field_value), &(op->argument.vc_field.field_value_length), &violation)) {
575
62
      FLAC__ASSERT(0 != violation);
576
62
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
577
62
      ok = false;
578
62
    }
579
1.00k
  }
580
25.5k
  else if(0 == strcmp(opt, "set-tag-from-file")) {
581
187
    const char *violation;
582
187
    op = append_shorthand_operation(options, OP__SET_VC_FIELD);
583
187
    FLAC__ASSERT(0 != option_argument);
584
187
    op->argument.vc_field.field_value_from_file = true;
585
187
    if(!parse_vorbis_comment_field(option_argument, &(op->argument.vc_field.field), &(op->argument.vc_field.field_name), &(op->argument.vc_field.field_value), &(op->argument.vc_field.field_value_length), &violation)) {
586
18
      FLAC__ASSERT(0 != violation);
587
18
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
588
18
      ok = false;
589
18
    }
590
187
  }
591
25.3k
  else if(0 == strcmp(opt, "import-tags-from")) {
592
173
    op = append_shorthand_operation(options, OP__IMPORT_VC_FROM);
593
173
    FLAC__ASSERT(0 != option_argument);
594
173
    if(!parse_string(option_argument, &(op->argument.filename.value))) {
595
18
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
596
18
      ok = false;
597
18
    }
598
173
  }
599
25.2k
  else if(0 == strcmp(opt, "export-tags-to")) {
600
71
    op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
601
71
    FLAC__ASSERT(0 != option_argument);
602
71
    if(!parse_string(option_argument, &(op->argument.filename.value))) {
603
18
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
604
18
      ok = false;
605
18
    }
606
71
  }
607
25.1k
  else if(0 == strcmp(opt, "import-cuesheet-from")) {
608
2.24k
    if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
609
52
      flac_fprintf(stderr, "ERROR (--%s): may be specified only once\n", opt);
610
52
      ok = false;
611
52
    }
612
2.24k
    op = append_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
613
2.24k
    FLAC__ASSERT(0 != option_argument);
614
2.24k
    if(!parse_string(option_argument, &(op->argument.import_cuesheet_from.filename))) {
615
46
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
616
46
      ok = false;
617
46
    }
618
2.24k
  }
619
22.8k
  else if(0 == strcmp(opt, "export-cuesheet-to")) {
620
273
    op = append_shorthand_operation(options, OP__EXPORT_CUESHEET_TO);
621
273
    FLAC__ASSERT(0 != option_argument);
622
273
    if(!parse_string(option_argument, &(op->argument.filename.value))) {
623
24
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
624
24
      ok = false;
625
24
    }
626
273
  }
627
22.6k
  else if(0 == strcmp(opt, "import-picture-from")) {
628
1.14k
    op = append_shorthand_operation(options, OP__IMPORT_PICTURE_FROM);
629
1.14k
    FLAC__ASSERT(0 != option_argument);
630
1.14k
    if(!parse_string(option_argument, &(op->argument.specification.value))) {
631
20
      flac_fprintf(stderr, "ERROR (--%s): missing specification\n", opt);
632
20
      ok = false;
633
20
    }
634
1.14k
  }
635
21.4k
  else if(0 == strcmp(opt, "export-picture-to")) {
636
292
    arg = find_argument(options, ARG__BLOCK_NUMBER);
637
292
    op = append_shorthand_operation(options, OP__EXPORT_PICTURE_TO);
638
292
    FLAC__ASSERT(0 != option_argument);
639
292
    if(!parse_string(option_argument, &(op->argument.export_picture_to.filename))) {
640
50
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
641
50
      ok = false;
642
50
    }
643
292
    op->argument.export_picture_to.block_number_link = arg? &(arg->value.block_number) : 0;
644
292
  }
645
21.1k
  else if(0 == strcmp(opt, "add-seekpoint")) {
646
1.82k
    const char *violation;
647
1.82k
    char *spec;
648
1.82k
    FLAC__ASSERT(0 != option_argument);
649
1.82k
    if(!parse_add_seekpoint(option_argument, &spec, &violation)) {
650
193
      FLAC__ASSERT(0 != violation);
651
193
      flac_fprintf(stderr, "ERROR (--%s): malformed seekpoint specification \"%s\",\n       %s\n", opt, option_argument, violation);
652
193
      ok = false;
653
193
    }
654
1.63k
    else {
655
1.63k
      op = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
656
1.63k
      if(0 == op)
657
1.43k
        op = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
658
1.63k
      local_strcat(&(op->argument.add_seekpoint.specification), spec);
659
1.63k
      local_strcat(&(op->argument.add_seekpoint.specification), ";");
660
1.63k
      free(spec);
661
1.63k
    }
662
1.82k
  }
663
19.3k
  else if(0 == strcmp(opt, "add-replay-gain")) {
664
80
    (void) append_shorthand_operation(options, OP__ADD_REPLAY_GAIN);
665
80
  }
666
19.2k
  else if(0 == strcmp(opt, "scan-replay-gain")) {
667
11.8k
    (void) append_shorthand_operation(options, OP__SCAN_REPLAY_GAIN);
668
11.8k
  }
669
7.38k
  else if(0 == strcmp(opt, "remove-replay-gain")) {
670
302
    const FLAC__byte * const tags[5] = {
671
302
      GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS,
672
302
      GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN,
673
302
      GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK,
674
302
      GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN,
675
302
      GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK
676
302
    };
677
302
    size_t i;
678
1.81k
    for(i = 0; i < sizeof(tags)/sizeof(tags[0]); i++) {
679
1.51k
      op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
680
1.51k
      op->argument.vc_field_name.value = local_strdup((const char *)tags[i]);
681
1.51k
    }
682
302
  }
683
7.08k
  else if(0 == strcmp(opt, "add-padding")) {
684
352
    op = append_shorthand_operation(options, OP__ADD_PADDING);
685
352
    FLAC__ASSERT(0 != option_argument);
686
352
    if(!parse_add_padding(option_argument, &(op->argument.add_padding.length))) {
687
24
      flac_fprintf(stderr, "ERROR (--%s): illegal length \"%s\", length must be >= 0 and < 2^%u\n", opt, option_argument, FLAC__STREAM_METADATA_LENGTH_LEN);
688
24
      ok = false;
689
24
    }
690
352
  }
691
6.72k
  else if(0 == strcmp(opt, "help")) {
692
20
    options->show_long_help = true;
693
20
  }
694
6.70k
  else if(0 == strcmp(opt, "version")) {
695
21
    options->show_version = true;
696
21
  }
697
6.68k
  else if(0 == strcmp(opt, "list")) {
698
2.10k
    (void) append_major_operation(options, OP__LIST);
699
2.10k
  }
700
4.57k
  else if(0 == strcmp(opt, "append")) {
701
629
    (void) append_major_operation(options, OP__APPEND);
702
629
  }
703
3.95k
  else if(0 == strcmp(opt, "remove")) {
704
123
    (void) append_major_operation(options, OP__REMOVE);
705
123
  }
706
3.82k
  else if(0 == strcmp(opt, "remove-all")) {
707
43
    (void) append_major_operation(options, OP__REMOVE_ALL);
708
43
  }
709
3.78k
  else if(0 == strcmp(opt, "merge-padding")) {
710
608
    (void) append_major_operation(options, OP__MERGE_PADDING);
711
608
  }
712
3.17k
  else if(0 == strcmp(opt, "sort-padding")) {
713
48
    (void) append_major_operation(options, OP__SORT_PADDING);
714
48
  }
715
3.12k
  else if(0 == strcmp(opt, "block-number")) {
716
732
    arg = append_argument(options, ARG__BLOCK_NUMBER);
717
732
    FLAC__ASSERT(0 != option_argument);
718
732
    if(!parse_block_number(option_argument, &(arg->value.block_number))) {
719
113
      flac_fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
720
113
      ok = false;
721
113
    }
722
732
  }
723
2.39k
  else if(0 == strcmp(opt, "block-type")) {
724
53
    arg = append_argument(options, ARG__BLOCK_TYPE);
725
53
    FLAC__ASSERT(0 != option_argument);
726
53
    if(!parse_block_type(option_argument, &(arg->value.block_type))) {
727
27
      flac_fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
728
27
      ok = false;
729
27
    }
730
53
    options->args.checks.has_block_type = true;
731
53
  }
732
2.34k
  else if(0 == strcmp(opt, "except-block-type")) {
733
1.05k
    arg = append_argument(options, ARG__EXCEPT_BLOCK_TYPE);
734
1.05k
    FLAC__ASSERT(0 != option_argument);
735
1.05k
    if(!parse_block_type(option_argument, &(arg->value.block_type))) {
736
838
      flac_fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
737
838
      ok = false;
738
838
    }
739
1.05k
    options->args.checks.has_except_block_type = true;
740
1.05k
  }
741
1.28k
  else if(0 == strcmp(opt, "data-format")) {
742
646
    arg = append_argument(options, ARG__DATA_FORMAT);
743
646
    FLAC__ASSERT(0 != option_argument);
744
646
    if(!parse_data_format(option_argument, &(arg->value.data_format))) {
745
192
      flac_fprintf(stderr, "ERROR (--%s): illegal data format \"%s\"\n", opt, option_argument);
746
192
      ok = false;
747
192
    }
748
646
    options->data_format_is_binary = arg->value.data_format.is_binary;
749
646
    options->data_format_is_binary_headerless = arg->value.data_format.is_headerless;
750
646
  }
751
642
  else if(0 == strcmp(opt, "application-data-format")) {
752
149
    FLAC__ASSERT(0 != option_argument);
753
149
    if(!parse_application_data_format(option_argument, &(options->application_data_format_is_hexdump))) {
754
109
      flac_fprintf(stderr, "ERROR (--%s): illegal application data format \"%s\"\n", opt, option_argument);
755
109
      ok = false;
756
109
    }
757
149
  }
758
493
  else if(0 == strcmp(opt, "from-file")) {
759
493
    arg = append_argument(options, ARG__FROM_FILE);
760
493
    FLAC__ASSERT(0 != option_argument);
761
493
    arg->value.from_file.file_name = option_argument;
762
493
  }
763
0
  else {
764
0
    FLAC__ASSERT(0);
765
0
  }
766
767
29.8k
  return ok;
768
29.8k
}
769
770
void append_new_operation(CommandLineOptions *options, Operation operation)
771
28.9k
{
772
28.9k
  if(options->ops.capacity == 0) {
773
14.2k
    options->ops.capacity = 50;
774
14.2k
    if(0 == (options->ops.operations = malloc(sizeof(Operation) * options->ops.capacity)))
775
0
      die("out of memory allocating space for option list");
776
14.2k
    memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
777
14.2k
  }
778
28.9k
  if(options->ops.capacity <= options->ops.num_operations) {
779
4
    unsigned original_capacity = options->ops.capacity;
780
4
    if(options->ops.capacity > UINT32_MAX / 2) /* overflow check */
781
0
      die("out of memory allocating space for option list");
782
4
    options->ops.capacity *= 2;
783
4
    if(0 == (options->ops.operations = safe_realloc_mul_2op_(options->ops.operations, sizeof(Operation), /*times*/options->ops.capacity)))
784
0
      die("out of memory allocating space for option list");
785
4
    memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
786
4
  }
787
788
28.9k
  options->ops.operations[options->ops.num_operations++] = operation;
789
28.9k
}
790
791
void append_new_argument(CommandLineOptions *options, Argument argument)
792
2.97k
{
793
2.97k
  if(options->args.capacity == 0) {
794
2.40k
    options->args.capacity = 50;
795
2.40k
    if(0 == (options->args.arguments = malloc(sizeof(Argument) * options->args.capacity)))
796
0
      die("out of memory allocating space for option list");
797
2.40k
    memset(options->args.arguments, 0, sizeof(Argument) * options->args.capacity);
798
2.40k
  }
799
2.97k
  if(options->args.capacity <= options->args.num_arguments) {
800
0
    unsigned original_capacity = options->args.capacity;
801
0
    if(options->args.capacity > UINT32_MAX / 2) /* overflow check */
802
0
      die("out of memory allocating space for option list");
803
0
    options->args.capacity *= 2;
804
0
    if(0 == (options->args.arguments = safe_realloc_mul_2op_(options->args.arguments, sizeof(Argument), /*times*/options->args.capacity)))
805
0
      die("out of memory allocating space for option list");
806
0
    memset(options->args.arguments + original_capacity, 0, sizeof(Argument) * (options->args.capacity - original_capacity));
807
0
  }
808
809
2.97k
  options->args.arguments[options->args.num_arguments++] = argument;
810
2.97k
}
811
812
Operation *append_major_operation(CommandLineOptions *options, OperationType type)
813
3.55k
{
814
3.55k
  Operation op;
815
3.55k
  memset(&op, 0, sizeof(op));
816
3.55k
  op.type = type;
817
3.55k
  append_new_operation(options, op);
818
3.55k
  options->args.checks.num_major_ops++;
819
3.55k
  return options->ops.operations + (options->ops.num_operations - 1);
820
3.55k
}
821
822
Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
823
25.3k
{
824
25.3k
  Operation op;
825
25.3k
  memset(&op, 0, sizeof(op));
826
25.3k
  op.type = type;
827
25.3k
  append_new_operation(options, op);
828
25.3k
  options->args.checks.num_shorthand_ops++;
829
25.3k
  return options->ops.operations + (options->ops.num_operations - 1);
830
25.3k
}
831
832
Argument *find_argument(CommandLineOptions *options, ArgumentType type)
833
292
{
834
292
  unsigned i;
835
398
  for(i = 0; i < options->args.num_arguments; i++)
836
247
    if(options->args.arguments[i].type == type)
837
141
      return &options->args.arguments[i];
838
151
  return 0;
839
292
}
840
841
Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type)
842
63.7k
{
843
63.7k
  unsigned i;
844
173k
  for(i = 0; i < options->ops.num_operations; i++)
845
114k
    if(options->ops.operations[i].type == type)
846
4.70k
      return &options->ops.operations[i];
847
59.0k
  return 0;
848
63.7k
}
849
850
Argument *append_argument(CommandLineOptions *options, ArgumentType type)
851
2.97k
{
852
2.97k
  Argument arg;
853
2.97k
  memset(&arg, 0, sizeof(arg));
854
2.97k
  arg.type = type;
855
2.97k
  append_new_argument(options, arg);
856
2.97k
  return options->args.arguments + (options->args.num_arguments - 1);
857
2.97k
}
858
859
FLAC__bool parse_md5(const char *src, FLAC__byte dest[16])
860
132
{
861
132
  unsigned i, d;
862
132
  int c;
863
132
  FLAC__ASSERT(0 != src);
864
132
  if(strlen(src) != 32)
865
27
    return false;
866
  /* strtoul() accepts negative numbers which we do not want, so we do it the hard way */
867
825
  for(i = 0; i < 16; i++) {
868
800
    c = (int)(*src++);
869
800
    if(isdigit(c))
870
312
      d = (unsigned)(c - '0');
871
488
    else if(c >= 'a' && c <= 'f')
872
155
      d = (unsigned)(c - 'a') + 10u;
873
333
    else if(c >= 'A' && c <= 'F')
874
290
      d = (unsigned)(c - 'A') + 10u;
875
43
    else
876
43
      return false;
877
757
    d <<= 4;
878
757
    c = (int)(*src++);
879
757
    if(isdigit(c))
880
309
      d |= (unsigned)(c - '0');
881
448
    else if(c >= 'a' && c <= 'f')
882
167
      d |= (unsigned)(c - 'a') + 10u;
883
281
    else if(c >= 'A' && c <= 'F')
884
244
      d |= (unsigned)(c - 'A') + 10u;
885
37
    else
886
37
      return false;
887
720
    dest[i] = (FLAC__byte)d;
888
720
  }
889
25
  return true;
890
105
}
891
892
FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest)
893
855
{
894
855
  FLAC__ASSERT(0 != src);
895
855
  if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
896
169
    return false;
897
686
  *dest = strtoul(src, 0, 10);
898
686
  return true;
899
855
}
900
901
FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest)
902
135
{
903
135
  FLAC__ASSERT(0 != src);
904
135
  if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
905
60
    return false;
906
75
  *dest = strtoull(src, 0, 10);
907
75
  return true;
908
135
}
909
910
FLAC__bool parse_string(const char *src, char **dest)
911
4.30k
{
912
4.30k
  if(0 == src || strlen(src) == 0)
913
176
    return false;
914
4.12k
  *dest = strdup(src);
915
4.12k
  return true;
916
4.30k
}
917
918
FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation)
919
457
{
920
457
  static const char * const violations[] = {
921
457
    "field name contains invalid character"
922
457
  };
923
924
457
  char *q, *s;
925
926
457
  s = local_strdup(field_ref);
927
928
1.50M
  for(q = s; *q; q++) {
929
1.50M
    if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
930
78
      free(s);
931
78
      *violation = violations[0];
932
78
      return false;
933
78
    }
934
1.50M
  }
935
936
379
  *name = s;
937
938
379
  return true;
939
457
}
940
941
FLAC__bool parse_vorbis_comment_field_names(const char *field_ref, char **names, const char **violation)
942
323
{
943
323
  static const char * const violations[] = {
944
323
    "field name contains invalid character"
945
323
  };
946
947
323
  char *q, *s;
948
949
323
  s = local_strdup(field_ref);
950
951
1.02M
  for(q = s; *q; q++) {
952
1.02M
    if(*q < 0x20 || *q > 0x7d) {
953
43
      free(s);
954
43
      *violation = violations[0];
955
43
      return false;
956
43
    }
957
1.02M
  }
958
959
280
  *names = s;
960
961
280
  return true;
962
323
}
963
964
FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation)
965
1.82k
{
966
1.82k
  static const char *garbled_ = "garbled specification";
967
1.82k
  const unsigned n = strlen(in);
968
969
1.82k
  FLAC__ASSERT(0 != in);
970
1.82k
  FLAC__ASSERT(0 != out);
971
972
1.82k
  if(n == 0) {
973
31
    *violation = "specification is empty";
974
31
    return false;
975
31
  }
976
977
1.79k
  if(n > strspn(in, "0123456789.Xsx")) {
978
63
    *violation = "specification contains invalid character";
979
63
    return false;
980
63
  }
981
982
1.72k
  if(in[n-1] == 'X') {
983
175
    if(n > 1) {
984
20
      *violation = garbled_;
985
20
      return false;
986
20
    }
987
175
  }
988
1.55k
  else if(in[n-1] == 's') {
989
581
    if(n-1 > strspn(in, "0123456789.")) {
990
35
      *violation = garbled_;
991
35
      return false;
992
35
    }
993
581
  }
994
973
  else if(in[n-1] == 'x') {
995
412
    if(n-1 > strspn(in, "0123456789")) {
996
17
      *violation = garbled_;
997
17
      return false;
998
17
    }
999
412
  }
1000
561
  else {
1001
561
    if(n > strspn(in, "0123456789")) {
1002
27
      *violation = garbled_;
1003
27
      return false;
1004
27
    }
1005
561
  }
1006
1007
1.63k
  *out = local_strdup(in);
1008
1.63k
  return true;
1009
1.72k
}
1010
1011
FLAC__bool parse_add_padding(const char *in, unsigned *out)
1012
352
{
1013
352
  FLAC__ASSERT(0 != in);
1014
352
  FLAC__ASSERT(0 != out);
1015
352
  *out = (unsigned)strtoul(in, 0, 10);
1016
352
  return *out < (1u << FLAC__STREAM_METADATA_LENGTH_LEN);
1017
352
}
1018
1019
FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
1020
732
{
1021
732
  char *p, *q, *s, *end;
1022
732
  long i;
1023
732
  unsigned entry;
1024
1025
732
  if(*in == '\0')
1026
28
    return false;
1027
1028
704
  s = local_strdup(in);
1029
1030
  /* first count the entries */
1031
1.94M
  for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1032
1.94M
    ;
1033
1034
  /* make space */
1035
704
  FLAC__ASSERT(out->num_entries > 0);
1036
704
  if(0 == (out->entries = safe_malloc_mul_2op_(sizeof(unsigned), /*times*/out->num_entries)))
1037
0
    die("out of memory allocating space for option list");
1038
1039
  /* load 'em up */
1040
704
  entry = 0;
1041
704
  q = s;
1042
1.67M
  while(q) {
1043
1.67M
    FLAC__ASSERT(entry < out->num_entries);
1044
1.67M
    if(0 != (p = strchr(q, ',')))
1045
1.67M
      *p++ = '\0';
1046
1.67M
    if(!isdigit((int)(*q)) || (i = strtol(q, &end, 10)) < 0 || *end) {
1047
85
      free(s);
1048
85
      return false;
1049
85
    }
1050
1.67M
    out->entries[entry++] = (unsigned)i;
1051
1.67M
    q = p;
1052
1.67M
  }
1053
619
  FLAC__ASSERT(entry == out->num_entries);
1054
1055
619
  free(s);
1056
619
  return true;
1057
619
}
1058
1059
FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
1060
1.10k
{
1061
1.10k
  char *p, *q, *r, *s;
1062
1.10k
  unsigned entry;
1063
1064
1.10k
  if(*in == '\0')
1065
32
    return false;
1066
1067
1.07k
  s = local_strdup(in);
1068
1069
  /* first count the entries */
1070
180k
  for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1071
178k
    ;
1072
1073
  /* make space */
1074
1.07k
  FLAC__ASSERT(out->num_entries > 0);
1075
1.07k
  if(0 == (out->entries = safe_malloc_mul_2op_(sizeof(Argument_BlockTypeEntry), /*times*/out->num_entries)))
1076
0
    die("out of memory allocating space for option list");
1077
1078
  /* load 'em up */
1079
1.07k
  entry = 0;
1080
1.07k
  q = s;
1081
2.76k
  while(q) {
1082
2.52k
    FLAC__ASSERT(entry < out->num_entries);
1083
2.52k
    if(0 != (p = strchr(q, ',')))
1084
1.51k
      *p++ = 0;
1085
2.52k
    r = strchr(q, ':');
1086
2.52k
    if(r)
1087
603
      *r++ = '\0';
1088
2.52k
    if(0 != r && 0 != strcmp(q, "APPLICATION")) {
1089
124
      free(s);
1090
124
      return false;
1091
124
    }
1092
2.39k
    if(0 == strcmp(q, "STREAMINFO")) {
1093
86
      out->entries[entry++].type = FLAC__METADATA_TYPE_STREAMINFO;
1094
86
    }
1095
2.31k
    else if(0 == strcmp(q, "PADDING")) {
1096
426
      out->entries[entry++].type = FLAC__METADATA_TYPE_PADDING;
1097
426
    }
1098
1.88k
    else if(0 == strcmp(q, "APPLICATION")) {
1099
568
      out->entries[entry].type = FLAC__METADATA_TYPE_APPLICATION;
1100
568
      out->entries[entry].filter_application_by_id = (0 != r);
1101
568
      if(0 != r) {
1102
479
        if(strlen(r) == sizeof (out->entries[entry].application_id)) {
1103
388
          memcpy(out->entries[entry].application_id, r, sizeof (out->entries[entry].application_id));
1104
388
        }
1105
91
        else if(strlen(r) == 10 && FLAC__STRNCASECMP(r, "0x", 2) == 0 && strspn(r+2, "0123456789ABCDEFabcdef") == 8) {
1106
10
          FLAC__uint32 x = strtoul(r+2, 0, 16);
1107
10
          out->entries[entry].application_id[3] = (FLAC__byte)(x & 0xff);
1108
10
          out->entries[entry].application_id[2] = (FLAC__byte)((x>>=8) & 0xff);
1109
10
          out->entries[entry].application_id[1] = (FLAC__byte)((x>>=8) & 0xff);
1110
10
          out->entries[entry].application_id[0] = (FLAC__byte)((x>>=8) & 0xff);
1111
10
        }
1112
81
        else {
1113
81
          free(s);
1114
81
          return false;
1115
81
        }
1116
479
      }
1117
487
      entry++;
1118
487
    }
1119
1.31k
    else if(0 == strcmp(q, "SEEKTABLE")) {
1120
214
      out->entries[entry++].type = FLAC__METADATA_TYPE_SEEKTABLE;
1121
214
    }
1122
1.10k
    else if(0 == strcmp(q, "VORBIS_COMMENT")) {
1123
10
      out->entries[entry++].type = FLAC__METADATA_TYPE_VORBIS_COMMENT;
1124
10
    }
1125
1.09k
    else if(0 == strcmp(q, "CUESHEET")) {
1126
236
      out->entries[entry++].type = FLAC__METADATA_TYPE_CUESHEET;
1127
236
    }
1128
859
    else if(0 == strcmp(q, "PICTURE")) {
1129
231
      out->entries[entry++].type = FLAC__METADATA_TYPE_PICTURE;
1130
231
    }
1131
628
    else {
1132
628
      free(s);
1133
628
      return false;
1134
628
    }
1135
1.69k
    q = p;
1136
1.69k
  }
1137
243
  FLAC__ASSERT(entry == out->num_entries);
1138
1139
243
  free(s);
1140
243
  return true;
1141
243
}
1142
1143
FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
1144
646
{
1145
646
  if(0 == strcmp(in, "binary-headerless")) {
1146
32
    out->is_binary = false;
1147
32
    out->is_headerless = true;
1148
32
  }
1149
614
  else if(0 == strcmp(in, "binary")) {
1150
412
    out->is_binary = true;
1151
412
    out->is_headerless = false;
1152
412
  }
1153
202
  else if(0 == strcmp(in, "text")) {
1154
10
    out->is_binary = false;
1155
10
    out->is_headerless = false;
1156
10
  }
1157
192
  else
1158
192
    return false;
1159
454
  return true;
1160
646
}
1161
1162
FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out)
1163
149
{
1164
149
  if(0 == strcmp(in, "hexdump"))
1165
22
    *out = true;
1166
127
  else if(0 == strcmp(in, "text"))
1167
18
    *out = false;
1168
109
  else
1169
109
    return false;
1170
40
  return true;
1171
149
}
1172
1173
void undocumented_warning(const char *opt)
1174
375
{
1175
375
  flac_fprintf(stderr, "WARNING: undocumented option --%s should be used with caution,\n         only for repairing a damaged STREAMINFO block\n", opt);
1176
375
}