Coverage Report

Created: 2026-05-16 06:50

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
12.5k
{
132
12.5k
  options->preserve_modtime = false;
133
134
  /* '2' is a hack to mean "use default if not forced on command line" */
135
12.5k
  FLAC__ASSERT(true != 2);
136
12.5k
  options->prefix_with_filename = 2;
137
138
12.5k
  options->utf8_convert = true;
139
12.5k
  options->use_padding = true;
140
12.5k
  options->cued_seekpoints = true;
141
12.5k
  options->show_long_help = false;
142
12.5k
  options->show_version = false;
143
12.5k
  options->data_format_is_binary = false;
144
12.5k
  options->data_format_is_binary_headerless = false;
145
12.5k
  options->application_data_format_is_hexdump = false;
146
147
12.5k
  options->ops.operations = 0;
148
12.5k
  options->ops.num_operations = 0;
149
12.5k
  options->ops.capacity = 0;
150
151
12.5k
  options->args.arguments = 0;
152
12.5k
  options->args.num_arguments = 0;
153
12.5k
  options->args.capacity = 0;
154
155
12.5k
  options->args.checks.num_shorthand_ops = 0;
156
12.5k
  options->args.checks.num_major_ops = 0;
157
12.5k
  options->args.checks.has_block_type = false;
158
12.5k
  options->args.checks.has_except_block_type = false;
159
160
12.5k
  options->num_files = 0;
161
12.5k
  options->filenames = 0;
162
12.5k
  options->output_name = 0;
163
12.5k
}
164
165
FLAC__bool parse_options(int argc, char *argv[], CommandLineOptions *options)
166
12.5k
{
167
12.5k
  int ret;
168
12.5k
  int option_index = 1;
169
12.5k
  FLAC__bool had_error = false;
170
171
37.4k
  while ((ret = share__getopt_long(argc, argv, "o:", long_options_, &option_index)) != -1) {
172
24.8k
    switch (ret) {
173
23.8k
      case 0:
174
23.8k
        had_error |= !parse_option(option_index, share__optarg, options);
175
23.8k
        break;
176
111
      case 'o':
177
111
        had_error |= !parse_option(0, share__optarg, options);
178
111
        break;
179
855
      case '?':
180
855
      case ':':
181
855
        had_error = true;
182
855
        break;
183
0
      default:
184
0
        FLAC__ASSERT(0);
185
0
        break;
186
24.8k
    }
187
24.8k
  }
188
189
12.5k
  if(options->prefix_with_filename == 2)
190
12.4k
    options->prefix_with_filename = (argc - share__optind > 1);
191
192
12.5k
  if(share__optind >= argc && !options->show_long_help && !options->show_version) {
193
65
    flac_fprintf(stderr,"ERROR: you must specify at least one FLAC file;\n");
194
65
    flac_fprintf(stderr,"       metaflac cannot be used as a pipe\n");
195
65
    had_error = true;
196
65
  }
197
198
12.5k
  options->num_files = argc - share__optind;
199
200
12.5k
  if(options->num_files > 0) {
201
12.5k
    unsigned i = 0;
202
12.5k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
203
12.5k
    extern char* allowed_filename;
204
12.5k
#endif
205
12.5k
    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
51.7k
    while(share__optind < argc)
208
39.2k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
209
39.2k
      if(strcmp(argv[share__optind],allowed_filename) == 0)
210
21.2k
#endif
211
21.2k
        options->filenames[i++] = local_strdup(argv[share__optind++]);
212
18.0k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
213
18.0k
      else {
214
18.0k
        if(options->num_files > 0)
215
18.0k
          options->num_files--;
216
18.0k
        share__optind++;
217
18.0k
      }
218
12.5k
#endif
219
12.5k
  }
220
221
12.5k
  if(options->args.checks.num_major_ops > 0) {
222
2.27k
    if(options->args.checks.num_major_ops > 1) {
223
30
      flac_fprintf(stderr, "ERROR: you may only specify one major operation at a time\n");
224
30
      had_error = true;
225
30
    }
226
2.24k
    else if(options->args.checks.num_shorthand_ops > 0) {
227
9
      flac_fprintf(stderr, "ERROR: you may not mix shorthand and major operations\n");
228
9
      had_error = true;
229
9
    }
230
2.27k
  }
231
232
12.5k
  if(!options->show_long_help && !options->show_version &&
233
12.5k
     (options->args.checks.num_major_ops == 0) && (options->args.checks.num_shorthand_ops == 0)) {
234
1.39k
    flac_fprintf(stderr, "ERROR: no operations specified\n");
235
1.39k
    had_error = true;
236
1.39k
  }
237
238
  /* check for only one FLAC file used with certain options */
239
12.5k
  if(!had_error && options->num_files > 1) {
240
7.29k
    if(0 != options->output_name) {
241
0
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC input file when specifying an output filename\n");
242
0
      had_error = true;
243
0
    }
244
7.29k
    if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
245
5
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--import-cuesheet-from'\n");
246
5
      had_error = true;
247
5
    }
248
7.29k
    if(0 != find_shorthand_operation(options, OP__EXPORT_CUESHEET_TO)) {
249
2
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-cuesheet-to'\n");
250
2
      had_error = true;
251
2
    }
252
7.29k
    if(0 != find_shorthand_operation(options, OP__EXPORT_PICTURE_TO)) {
253
4
      flac_fprintf(stderr, "ERROR: you may only specify one FLAC file when using '--export-picture-to'\n");
254
4
      had_error = true;
255
4
    }
256
7.29k
    if(
257
7.29k
      0 != find_shorthand_operation(options, OP__IMPORT_VC_FROM) &&
258
19
      0 == strcmp(find_shorthand_operation(options, OP__IMPORT_VC_FROM)->argument.filename.value, "-")
259
7.29k
    ) {
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
7.29k
  }
264
265
12.5k
  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
12.5k
  if(had_error)
271
2.00k
    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
12.5k
  if(options->cued_seekpoints) {
279
12.5k
    Operation *op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
280
12.5k
    if(0 != op) {
281
1.84k
      Operation *op2 = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
282
1.84k
      if(0 == op2) {
283
1.84k
        op2 = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
284
        /* Need to re-find op, because the appending might have caused realloc */
285
1.84k
        op = find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
286
1.84k
      }
287
1.84k
      op->argument.import_cuesheet_from.add_seekpoint_link = &(op2->argument.add_seekpoint);
288
1.84k
    }
289
12.5k
  }
290
291
12.5k
  return had_error;
292
12.5k
}
293
294
void free_options(CommandLineOptions *options)
295
12.5k
{
296
12.5k
  unsigned i;
297
12.5k
  Operation *op;
298
12.5k
  Argument *arg;
299
300
12.5k
  FLAC__ASSERT(0 == options->ops.operations || options->ops.num_operations > 0);
301
12.5k
  FLAC__ASSERT(0 == options->args.arguments || options->args.num_arguments > 0);
302
303
36.0k
  for(i = 0, op = options->ops.operations; i < options->ops.num_operations; i++, op++) {
304
23.5k
    switch(op->type) {
305
136
      case OP__SHOW_VC_FIELD:
306
1.29k
      case OP__REMOVE_VC_FIELD:
307
1.50k
      case OP__REMOVE_VC_FIRSTFIELD:
308
1.74k
      case OP__REMOVE_VC_ALL_EXCEPT:
309
1.74k
        if(0 != op->argument.vc_field_name.value)
310
1.62k
          free(op->argument.vc_field_name.value);
311
1.74k
        break;
312
759
      case OP__SET_VC_FIELD:
313
759
        if(0 != op->argument.vc_field.field)
314
759
          free(op->argument.vc_field.field);
315
759
        if(0 != op->argument.vc_field.field_name)
316
681
          free(op->argument.vc_field.field_name);
317
759
        if(0 != op->argument.vc_field.field_value)
318
681
          free(op->argument.vc_field.field_value);
319
759
        break;
320
123
      case OP__IMPORT_VC_FROM:
321
246
      case OP__EXPORT_VC_TO:
322
340
      case OP__EXPORT_CUESHEET_TO:
323
340
        if(0 != op->argument.filename.value)
324
280
          free(op->argument.filename.value);
325
340
        break;
326
1.91k
      case OP__IMPORT_CUESHEET_FROM:
327
1.91k
        if(0 != op->argument.import_cuesheet_from.filename)
328
1.86k
          free(op->argument.import_cuesheet_from.filename);
329
1.91k
        break;
330
891
      case OP__IMPORT_PICTURE_FROM:
331
891
        if(0 != op->argument.specification.value)
332
871
          free(op->argument.specification.value);
333
891
        break;
334
237
      case OP__EXPORT_PICTURE_TO:
335
237
        if(0 != op->argument.export_picture_to.filename)
336
190
          free(op->argument.export_picture_to.filename);
337
237
        break;
338
3.06k
      case OP__ADD_SEEKPOINT:
339
3.06k
        if(0 != op->argument.add_seekpoint.specification)
340
1.53k
          free(op->argument.add_seekpoint.specification);
341
3.06k
        break;
342
14.5k
      default:
343
14.5k
        break;
344
23.5k
    }
345
23.5k
  }
346
347
14.8k
  for(i = 0, arg = options->args.arguments; i < options->args.num_arguments; i++, arg++) {
348
2.29k
    switch(arg->type) {
349
524
      case ARG__BLOCK_NUMBER:
350
524
        if(0 != arg->value.block_number.entries)
351
500
          free(arg->value.block_number.entries);
352
524
        break;
353
47
      case ARG__BLOCK_TYPE:
354
999
      case ARG__EXCEPT_BLOCK_TYPE:
355
999
        if(0 != arg->value.block_type.entries)
356
970
          free(arg->value.block_type.entries);
357
999
        break;
358
769
      default:
359
769
        break;
360
2.29k
    }
361
2.29k
  }
362
363
12.5k
  if(0 != options->ops.operations)
364
11.1k
    free(options->ops.operations);
365
366
12.5k
  if(0 != options->args.arguments)
367
1.75k
    free(options->args.arguments);
368
369
12.5k
  if(0 != options->filenames) {
370
33.7k
    for(i = 0; i < options->num_files; i++) {
371
21.2k
      if(0 != options->filenames[i])
372
21.2k
        free(options->filenames[i]);
373
21.2k
    }
374
12.5k
    free(options->filenames);
375
12.5k
  }
376
12.5k
}
377
378
/*
379
 * local routines
380
 */
