diff options
Diffstat (limited to 'src/options.c')
-rw-r--r-- | src/options.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000000..b50d4e3eb5 --- /dev/null +++ b/src/options.c @@ -0,0 +1,428 @@ +/* + * Copyright(c) 2012 Tim Ruehsen + * Copyright(c) 2015-2019 Free Software Foundation, Inc. + * + * This file is part of GnuTLS. + * + * GnuTLS is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <c-ctype.h> // c_tolower, c_isalnum + +#include <gnutls/gnutls.h> +#include "options.h" + +int parse_integer(option_t opt, const char *val, const char invert) +{ + *((int *)opt->var) = val ? atoi(val) : 0; + + return 0; +} + +static int parse_filename(option_t opt, const char *val, const char invert) +{ + free(*((char **)opt->var)); +// *((const char **)opt->var) = val ? shell_expand(val) : NULL; + *((char **)opt->var) = val ? strdup(val) : NULL; + + return 0; +} + +int parse_string(option_t opt, const char *val, const char invert) +{ + free(*((char **)opt->var)); + *((char **)opt->var) = val ? strdup(val) : NULL; + + return 0; +} + +/* +static int parse_stringset(option_t opt, const char *val, const char invert) +{ + wget_stringmap_t *map = *((wget_stringmap_t **)opt->var); + + if (val) { + const char *s, *p; + + wget_stringmap_clear(map); + + for (s = p = val; *p; s = p + 1) { + if ((p = strchrnul(s, ',')) != s) + wget_stringmap_put_noalloc(map, wget_strmemdup(s, p - s), NULL); + } + } else { + wget_stringmap_clear(map); + } + + return 0; +} + +static const char *_strchrnul_esc(const char *s, char c) +{ + const char *p; + + for (p = s; *p; p++) { + if (*p == '\\' && (p[1] == '\\' || p[1] == c)) + p++; + else if (*p == c) + return p; + } + + return p; // pointer to trailing \0 +} + +static char *_strmemdup_esc(const char *s, size_t size) +{ + const char *p, *e; + size_t newsize = 0; + + for (p = s, e = s + size; p < e; p++) { + if (*p == '\\') { + if (p < e - 1) { + newsize++; + p++; + } + } else + newsize++; + } + + char *ret = malloc(newsize + 1); + char *dst = ret; + + for (p = s, e = s + size; p < e; p++) { + if (*p == '\\') { + if (p < e - 1) + *dst++ = *++p; + } else + *dst++ = *p; + } + *dst = 0; + + return ret; +} + +static int parse_stringlist_expand(option_t opt, const char *val, int expand, int max_entries) +{ + if (val && *val) { + wget_vector_t *v = *((wget_vector_t **)opt->var); + const char *s, *p; + + if (!v) + v = *((wget_vector_t **)opt->var) = wget_vector_create(8, (wget_vector_compare_t)strcmp); + + for (s = p = val; *p; s = p + 1) { + if ((p = _strchrnul_esc(s, ',')) != s) { + if (wget_vector_size(v) >= max_entries) { + wget_debug_printf("%s: More than %d entries, ignoring overflow\n", __func__, max_entries); + return -1; + } + + const char *fname = _strmemdup_esc(s, p - s); + + if (expand && *s == '~') { + wget_vector_add_noalloc(v, shell_expand(fname)); + xfree(fname); + } else + wget_vector_add_noalloc(v, fname); + } + } + } else { + wget_vector_free(opt->var); + } + + return 0; +} + +static int parse_stringlist(option_t opt, const char *val, const char invert) +{ + // max number of 1024 entries to avoid out-of-memory + return parse_stringlist_expand(opt, val, 0, 1024); +} +*/ + +int parse_bool(option_t opt, const char *val, const char invert) +{ + if (opt->var) { + if (!val || !strcmp(val, "1") || !strcmp(val, "y") || !strcmp(val, "yes") || !strcmp(val, "on")) + *((char *) opt->var) = !invert; + else if (!*val || !strcmp(val, "0") || !strcmp(val, "n") || !strcmp(val, "no") || !strcmp(val, "off")) + *((char *) opt->var) = invert; + else { + fprintf(stderr, "Invalid boolean value '%s'\n", val); + return -1; + } + } + + return 0; +} + +static inline void print_first(const char s, const char *l, const char *msg) +{ + if (s) + printf(" -%c, --%-20s %s", s, l, msg); + else + printf(" --%-20s %s", l, msg); +} + +static inline void print_next(const char *msg) +{ + printf("%32s%s", "", msg); +} + +void print_options(const struct optionw *options, int noptions) +{ + for (int it = 0; it < noptions; it++) { + print_first(options[it].short_name, + options[it].long_name, + options[it].help_str[0]); + for (unsigned i = 1; i < countof(options[it].help_str) && options[it].help_str[i]; i++) + print_next(options[it].help_str[i]); + } +} + +static inline void print_first_md(const char s, const char *l, const char *msg) +{ + if (s) + printf("## `-%c`, `--%s`\n\n%s", s, l, msg); + else + printf("## `--%s`\n\n%s", l, msg); +} + +static void print_options_md(const struct optionw *options, int noptions) +{ + for (int it = 0; it < noptions; it++) { + print_first_md(options[it].short_name, + options[it].long_name, + options[it].help_str[0]); + for (unsigned i = 1; i < countof(options[it].help_str) && options[it].help_str[i]; i++) + printf("%s", options[it].help_str[i]); + printf("\n"); + } +} + +static int opt_compare(const void *key, const void *option) +{ + return strcmp(key, ((option_t) option)->long_name); +} + +static int opt_compare_config_linear(const char *key, const char *command) +{ + const char *s1 = key, *s2 = command; + + for (; *s1 && *s2; s1++, s2++) { + if (*s2 == '-' || *s2 == '_') { + if (*s1 == '-' || *s1 == '_') + s1++; + s2++; + } + + if (!*s1 || !*s2 || c_tolower(*s1) != *s2) break; + // *s2 is guaranteed to be lower case so convert *s1 to lower case + } + + return *s1 != *s2; // no need for tolower() here +} + +// return values: +// < 0 : parse error +// >= 0 : number of arguments processed +static int set_long_option(const char *name, const char *value, const struct optionw *options, int noptions) +{ + option_t opt; + char invert = 0, value_present = 0, case_insensitive = 1; + char namebuf[strlen(name) + 1], *p; + int ret = 0, rc; + + if ((p = strchr(name, '='))) { + // option with appended value + memcpy(namebuf, name, p - name); + namebuf[p - name] = 0; + name = namebuf; + value = p + 1; + value_present = 1; + } + + // If the option is passed from .wget2rc (--*), delete the "--" prefix + if (!strncmp(name, "--", 2)) { + case_insensitive = 0; + name += 2; + } + // If the option is negated (--no-) delete the "no-" prefix + if (!strncmp(name, "no-", 3)) { + invert = 1; + name += 3; + } + + if (case_insensitive) { + opt = bsearch(name, options, noptions, sizeof(options[0]), opt_compare); + if (!opt) { + // Fallback to linear search for 'unsharp' searching. + // Maybe the user asked for e.g. https_only or httpsonly instead of https-only + // opt_compare_config_linear() will find these. Wget -e/--execute compatibility. + for (int it = 0; it < noptions && !opt; it++) + if (opt_compare_config_linear(name, options[it].long_name) == 0) + opt = &options[it]; + } + } else + opt = bsearch(name, options, noptions, sizeof(options[0]), opt_compare); + + if (!opt) { + fprintf(stderr, "Unknown option '%s'\n", name); + return -1; + } + + if (value_present) { + // "option=*" + if (invert) { + if (!opt->nargs || opt->parser == parse_string || +// opt->parser == parse_stringset || +// opt->parser == parse_stringlist || + opt->parser == parse_filename) +// || opt->parser == parse_filenames) + { + fprintf(stderr, "Option 'no-%s' doesn't allow an argument\n", name); + return -1; + } + } else if (!opt->nargs) { + printf("Option '%s' doesn't allow an argument\n", name); + return -1; + } + } else { + // "option" + switch (opt->nargs) { + case 0: + value = NULL; + break; + case 1: + if (!value) { + fprintf(stderr, "Missing argument for option '%s'\n", name); + // empty string is allowed in value i.e. *value = '\0' + return -1; + } + + if (invert && (opt->parser == parse_string || +// opt->parser == parse_stringset || +// opt->parser == parse_stringlist || + opt->parser == parse_filename)) +// || opt->parser == parse_filenames)) + value = NULL; + else + ret = opt->nargs; + break; + case -1: + if(value) + ret = 1; + break; + default: + break; + } + } + + if ((rc = opt->parser(opt, value, invert)) < 0) + return rc; + + return ret; +} + +int parse_command_line(int argc, const char **argv, const struct optionw *options, int noptions) +{ + static short shortcut_to_option[128]; + const char *first_arg = NULL; + int n, rc; + + if (argc == 2 && !strcmp(argv[1], "--options-md")) { + print_options_md(options, noptions); + exit(EXIT_SUCCESS); + } + + // init the short option lookup table + if (!shortcut_to_option[0]) { + for (int it = 0; it < noptions; it++) { + if (options[it].short_name) + shortcut_to_option[(unsigned char)options[it].short_name] = it + 1; + } + } + + // I like the idea of getopt() but not it's implementation (e.g. global variables). + // Therefore I implement my own getopt() behavior. + for (n = 1; n < argc && first_arg != argv[n]; n++) { + const char *argp = argv[n]; + + if (argp[0] != '-') { + // Move args behind options to allow mixed args/options like getopt(). + // In the end, the order of the args is as before. + const char *cur = argv[n]; + for (int it = n; it < argc - 1; it++) + argv[it] = argv[it + 1]; + argv[argc - 1] = cur; + + // Once we see the first arg again, we are done + if (!first_arg) + first_arg = cur; + + n--; + continue; + } + + if (argp[1] == '-') { + // long option + if (argp[2] == 0) + return n + 1; + + if ((rc = set_long_option(argp + 2, n < argc - 1 ? argv[n+1] : NULL, options, noptions)) < 0) + return rc; + + n += rc; + + } else if (argp[1]) { + // short option(s) + for (int pos = 1; argp[pos]; pos++) { + option_t opt; + int idx; + + if (c_isalnum(argp[pos]) && (idx = shortcut_to_option[(unsigned char)argp[pos]])) { + opt = &options[idx - 1]; + // printf("opt=%p [%c]\n",(void *)opt,argp[pos]); + // printf("name=%s\n",opt->long_name); + if (opt->nargs > 0) { + const char *val; + + if (!argp[pos + 1] && argc <= n + opt->nargs) { + fprintf(stderr,"Missing argument(s) for option '-%c'\n", argp[pos]); + return -1; + } + val = argp[pos + 1] ? argp + pos + 1 : argv[++n]; + if ((rc = opt->parser(opt, val, 0)) < 0) + return rc; + n += rc; + break; + } else {//if (opt->args == 0) + if ((rc = opt->parser(opt, NULL, 0)) < 0) + return rc; + } + } else { + fprintf(stderr,"Unknown option '-%c'\n", argp[pos]); + return -1; + } + } + } + } + + return n; +} |