/*
* 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;
}