/* * 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 . */ #include #include #include #include #include // c_tolower, c_isalnum #include #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; }