381
382
FLAC__bool parse_option(int option_index, const char *option_argument, CommandLineOptions *options)
383
24.0k
{
384
24.0k
  const char *opt = long_options_[option_index].name;
385
24.0k
  Operation *op;
386
24.0k
  Argument *arg;
387
24.0k
  FLAC__bool ok = true;
388
389
24.0k
  if(0 == strcmp(opt, "preserve-modtime")) {
390
28
    options->preserve_modtime = true;
391
28
  }
392
23.9k
  else if(0 == strcmp(opt, "with-filename")) {
393
19
    options->prefix_with_filename = true;
394
19
  }
395
23.9k
  else if(0 == strcmp(opt, "no-filename")) {
396
106
    options->prefix_with_filename = false;
397
106
  }
398
23.8k
  else if(0 == strcmp(opt, "no-utf8-convert")) {
399
122
    options->utf8_convert = false;
400
122
  }
401
23.7k
  else if(0 == strcmp(opt, "dont-use-padding")) {
402
53
    options->use_padding = false;
403
53
  }
404
23.6k
  else if(0 == strcmp(opt, "no-cued-seekpoints")) {
405
34
    options->cued_seekpoints = false;
406
34
  }
407
23.6k
  else if(0 == strcmp(opt, "output-name")) {
408
111
    options->output_name = option_argument;
409
111
  }
410
23.5k
  else if(0 == strcmp(opt, "show-md5sum")) {
411
27
    (void) append_shorthand_operation(options, OP__SHOW_MD5SUM);
412
27
  }
413
23.5k
  else if(0 == strcmp(opt, "show-min-blocksize")) {
414
26
    (void) append_shorthand_operation(options, OP__SHOW_MIN_BLOCKSIZE);
415
26
  }
416
23.4k
  else if(0 == strcmp(opt, "show-max-blocksize")) {
417
25
    (void) append_shorthand_operation(options, OP__SHOW_MAX_BLOCKSIZE);
418
25
  }
419
23.4k
  else if(0 == strcmp(opt, "show-min-framesize")) {
420
33
    (void) append_shorthand_operation(options, OP__SHOW_MIN_FRAMESIZE);
421
33
  }
422
23.4k
  else if(0 == strcmp(opt, "show-max-framesize")) {
423
25
    (void) append_shorthand_operation(options, OP__SHOW_MAX_FRAMESIZE);
424
25
  }
425
23.3k
  else if(0 == strcmp(opt, "show-sample-rate")) {
426
27
    (void) append_shorthand_operation(options, OP__SHOW_SAMPLE_RATE);
427
27
  }
428
23.3k
  else if(0 == strcmp(opt, "show-channels")) {
429
35
    (void) append_shorthand_operation(options, OP__SHOW_CHANNELS);
430
35
  }
431
23.3k
  else if(0 == strcmp(opt, "show-bps")) {
432
34
    (void) append_shorthand_operation(options, OP__SHOW_BPS);
433
34
  }
434
23.2k
  else if(0 == strcmp(opt, "show-total-samples")) {
435
35
    (void) append_shorthand_operation(options, OP__SHOW_TOTAL_SAMPLES);
436
35
  }
437
23.2k
  else if(0 == strcmp(opt, "set-md5sum")) {
438
106
    op = append_shorthand_operation(options, OP__SET_MD5SUM);
439
106
    FLAC__ASSERT(0 != option_argument);
440
106
    if(!parse_md5(option_argument, op->argument.streaminfo_md5.value)) {
441
84
      flac_fprintf(stderr, "ERROR (--%s): bad MD5 sum\n", opt);
442
84
      ok = false;
443
84
    }
444
22
    else
445
22
      undocumented_warning(opt);
446
106
  }
447
23.1k
  else if(0 == strcmp(opt, "set-min-blocksize")) {
448
112
    op = append_shorthand_operation(options, OP__SET_MIN_BLOCKSIZE);
449
112
    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
80
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
451
80
      ok = false;
452
80
    }
453
32
    else
454
32
      undocumented_warning(opt);
455
112
  }
