utf8: don't allow NUL in decoded strings.
[ccan] / ccan / opt / parse.c
1 /* Licensed under GPLv2+ - see LICENSE file for details */
2 /* Actual code to parse commandline. */
3 #include <ccan/opt/opt.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <assert.h>
7 #include "private.h"
8
9 /* glibc does this as:
10 /tmp/opt-example: invalid option -- 'x'
11 /tmp/opt-example: unrecognized option '--long'
12 /tmp/opt-example: option '--someflag' doesn't allow an argument
13 /tmp/opt-example: option '--s' is ambiguous
14 /tmp/opt-example: option requires an argument -- 's'
15 */
16 static int parse_err(void (*errlog)(const char *fmt, ...),
17                      const char *argv0, const char *arg, unsigned len,
18                      const char *problem)
19 {
20         errlog("%s: %.*s: %s", argv0, len, arg, problem);
21         return -1;
22 }
23
24 static void consume_option(int *argc, char *argv[], unsigned optnum)
25 {
26         memmove(&argv[optnum], &argv[optnum+1],
27                 sizeof(argv[optnum]) * (*argc-optnum));
28         (*argc)--;
29 }
30
31 /* Returns 1 if argument consumed, 0 if all done, -1 on error. */
32 int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset,
33               void (*errlog)(const char *fmt, ...), bool unknown_ok)
34 {
35         unsigned i, arg, len;
36         const char *o, *optarg = NULL;
37         char *problem = NULL;
38
39         if (getenv("POSIXLY_CORRECT")) {
40                 /* Don't find options after non-options. */
41                 arg = 1;
42         } else {
43                 for (arg = 1; argv[arg]; arg++) {
44                         if (argv[arg][0] == '-')
45                                 break;
46                 }
47         }
48
49         if (!argv[arg] || argv[arg][0] != '-')
50                 return 0;
51
52         /* Special arg terminator option. */
53         if (strcmp(argv[arg], "--") == 0) {
54                 consume_option(argc, argv, arg);
55                 return 0;
56         }
57
58         /* Long options start with -- */
59         if (argv[arg][1] == '-') {
60                 assert(*offset == 0);
61                 for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) {
62                         if (strncmp(argv[arg] + 2, o, len) != 0)
63                                 continue;
64                         if (argv[arg][2 + len] == '=')
65                                 optarg = argv[arg] + 2 + len + 1;
66                         else if (argv[arg][2 + len] != '\0')
67                                 continue;
68                         break;
69                 }
70                 if (!o) {
71                         if (unknown_ok)
72                                 goto ok;
73                         return parse_err(errlog, argv[0],
74                                          argv[arg], strlen(argv[arg]),
75                                          "unrecognized option");
76                 }
77                 /* For error messages, we include the leading '--' */
78                 o -= 2;
79                 len += 2;
80         } else {
81                 /* offset allows us to handle -abc */
82                 for (o = first_sopt(&i); o; o = next_sopt(o, &i)) {
83                         if (argv[arg][*offset + 1] != *o)
84                                 continue;
85                         (*offset)++;
86                         break;
87                 }
88                 if (!o) {
89                         if (unknown_ok) {
90                                 (*offset)++;
91                                 goto ok;
92                         }
93                         return parse_err(errlog, argv[0],
94                                          argv[arg], strlen(argv[arg]),
95                                          "unrecognized option");
96                 }
97                 /* For error messages, we include the leading '-' */
98                 o--;
99                 len = 2;
100         }
101
102         if ((opt_table[i].type & ~OPT_EARLY) == OPT_NOARG) {
103                 if (optarg)
104                         return parse_err(errlog, argv[0], o, len,
105                                          "doesn't allow an argument");
106                 if ((opt_table[i].type & OPT_EARLY) == is_early)
107                         problem = opt_table[i].cb(opt_table[i].u.arg);
108         } else {
109                 if (!optarg) {
110                         /* Swallow any short options as optarg, eg -afile */
111                         if (*offset && argv[arg][*offset + 1]) {
112                                 optarg = argv[arg] + *offset + 1;
113                                 *offset = 0;
114                         } else
115                                 optarg = argv[arg+1];
116                 }
117                 if (!optarg)
118                         return parse_err(errlog, argv[0], o, len,
119                                          "requires an argument");
120                 if ((opt_table[i].type & OPT_EARLY) == is_early)
121                         problem = opt_table[i].cb_arg(optarg,
122                                                       opt_table[i].u.arg);
123         }
124
125         if (problem) {
126                 parse_err(errlog, argv[0], o, len, problem);
127                 opt_alloc.free(problem);
128                 return -1;
129         }
130
131 ok:
132         /* If no more letters in that short opt, reset offset. */
133         if (*offset && !argv[arg][*offset + 1])
134                 *offset = 0;
135
136         /* All finished with that option? */
137         if (*offset == 0) {
138                 consume_option(argc, argv, arg);
139                 if (optarg && optarg == argv[arg])
140                         consume_option(argc, argv, arg);
141         }
142         return 1;
143 }