456
23.0k
  else if(0 == strcmp(opt, "set-max-blocksize")) {
457
119
    op = append_shorthand_operation(options, OP__SET_MAX_BLOCKSIZE);
458
119
    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
85
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BLOCK_SIZE, FLAC__MAX_BLOCK_SIZE);
460
85
      ok = false;
461
85
    }
462
34
    else
463
34
      undocumented_warning(opt);
464
119
  }
465
22.9k
  else if(0 == strcmp(opt, "set-min-framesize")) {
466
40
    op = append_shorthand_operation(options, OP__SET_MIN_FRAMESIZE);
467
40
    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
26
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN);
469
26
      ok = false;
470
26
    }
471
14
    else
472
14
      undocumented_warning(opt);
473
40
  }
474
22.8k
  else if(0 == strcmp(opt, "set-max-framesize")) {
475
66
    op = append_shorthand_operation(options, OP__SET_MAX_FRAMESIZE);
476
66
    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
39
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN);
478
39
      ok = false;
479
39
    }
480
27
    else
481
27
      undocumented_warning(opt);
482
66
  }
483
22.8k
  else if(0 == strcmp(opt, "set-sample-rate")) {
484
92
    op = append_shorthand_operation(options, OP__SET_SAMPLE_RATE);
485
92
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || !FLAC__format_sample_rate_is_valid(op->argument.streaminfo_uint32.value)) {
486
46
      flac_fprintf(stderr, "ERROR (--%s): invalid sample rate\n", opt);
487
46
      ok = false;
488
46
    }
489
46
    else
490
46
      undocumented_warning(opt);
491
92
  }
492
22.7k
  else if(0 == strcmp(opt, "set-channels")) {
493
95
    op = append_shorthand_operation(options, OP__SET_CHANNELS);
494
95
    if(!parse_uint32(option_argument, &(op->argument.streaminfo_uint32.value)) || op->argument.streaminfo_uint32.value > FLAC__MAX_CHANNELS) {
495
58
      flac_fprintf(stderr, "ERROR (--%s): value must be > 0 and <= %u\n", opt, FLAC__MAX_CHANNELS);
496
58
      ok = false;
497
58
    }
498
37
    else
499
37
      undocumented_warning(opt);
500
95
  }
501
22.6k
  else if(0 == strcmp(opt, "set-bps")) {
502
141
    op = append_shorthand_operation(options, OP__SET_BPS);
503
141
    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
114
      flac_fprintf(stderr, "ERROR (--%s): value must be >= %u and <= %u\n", opt, FLAC__MIN_BITS_PER_SAMPLE, FLAC__MAX_BITS_PER_SAMPLE);
505
114
      ok = false;
506
114
    }
507
27
    else
508
27
      undocumented_warning(opt);
509
141
  }
510
22.4k
  else if(0 == strcmp(opt, "set-total-samples")) {
511
122
    op = append_shorthand_operation(options, OP__SET_TOTAL_SAMPLES);
512
122
    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
86
      flac_fprintf(stderr, "ERROR (--%s): value must be a %u-bit unsigned integer\n", opt, FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN);
514
86
      ok = false;
515
86
    }
516
36
    else
517
36
      undocumented_warning(opt);
518
122
  }
519
22.3k
  else if(0 == strcmp(opt, "show-vendor-tag")) {
520
34
    (void) append_shorthand_operation(options, OP__SHOW_VC_VENDOR);
521
34
  }
522
22.3k
  else if(0 == strcmp(opt, "show-tag")) {
523
136
    const char *violation;
524
136
    op = append_shorthand_operation(options, OP__SHOW_VC_FIELD);
525
136
    FLAC__ASSERT(0 != option_argument);
526
136
    if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
527
31
      FLAC__ASSERT(0 != violation);
528
31
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
529
31
      ok = false;
530
31
    }
531
136
  }
532
22.1k
  else if(0 == strcmp(opt, "show-all-tags")) {
533
32
    op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
534
32
    parse_string("-",&op->argument.filename.value);
535
32
  }
536
22.1k
  else if(0 == strcmp(opt, "remove-all-tags")) {
537
41
    (void) append_shorthand_operation(options, OP__REMOVE_VC_ALL);
538
41
  }
539
22.1k
  else if(0 == strcmp(opt, "remove-all-tags-except")) {
540
244
    const char *violation;
541
244
    op = append_shorthand_operation(options, OP__REMOVE_VC_ALL_EXCEPT);
542
244
    FLAC__ASSERT(0 != option_argument);
543
244
    if(!parse_vorbis_comment_field_names(option_argument, &(op->argument.vc_field_name.value), &violation)) {
544
41
      FLAC__ASSERT(0 != violation);
545
41
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
546
41
      ok = false;
547
41
    }
548
244
  }
549
21.8k
  else if(0 == strcmp(opt, "remove-tag")) {
550
86
    const char *violation;
551
86
    op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
552
86
    FLAC__ASSERT(0 != option_argument);
553
86
    if(!parse_vorbis_comment_field_name(option_argument, &(op->argument.vc_field_name.value), &violation)) {
554
28
      FLAC__ASSERT(0 != violation);
555
28
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field name \"%s\",\n       %s\n", opt, option_argument, violation);
556
28
      ok = false;
557
28
    }
558
86
  }
559
21.7k
  else if(0 == strcmp(opt, "remove-first-tag")) {
560
211
    const char *violation;
561
211
    op = append_shorthand_operation(options, OP__REMOVE_VC_FIRSTFIELD);
562
211
    FLAC__ASSERT(0 != option_argument);
563
211
    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
211
  }
569
21.5k
  else if(0 == strcmp(opt, "set-tag")) {
570
645
    const char *violation;
571
645
    op = append_shorthand_operation(options, OP__SET_VC_FIELD);
572
645
    FLAC__ASSERT(0 != option_argument);
573
645
    op->argument.vc_field.field_value_from_file = false;
574
645
    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
60
      FLAC__ASSERT(0 != violation);
576
60
      flac_fprintf(stderr, "ERROR (--%s): malformed vorbis comment field \"%s\",\n       %s\n", opt, option_argument, violation);
577
60
      ok = false;
578
60
    }
579
645
  }
580
20.9k
  else if(0 == strcmp(opt, "set-tag-from-file")) {
581
114
    const char *violation;
582
114
    op = append_shorthand_operation(options, OP__SET_VC_FIELD);
583
114
    FLAC__ASSERT(0 != option_argument);
584
114
    op->argument.vc_field.field_value_from_file = true;
585
114
    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
114
  }
591
20.8k
  else if(0 == strcmp(opt, "import-tags-from")) {
592
123
    op = append_shorthand_operation(options, OP__IMPORT_VC_FROM);
593
123
    FLAC__ASSERT(0 != option_argument);
594
123
    if(!parse_string(option_argument, &(op->argument.filename.value))) {
595
33
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
596
33
      ok = false;
597
33
    }
598
123
  }
599
20.7k
  else if(0 == strcmp(opt, "export-tags-to")) {
600
91
    op = append_shorthand_operation(options, OP__EXPORT_VC_TO);
601
91
    FLAC__ASSERT(0 != option_argument);
602
91
    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
91
  }
607
20.6k
  else if(0 == strcmp(opt, "import-cuesheet-from")) {
608
1.91k
    if(0 != find_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM)) {
609
54
      flac_fprintf(stderr, "ERROR (--%s): may be specified only once\n", opt);
610
54
      ok = false;
611
54
    }
612
1.91k
    op = append_shorthand_operation(options, OP__IMPORT_CUESHEET_FROM);
613
1.91k
    FLAC__ASSERT(0 != option_argument);
614
1.91k
    if(!parse_string(option_argument, &(op->argument.import_cuesheet_from.filename))) {
615
48
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
616
48
      ok = false;
617
48
    }
618
1.91k
  }
619
18.6k
  else if(0 == strcmp(opt, "export-cuesheet-to")) {
620
94
    op = append_shorthand_operation(options, OP__EXPORT_CUESHEET_TO);
621
94
    FLAC__ASSERT(0 != option_argument);
622
94
    if(!parse_string(option_argument, &(op->argument.filename.value))) {
623
9
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
624
9
      ok = false;
625
9
    }
626
94
  }
627
18.6k
  else if(0 == strcmp(opt, "import-picture-from")) {
628
891
    op = append_shorthand_operation(options, OP__IMPORT_PICTURE_FROM);
629
891
    FLAC__ASSERT(0 != option_argument);
630
891
    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
891
  }
635
17.7k
  else if(0 == strcmp(opt, "export-picture-to")) {
636
237
    arg = find_argument(options, ARG__BLOCK_NUMBER);
637
237
    op = append_shorthand_operation(options, OP__EXPORT_PICTURE_TO);
638
237
    FLAC__ASSERT(0 != option_argument);
639
237
    if(!parse_string(option_argument, &(op->argument.export_picture_to.filename))) {
640
47
      flac_fprintf(stderr, "ERROR (--%s): missing filename\n", opt);
641
47
      ok = false;
642
47
    }
643
237
    op->argument.export_picture_to.block_number_link = arg? &(arg->value.block_number) : 0;
644
237
  }
645
17.4k
  else if(0 == strcmp(opt, "add-seekpoint")) {
646
1.48k
    const char *violation;
647
1.48k
    char *spec;
648
1.48k
    FLAC__ASSERT(0 != option_argument);
649
1.48k
    if(!parse_add_seekpoint(option_argument, &spec, &violation)) {
650
157
      FLAC__ASSERT(0 != violation);
651
157
      flac_fprintf(stderr, "ERROR (--%s): malformed seekpoint specification \"%s\",\n       %s\n", opt, option_argument, violation);
652
157
      ok = false;
653
157
    }
654
1.32k
    else {
655
1.32k
      op = find_shorthand_operation(options, OP__ADD_SEEKPOINT);
656
1.32k
      if(0 == op)
657
1.21k
        op = append_shorthand_operation(options, OP__ADD_SEEKPOINT);
658
1.32k
      local_strcat(&(op->argument.add_seekpoint.specification), spec);
659
1.32k
      local_strcat(&(op->argument.add_seekpoint.specification), ";");
660
1.32k
      free(spec);
661
1.32k
    }
662
1.48k
  }
663
15.9k
  else if(0 == strcmp(opt, "add-replay-gain")) {
664
42
    (void) append_shorthand_operation(options, OP__ADD_REPLAY_GAIN);
665
42
  }
666
15.9k
  else if(0 == strcmp(opt, "scan-replay-gain")) {
667
10.6k
    (void) append_shorthand_operation(options, OP__SCAN_REPLAY_GAIN);
668
10.6k
  }
669
5.24k
  else if(0 == strcmp(opt, "remove-replay-gain")) {
670
214
    const FLAC__byte * const tags[5] = {
671
214
      GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS,
672
214
      GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN,
673
214
      GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK,
674
214
      GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN,
675
214
      GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK
676
214
    };
677
214
    size_t i;
678
1.28k
    for(i = 0; i < sizeof(tags)/sizeof(tags[0]); i++) {
679
1.07k
      op = append_shorthand_operation(options, OP__REMOVE_VC_FIELD);
680
1.07k
      op->argument.vc_field_name.value = local_strdup((const char *)tags[i]);
681
1.07k
    }
682
214
  }
683
5.03k
  else if(0 == strcmp(opt, "add-padding")) {
684
206
    op = append_shorthand_operation(options, OP__ADD_PADDING);
685
206
    FLAC__ASSERT(0 != option_argument);
686
206
    if(!parse_add_padding(option_argument, &(op->argument.add_padding.length))) {
687
10
      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
10
      ok = false;
689
10
    }
690
206
  }
691
4.82k
  else if(0 == strcmp(opt, "help")) {
692
22
    options->show_long_help = true;
693
22
  }
694
4.80k
  else if(0 == strcmp(opt, "version")) {
695
20
    options->show_version = true;
696
20
  }
697
4.78k
  else if(0 == strcmp(opt, "list")) {
698
1.16k
    (void) append_major_operation(options, OP__LIST);
699
1.16k
  }
700
3.61k
  else if(0 == strcmp(opt, "append")) {
701
544
    (void) append_major_operation(options, OP__APPEND);
702
544
  }
703
3.07k
  else if(0 == strcmp(opt, "remove")) {
704
78
    (void) append_major_operation(options, OP__REMOVE);
705
78
  }
706
2.99k
  else if(0 == strcmp(opt, "remove-all")) {
707
34
    (void) append_major_operation(options, OP__REMOVE_ALL);
708
34
  }
709
2.96k
  else if(0 == strcmp(opt, "merge-padding")) {
710
501
    (void) append_major_operation(options, OP__MERGE_PADDING);
711
501
  }
712
2.45k
  else if(0 == strcmp(opt, "sort-padding")) {
713
42
    (void) append_major_operation(options, OP__SORT_PADDING);
714
42
  }
715
2.41k
  else if(0 == strcmp(opt, "block-number")) {
716
524
    arg = append_argument(options, ARG__BLOCK_NUMBER);
717
524
    FLAC__ASSERT(0 != option_argument);
718
524
    if(!parse_block_number(option_argument, &(arg->value.block_number))) {
719
118
      flac_fprintf(stderr, "ERROR: malformed block number specification \"%s\"\n", option_argument);
720
118
      ok = false;
721
118
    }
722
524
  }
723
1.89k
  else if(0 == strcmp(opt, "block-type")) {
724
47
    arg = append_argument(options, ARG__BLOCK_TYPE);
725
47
    FLAC__ASSERT(0 != option_argument);
726
47
    if(!parse_block_type(option_argument, &(arg->value.block_type))) {
727
20
      flac_fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
728
20
      ok = false;
729
20
    }
730
47
    options->args.checks.has_block_type = true;
731
47
  }
732
1.84k
  else if(0 == strcmp(opt, "except-block-type")) {
733
952
    arg = append_argument(options, ARG__EXCEPT_BLOCK_TYPE);
734
952
    FLAC__ASSERT(0 != option_argument);
735
952
    if(!parse_block_type(option_argument, &(arg->value.block_type))) {
736
737
      flac_fprintf(stderr, "ERROR (--%s): malformed block type specification \"%s\"\n", opt, option_argument);
737
737
      ok = false;
738
737
    }
739
952
    options->args.checks.has_except_block_type = true;
740
952
  }
741
894
  else if(0 == strcmp(opt, "data-format")) {
742
463
    arg = append_argument(options, ARG__DATA_FORMAT);
743
463
    FLAC__ASSERT(0 != option_argument);
744
463
    if(!parse_data_format(option_argument, &(arg->value.data_format))) {
745
152
      flac_fprintf(stderr, "ERROR (--%s): illegal data format \"%s\"\n", opt, option_argument);
746
152
      ok = false;
747
152
    }
748
463
    options->data_format_is_binary = arg->value.data_format.is_binary;
749
463
    options->data_format_is_binary_headerless = arg->value.data_format.is_headerless;
750
463
  }
751
431
  else if(0 == strcmp(opt, "application-data-format")) {
752
125
    FLAC__ASSERT(0 != option_argument);
753
125
    if(!parse_application_data_format(option_argument, &(options->application_data_format_is_hexdump))) {
754
89
      flac_fprintf(stderr, "ERROR (--%s): illegal application data format \"%s\"\n", opt, option_argument);
755
89
      ok = false;
756
89
    }
757
125
  }
758
306
  else if(0 == strcmp(opt, "from-file")) {
759
306
    arg = append_argument(options, ARG__FROM_FILE);
760
306
    FLAC__ASSERT(0 != option_argument);
761
306
    arg->value.from_file.file_name = option_argument;
762
306
  }
763
0
  else {
764
0
    FLAC__ASSERT(0);
765
0
  }
766
767
24.0k
  return ok;
768
24.0k
}
769
770
void append_new_operation(CommandLineOptions *options, Operation operation)
771
23.5k
{
772
23.5k
  if(options->ops.capacity == 0) {
773
11.1k
    options->ops.capacity = 50;
774
11.1k
    if(0 == (options->ops.operations = malloc(sizeof(Operation) * options->ops.capacity)))
775
0
      die("out of memory allocating space for option list");
776
11.1k
    memset(options->ops.operations, 0, sizeof(Operation) * options->ops.capacity);
777
11.1k
  }
778
23.5k
  if(options->ops.capacity <= options->ops.num_operations) {
779
6
    unsigned original_capacity = options->ops.capacity;
780
6
    if(options->ops.capacity > UINT32_MAX / 2) /* overflow check */
781
0
      die("out of memory allocating space for option list");
782
6
    options->ops.capacity *= 2;
783
6
    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
6
    memset(options->ops.operations + original_capacity, 0, sizeof(Operation) * (options->ops.capacity - original_capacity));
786
6
  }
787
788
23.5k
  options->ops.operations[options->ops.num_operations++] = operation;
789
23.5k
}
790
791
void append_new_argument(CommandLineOptions *options, Argument argument)
792
2.29k
{
793
2.29k
  if(options->args.capacity == 0) {
794
1.75k
    options->args.capacity = 50;
795
1.75k
    if(0 == (options->args.arguments = malloc(sizeof(Argument) * options->args.capacity)))
796
0
      die("out of memory allocating space for option list");
797
1.75k
    memset(options->args.arguments, 0, sizeof(Argument) * options->args.capacity);
798
1.75k
  }
799
2.29k
  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.29k
  options->args.arguments[options->args.num_arguments++] = argument;
810
2.29k
}
811
812
Operation *append_major_operation(CommandLineOptions *options, OperationType type)
813
2.36k
{
814
2.36k
  Operation op;
815
2.36k
  memset(&op, 0, sizeof(op));
816
2.36k
  op.type = type;
817
2.36k
  append_new_operation(options, op);
818
2.36k
  options->args.checks.num_major_ops++;
819
2.36k
  return options->ops.operations + (options->ops.num_operations - 1);
820
2.36k
}
821
822
Operation *append_shorthand_operation(CommandLineOptions *options, OperationType type)
823
21.1k
{
824
21.1k
  Operation op;
825
21.1k
  memset(&op, 0, sizeof(op));
826
21.1k
  op.type = type;
827
21.1k
  append_new_operation(options, op);
828
21.1k
  options->args.checks.num_shorthand_ops++;
829
21.1k
  return options->ops.operations + (options->ops.num_operations - 1);
830
21.1k
}
831
832
Argument *find_argument(CommandLineOptions *options, ArgumentType type)
833
237
{
834
237
  unsigned i;
835
343
  for(i = 0; i < options->args.num_arguments; i++)
836
200
    if(options->args.arguments[i].type == type)
837
94
      return &options->args.arguments[i];
838
143
  return 0;
839
237
}
840
841
Operation *find_shorthand_operation(CommandLineOptions *options, OperationType type)
842
48.7k
{
843
48.7k
  unsigned i;
844
136k
  for(i = 0; i < options->ops.num_operations; i++)
845
91.4k
    if(options->ops.operations[i].type == type)
846
3.90k
      return &options->ops.operations[i];
847
44.7k
  return 0;
848
48.7k
}
849
850
Argument *append_argument(CommandLineOptions *options, ArgumentType type)
851
2.29k
{
852
2.29k
  Argument arg;
853
2.29k
  memset(&arg, 0, sizeof(arg));
854
2.29k
  arg.type = type;
855
2.29k
  append_new_argument(options, arg);
856
2.29k
  return options->args.arguments + (options->args.num_arguments - 1);
857
2.29k
}
858
859
FLAC__bool parse_md5(const char *src, FLAC__byte dest[16])
860
106
{
861
106
  unsigned i, d;
862
106
  int c;
863
106
  FLAC__ASSERT(0 != src);
864
106
  if(strlen(src) != 32)
865
24
    return false;
866
  /* strtoul() accepts negative numbers which we do not want, so we do it the hard way */
867
648
  for(i = 0; i < 16; i++) {
868
626
    c = (int)(*src++);
869
626
    if(isdigit(c))
870
148
      d = (unsigned)(c - '0');
871
478
    else if(c >= 'a' && c <= 'f')
872
256
      d = (unsigned)(c - 'a') + 10u;
873
222
    else if(c >= 'A' && c <= 'F')
874
185
      d = (unsigned)(c - 'A') + 10u;
875
37
    else
876
37
      return false;
877
589
    d <<= 4;
878
589
    c = (int)(*src++);
879
589
    if(isdigit(c))
880
153
      d |= (unsigned)(c - '0');
881
436
    else if(c >= 'a' && c <= 'f')
882
257
      d |= (unsigned)(c - 'a') + 10u;
883
179
    else if(c >= 'A' && c <= 'F')
884
156
      d |= (unsigned)(c - 'A') + 10u;
885
23
    else
886
23
      return false;
887
566
    dest[i] = (FLAC__byte)d;
888
566
  }
889
22
  return true;
890
82
}
891
892
FLAC__bool parse_uint32(const char *src, FLAC__uint32 *dest)
893
665
{
894
665
  FLAC__ASSERT(0 != src);
895
665
  if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
896
139
    return false;
897
526
  *dest = strtoul(src, 0, 10);
898
526
  return true;
899
665
}
900
901
FLAC__bool parse_uint64(const char *src, FLAC__uint64 *dest)
902
122
{
903
122
  FLAC__ASSERT(0 != src);
904
122
  if(strlen(src) == 0 || strspn(src, "0123456789") != strlen(src))
905
53
    return false;
906
69
  *dest = strtoull(src, 0, 10);
907
69
  return true;
908
122
}
909
910
FLAC__bool parse_string(const char *src, char **dest)
911
3.38k
{
912
3.38k
  if(0 == src || strlen(src) == 0)
913
175
    return false;
914
3.21k
  *dest = strdup(src);
915
3.21k
  return true;
916
3.38k
}
917
918
FLAC__bool parse_vorbis_comment_field_name(const char *field_ref, char **name, const char **violation)
919
433
{
920
433
  static const char * const violations[] = {
921
433
    "field name contains invalid character"
922
433
  };
923
924
433
  char *q, *s;
925
926
433
  s = local_strdup(field_ref);
927
928
1.52M
  for(q = s; *q; q++) {
929
1.52M
    if(*q < 0x20 || *q > 0x7d || *q == 0x3d) {
930
77
      free(s);
931
77
      *violation = violations[0];
932
77
      return false;
933
77
    }
934
1.52M
  }
935
936
356
  *name = s;
937
938
356
  return true;
939
433
}
940
941
FLAC__bool parse_vorbis_comment_field_names(const char *field_ref, char **names, const char **violation)
942
244
{
943
244
  static const char * const violations[] = {
944
244
    "field name contains invalid character"
945
244
  };
946
947
244
  char *q, *s;
948
949
244
  s = local_strdup(field_ref);
950
951
1.03M
  for(q = s; *q; q++) {
952
1.02M
    if(*q < 0x20 || *q > 0x7d) {
953
41
      free(s);
954
41
      *violation = violations[0];
955
41
      return false;
956
41
    }
957
1.02M
  }
958
959
203
  *names = s;
960
961
203
  return true;
962
244
}
963
964
FLAC__bool parse_add_seekpoint(const char *in, char **out, const char **violation)
965
1.48k
{
966
1.48k
  static const char *garbled_ = "garbled specification";
967
1.48k
  const unsigned n = strlen(in);
968
969
1.48k
  FLAC__ASSERT(0 != in);
970
1.48k
  FLAC__ASSERT(0 != out);
971
972
1.48k
  if(n == 0) {
973
13
    *violation = "specification is empty";
974
13
    return false;
975
13
  }
976
977
1.47k
  if(n > strspn(in, "0123456789.Xsx")) {
978
48
    *violation = "specification contains invalid character";
979
48
    return false;
980
48
  }
981
982
1.42k
  if(in[n-1] == 'X') {
983
85
    if(n > 1) {
984
14
      *violation = garbled_;
985
14
      return false;
986
14
    }
987
85
  }
988
1.34k
  else if(in[n-1] == 's') {
989
495
    if(n-1 > strspn(in, "0123456789.")) {
990
16
      *violation = garbled_;
991
16
      return false;
992
16
    }
993
495
  }
994
845
  else if(in[n-1] == 'x') {
995
370
    if(n-1 > strspn(in, "0123456789")) {
996
31
      *violation = garbled_;
997
31
      return false;
998
31
    }
999
370
  }
1000
475
  else {
1001
475
    if(n > strspn(in, "0123456789")) {
1002
35
      *violation = garbled_;
1003
35
      return false;
1004
35
    }
1005
475
  }
1006
1007
1.32k
  *out = local_strdup(in);
1008
1.32k
  return true;
1009
1.42k
}
1010
1011
FLAC__bool parse_add_padding(const char *in, unsigned *out)
1012
206
{
1013
206
  FLAC__ASSERT(0 != in);
1014
206
  FLAC__ASSERT(0 != out);
1015
206
  *out = (unsigned)strtoul(in, 0, 10);
1016
206
  return *out < (1u << FLAC__STREAM_METADATA_LENGTH_LEN);
1017
206
}
1018
1019
FLAC__bool parse_block_number(const char *in, Argument_BlockNumber *out)
1020
524
{
1021
524
  char *p, *q, *s, *end;
1022
524
  long i;
1023
524
  unsigned entry;
1024
1025
524
  if(*in == '\0')
1026
24
    return false;
1027
1028
500
  s = local_strdup(in);
1029
1030
  /* first count the entries */
1031
1.39M
  for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1032
1.39M
    ;
1033
1034
  /* make space */
1035
500
  FLAC__ASSERT(out->num_entries > 0);
1036
500
  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
500
  entry = 0;
1041
500
  q = s;
1042
1.39M
  while(q) {
1043
1.39M
    FLAC__ASSERT(entry < out->num_entries);
1044
1.39M
    if(0 != (p = strchr(q, ',')))
1045
1.38M
      *p++ = '\0';
1046
1.39M
    if(!isdigit((int)(*q)) || (i = strtol(q, &end, 10)) < 0 || *end) {
1047
94
      free(s);
1048
94
      return false;
1049
94
    }
1050
1.38M
    out->entries[entry++] = (unsigned)i;
1051
1.38M
    q = p;
1052
1.38M
  }
1053
406
  FLAC__ASSERT(entry == out->num_entries);
1054
1055
406
  free(s);
1056
406
  return true;
1057
406
}
1058
1059
FLAC__bool parse_block_type(const char *in, Argument_BlockType *out)
1060
999
{
1061
999
  char *p, *q, *r, *s;
1062
999
  unsigned entry;
1063
1064
999
  if(*in == '\0')
1065
29
    return false;
1066
1067
970
  s = local_strdup(in);
1068
1069
  /* first count the entries */
1070
111k
  for(out->num_entries = 1, p = strchr(s, ','); p; out->num_entries++, p = strchr(++p, ','))
1071
110k
    ;
1072
1073
  /* make space */
1074
970
  FLAC__ASSERT(out->num_entries > 0);
1075
970
  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
970
  entry = 0;
1080
970
  q = s;
1081
1.89k
  while(q) {
1082
1.65k
    FLAC__ASSERT(entry < out->num_entries);
1083
1.65k
    if(0 != (p = strchr(q, ',')))
1084
740
      *p++ = 0;
1085
1.65k
    r = strchr(q, ':');
1086
1.65k
    if(r)
1087
257
      *r++ = '\0';
1088
1.65k
    if(0 != r && 0 != strcmp(q, "APPLICATION")) {
1089
116
      free(s);
1090
116
      return false;
1091
116
    }
1092
1.54k
    if(0 == strcmp(q, "STREAMINFO")) {
1093
109
      out->entries[entry++].type = FLAC__METADATA_TYPE_STREAMINFO;
1094
109
    }
1095
1.43k
    else if(0 == strcmp(q, "PADDING")) {
1096
185
      out->entries[entry++].type = FLAC__METADATA_TYPE_PADDING;
1097
185
    }
1098
1.24k
    else if(0 == strcmp(q, "APPLICATION")) {
1099
335
      out->entries[entry].type = FLAC__METADATA_TYPE_APPLICATION;
1100
335
      out->entries[entry].filter_application_by_id = (0 != r);
1101
335
      if(0 != r) {
1102
141
        if(strlen(r) == sizeof (out->entries[entry].application_id)) {
1103
48
          memcpy(out->entries[entry].application_id, r, sizeof (out->entries[entry].application_id));
1104
48
        }
1105
93
        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
83
        else {
1113
83
          free(s);
1114
83
          return false;
1115
83
        }
1116
141
      }
1117
252
      entry++;
1118
252
    }
1119
911
    else if(0 == strcmp(q, "SEEKTABLE")) {
1120
193
      out->entries[entry++].type = FLAC__METADATA_TYPE_SEEKTABLE;
1121
193
    }
1122
718
    else if(0 == strcmp(q, "VORBIS_COMMENT")) {
1123
10
      out->entries[entry++].type = FLAC__METADATA_TYPE_VORBIS_COMMENT;
1124
10
    }
1125
708
    else if(0 == strcmp(q, "CUESHEET")) {
1126
78
      out->entries[entry++].type = FLAC__METADATA_TYPE_CUESHEET;
1127
78
    }
1128
630
    else if(0 == strcmp(q, "PICTURE")) {
1129
101
      out->entries[entry++].type = FLAC__METADATA_TYPE_PICTURE;
1130
101
    }
1131
529
    else {
1132
529
      free(s);
1133
529
      return false;
1134
529
    }
1135
928
    q = p;
1136
928
  }
1137
242
  FLAC__ASSERT(entry == out->num_entries);
1138
1139
242
  free(s);
1140
242
  return true;
1141
242
}
1142
1143
FLAC__bool parse_data_format(const char *in, Argument_DataFormat *out)
1144
463
{
1145
463
  if(0 == strcmp(in, "binary-headerless")) {
1146
24
    out->is_binary = false;
1147
24
    out->is_headerless = true;
1148
24
  }
1149
439
  else if(0 == strcmp(in, "binary")) {
1150
277
    out->is_binary = true;
1151
277
    out->is_headerless = false;
1152
277
  }
1153
162
  else if(0 == strcmp(in, "text")) {
1154
10
    out->is_binary = false;
1155
10
    out->is_headerless = false;
1156
10
  }
1157
152
  else
1158
152
    return false;
1159
311
  return true;
1160
463
}
1161
1162
FLAC__bool parse_application_data_format(const char *in, FLAC__bool *out)
1163
125
{
1164
125
  if(0 == strcmp(in, "hexdump"))
1165
18
    *out = true;
1166
107
  else if(0 == strcmp(in, "text"))
1167
18
    *out = false;
1168
89
  else
1169
89
    return false;
1170
36
  return true;
1171
125
}
1172
1173
void undocumented_warning(const char *opt)
1174
275
{
1175
275
  flac_fprintf(stderr, "WARNING: undocumented option --%s should be used with caution,\n         only for repairing a damaged STREAMINFO block\n", opt);
1176
275
}