/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
#include "tool_setup.h"

#include "tool_cfgable.h"
#include "tool_cb_prg.h"
#include "tool_filetime.h"
#include "tool_formparse.h"
#include "tool_getparam.h"
#include "tool_helpers.h"
#include "tool_libinfo.h"
#include "tool_msgs.h"
#include "tool_paramhlp.h"
#include "tool_parsecfg.h"
#include "tool_main.h"
#include "tool_stderr.h"
#include "tool_help.h"
#include "var.h"

#define ALLOW_BLANK TRUE
#define DENY_BLANK  FALSE

static ParameterError getstr(char **str, const char *val, bool allowblank)
{
  if(*str)
    curlx_safefree(*str);
  DEBUGASSERT(val);
  if(!allowblank && !val[0])
    return PARAM_BLANK_STRING;

  *str = curlx_strdup(val);
  if(!*str)
    return PARAM_NO_MEM;

  return PARAM_OK;
}

static ParameterError getstrn(char **str, const char *val,
                              size_t len, bool allowblank)
{
  if(*str)
    curlx_safefree(*str);
  DEBUGASSERT(val);
  if(!allowblank && !val[0])
    return PARAM_BLANK_STRING;

  *str = curlx_memdup0(val, len);
  if(!*str)
    return PARAM_NO_MEM;

  return PARAM_OK;
}

/* this array MUST be alphasorted based on the 'lname' */
static const struct LongShort aliases[] = {
  {"abstract-unix-socket",       ARG_FILE, ' ', C_ABSTRACT_UNIX_SOCKET},
  {"alpn",                       ARG_BOOL|ARG_NO|ARG_TLS, ' ', C_ALPN},
  {"alt-svc",                    ARG_STRG, ' ', C_ALT_SVC},
  {"anyauth",                    ARG_NONE, ' ', C_ANYAUTH},
  {"append",                     ARG_BOOL, 'a', C_APPEND},
  {"aws-sigv4",                  ARG_STRG, ' ', C_AWS_SIGV4},
  {"basic",                      ARG_BOOL, ' ', C_BASIC},
  {"buffer",                     ARG_BOOL|ARG_NO, 'N', C_BUFFER},
  {"ca-native",                  ARG_BOOL|ARG_TLS, ' ', C_CA_NATIVE},
  {"cacert",                     ARG_FILE|ARG_TLS, ' ', C_CACERT},
  {"capath",                     ARG_FILE|ARG_TLS, ' ', C_CAPATH},
  {"cert",                       ARG_FILE|ARG_TLS|ARG_CLEAR, 'E', C_CERT},
  {"cert-status",                ARG_BOOL|ARG_TLS, ' ', C_CERT_STATUS},
  {"cert-type",                  ARG_STRG|ARG_TLS, ' ', C_CERT_TYPE},
  {"ciphers",                    ARG_STRG|ARG_TLS, ' ', C_CIPHERS},
  {"clobber",                    ARG_BOOL|ARG_NO, ' ', C_CLOBBER},
  {"compressed",                 ARG_BOOL, ' ', C_COMPRESSED},
  {"compressed-ssh",             ARG_BOOL, ' ', C_COMPRESSED_SSH},
  {"config",                     ARG_FILE, 'K', C_CONFIG},
  {"connect-timeout",            ARG_SECS, ' ', C_CONNECT_TIMEOUT},
  {"connect-to",                 ARG_STRG, ' ', C_CONNECT_TO},
  {"continue-at",                ARG_STRG, 'C', C_CONTINUE_AT},
  {"cookie",                     ARG_STRG, 'b', C_COOKIE},
  {"cookie-jar",                 ARG_STRG, 'c', C_COOKIE_JAR},
  {"create-dirs",                ARG_BOOL, ' ', C_CREATE_DIRS},
  {"create-file-mode",           ARG_STRG, ' ', C_CREATE_FILE_MODE},
  {"crlf",                       ARG_BOOL, ' ', C_CRLF},
  {"crlfile",                    ARG_FILE|ARG_TLS, ' ', C_CRLFILE},
  {"curves",                     ARG_STRG|ARG_TLS, ' ', C_CURVES},
  {"data",                       ARG_STRG, 'd', C_DATA},
  {"data-ascii",                 ARG_STRG, ' ', C_DATA_ASCII},
  {"data-binary",                ARG_STRG, ' ', C_DATA_BINARY},
  {"data-raw",                   ARG_STRG, ' ', C_DATA_RAW},
  {"data-urlencode",             ARG_STRG, ' ', C_DATA_URLENCODE},
  {"delegation",                 ARG_STRG, ' ', C_DELEGATION},
  {"digest",                     ARG_BOOL, ' ', C_DIGEST},
  {"disable",                    ARG_BOOL, 'q', C_DISABLE},
  {"disable-eprt",               ARG_BOOL, ' ', C_DISABLE_EPRT},
  {"disable-epsv",               ARG_BOOL, ' ', C_DISABLE_EPSV},
  {"disallow-username-in-url",   ARG_BOOL, ' ', C_DISALLOW_USERNAME_IN_URL},
  {"dns-interface",              ARG_STRG, ' ', C_DNS_INTERFACE},
  {"dns-ipv4-addr",              ARG_STRG, ' ', C_DNS_IPV4_ADDR},
  {"dns-ipv6-addr",              ARG_STRG, ' ', C_DNS_IPV6_ADDR},
  {"dns-servers",                ARG_STRG, ' ', C_DNS_SERVERS},
  {"doh-cert-status",            ARG_BOOL|ARG_TLS, ' ', C_DOH_CERT_STATUS},
  {"doh-insecure",               ARG_BOOL|ARG_TLS, ' ', C_DOH_INSECURE},
  {"doh-url",                    ARG_STRG, ' ', C_DOH_URL},
  {"dump-ca-embed",              ARG_NONE|ARG_TLS, ' ', C_DUMP_CA_EMBED},
  {"dump-header",                ARG_FILE, 'D', C_DUMP_HEADER},
  {"ech",                        ARG_STRG|ARG_TLS, ' ', C_ECH},
  {"egd-file",                   ARG_STRG|ARG_DEPR, ' ', C_EGD_FILE},
  {"engine",                     ARG_STRG|ARG_TLS, ' ', C_ENGINE},
  {"eprt",                       ARG_BOOL, ' ', C_EPRT},
  {"epsv",                       ARG_BOOL, ' ', C_EPSV},
  {"etag-compare",               ARG_FILE, ' ', C_ETAG_COMPARE},
  {"etag-save",                  ARG_FILE, ' ', C_ETAG_SAVE},
  {"expect100-timeout",          ARG_SECS, ' ', C_EXPECT100_TIMEOUT},
  {"fail",                       ARG_BOOL, 'f', C_FAIL},
  {"fail-early",                 ARG_BOOL, ' ', C_FAIL_EARLY},
  {"fail-with-body",             ARG_BOOL, ' ', C_FAIL_WITH_BODY},
  {"false-start",                ARG_BOOL, ' ', C_FALSE_START},
  {"follow",                     ARG_BOOL, ' ', C_FOLLOW},
  {"form",                       ARG_STRG, 'F', C_FORM},
  {"form-escape",                ARG_BOOL, ' ', C_FORM_ESCAPE},
  {"form-string",                ARG_STRG, ' ', C_FORM_STRING},
  {"ftp-account",                ARG_STRG, ' ', C_FTP_ACCOUNT},
  {"ftp-alternative-to-user",    ARG_STRG, ' ', C_FTP_ALTERNATIVE_TO_USER},
  {"ftp-create-dirs",            ARG_BOOL, ' ', C_FTP_CREATE_DIRS},
  {"ftp-method",                 ARG_STRG, ' ', C_FTP_METHOD},
  {"ftp-pasv",                   ARG_NONE, ' ', C_FTP_PASV},
  {"ftp-port",                   ARG_STRG, 'P', C_FTP_PORT},
  {"ftp-pret",                   ARG_BOOL, ' ', C_FTP_PRET},
  {"ftp-skip-pasv-ip",           ARG_BOOL, ' ', C_FTP_SKIP_PASV_IP},
  {"ftp-ssl",                    ARG_BOOL|ARG_TLS, ' ', C_FTP_SSL},
  {"ftp-ssl-ccc",                ARG_BOOL|ARG_TLS, ' ', C_FTP_SSL_CCC},
  {"ftp-ssl-ccc-mode",           ARG_STRG|ARG_TLS, ' ', C_FTP_SSL_CCC_MODE},
  {"ftp-ssl-control",            ARG_BOOL|ARG_TLS, ' ', C_FTP_SSL_CONTROL},
  {"ftp-ssl-reqd",               ARG_BOOL|ARG_TLS, ' ', C_FTP_SSL_REQD},
  {"get",                        ARG_BOOL, 'G', C_GET},
  {"globoff",                    ARG_BOOL, 'g', C_GLOBOFF},
  {"happy-eyeballs-timeout-ms",  ARG_UNUM, ' ', C_HAPPY_EYEBALLS_TIMEOUT_MS},
  {"haproxy-clientip",           ARG_STRG, ' ', C_HAPROXY_CLIENTIP},
  {"haproxy-protocol",           ARG_BOOL, ' ', C_HAPROXY_PROTOCOL},
  {"head",                       ARG_BOOL, 'I', C_HEAD},
  {"header",                     ARG_STRG, 'H', C_HEADER},
  {"help",                       ARG_STRG, 'h', C_HELP},
  {"hostpubmd5",                 ARG_STRG, ' ', C_HOSTPUBMD5},
  {"hostpubsha256",              ARG_STRG, ' ', C_HOSTPUBSHA256},
  {"hsts",                       ARG_STRG|ARG_TLS, ' ', C_HSTS},
  {"http0.9",                    ARG_BOOL, ' ', C_HTTP0_9},
  {"http1.0",                    ARG_NONE, '0', C_HTTP1_0},
  {"http1.1",                    ARG_NONE, ' ', C_HTTP1_1},
  {"http2",                      ARG_NONE, ' ', C_HTTP2},
  {"http2-prior-knowledge",      ARG_NONE, ' ', C_HTTP2_PRIOR_KNOWLEDGE},
  {"http3",                      ARG_NONE|ARG_TLS, ' ', C_HTTP3},
  {"http3-only",                 ARG_NONE|ARG_TLS, ' ', C_HTTP3_ONLY},
#ifndef CURL_DISABLE_HTTPSIG
  {"httpsig-algo",               ARG_STRG, ' ', C_HTTPSIG_ALGORITHM},
  {"httpsig-headers",            ARG_STRG, ' ', C_HTTPSIG_HEADERS},
  {"httpsig-key",                ARG_FILE|ARG_CLEAR, ' ', C_HTTPSIG_KEY},
  {"httpsig-keyid",              ARG_STRG, ' ', C_HTTPSIG_KEYID},
#endif
  {"ignore-content-length",      ARG_BOOL, ' ', C_IGNORE_CONTENT_LENGTH},
  {"include",                    ARG_BOOL, ' ', C_INCLUDE},
  {"insecure",                   ARG_BOOL, 'k', C_INSECURE},
  {"interface",                  ARG_STRG, ' ', C_INTERFACE},
  {"ip-tos",                     ARG_STRG, ' ', C_IP_TOS},
#ifndef CURL_DISABLE_IPFS
  {"ipfs-gateway",               ARG_STRG, ' ', C_IPFS_GATEWAY},
#endif
  {"ipv4",                       ARG_NONE, '4', C_IPV4},
  {"ipv6",                       ARG_NONE, '6', C_IPV6},
  {"json",                       ARG_STRG, ' ', C_JSON},
  {"junk-session-cookies",       ARG_BOOL, 'j', C_JUNK_SESSION_COOKIES},
  {"keepalive",                  ARG_BOOL|ARG_NO, ' ', C_KEEPALIVE},
  {"keepalive-cnt",              ARG_UNUM, ' ', C_KEEPALIVE_CNT},
  {"keepalive-time",             ARG_UNUM, ' ', C_KEEPALIVE_TIME},
  {"key",                        ARG_FILE, ' ', C_KEY},
  {"key-type",                   ARG_STRG|ARG_TLS, ' ', C_KEY_TYPE},
  {"knownhosts",                 ARG_FILE, ' ', C_KNOWNHOSTS},
  {"krb",                        ARG_STRG|ARG_DEPR, ' ', C_KRB},
  {"krb4",                       ARG_STRG|ARG_DEPR, ' ', C_KRB4},
  {"libcurl",                    ARG_STRG, ' ', C_LIBCURL},
  {"limit-rate",                 ARG_STRG, ' ', C_LIMIT_RATE},
  {"list-only",                  ARG_BOOL, 'l', C_LIST_ONLY},
  {"local-port",                 ARG_STRG, ' ', C_LOCAL_PORT},
  {"location",                   ARG_BOOL, 'L', C_LOCATION},
  {"location-trusted",           ARG_BOOL, ' ', C_LOCATION_TRUSTED},
  {"login-options",              ARG_STRG, ' ', C_LOGIN_OPTIONS},
  {"mail-auth",                  ARG_STRG, ' ', C_MAIL_AUTH},
  {"mail-from",                  ARG_STRG, ' ', C_MAIL_FROM},
  {"mail-rcpt",                  ARG_STRG, ' ', C_MAIL_RCPT},
  {"mail-rcpt-allowfails",       ARG_BOOL, ' ', C_MAIL_RCPT_ALLOWFAILS},
  {"manual",                     ARG_BOOL, 'M', C_MANUAL},
  {"max-filesize",               ARG_STRG, ' ', C_MAX_FILESIZE},
  {"max-redirs",                 ARG_STRG, ' ', C_MAX_REDIRS},
  {"max-time",                   ARG_SECS, 'm', C_MAX_TIME},
  {"metalink",                   ARG_BOOL|ARG_DEPR, ' ', C_METALINK},
  {"mptcp",                      ARG_BOOL, ' ', C_MPTCP},
  {"negotiate",                  ARG_BOOL, ' ', C_NEGOTIATE},
  {"netrc",                      ARG_BOOL, 'n', C_NETRC},
  {"netrc-file",                 ARG_FILE, ' ', C_NETRC_FILE},
  {"netrc-optional",             ARG_BOOL, ' ', C_NETRC_OPTIONAL},
  {"next",                       ARG_NONE, ':', C_NEXT},
  {"noproxy",                    ARG_STRG, ' ', C_NOPROXY},
  {"npn",                        ARG_BOOL|ARG_DEPR, ' ', C_NPN},
  {"ntlm",                       ARG_BOOL, ' ', C_NTLM},
  {"ntlm-wb",                    ARG_BOOL|ARG_DEPR, ' ', C_NTLM_WB},
  {"oauth2-bearer",              ARG_STRG|ARG_CLEAR, ' ', C_OAUTH2_BEARER},
  {"out-null",                   ARG_BOOL, ' ', C_OUT_NULL},
  {"output",                     ARG_FILE, 'o', C_OUTPUT},
  {"output-dir",                 ARG_STRG, ' ', C_OUTPUT_DIR},
  {"parallel",                   ARG_BOOL, 'Z', C_PARALLEL},
  {"parallel-immediate",         ARG_BOOL, ' ', C_PARALLEL_IMMEDIATE},
  {"parallel-max",               ARG_UNUM, ' ', C_PARALLEL_MAX},
  {"parallel-max-host",          ARG_UNUM, ' ', C_PARALLEL_HOST},
  {"pass",                       ARG_STRG|ARG_CLEAR, ' ', C_PASS},
  {"path-as-is",                 ARG_BOOL, ' ', C_PATH_AS_IS},
  {"pinnedpubkey",               ARG_STRG|ARG_TLS, ' ', C_PINNEDPUBKEY},
  {"post301",                    ARG_BOOL, ' ', C_POST301},
  {"post302",                    ARG_BOOL, ' ', C_POST302},
  {"post303",                    ARG_BOOL, ' ', C_POST303},
  {"preproxy",                   ARG_STRG, ' ', C_PREPROXY},
  {"progress-bar",               ARG_BOOL, '#', C_PROGRESS_BAR},
  {"progress-meter",             ARG_BOOL|ARG_NO, ' ', C_PROGRESS_METER},
  {"proto",                      ARG_STRG, ' ', C_PROTO},
  {"proto-default",              ARG_STRG, ' ', C_PROTO_DEFAULT},
  {"proto-redir",                ARG_STRG, ' ', C_PROTO_REDIR},
  {"proxy",                      ARG_STRG, 'x', C_PROXY},
  {"proxy-anyauth",              ARG_BOOL, ' ', C_PROXY_ANYAUTH},
  {"proxy-basic",                ARG_BOOL, ' ', C_PROXY_BASIC},
  {"proxy-ca-native",            ARG_BOOL|ARG_TLS, ' ', C_PROXY_CA_NATIVE},
  {"proxy-cacert",               ARG_FILE|ARG_TLS, ' ', C_PROXY_CACERT},
  {"proxy-capath",               ARG_FILE|ARG_TLS, ' ', C_PROXY_CAPATH},
  {"proxy-cert",                 ARG_FILE|ARG_TLS|ARG_CLEAR, ' ',
   C_PROXY_CERT},
  {"proxy-cert-type",            ARG_STRG|ARG_TLS, ' ', C_PROXY_CERT_TYPE},
  {"proxy-ciphers",              ARG_STRG|ARG_TLS, ' ', C_PROXY_CIPHERS},
  {"proxy-crlfile",              ARG_FILE|ARG_TLS, ' ', C_PROXY_CRLFILE},
  {"proxy-digest",               ARG_BOOL, ' ', C_PROXY_DIGEST},
  {"proxy-header",               ARG_STRG, ' ', C_PROXY_HEADER},
  {"proxy-http2",                ARG_BOOL, ' ', C_PROXY_HTTP2},
  {"proxy-http3",                ARG_BOOL, ' ', C_PROXY_HTTP3},
  {"proxy-insecure",             ARG_BOOL, ' ', C_PROXY_INSECURE},
  {"proxy-key",                  ARG_FILE|ARG_TLS, ' ', C_PROXY_KEY},
  {"proxy-key-type",             ARG_STRG|ARG_TLS, ' ', C_PROXY_KEY_TYPE},
  {"proxy-negotiate",            ARG_BOOL, ' ', C_PROXY_NEGOTIATE},
  {"proxy-ntlm",                 ARG_BOOL, ' ', C_PROXY_NTLM},
  {"proxy-pass",                 ARG_STRG|ARG_CLEAR, ' ', C_PROXY_PASS},
  {"proxy-pinnedpubkey",         ARG_STRG|ARG_TLS, ' ', C_PROXY_PINNEDPUBKEY},
  {"proxy-service-name",         ARG_STRG, ' ', C_PROXY_SERVICE_NAME},
  {"proxy-ssl-allow-beast",      ARG_BOOL|ARG_TLS, ' ',
   C_PROXY_SSL_ALLOW_BEAST},
  {"proxy-ssl-auto-client-cert", ARG_BOOL|ARG_TLS, ' ',
   C_PROXY_SSL_AUTO_CLIENT_CERT},
  {"proxy-tls13-ciphers",        ARG_STRG|ARG_TLS, ' ', C_PROXY_TLS13_CIPHERS},
  {"proxy-tlsauthtype",   ARG_STRG|ARG_TLS|ARG_DEPR, ' ', C_PROXY_TLSAUTHTYPE},
  {"proxy-tlspassword",          ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ',
   C_PROXY_TLSPASSWORD},
  {"proxy-tlsuser",              ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ',
   C_PROXY_TLSUSER},
  {"proxy-tlsv1",                ARG_NONE|ARG_TLS, ' ', C_PROXY_TLSV1},
  {"proxy-user",                 ARG_STRG|ARG_CLEAR, 'U', C_PROXY_USER},
  {"proxy1.0",                   ARG_STRG, ' ', C_PROXY1_0},
  {"proxytunnel",                ARG_BOOL, 'p', C_PROXYTUNNEL},
  {"pubkey",                     ARG_STRG, ' ', C_PUBKEY},
  {"quote",                      ARG_STRG, 'Q', C_QUOTE},
  {"random-file",                ARG_FILE|ARG_DEPR, ' ', C_RANDOM_FILE},
  {"range",                      ARG_STRG, 'r', C_RANGE},
  {"rate",                       ARG_STRG, ' ', C_RATE},
  {"raw",                        ARG_BOOL, ' ', C_RAW},
  {"referer",                    ARG_STRG, 'e', C_REFERER},
  {"remote-header-name",         ARG_BOOL, 'J', C_REMOTE_HEADER_NAME},
  {"remote-name",                ARG_BOOL, 'O', C_REMOTE_NAME},
  {"remote-name-all",            ARG_BOOL, ' ', C_REMOTE_NAME_ALL},
  {"remote-time",                ARG_BOOL, 'R', C_REMOTE_TIME},
  {"remove-on-error",            ARG_BOOL, ' ', C_REMOVE_ON_ERROR},
  {"request",                    ARG_STRG, 'X', C_REQUEST},
  {"request-target",             ARG_STRG, ' ', C_REQUEST_TARGET},
  {"resolve",                    ARG_STRG, ' ', C_RESOLVE},
  {"retry",                      ARG_UNUM, ' ', C_RETRY},
  {"retry-all-errors",           ARG_BOOL, ' ', C_RETRY_ALL_ERRORS},
  {"retry-connrefused",          ARG_BOOL, ' ', C_RETRY_CONNREFUSED},
  {"retry-delay",                ARG_SECS, ' ', C_RETRY_DELAY},
  {"retry-max-time",             ARG_SECS, ' ', C_RETRY_MAX_TIME},
  {"sasl-authzid",               ARG_STRG, ' ', C_SASL_AUTHZID},
  {"sasl-ir",                    ARG_BOOL, ' ', C_SASL_IR},
  {"service-name",               ARG_STRG, ' ', C_SERVICE_NAME},
  {"sessionid",                  ARG_BOOL|ARG_NO, ' ', C_SESSIONID},
  {"show-error",                 ARG_BOOL, 'S', C_SHOW_ERROR},
  {"show-headers",               ARG_BOOL, 'i', C_SHOW_HEADERS},
  {"sigalgs",                    ARG_STRG|ARG_TLS, ' ',
   C_SIGNATURE_ALGORITHMS},
  {"silent",                     ARG_BOOL, 's', C_SILENT},
  {"skip-existing",              ARG_BOOL, ' ', C_SKIP_EXISTING},
  {"socks4",                     ARG_STRG, ' ', C_SOCKS4},
  {"socks4a",                    ARG_STRG, ' ', C_SOCKS4A},
  {"socks5",                     ARG_STRG, ' ', C_SOCKS5},
  {"socks5-basic",               ARG_BOOL, ' ', C_SOCKS5_BASIC},
  {"socks5-gssapi",              ARG_BOOL, ' ', C_SOCKS5_GSSAPI},
  {"socks5-gssapi-nec",          ARG_BOOL, ' ', C_SOCKS5_GSSAPI_NEC},
  {"socks5-gssapi-service",      ARG_STRG, ' ', C_SOCKS5_GSSAPI_SERVICE},
  {"socks5-hostname",            ARG_STRG, ' ', C_SOCKS5_HOSTNAME},
  {"speed-limit",                ARG_UNUM, 'Y', C_SPEED_LIMIT},
  {"speed-time",                 ARG_UNUM, 'y', C_SPEED_TIME},
  {"ssl",                        ARG_BOOL|ARG_TLS, ' ', C_SSL},
  {"ssl-allow-beast",            ARG_BOOL|ARG_TLS, ' ', C_SSL_ALLOW_BEAST},
  {"ssl-auto-client-cert",       ARG_BOOL|ARG_TLS, ' ',
   C_SSL_AUTO_CLIENT_CERT},
  {"ssl-no-revoke",              ARG_BOOL|ARG_TLS, ' ', C_SSL_NO_REVOKE},
  {"ssl-reqd",                   ARG_BOOL|ARG_TLS, ' ', C_SSL_REQD},
  {"ssl-revoke-best-effort",     ARG_BOOL|ARG_TLS, ' ',
   C_SSL_REVOKE_BEST_EFFORT},
  {"ssl-sessions",               ARG_FILE|ARG_TLS, ' ', C_SSL_SESSIONS},
  {"sslv2",                      ARG_NONE|ARG_DEPR, '2', C_SSLV2},
  {"sslv3",                      ARG_NONE|ARG_DEPR, '3', C_SSLV3},
  {"stderr",                     ARG_FILE, ' ', C_STDERR},
  {"styled-output",              ARG_BOOL, ' ', C_STYLED_OUTPUT},
  {"suppress-connect-headers",   ARG_BOOL, ' ', C_SUPPRESS_CONNECT_HEADERS},
  {"tcp-fastopen",               ARG_BOOL, ' ', C_TCP_FASTOPEN},
  {"tcp-nodelay",                ARG_BOOL, ' ', C_TCP_NODELAY},
  {"telnet-option",              ARG_STRG, 't', C_TELNET_OPTION},
#ifdef DEBUGBUILD
  {"test-duphandle",             ARG_BOOL, ' ', C_TEST_DUPHANDLE},
  {"test-event",                 ARG_BOOL, ' ', C_TEST_EVENT},
#endif
  {"tftp-blksize",               ARG_UNUM, ' ', C_TFTP_BLKSIZE},
  {"tftp-no-options",            ARG_BOOL, ' ', C_TFTP_NO_OPTIONS},
  {"time-cond",                  ARG_STRG, 'z', C_TIME_COND},
  {"tls-earlydata",              ARG_BOOL|ARG_TLS, ' ', C_TLS_EARLYDATA},
  {"tls-max",                    ARG_STRG|ARG_TLS, ' ', C_TLS_MAX},
  {"tls13-ciphers",              ARG_STRG|ARG_TLS, ' ', C_TLS13_CIPHERS},
  {"tlsauthtype",               ARG_STRG|ARG_TLS|ARG_DEPR, ' ', C_TLSAUTHTYPE},
  {"tlspassword",     ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', C_TLSPASSWORD},
  {"tlsuser",             ARG_STRG|ARG_TLS|ARG_CLEAR|ARG_DEPR, ' ', C_TLSUSER},
  {"tlsv1",                      ARG_NONE|ARG_TLS, '1', C_TLSV1},
  {"tlsv1.0",                    ARG_NONE|ARG_TLS, ' ', C_TLSV1_0},
  {"tlsv1.1",                    ARG_NONE|ARG_TLS, ' ', C_TLSV1_1},
  {"tlsv1.2",                    ARG_NONE|ARG_TLS, ' ', C_TLSV1_2},
  {"tlsv1.3",                    ARG_NONE|ARG_TLS, ' ', C_TLSV1_3},
  {"tr-encoding",                ARG_BOOL, ' ', C_TR_ENCODING},
  {"trace",                      ARG_FILE, ' ', C_TRACE},
  {"trace-ascii",                ARG_FILE, ' ', C_TRACE_ASCII},
  {"trace-config",               ARG_STRG, ' ', C_TRACE_CONFIG},
  {"trace-ids",                  ARG_BOOL, ' ', C_TRACE_IDS},
  {"trace-time",                 ARG_BOOL, ' ', C_TRACE_TIME},
  {"unix-socket",                ARG_FILE, ' ', C_UNIX_SOCKET},
  {"upload-file",                ARG_FILE, 'T', C_UPLOAD_FILE},
  {"upload-flags",               ARG_STRG, ' ', C_UPLOAD_FLAGS},
  {"url",                        ARG_STRG, ' ', C_URL},
  {"url-query",                  ARG_STRG, ' ', C_URL_QUERY},
  {"use-ascii",                  ARG_BOOL, 'B', C_USE_ASCII},
  {"user",                       ARG_STRG|ARG_CLEAR, 'u', C_USER},
  {"user-agent",                 ARG_STRG, 'A', C_USER_AGENT},
  {"variable",                   ARG_STRG, ' ', C_VARIABLE},
  {"verbose",                    ARG_BOOL, 'v', C_VERBOSE},
  {"version",                    ARG_BOOL, 'V', C_VERSION},
  {"vlan-priority",              ARG_UNUM, ' ', C_VLAN_PRIORITY},
#ifdef USE_WATT32
  {"wdebug",                     ARG_BOOL, ' ', C_WDEBUG},
#endif
  {"write-out",                  ARG_STRG, 'w', C_WRITE_OUT},
  {"xattr",                      ARG_BOOL, ' ', C_XATTR},
};

/* Split the argument of -E to 'certname' and 'passphrase' separated by colon.
 * We allow ':' and '\' to be escaped by '\' so that we can use certificate
 * nicknames containing ':'. See <https://sourceforge.net/p/curl/bugs/1196/>
 * for details.
 *
 * Unit test 1394
 */
UNITTEST ParameterError parse_cert_parameter(const char *cert_parameter,
                                             char **certname,
                                             char **passphrase)
{
  size_t param_length = strlen(cert_parameter);
  size_t span;
  const char *param_place = NULL;
  char *certname_place = NULL;
  ParameterError err = PARAM_OK;
  *certname = NULL;
  *passphrase = NULL;

  /* most trivial assumption: cert_parameter is empty */
  if(param_length == 0)
    return PARAM_BLANK_STRING;

  /* next less trivial: cert_parameter starts 'pkcs11:' and thus
   * looks like a RFC7512 PKCS#11 URI which can be used as-is.
   * Also if cert_parameter contains no colon nor backslash, this
   * means no passphrase was given and no characters escaped */
  if(curl_strnequal(cert_parameter, "pkcs11:", 7) ||
     !strpbrk(cert_parameter, ":\\")) {
    *certname = curlx_strdup(cert_parameter);
    if(!*certname)
      return PARAM_NO_MEM;
    return PARAM_OK;
  }
  /* deal with escaped chars; find unescaped colon if it exists */
  certname_place = curlx_malloc(param_length + 1);
  if(!certname_place) {
    err = PARAM_NO_MEM;
    goto done;
  }

  *certname = certname_place;
  param_place = cert_parameter;
  while(*param_place) {
    span = strcspn(param_place, ":\\");
    memcpy(certname_place, param_place, span);
    param_place += span;
    certname_place += span;
    /* we ate all the non-special chars. now we are on either a special
     * char or the end of the string. */
    switch(*param_place) {
    case '\0':
      break;
    case '\\':
      param_place++;
      switch(*param_place) {
      case '\0':
        *certname_place++ = '\\';
        break;
      case '\\':
        *certname_place++ = '\\';
        param_place++;
        break;
      case ':':
        *certname_place++ = ':';
        param_place++;
        break;
      default:
        *certname_place++ = '\\';
        *certname_place++ = *param_place;
        param_place++;
        break;
      }
      break;
    case ':':
      /* Since we live in a world of weirdness and confusion, the Windows
         dudes can use : when using drive letters and thus c:\file:password
         needs to work. In order not to break compatibility, we still use : as
         separator, but we try to detect when it is used for a filename! On
         Windows. */
#ifdef _WIN32
      if((param_place == &cert_parameter[1]) &&
         (cert_parameter[2] == '\\' || cert_parameter[2] == '/') &&
         ISALPHA(cert_parameter[0])) {
        /* colon in the second column, followed by a backslash, and the
           first character is an alphabetic letter:

           this is a drive letter colon */
        *certname_place++ = ':';
        param_place++;
        break;
      }
#endif
      /* escaped colons and Windows drive letter colons were handled
       * above; if we are still here, this is a separating colon */
      param_place++;
      if(*param_place) {
        *passphrase = curlx_strdup(param_place);
        if(!*passphrase)
          err = PARAM_NO_MEM;
      }
      goto done;
    }
  }
done:
  if(err) {
    curlx_safefree(*certname);
  }
  else
    *certname_place = '\0';
  return err;
}

/* Replace (in-place) '%20' by '+' according to RFC1866 */
static size_t replace_url_encoded_space_by_plus(char *url)
{
  size_t orig_len = strlen(url);
  size_t orig_index = 0;
  size_t new_index = 0;

  while(orig_index < orig_len) {
    if((url[orig_index] == '%') &&
       (url[orig_index + 1] == '2') &&
       (url[orig_index + 2] == '0')) {
      url[new_index] = '+';
      orig_index += 3;
    }
    else {
      if(new_index != orig_index) {
        url[new_index] = url[orig_index];
      }
      orig_index++;
    }
    new_index++;
  }

  url[new_index] = 0; /* terminate string */

  return new_index; /* new size */
}

static ParameterError GetFileAndPassword(const char *nextarg, char **file,
                                         char **password)
{
  char *certname, *passphrase;
  ParameterError err;
  /* nextarg is never NULL here */
  err = parse_cert_parameter(nextarg, &certname, &passphrase);
  if(!err) {
    curlx_free(*file);
    *file = certname;
    if(passphrase) {
      curlx_free(*password);
      *password = passphrase;
    }
  }
  return err;
}

struct sizeunit {
  char unit; /* single lowercase ASCII letter */
  curl_off_t mul;
  size_t mlen; /* number of digits in 'mul', when written in decimal */
};

static const struct sizeunit *getunit(char unit)
{
  static const struct sizeunit list[] = {
    { 'p', (curl_off_t)1125899906842624, 16 }, /* Peta */
    { 't', (curl_off_t)1099511627776,    13 }, /* Tera */
    { 'g', 1073741824,                   10 }, /* Giga */
    { 'm', 1048576,                       7 }, /* Mega */
    { 'k', 1024,                          4 }, /* Kilo */
  };

  size_t i;
  for(i = 0; i < CURL_ARRAYSIZE(list); i++)
    if((unit | 0x20) == list[i].unit)
      return &list[i];
  return NULL;
}

/* Get a size parameter for '--limit-rate' or '--max-filesize'.
   We support P, T, G, M and K (case insensitive) suffixes.

   Unit test 1623
 */
UNITTEST ParameterError GetSizeParameter(const char *arg, curl_off_t *out)
{
  const char *unit = arg;
  curl_off_t value;
  curl_off_t prec = 0;
  size_t plen = 0;
  curl_off_t add = 0;
  curl_off_t mul = 1;
  int rc;

  rc = curlx_str_number(&unit, &value, CURL_OFF_T_MAX);
  if(rc == STRE_OVERFLOW)
    return PARAM_NUMBER_TOO_LARGE;
  else if(rc)
    return PARAM_BAD_NUMERIC;

  if(!curlx_str_single(&unit, '.')) {
    const char *s = unit;
    if(curlx_str_number(&unit, &prec, CURL_OFF_T_MAX))
      return PARAM_BAD_NUMERIC;
    plen = unit - s;
  }

  if(strlen(unit) > 1)
    return PARAM_BAD_USE;
  else if(!*unit || ((*unit | 0x20) == 'b')) {
    if(plen)
      /* cannot handle partial bytes */
      return PARAM_BAD_USE;
  }
  else {
    const struct sizeunit *su = getunit(*unit);
    if(!su)
      return PARAM_BAD_USE;
    mul = su->mul;

    if(prec) {
      /* precision was provided */
      curl_off_t frac = 1;

      /* too many precision digits, trim them */
      while(su->mlen <= plen) {
        prec /= 10;
        plen--;
      }

      while(plen--)
        frac *= 10;

      if((CURL_OFF_T_MAX / mul) > prec)
        add = mul * prec / frac;
      else
        add = (mul / frac) * prec;
    }
  }
  if(value > ((CURL_OFF_T_MAX - add) / mul))
    return PARAM_NUMBER_TOO_LARGE;

  *out = (value * mul) + add;
  return PARAM_OK;
}

#ifdef HAVE_WRITABLE_ARGV
static void cleanarg(char *str)
{
  /* now that getstr has copied the contents of nextarg, wipe the next
   * argument out so that the username:password is not displayed in the
   * system process list */
  if(str) {
    size_t len = strlen(str);
    memset(str, '*', len);
  }
}
#else
#define cleanarg(x) tool_nop_stmt
#endif

/* the maximum size we allow the dynbuf generated string */
#define MAX_DATAURLENCODE (500 * 1024 * 1024)

/* --data-urlencode */
static ParameterError data_urlencode(const char *nextarg,
                                     char **postp,
                                     size_t *lenp)
{
  /* [name]=[content], we encode the content part only
   * [name]@[filename]
   *
   * Case 2: we first load the file using that name and then encode
   * the content.
   */
  ParameterError err;
  const char *p = strchr(nextarg, '=');
  size_t nlen;
  char is_file;
  char *postdata = NULL;
  size_t size = 0;
  if(!p)
    /* there was no '=' letter, check for a '@' instead */
    p = strchr(nextarg, '@');
  if(p) {
    nlen = p - nextarg; /* length of the name part */
    is_file = *p++; /* pass the separator */
  }
  else {
    /* neither @ nor =, so no name and it is not a file */
    nlen = 0;
    is_file = 0;
    p = nextarg;
  }
  if('@' == is_file) {
    FILE *file;
    /* a '@' letter, it means that a filename or - (stdin) follows */
    if(!strcmp("-", p)) {
      file = stdin;
      CURL_BINMODE(stdin);
    }
    else {
      file = curlx_fopen(p, "rb");
      if(!file) {
        errorf("Failed to open %s", p);
        return PARAM_READ_ERROR;
      }
    }

    err = file2memory(&postdata, &size, file);

    if(file && (file != stdin))
      curlx_fclose(file);
    if(err)
      return err;
  }
  else {
    err = getstr(&postdata, p, ALLOW_BLANK);
    if(err)
      goto error;
    size = strlen(postdata);
  }

  if(!postdata) {
    /* no data from the file, point to a zero byte string to make this
       get sent as a POST anyway */
    postdata = curlx_strdup("");
    if(!postdata)
      return PARAM_NO_MEM;
    size = 0;
  }
  else {
    char *enc = curl_easy_escape(NULL, postdata, (int)size);
    curlx_safefree(postdata); /* no matter if it worked or not */
    if(enc) {
      char *n;
      replace_url_encoded_space_by_plus(enc);
      if(nlen > 0) { /* only append '=' if we have a name */
        struct dynbuf dyn;
        curlx_dyn_init(&dyn, MAX_DATAURLENCODE);
        if(curlx_dyn_addn(&dyn, nextarg, nlen) ||
           curlx_dyn_addn(&dyn, "=", 1) ||
           curlx_dyn_add(&dyn, enc)) {
          curl_free(enc);
          return PARAM_NO_MEM;
        }
        curl_free(enc);
        n = curlx_dyn_ptr(&dyn);
        size = curlx_dyn_len(&dyn);
      }
      else {
        /* make sure we return "our memory" */
        n = curlx_strdup(enc);
        curl_free(enc);
        if(!n)
          return PARAM_NO_MEM;
        size = strlen(n);
      }
      postdata = n;
    }
    else
      return PARAM_NO_MEM;
  }
  *postp = postdata;
  *lenp = size;
  return PARAM_OK;
error:
  return err;
}

static void sethttpver(struct OperationConfig *config, long httpversion)
{
  if(config->httpversion &&
     (config->httpversion != httpversion))
    warnf("Overrides previous HTTP version option");

  config->httpversion = httpversion;
}

static CURLcode set_trace_config(const char *token)
{
  CURLcode result = CURLE_OK;
  const char *next, *name;
  bool toggle;

  while(token) {
    size_t len;
    next = strchr(token, ',');

    if(next)
      len = next - token;
    else
      len = strlen(token);

    switch(*token) {
    case '-':
      toggle = FALSE;
      name = token + 1;
      len--;
      break;
    case '+':
      toggle = TRUE;
      name = token + 1;
      len--;
      break;
    default:
      toggle = TRUE;
      name = token;
      break;
    }

    if((len == 3) && curl_strnequal(name, "all", 3)) {
      global->traceids = toggle;
      global->tracetime = toggle;
      if(toggle)
        result = curl_global_trace("all,-lib-ids");
      else
        result = curl_global_trace(token);
      if(result)
        goto out;
    }
    else if((len == 3) && curl_strnequal(name, "ids", 3)) {
      global->traceids = toggle;
    }
    else if((len == 4) && curl_strnequal(name, "time", 4)) {
      global->tracetime = toggle;
    }
    else {
      char buffer[64];
      curl_msnprintf(buffer, sizeof(buffer), "%c%.*s,-lib-ids",
                     toggle ? '+' : '-', (int)len, name);
      result = curl_global_trace(buffer);
      if(result)
        goto out;
    }
    if(next) {
      next++;
      if(*next == ' ')
        next++;
    }
    token = next;
  }
out:
  return result;
}

static int findarg(const void *a, const void *b)
{
  const struct LongShort *aa = a;
  const struct LongShort *bb = b;
  return strcmp(aa->lname, bb->lname);
}

const struct LongShort *findshortopt(char letter)
{
  static const struct LongShort *singles[128 - ' ']; /* ASCII => pointer */
  static bool singles_done = FALSE;
  if((letter >= 127) || (letter <= ' '))
    return NULL;

  if(!singles_done) {
    unsigned int j;
    for(j = 0; j < CURL_ARRAYSIZE(aliases); j++) {
      if(aliases[j].letter != ' ') {
        unsigned char l = (unsigned char)aliases[j].letter;
        singles[l - ' '] = &aliases[j];
      }
    }
    singles_done = TRUE;
  }
  return singles[letter - ' '];
}

struct TOSEntry {
  const char *name;
  unsigned char value;
};

static const struct TOSEntry tos_entries[] = {
  { "AF11", 0x28 },
  { "AF12", 0x30 },
  { "AF13", 0x38 },
  { "AF21", 0x48 },
  { "AF22", 0x50 },
  { "AF23", 0x58 },
  { "AF31", 0x68 },
  { "AF32", 0x70 },
  { "AF33", 0x78 },
  { "AF41", 0x88 },
  { "AF42", 0x90 },
  { "AF43", 0x98 },
  { "CE",   0x03 },
  { "CS0",  0x00 },
  { "CS1",  0x20 },
  { "CS2",  0x40 },
  { "CS3",  0x60 },
  { "CS4",  0x80 },
  { "CS5",  0xa0 },
  { "CS6",  0xc0 },
  { "CS7",  0xe0 },
  { "ECT0", 0x02 },
  { "ECT1", 0x01 },
  { "EF",   0xb8 },
  { "LE",   0x04 },
  { "LOWCOST",     0x02 },
  { "LOWDELAY",    0x10 },
  { "MINCOST",     0x02 },
  { "RELIABILITY", 0x04 },
  { "THROUGHPUT",  0x08 },
  { "VOICE-ADMIT", 0xb0 }
};

static int find_tos(const void *a, const void *b)
{
  const struct TOSEntry *aa = a;
  const struct TOSEntry *bb = b;
  return strcmp(aa->name, bb->name);
}

#define MAX_QUERY_LEN 100000 /* larger is not likely to ever work */
static ParameterError url_query(const char *nextarg,
                                struct OperationConfig *config)
{
  size_t size = 0;
  ParameterError err = PARAM_OK;
  char *query;
  struct dynbuf dyn;
  curlx_dyn_init(&dyn, MAX_QUERY_LEN);

  if(*nextarg == '+') {
    /* use without encoding */
    query = curlx_strdup(&nextarg[1]);
    if(!query)
      err = PARAM_NO_MEM;
  }
  else
    err = data_urlencode(nextarg, &query, &size);

  if(!err) {
    if(config->query) {
      CURLcode result = curlx_dyn_addf(&dyn, "%s&%s", config->query, query);
      curlx_free(query);
      if(result)
        err = PARAM_NO_MEM;
      else {
        curlx_free(config->query);
        config->query = curlx_dyn_ptr(&dyn);
      }
    }
    else
      config->query = query;
  }
  return err;
}

static ParameterError set_data(cmdline_t cmd,
                               const char *nextarg,
                               struct OperationConfig *config)
{
  char *postdata = NULL;
  FILE *file;
  size_t size = 0;
  ParameterError err = PARAM_OK;

  if(cmd == C_DATA_URLENCODE) { /* --data-urlencode */
    err = data_urlencode(nextarg, &postdata, &size);
    if(err)
      return err;
  }
  else if('@' == *nextarg && (cmd != C_DATA_RAW)) {
    /* the data begins with a '@' letter, it means that a filename
       or - (stdin) follows */
    nextarg++; /* pass the @ */

    if(!strcmp("-", nextarg)) {
      file = stdin;
      if(cmd == C_DATA_BINARY) /* forced data-binary */
        CURL_BINMODE(stdin);
    }
    else {
      file = curlx_fopen(nextarg, "rb");
      if(!file) {
        errorf("Failed to open %s", nextarg);
        return PARAM_READ_ERROR;
      }
    }

    if((cmd == C_DATA_BINARY) || /* --data-binary */
       (cmd == C_JSON) /* --json */)
      /* forced binary */
      err = file2memory(&postdata, &size, file);
    else {
      err = file2string(&postdata, file);
      if(postdata)
        size = strlen(postdata);
    }

    if(file && (file != stdin))
      curlx_fclose(file);
    if(err)
      return err;

    if(!postdata) {
      /* no data from the file, point to a zero byte string to make this
         get sent as a POST anyway */
      postdata = curlx_strdup("");
      if(!postdata)
        return PARAM_NO_MEM;
    }
  }
  else {
    err = getstr(&postdata, nextarg, ALLOW_BLANK);
    if(err)
      return err;
    size = strlen(postdata);
  }
  if(cmd == C_JSON)
    config->jsoned = TRUE;

  if(curlx_dyn_len(&config->postdata)) {
    /* skip separator append for --json */
    if(!err && (cmd != C_JSON) &&
       curlx_dyn_addn(&config->postdata, "&", 1))
      err = PARAM_NO_MEM;
  }

  if(!err && curlx_dyn_addn(&config->postdata, postdata, size))
    err = PARAM_NO_MEM;

  curlx_safefree(postdata);

  config->postfields = curlx_dyn_ptr(&config->postdata);
  return err;
}

static ParameterError set_rate(const char *nextarg)
{
  /* --rate */
  /* support a few different suffixes, extract the suffix first, then
     get the number and convert to per hour.
     /s == per second
     /m == per minute
     /h == per hour (default)
     /d == per day (24 hours)
   */
  ParameterError err = PARAM_OK;
  const char *p = nextarg;
  curl_off_t denominator;
  curl_off_t numerator = 60 * 60 * 1000; /* default per hour */

  if(curlx_str_number(&p, &denominator, CURL_OFF_T_MAX))
    return PARAM_BAD_NUMERIC;

  if(denominator < 1)
    return PARAM_BAD_USE;

  if(!curlx_str_single(&p, '/')) {
    curl_off_t numunits;

    if(curlx_str_number(&p, &numunits, CURL_OFF_T_MAX))
      numunits = 1;

    switch(*p) {
    case 's': /* per second */
      numerator = 1000;
      break;
    case 'm': /* per minute */
      numerator = 60 * 1000;
      break;
    case 'h': /* per hour */
      break;
    case 'd': /* per day */
      numerator = 24 * 60 * 60 * 1000;
      break;
    default:
      errorf("unsupported --rate unit");
      err = PARAM_BAD_USE;
      break;
    }

    if((CURL_OFF_T_MAX / numerator) < numunits) {
      /* overflow, too large number */
      errorf("too large --rate unit");
      err = PARAM_NUMBER_TOO_LARGE;
    }
    else
      /* this typecast is okay based on the check above */
      numerator *= numunits;
  }

  if(err)
    ;
  else if(denominator > numerator)
    err = PARAM_NUMBER_TOO_LARGE;
  else
    global->ms_per_transfer = numerator / denominator;

  return err;
}

const struct LongShort *findlongopt(const char *opt)
{
  struct LongShort key;
  key.lname = opt;

  return bsearch(&key, aliases, CURL_ARRAYSIZE(aliases),
                 sizeof(aliases[0]), findarg);
}

static ParameterError add_url(struct OperationConfig *config,
                              const char *thisurl,
                              bool remote_noglob)
{
  ParameterError err = PARAM_OK;
  struct getout *url;

  if(!config->url_get)
    config->url_get = config->url_list;

  if(config->url_get) {
    /* there is a node here, if it already is filled-in continue to find
       an "empty" node */
    while(config->url_get && config->url_get->urlset)
      config->url_get = config->url_get->next;
  }

  /* now there might or might not be an available node to fill in! */

  if(config->url_get)
    /* existing node */
    url = config->url_get;
  else
    /* there was no free node, create one! */
    config->url_get = url = new_getout(config);

  if(!url)
    return PARAM_NO_MEM;
  else {
    /* fill in the URL */
    err = getstr(&url->url, thisurl, DENY_BLANK);
    url->urlset = TRUE;
    if(remote_noglob)
      url->useremote = url->noglob = TRUE;
    if(!err && (++config->num_urls > 1) &&
       (config->etag_save_file || config->etag_compare_file)) {
      errorf("The etag options only work on a single URL");
      return PARAM_BAD_USE;
    }
  }
  return err;
}

static ParameterError parse_url(struct OperationConfig *config,
                                const char *nextarg)
{
  /* nextarg is never NULL here */
  if(nextarg[0] == '@') {
    /* read URLs from a file, treat all as -O */
    struct dynbuf line;
    ParameterError err = PARAM_OK;
    bool error = FALSE;
    bool fromstdin = !strcmp("-", &nextarg[1]);
    FILE *f;

    if(fromstdin)
      f = stdin;
    else
      f = curlx_fopen(&nextarg[1], FOPEN_READTEXT);
    if(f) {
      curlx_dyn_init(&line, 8092);
      while(my_get_line(f, &line, &error)) {
        const char *ptr = curlx_dyn_ptr(&line);
        err = add_url(config, ptr, TRUE);
        if(err)
          break;
      }
      if(!fromstdin)
        curlx_fclose(f);
      curlx_dyn_free(&line);
      if(error || err)
        return PARAM_READ_ERROR;
      return PARAM_OK;
    }
    return PARAM_READ_ERROR; /* file not found */
  }
  return add_url(config, nextarg, FALSE);
}

static ParameterError parse_localport(struct OperationConfig *config,
                                      const char *nextarg)
{
  const char *pp = NULL;
  const char *p = nextarg;
  char buffer[22];
  size_t plen = 0;
  while(ISDIGIT(*p))
    p++;
  plen = p - nextarg;
  if(*p) {
    pp = p;
    /* check for ' - [end]' */
    if(ISBLANK(*pp))
      pp++;
    if(*pp != '-')
      return PARAM_BAD_USE;
    pp++;
    if(ISBLANK(*pp))
      pp++;
  }
  curl_msnprintf(buffer, sizeof(buffer), "%.*s", (int)plen, nextarg);
  if(str2unummax(&config->localport, buffer, 65535))
    return PARAM_BAD_USE;
  if(!pp)
    config->localportrange = 1; /* default number of ports to try */
  else {
    if(str2unummax(&config->localportrange, pp, 65535))
      return PARAM_BAD_USE;
    config->localportrange -= (config->localport - 1);
    if(config->localportrange < 1)
      return PARAM_BAD_USE;
  }
  return PARAM_OK;
}

static ParameterError parse_continue_at(struct OperationConfig *config,
                                        const char *nextarg)
{
  ParameterError err = PARAM_OK;
  if(config->range) {
    errorf("--continue-at is mutually exclusive with --range");
    return PARAM_BAD_USE;
  }
  if(config->rm_partial) {
    errorf("--continue-at is mutually exclusive with --remove-on-error");
    return PARAM_BAD_USE;
  }
  if(config->file_clobber_mode == CLOBBER_NEVER) {
    errorf("--continue-at is mutually exclusive with --no-clobber");
    return PARAM_BAD_USE;
  }
  /* This makes us continue an ftp transfer at given position */
  if(strcmp(nextarg, "-")) {
    err = str2offset(&config->resume_from, nextarg);
    config->resume_from_current = FALSE;
  }
  else {
    config->resume_from_current = TRUE;
    config->resume_from = 0;
  }
  config->use_resume = TRUE;
  return err;
}

static ParameterError parse_ech(struct OperationConfig *config,
                                const char *nextarg)
{
  ParameterError err = PARAM_OK;
  if(!feature_ech)
    err = PARAM_LIBCURL_DOESNT_SUPPORT;
  else if(strlen(nextarg) > 4 && curl_strnequal("pn:", nextarg, 3)) {
    /* a public_name */
    err = getstr(&config->ech_public, nextarg, DENY_BLANK);
  }
  else if(strlen(nextarg) > 5 && curl_strnequal("ecl:", nextarg, 4)) {
    /* an ECHConfigList */
    if('@' != *(nextarg + 4)) {
      err = getstr(&config->ech_config, nextarg, DENY_BLANK);
    }
    else {
      /* Indirect case: @filename or @- for stdin */
      char *tmpcfg = NULL;
      FILE *file;

      nextarg += 5;        /* skip over 'ecl:@' */
      if(!strcmp("-", nextarg)) {
        file = stdin;
      }
      else {
        file = curlx_fopen(nextarg, FOPEN_READTEXT);
      }
      if(!file) {
        warnf("Could not read file \"%s\" "
              "specified for \"--ech ecl:\" option",
              nextarg);
        return PARAM_BAD_USE;
      }
      err = file2string(&tmpcfg, file);
      if(file != stdin)
        curlx_fclose(file);
      if(err)
        return err;
      {
        char *tmp = curl_maprintf("ecl:%s", tmpcfg);
        curlx_free(tmpcfg);
        if(!tmp)
          return PARAM_NO_MEM;
        config->ech_config = curlx_strdup(tmp);
        curl_free(tmp);
        if(!config->ech_config)
          return PARAM_NO_MEM;
      }
    } /* file done */
  }
  else {
    /* Simple case: a string, with a keyword */
    err = getstr(&config->ech, nextarg, DENY_BLANK);
  }
  return err;
}

static ParameterError parse_header(struct OperationConfig *config,
                                   cmdline_t cmd,
                                   const char *nextarg)
{
  ParameterError err = PARAM_OK;

  /* A custom header to append to a list */
  if(nextarg[0] == '@') {
    /* read many headers from a file or stdin */
    bool use_stdin = !strcmp(&nextarg[1], "-");
    FILE *file = use_stdin ? stdin : curlx_fopen(&nextarg[1], FOPEN_READTEXT);
    if(!file) {
      errorf("Failed to open %s", &nextarg[1]);
      err = PARAM_READ_ERROR;
    }
    else {
      struct dynbuf line;
      bool error = FALSE;
      curlx_dyn_init(&line, 1024 * 100);
      while(my_get_line(file, &line, &error)) {
        const char *ptr = curlx_dyn_ptr(&line);
        err = add2list(cmd == C_PROXY_HEADER ? /* --proxy-header? */
                       &config->proxyheaders :
                       &config->headers, ptr);
        if(err)
          break;
      }
      if(error)
        err = PARAM_READ_ERROR;
      curlx_dyn_free(&line);
      if(!use_stdin)
        curlx_fclose(file);
    }
  }
  else {
    if(!strchr(nextarg, ':') && !strchr(nextarg, ';')) {
      warnf("The provided %s header '%s' does not look like a header?",
            (cmd == C_PROXY_HEADER) ? "proxy" : "HTTP", nextarg);
    }
    if(cmd == C_PROXY_HEADER) /* --proxy-header */
      err = add2list(&config->proxyheaders, nextarg);
    else
      err = add2list(&config->headers, nextarg);
  }
  return err;
}

static ParameterError parse_output(struct OperationConfig *config,
                                   const char *nextarg)
{
  ParameterError err = PARAM_OK;
  struct getout *url;

  /* output file */
  if(!config->url_out)
    config->url_out = config->url_list;
  if(config->url_out) {
    /* there is a node here, if it already is filled-in continue to find
       an "empty" node */
    while(config->url_out && config->url_out->outset)
      config->url_out = config->url_out->next;
  }

  /* now there might or might not be an available node to fill in! */

  if(config->url_out)
    /* existing node */
    url = config->url_out;
  else {
    /* there was no free node, create one! */
    config->url_out = url = new_getout(config);
  }

  if(!url)
    return PARAM_NO_MEM;

  /* fill in the outfile */
  if(nextarg)
    err = getstr(&url->outfile, nextarg, DENY_BLANK);
  else
    url->outfile = NULL;
  url->useremote = FALSE; /* switch off */
  url->outset = TRUE;
  url->out_null = !nextarg;
  return err;
}

static ParameterError parse_remote_name(struct OperationConfig *config,
                                        bool toggle)
{
  ParameterError err = PARAM_OK;
  struct getout *url;

  if(!toggle && !config->remote_name_all)
    return err; /* nothing to do */

  /* output file */
  if(!config->url_out)
    config->url_out = config->url_list;
  if(config->url_out) {
    /* there is a node here, if it already is filled-in continue to find
       an "empty" node */
    while(config->url_out && config->url_out->outset)
      config->url_out = config->url_out->next;
  }

  /* now there might or might not be an available node to fill in! */

  if(config->url_out)
    /* existing node */
    url = config->url_out;
  else {
    /* there was no free node, create one! */
    config->url_out = url = new_getout(config);
  }

  if(!url)
    return PARAM_NO_MEM;

  url->outfile = NULL; /* leave it */
  url->useremote = toggle;
  url->outset = TRUE;
  url->out_null = FALSE;
  return PARAM_OK;
}

static ParameterError parse_quote(struct OperationConfig *config,
                                  const char *nextarg)
{
  ParameterError err = PARAM_OK;

  /* QUOTE command to send to FTP server */
  switch(nextarg[0]) {
  case '-':
    /* prefixed with a dash makes it a POST TRANSFER one */
    nextarg++;
    err = add2list(&config->postquote, nextarg);
    break;
  case '+':
    /* prefixed with a plus makes it an immediately-before-transfer one */
    nextarg++;
    err = add2list(&config->prequote, nextarg);
    break;
  default:
    err = add2list(&config->quote, nextarg);
    break;
  }
  return err;
}

static ParameterError parse_range(struct OperationConfig *config,
                                  const char *nextarg)
{
  ParameterError err = PARAM_OK;
  curl_off_t value;
  const char *orig = nextarg;

  if(config->use_resume) {
    errorf("--continue-at is mutually exclusive with --range");
    return PARAM_BAD_USE;
  }
  if(!curlx_str_number(&nextarg, &value, CURL_OFF_T_MAX) &&
     curlx_str_single(&nextarg, '-')) {
    /* Specifying a range WITHOUT A DASH does create an illegal HTTP range
       (and does not actually be range by definition). The man page previously
       claimed that to be a good way, why this code is added to work around
       it. */
    char buffer[32];
    warnf("A specified range MUST include at least one dash (-). "
          "Appending one for you");
    curl_msnprintf(buffer, sizeof(buffer), "%" CURL_FORMAT_CURL_OFF_T "-",
                   value);
    curlx_free(config->range);
    config->range = curlx_strdup(buffer);
    if(!config->range)
      err = PARAM_NO_MEM;
  }
  else {
    /* byte range requested */
    while(*nextarg) {
      if(!ISDIGIT(*nextarg) && *nextarg != '-' && *nextarg != ',') {
        warnf("Invalid character is found in given range. "
              "A specified range MUST have only digits in "
              "\'start\'-\'stop\'. The server's response to this "
              "request is uncertain.");
        break;
      }
      nextarg++;
    }
    err = getstr(&config->range, orig, DENY_BLANK);
  }
  return err;
}

static ParameterError parse_upload_file(struct OperationConfig *config,
                                        const char *nextarg)
{
  ParameterError err = PARAM_OK;
  struct getout *url;

  /* we are uploading */
  if(!config->url_ul)
    config->url_ul = config->url_list;
  if(config->url_ul) {
    /* there is a node here, if it already is filled-in continue to find
       an "empty" node */
    while(config->url_ul && config->url_ul->uploadset)
      config->url_ul = config->url_ul->next;
  }

  /* now there might or might not be an available node to fill in! */

  if(config->url_ul)
    /* existing node */
    url = config->url_ul;
  else
    /* there was no free node, create one! */
    config->url_ul = url = new_getout(config);

  if(!url)
    return PARAM_NO_MEM;

  url->uploadset = TRUE; /* mark -T used */
  if(!*nextarg)
    url->noupload = TRUE;
  else {
    /* "-" equals stdin, but keep the string around for now */
    err = getstr(&url->infile, nextarg, DENY_BLANK);
  }
  return err;
}

static size_t verbose_nopts;

static ParameterError parse_verbose(bool toggle)
{
  ParameterError err = PARAM_OK;

  /* This option is a super-boolean with side effect when applied
   * more than once in the same argument flag, like `-vvv`. */
  if(!toggle) {
    global->verbosity = 0;
    if(set_trace_config("-all"))
      err = PARAM_NO_MEM;
    global->tracetype = TRACE_NONE;
    return err;
  }
  else if(!verbose_nopts) {
    /* first `-v` in an argument resets to base verbosity */
    global->verbosity = 0;
    if(!global->trace_set && set_trace_config("-all"))
      return PARAM_NO_MEM;
  }
  /* the '%' thing here causes the trace get sent to stderr */
  switch(global->verbosity) {
  case 0:
    global->verbosity = 1;
    curlx_free(global->trace_dump);
    global->trace_dump = curlx_strdup("%");
    if(!global->trace_dump)
      err = PARAM_NO_MEM;
    else {
      if(global->tracetype && (global->tracetype != TRACE_PLAIN))
        warnf("-v, --verbose overrides an earlier trace option");
      global->tracetype = TRACE_PLAIN;
    }
    break;
  case 1:
    global->verbosity = 2;
    if(set_trace_config("ids,time,protocol"))
      err = PARAM_NO_MEM;
    break;
  case 2:
    global->verbosity = 3;
    global->tracetype = TRACE_ASCII;
    if(set_trace_config("ssl,read,write"))
      err = PARAM_NO_MEM;
    break;
  case 3:
    global->verbosity = 4;
    if(set_trace_config("network"))
      err = PARAM_NO_MEM;
    break;
  default:
    /* no effect for now */
    break;
  }
  return err;
}

static ParameterError parse_writeout(struct OperationConfig *config,
                                     const char *nextarg)
{
  ParameterError err = PARAM_OK;

  /* get the output string */
  if('@' == *nextarg) {
    /* the data begins with a '@' letter, it means that a filename
       or - (stdin) follows */
    FILE *file;
    const char *fname;
    nextarg++; /* pass the @ */
    if(!strcmp("-", nextarg)) {
      fname = "<stdin>";
      file = stdin;
    }
    else {
      fname = nextarg;
      file = curlx_fopen(fname, FOPEN_READTEXT);
      if(!file) {
        errorf("Failed to open %s", fname);
        return PARAM_READ_ERROR;
      }
    }
    curlx_safefree(config->writeout);
    err = file2string(&config->writeout, file);
    if(file && (file != stdin))
      curlx_fclose(file);
    if(err)
      return err;
    if(!config->writeout)
      warnf("Failed to read %s", fname);
  }
  else
    err = getstr(&config->writeout, nextarg, ALLOW_BLANK);

  return err;
}

static ParameterError parse_time_cond(struct OperationConfig *config,
                                      const char *nextarg)
{
  ParameterError err = PARAM_OK;

  switch(*nextarg) {
  case '+':
    nextarg++;
    FALLTHROUGH();
  default:
    /* If-Modified-Since: (section 14.28 in RFC2068) */
    config->timecond = CURL_TIMECOND_IFMODSINCE;
    break;
  case '-':
    /* If-Unmodified-Since: (section 14.24 in RFC2068) */
    config->timecond = CURL_TIMECOND_IFUNMODSINCE;
    nextarg++;
    break;
  case '=':
    /* Last-Modified: (section 14.29 in RFC2068) */
    config->timecond = CURL_TIMECOND_LASTMOD;
    nextarg++;
    break;
  }
  config->condtime = (curl_off_t)curl_getdate(nextarg, NULL);
  if(config->condtime == -1) {
    curl_off_t value;
    /* now let's see if it is a filename to get the time from instead! */
    int rc = getfiletime(nextarg, &value);
    if(!rc)
      /* pull the time out from the file */
      config->condtime = value;
    else {
      /* failed, remove time condition */
      config->timecond = CURL_TIMECOND_NONE;
      warnf("Illegal date format for -z, --time-cond (and not "
            "a filename). Disabling time condition. "
            "See curl_getdate(3) for valid date syntax.");
    }
  }
  return err;
}

struct flagmap {
  const char *name;
  size_t len;
  unsigned char flag;
};

static const struct flagmap flag_table[] = {
  { "answered", 8, CURLULFLAG_ANSWERED },
  { "deleted",  7, CURLULFLAG_DELETED },
  { "draft",    5, CURLULFLAG_DRAFT },
  { "flagged",  7, CURLULFLAG_FLAGGED },
  { "seen",     4, CURLULFLAG_SEEN },
  { NULL,       0, 0 }
};

static ParameterError parse_upload_flags(struct OperationConfig *config,
                                         const char *flag)
{
  ParameterError err = PARAM_OK;

  while(flag) {
    bool negate;
    const struct flagmap *map;
    size_t len;
    const char *next = strchr(flag, ','); /* Find next comma or end */
    if(next)
      len = next - flag;
    else
      len = strlen(flag);

    negate = (*flag == '-');
    if(negate) {
      flag++;
      len--;
    }

    for(map = flag_table; map->name; map++) {
      if((len == map->len) && !strncmp(flag, map->name, map->len)) {
        if(negate)
          config->upload_flags &= (unsigned char)~map->flag;
        else
          config->upload_flags |= map->flag;
        break;
      }
    }

    if(!map->name) {
      err = PARAM_OPTION_UNKNOWN;
      break;
    }

    if(next)
      /* move over the comma */
      next++;
    flag = next;
  }

  return err;
}

/* if 'toggle' is TRUE, set the 'bits' in 'modify'.
   if 'toggle' is FALSE, clear the 'bits' in 'modify' */
static void togglebit(bool toggle, unsigned long *modify, unsigned long bits)
{
  if(toggle)
    *modify |= bits;
  else
    *modify &= ~bits;
}

/* opt_depr is the function that handles ARG_DEPR options */
static void opt_depr(const struct LongShort *a)
{
  warnf("--%s is deprecated and has no function anymore", a->lname);
}

static ParameterError opt_sslver(struct OperationConfig *config,
                                 unsigned char ver)
{
  if(config->ssl_version_max &&
     (config->ssl_version_max < ver)) {
    errorf("Minimum TLS version set higher than max");
    return PARAM_BAD_USE;
  }
  config->ssl_version = ver;
  return PARAM_OK;
}

/* opt_none is the function that handles ARG_NONE options */
static ParameterError opt_none(struct OperationConfig *config,
                               const struct LongShort *a)
{
  ParameterError err = PARAM_OK;
  switch(a->cmd) {
  case C_ANYAUTH: /* --anyauth */
    config->authtype = CURLAUTH_ANY;
    break;
  case C_DUMP_CA_EMBED: /* --dump-ca-embed */
    return PARAM_CA_EMBED_REQUESTED;
  case C_FTP_PASV: /* --ftp-pasv */
    curlx_safefree(config->ftpport);
    break;

  case C_HTTP1_0: /* --http1.0 */
    /* HTTP version 1.0 */
    sethttpver(config, CURL_HTTP_VERSION_1_0);
    break;
  case C_HTTP1_1: /* --http1.1 */
    /* HTTP version 1.1 */
    sethttpver(config, CURL_HTTP_VERSION_1_1);
    break;
  case C_HTTP2: /* --http2 */
    /* HTTP version 2.0 */
    if(!feature_http2)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    sethttpver(config, CURL_HTTP_VERSION_2_0);
    break;
  case C_HTTP2_PRIOR_KNOWLEDGE: /* --http2-prior-knowledge */
    /* HTTP version 2.0 over clean TCP */
    if(!feature_http2)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    sethttpver(config, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
    break;
  case C_HTTP3: /* --http3: */
    /* Try HTTP/3, allow fallback */
    if(!feature_http3)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      sethttpver(config, CURL_HTTP_VERSION_3);
    break;
  case C_HTTP3_ONLY: /* --http3-only */
    /* Try HTTP/3 without fallback */
    if(!feature_http3)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      sethttpver(config, CURL_HTTP_VERSION_3ONLY);
    break;
  case C_TLSV1: /* --tlsv1 */
    err = opt_sslver(config, 1);
    break;
  case C_TLSV1_0: /* --tlsv1.0 */
    err = opt_sslver(config, 1);
    break;
  case C_TLSV1_1: /* --tlsv1.1 */
    err = opt_sslver(config, 2);
    break;
  case C_TLSV1_2: /* --tlsv1.2 */
    err = opt_sslver(config, 3);
    break;
  case C_TLSV1_3: /* --tlsv1.3 */
    err = opt_sslver(config, 4);
    break;
  case C_IPV4: /* --ipv4 */
    config->ip_version = CURL_IPRESOLVE_V4;
    break;
  case C_IPV6: /* --ipv6 */
    config->ip_version = CURL_IPRESOLVE_V6;
    break;
  case C_NEXT: /* --next */
    return PARAM_NEXT_OPERATION;
  case C_PROXY_TLSV1: /* --proxy-tlsv1 */
    /* TLS version 1 for proxy */
    config->proxy_ssl_version = CURL_SSLVERSION_TLSv1;
    break;
  }
  return err;
}

/* opt_bool is the function that handles boolean options */
static ParameterError opt_bool(struct OperationConfig *config,
                               const struct LongShort *a,
                               bool toggle)
{
  switch(a->cmd) {
  case C_ALPN: /* --alpn */
    config->noalpn = !toggle;
    break;
  case C_DISABLE_EPSV: /* --disable-epsv */
    config->disable_epsv = toggle;
    break;
  case C_DISALLOW_USERNAME_IN_URL: /* --disallow-username-in-url */
    config->disallow_username_in_url = toggle;
    break;
  case C_EPSV: /* --epsv */
    config->disable_epsv = !toggle;
    break;
  case C_COMPRESSED: /* --compressed */
    if(toggle && !(feature_libz || feature_brotli || feature_zstd))
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      config->encoding = toggle;
    break;
  case C_TR_ENCODING: /* --tr-encoding */
    config->tr_encoding = toggle;
    break;
  case C_DIGEST: /* --digest */
    togglebit(toggle, &config->authtype, CURLAUTH_DIGEST);
    break;
  case C_FTP_CREATE_DIRS: /* --ftp-create-dirs */
    config->ftp_create_dirs = toggle;
    break;
  case C_CREATE_DIRS: /* --create-dirs */
    config->create_dirs = toggle;
    break;
  case C_PROXY_NTLM: /* --proxy-ntlm */
    if(!feature_ntlm)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      config->proxyntlm = toggle;
    break;
  case C_CRLF: /* --crlf */
    config->crlf = toggle;
    break;
  case C_HAPROXY_PROTOCOL: /* --haproxy-protocol */
    config->haproxy_protocol = toggle;
    break;
  case C_NEGOTIATE: /* --negotiate */
    if(!feature_spnego && toggle)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    togglebit(toggle, &config->authtype, CURLAUTH_NEGOTIATE);
    break;
  case C_NTLM: /* --ntlm */
    if(!feature_ntlm && toggle)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    togglebit(toggle, &config->authtype, CURLAUTH_NTLM);
    break;
  case C_OUT_NULL: /* --out-null */
    return parse_output(config, NULL);
  case C_BASIC: /* --basic */
    togglebit(toggle, &config->authtype, CURLAUTH_BASIC);
    break;
#ifdef USE_WATT32
  case C_WDEBUG: /* --wdebug */
    dbug_init();
    break;
#endif
  case C_DISABLE_EPRT: /* --disable-eprt */
    config->disable_eprt = toggle;
    break;
  case C_EPRT: /* --eprt */
    config->disable_eprt = !toggle;
    break;
  case C_XATTR: /* --xattr */
    config->xattr = toggle;
    break;
  case C_FTP_SSL: /* --ftp-ssl */
  case C_SSL: /* --ssl */
    config->ftp_ssl = toggle;
    if(config->ftp_ssl)
      warnf("--%s is an insecure option, consider --ssl-reqd instead",
            a->lname);
    if(toggle && config->ftp_ssl_control) {
      config->ftp_ssl_control = FALSE;
      warnf("--%s overrides --ftp-ssl-control", a->lname);
    }
    break;
  case C_FTP_SSL_CCC: /* --ftp-ssl-ccc */
    config->ftp_ssl_ccc = toggle;
    if(!config->ftp_ssl_ccc_mode)
      config->ftp_ssl_ccc_mode = CURLFTPSSL_CCC_PASSIVE;
    break;
  case C_TCP_NODELAY: /* --tcp-nodelay */
    config->tcp_nodelay = toggle;
    break;
  case C_PROXY_DIGEST: /* --proxy-digest */
    config->proxydigest = toggle;
    break;
  case C_PROXY_BASIC: /* --proxy-basic */
    config->proxybasic = toggle;
    break;
  case C_RETRY_CONNREFUSED: /* --retry-connrefused */
    config->retry_connrefused = toggle;
    break;
  case C_RETRY_ALL_ERRORS: /* --retry-all-errors */
    config->retry_all_errors = toggle;
    break;
  case C_PROXY_NEGOTIATE: /* --proxy-negotiate */
    if(!feature_spnego)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      config->proxynegotiate = toggle;
    break;
  case C_FORM_ESCAPE: /* --form-escape */
    togglebit(toggle, &config->mime_options, CURLMIMEOPT_FORMESCAPE);
    break;
  case C_PROXY_ANYAUTH: /* --proxy-anyauth */
    config->proxyanyauth = toggle;
    break;
  case C_TRACE_TIME: /* --trace-time */
    global->tracetime = toggle;
    break;
  case C_IGNORE_CONTENT_LENGTH: /* --ignore-content-length */
    config->ignorecl = toggle;
    break;
  case C_FTP_SKIP_PASV_IP: /* --ftp-skip-pasv-ip */
    config->ftp_skip_ip = toggle;
    break;
  case C_FTP_SSL_REQD: /* --ftp-ssl-reqd */
  case C_SSL_REQD: /* --ssl-reqd */
    config->ftp_ssl_reqd = toggle;
    break;
  case C_SESSIONID: /* --sessionid */
    config->disable_sessionid = !toggle;
    break;
  case C_FTP_SSL_CONTROL: /* --ftp-ssl-control */
    config->ftp_ssl_control = toggle;
    if(toggle && config->ftp_ssl) {
      config->ftp_ssl = FALSE;
      warnf("--%s overrides --ssl", a->lname);
    }
    break;
  case C_RAW: /* --raw */
    config->raw = toggle;
    break;
  case C_KEEPALIVE: /* --keepalive */
    config->nokeepalive = !toggle;
    break;
  case C_POST301: /* --post301 */
    config->post301 = toggle;
    break;
  case C_POST302: /* --post302 */
    config->post302 = toggle;
    break;
  case C_POST303: /* --post303 */
    config->post303 = toggle;
    break;
  case C_SOCKS5_GSSAPI_NEC: /* --socks5-gssapi-nec */
    config->socks5_gssapi_nec = toggle;
    break;
  case C_FTP_PRET: /* --ftp-pret */
    config->ftp_pret = toggle;
    break;
  case C_SASL_IR: /* --sasl-ir */
    config->sasl_ir = toggle;
    break;
#ifdef DEBUGBUILD
  case C_TEST_DUPHANDLE: /* --test-duphandle */
    global->test_duphandle = toggle;
    break;
  case C_TEST_EVENT: /* --test-event */
    global->test_event_based = toggle;
    break;
#endif
  case C_PATH_AS_IS: /* --path-as-is */
    config->path_as_is = toggle;
    break;
  case C_TFTP_NO_OPTIONS: /* --tftp-no-options */
    config->tftp_no_options = toggle;
    break;
  case C_TLS_EARLYDATA: /* --tls-earlydata */
    config->ssl_allow_earlydata = toggle;
    break;
  case C_SUPPRESS_CONNECT_HEADERS: /* --suppress-connect-headers */
    config->suppress_connect_headers = toggle;
    break;
  case C_COMPRESSED_SSH: /* --compressed-ssh */
    config->ssh_compression = toggle;
    break;
  case C_TRACE_IDS: /* --trace-ids */
    global->traceids = toggle;
    break;
  case C_PROGRESS_METER: /* --progress-meter */
    global->noprogress = !toggle;
    break;
  case C_PROGRESS_BAR: /* --progress-bar */
    global->progressmode = toggle ? CURL_PROGRESS_BAR : CURL_PROGRESS_STATS;
    break;
  case C_HTTP0_9: /* --http0.9 */
    config->http09_allowed = toggle;
    break;
  case C_PROXY_HTTP2: /* --proxy-http2 */
    if(!feature_httpsproxy || !feature_http2)
      return PARAM_LIBCURL_DOESNT_SUPPORT;

    config->proxyver = toggle ? CURLPROXY_HTTPS2 : CURLPROXY_HTTPS;
    break;
  case C_PROXY_HTTP3: /* --proxy-http3 */
#ifndef USE_PROXY_HTTP3
    if(toggle)
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    config->proxyver = CURLPROXY_HTTPS;
#else
    if(!feature_httpsproxy || !feature_http3)
      return PARAM_LIBCURL_DOESNT_SUPPORT;

    config->proxyver = toggle ? CURLPROXY_HTTPS3 : CURLPROXY_HTTPS;
#endif
    break;
  case C_APPEND: /* --append */
    config->ftp_append = toggle;
    break;
  case C_USE_ASCII: /* --use-ascii */
    config->use_ascii = toggle;
    break;
  case C_CA_NATIVE: /* --ca-native */
    config->native_ca_store = toggle;
    break;
  case C_PROXY_CA_NATIVE: /* --proxy-ca-native */
    config->proxy_native_ca_store = toggle;
    break;
  case C_SSL_ALLOW_BEAST: /* --ssl-allow-beast */
    config->ssl_allow_beast = toggle;
    break;
  case C_SSL_AUTO_CLIENT_CERT: /* --ssl-auto-client-cert */
    config->ssl_auto_client_cert = toggle;
    break;
  case C_PROXY_SSL_AUTO_CLIENT_CERT: /* --proxy-ssl-auto-client-cert */
    config->proxy_ssl_auto_client_cert = toggle;
    break;
  case C_CERT_STATUS: /* --cert-status */
    config->verifystatus = toggle;
    break;
  case C_DOH_CERT_STATUS: /* --doh-cert-status */
    config->doh_verifystatus = toggle;
    break;
  case C_FALSE_START: /* --false-start */
    opt_depr(a);
    break;
  case C_SSL_NO_REVOKE: /* --ssl-no-revoke */
    config->ssl_no_revoke = toggle;
    break;
  case C_SSL_REVOKE_BEST_EFFORT: /* --ssl-revoke-best-effort */
    config->ssl_revoke_best_effort = toggle;
    break;
  case C_TCP_FASTOPEN: /* --tcp-fastopen */
    config->tcp_fastopen = toggle;
    break;
  case C_PROXY_SSL_ALLOW_BEAST: /* --proxy-ssl-allow-beast */
    config->proxy_ssl_allow_beast = toggle;
    break;
  case C_PROXY_INSECURE: /* --proxy-insecure */
    config->proxy_insecure_ok = toggle;
    break;
  case C_SOCKS5_BASIC: /* --socks5-basic */
    togglebit(toggle, &config->socks5_auth, CURLAUTH_BASIC);
    break;
  case C_SOCKS5_GSSAPI: /* --socks5-gssapi */
    togglebit(toggle, &config->socks5_auth, CURLAUTH_GSSAPI);
    break;
  case C_FAIL_EARLY: /* --fail-early */
    global->fail_early = toggle;
    break;
  case C_STYLED_OUTPUT: /* --styled-output */
    global->styled_output = toggle;
    break;
  case C_MAIL_RCPT_ALLOWFAILS: /* --mail-rcpt-allowfails */
    config->mail_rcpt_allowfails = toggle;
    break;
  case C_REMOVE_ON_ERROR: /* --remove-on-error */
    if(config->use_resume && toggle) {
      errorf("--continue-at is mutually exclusive with --remove-on-error");
      return PARAM_BAD_USE;
    }
    config->rm_partial = toggle;
    break;
  case C_FAIL: /* --fail without body */
    if(toggle && (config->fail == FAIL_WITH_BODY))
      warnf("--fail deselects --fail-with-body here");
    config->fail = toggle ? FAIL_WO_BODY : FAIL_NONE;
    break;
  case C_FAIL_WITH_BODY: /* --fail-with-body */
    if(toggle && (config->fail == FAIL_WO_BODY))
      warnf("--fail-with-body deselects --fail here");
    config->fail = toggle ? FAIL_WITH_BODY : FAIL_NONE;
    break;
  case C_GLOBOFF: /* --globoff */
    config->globoff = toggle;
    break;
  case C_GET: /* --get */
    config->use_httpget = toggle;
    break;
  case C_INCLUDE: /* --include */
  case C_SHOW_HEADERS: /* --show-headers */
    config->show_headers = toggle;
    break;
  case C_JUNK_SESSION_COOKIES: /* --junk-session-cookies */
    config->cookiesession = toggle;
    break;
  case C_HEAD: /* --head */
    config->no_body = toggle;
    config->show_headers = toggle;
    if(SetHTTPrequest(config->no_body ? TOOL_HTTPREQ_HEAD :
                      TOOL_HTTPREQ_GET, &config->httpreq))
      return PARAM_BAD_USE;
    break;
  case C_REMOTE_HEADER_NAME: /* --remote-header-name */
    config->content_disposition = toggle;
    break;
  case C_INSECURE: /* --insecure */
    config->insecure_ok = toggle;
    break;
  case C_DOH_INSECURE: /* --doh-insecure */
    config->doh_insecure_ok = toggle;
    break;
  case C_LIST_ONLY: /* --list-only */
    config->dirlistonly = toggle; /* only list names of the FTP directory */
    break;
  case C_MANUAL: /* --manual */
    if(toggle)   /* --no-manual shows no manual... */
      return PARAM_MANUAL_REQUESTED;
    break;
  case C_NETRC_OPTIONAL: /* --netrc-optional */
    config->netrc_opt = toggle;
    break;
  case C_NETRC: /* --netrc */
    config->netrc = toggle;
    break;
  case C_BUFFER: /* --buffer */
    config->nobuffer = !toggle;
    break;
  case C_REMOTE_NAME_ALL: /* --remote-name-all */
    config->remote_name_all = toggle;
    break;
  case C_CLOBBER: /* --clobber */
    if(config->use_resume && !toggle) {
      errorf("--continue-at is mutually exclusive with --no-clobber");
      return PARAM_BAD_USE;
    }
    config->file_clobber_mode = toggle ? CLOBBER_ALWAYS : CLOBBER_NEVER;
    break;
  case C_REMOTE_NAME: /* --remote-name */
    return parse_remote_name(config, toggle);
  case C_PROXYTUNNEL: /* --proxytunnel */
    config->proxytunnel = toggle;
    break;
  case C_DISABLE: /* --disable */
    /* if used first, already taken care of, we do it like this so we do not
       cause an error! */
    break;
  case C_REMOTE_TIME: /* --remote-time */
    config->remote_time = toggle;
    break;
  case C_SILENT: /* --silent */
    global->silent = toggle;
    break;
  case C_SKIP_EXISTING: /* --skip-existing */
    config->skip_existing = toggle;
    break;
  case C_SHOW_ERROR: /* --show-error */
    global->showerror = toggle;
    break;
  case C_VERBOSE: /* --verbose */
    return parse_verbose(toggle);
  case C_VERSION: /* --version */
    if(toggle)    /* --no-version yields no output! */
      return PARAM_VERSION_INFO_REQUESTED;
    break;
  case C_PARALLEL: /* --parallel */
    global->parallel = toggle;
    break;
  case C_PARALLEL_IMMEDIATE:   /* --parallel-immediate */
    global->parallel_connect = toggle;
    break;
  case C_MPTCP: /* --mptcp */
    config->mptcp = toggle;
    break;
  case C_LOCATION_TRUSTED: /* --location-trusted */
    config->unrestricted_auth = toggle;
    FALLTHROUGH();
  case C_LOCATION: /* --location */
    if(config->followlocation == CURLFOLLOW_OBEYCODE)
      warnf("--location overrides --follow");
    config->followlocation = toggle ? CURLFOLLOW_ALL : 0;
    break;
  case C_FOLLOW: /* --follow */
    if(config->followlocation == CURLFOLLOW_ALL)
      warnf("--follow overrides --location");
    config->followlocation = toggle ? CURLFOLLOW_OBEYCODE : 0;
    break;
  default:
    return PARAM_OPTION_UNKNOWN;
  }
  return PARAM_OK;
}

static ParameterError existingfile(char **store,
                                   const struct LongShort *a,
                                   const char *filename)
{
  curlx_struct_stat info;
  if(curlx_stat(filename, &info)) {
    errorf("The file '%s' provided to --%s does not exist",
           filename, a->lname);
    return PARAM_BAD_USE;
  }
  return getstr(store, filename, DENY_BLANK);
}

/* opt_file handles file options */
static ParameterError opt_file(struct OperationConfig *config,
                               const struct LongShort *a,
                               const char *nextarg,
                               int max_recursive)
{
  ParameterError err = PARAM_OK;
  if((nextarg[0] == '-') && nextarg[1]) {
    /* if the filename looks like a command line option */
    warnf("The filename argument '%s' looks like a flag.", nextarg);
  }
  switch(a->cmd) {
  case C_ABSTRACT_UNIX_SOCKET: /* --abstract-unix-socket */
    config->abstract_unix_socket = TRUE;
    err = getstr(&config->unix_socket_path, nextarg, DENY_BLANK);
    break;
  case C_CACERT: /* --cacert */
    err = existingfile(&config->cacert, a, nextarg);
    break;
  case C_CAPATH: /* --capath */
    err = getstr(&config->capath, nextarg, DENY_BLANK);
    break;
  case C_CERT: /* --cert */
    err = GetFileAndPassword(nextarg, &config->cert, &config->key_passwd);
    break;
  case C_CONFIG: /* --config */
    if(--max_recursive < 0) {
      errorf("Max config file recursion level reached (%d)",
             CONFIG_MAX_LEVELS);
      err = PARAM_BAD_USE;
    }
    else {
      err = parseconfig(nextarg, max_recursive, NULL);
    }
    break;
  case C_CRLFILE: /* --crlfile */
    err = existingfile(&config->crlfile, a, nextarg);
    break;
  case C_DUMP_HEADER: /* --dump-header */
    err = getstr(&config->headerfile, nextarg, DENY_BLANK);
    break;
  case C_ETAG_SAVE: /* --etag-save */
    if(config->num_urls > 1) {
      errorf("The etag options only work on a single URL");
      err = PARAM_BAD_USE;
    }
    else
      err = getstr(&config->etag_save_file, nextarg, DENY_BLANK);
    break;
  case C_ETAG_COMPARE: /* --etag-compare */
    if(config->num_urls > 1) {
      errorf("The etag options only work on a single URL");
      err = PARAM_BAD_USE;
    }
    else
      err = getstr(&config->etag_compare_file, nextarg, DENY_BLANK);
    break;
  case C_KEY: /* --key */
    err = getstr(&config->key, nextarg, DENY_BLANK);
    break;
  case C_KNOWNHOSTS: /* --knownhosts */
    err = existingfile(&config->knownhosts, a, nextarg);
    break;
  case C_NETRC_FILE: /* --netrc-file */
    err = existingfile(&config->netrc_file, a, nextarg);
    break;
  case C_OUTPUT: /* --output */
    err = parse_output(config, nextarg);
    break;
  case C_PROXY_CACERT: /* --proxy-cacert */
    err = existingfile(&config->proxy_cacert, a, nextarg);
    break;
  case C_PROXY_CAPATH: /* --proxy-capath */
    err = getstr(&config->proxy_capath, nextarg, DENY_BLANK);
    break;
  case C_PROXY_CERT: /* --proxy-cert */
    err = GetFileAndPassword(nextarg, &config->proxy_cert,
                             &config->proxy_key_passwd);
    break;
  case C_PROXY_CRLFILE: /* --proxy-crlfile */
    err = existingfile(&config->proxy_crlfile, a, nextarg);
    break;
  case C_PROXY_KEY: /* --proxy-key */
    err = getstr(&config->proxy_key, nextarg, ALLOW_BLANK);
    break;
  case C_SSL_SESSIONS: /* --ssl-sessions */
    if(feature_ssls_export)
      err = getstr(&global->ssl_sessions, nextarg, DENY_BLANK);
    else
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    break;
  case C_STDERR: /* --stderr */
    tool_set_stderr_file(nextarg);
    break;
  case C_TRACE: /* --trace */
    err = getstr(&global->trace_dump, nextarg, DENY_BLANK);
    if(!err) {
      if(global->tracetype && (global->tracetype != TRACE_BIN))
        warnf("--trace overrides an earlier trace/verbose option");
      global->tracetype = TRACE_BIN;
    }
    break;
  case C_TRACE_ASCII: /* --trace-ascii */
    err = getstr(&global->trace_dump, nextarg, DENY_BLANK);
    if(!err) {
      if(global->tracetype && (global->tracetype != TRACE_ASCII))
        warnf("--trace-ascii overrides an earlier trace/verbose option");
      global->tracetype = TRACE_ASCII;
    }
    break;
  case C_UNIX_SOCKET: /* --unix-socket */
    config->abstract_unix_socket = FALSE;
    err = getstr(&config->unix_socket_path, nextarg, DENY_BLANK);
    break;
  case C_UPLOAD_FILE: /* --upload-file */
    err = parse_upload_file(config, nextarg);
    break;
  case C_HTTPSIG_KEY: /* --httpsig-key */
    if(!feature_httpsig)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      err = getstr(&config->httpsig_key, nextarg, DENY_BLANK);
    break;
  }
  return err;
}

/* options that accept unsigned numbers */
static ParameterError opt_unum(struct OperationConfig *config,
                               const struct LongShort *a,
                               const char *nextarg)
{
  ParameterError err = PARAM_OK;
  long val;
  err = str2unum(&val, nextarg);
  if(err)
    return err;
  switch(a->cmd) {
  case C_VLAN_PRIORITY: /* --vlan-priority */
    if(val > 7)
      return PARAM_NUMBER_TOO_LARGE;
    config->vlan_priority = val;
    break;
  case C_RETRY: /* --retry */
    config->req_retry = val;
    break;
  case C_KEEPALIVE_TIME: /* --keepalive-time */
    config->alivetime = val;
    break;
  case C_KEEPALIVE_CNT: /* --keepalive-cnt */
    config->alivecnt = val;
    break;
  case C_TFTP_BLKSIZE: /* --tftp-blksize */
    config->tftp_blksize = val;
    break;
  case C_HAPPY_EYEBALLS_TIMEOUT_MS: /* --happy-eyeballs-timeout-ms */
    config->happy_eyeballs_timeout_ms = val;
    /* 0 is a valid value for this timeout */
    break;
  case C_SPEED_TIME: /* --speed-time */
    /* low speed time */
    config->low_speed_time = val;
    if(!config->low_speed_limit)
      config->low_speed_limit = 1;
    break;
  case C_SPEED_LIMIT: /* --speed-limit */
    /* low speed limit */
    config->low_speed_limit = val;
    if(!config->low_speed_time)
      config->low_speed_time = 30;
    break;
  case C_PARALLEL_HOST: /* --parallel-max-host */
    if(val > MAX_PARALLEL_HOST)
      global->parallel_host = MAX_PARALLEL_HOST;
    else if(val < 1)
      global->parallel_host = PARALLEL_HOST_DEFAULT;
    else
      global->parallel_host = (unsigned short)val;
    break;
  case C_PARALLEL_MAX:  /* --parallel-max */
    if(val > MAX_PARALLEL)
      global->parallel_max = MAX_PARALLEL;
    else if(val < 1)
      global->parallel_max = PARALLEL_DEFAULT;
    else
      global->parallel_max = (unsigned short)val;
    break;
  default:
    DEBUGASSERT(0);
    return PARAM_OPTION_UNKNOWN;
  }
  return err;
}

/* handle options that accept seconds */
static ParameterError opt_secs(struct OperationConfig *config,
                               const struct LongShort *a,
                               const char *nextarg)
{
  long val;
  ParameterError err = secs2ms(&val, nextarg);
  if(err)
    return err;
  switch(a->cmd) {
  case C_CONNECT_TIMEOUT: /* --connect-timeout */
    config->connecttimeout_ms = val;
    break;
  case C_RETRY_DELAY: /* --retry-delay */
    if(val >= INT32_MAX)
      val = INT32_MAX;
    config->retry_delay_ms = (uint32_t)val;
    break;
  case C_RETRY_MAX_TIME: /* --retry-max-time */
    config->retry_maxtime_ms = val;
    break;
  case C_EXPECT100_TIMEOUT: /* --expect100-timeout */
    config->expect100timeout_ms = val;
    break;
  case C_MAX_TIME: /* --max-time */
    /* specified max time */
    config->timeout_ms = val;
    break;
  default:
    DEBUGASSERT(0);
    return PARAM_OPTION_UNKNOWN;
  }
  return err;
}

/* opt_string handles string options */
static ParameterError opt_string(struct OperationConfig *config,
                                 const struct LongShort *a,
                                 const char *nextarg)
{
  ParameterError err = PARAM_OK;
  curl_off_t value;
  static const char * const redir_protos[] = {
    "http",
    "https",
    "ftp",
    "ftps",
    NULL
  };
  if(!nextarg)
    nextarg = "";

  switch(a->cmd) {
  case C_DNS_IPV4_ADDR: /* --dns-ipv4-addr */
    if(!curlinfo->ares_num) /* c-ares is needed for this */
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    /* addr in dot notation */
    return getstr(&config->dns_ipv4_addr, nextarg, DENY_BLANK);

  case C_DNS_IPV6_ADDR: /* --dns-ipv6-addr */
    if(!curlinfo->ares_num) /* c-ares is needed for this */
      return PARAM_LIBCURL_DOESNT_SUPPORT;
    /* addr in dot notation */
    return getstr(&config->dns_ipv6_addr, nextarg, DENY_BLANK);

  case C_OAUTH2_BEARER: /* --oauth2-bearer */
    config->authtype |= CURLAUTH_BEARER;
    return getstr(&config->oauth_bearer, nextarg, DENY_BLANK);

  case C_DOH_URL: /* --doh-url */
    err = getstr(&config->doh_url, nextarg, ALLOW_BLANK);
    if(!err && config->doh_url && !config->doh_url[0])
      /* if given a blank string, make it NULL again */
      curlx_safefree(config->doh_url);
    break;

  case C_CIPHERS: /* -- ciphers */
    err = getstr(&config->cipher_list, nextarg, DENY_BLANK);
    break;

  case C_DNS_INTERFACE: /* --dns-interface */
    if(!curlinfo->ares_num) /* c-ares is needed for this */
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      /* interface name */
      err = getstr(&config->dns_interface, nextarg, DENY_BLANK);
    break;
  case C_DNS_SERVERS: /* --dns-servers */
    if(!curlinfo->ares_num) /* c-ares is needed for this */
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      /* IP addrs of DNS servers */
      err = getstr(&config->dns_servers, nextarg, DENY_BLANK);
    break;
  case C_LIMIT_RATE: /* --limit-rate */
    err = GetSizeParameter(nextarg, &value);
    if(!err) {
      config->recvpersecond = value;
      config->sendpersecond = value;
    }
    break;
  case C_RATE:
    err = set_rate(nextarg);
    break;
  case C_CREATE_FILE_MODE: /* --create-file-mode */
    err = oct2nummax(&config->create_file_mode, nextarg, 0777);
    break;
  case C_MAX_REDIRS: /* --max-redirs */
    /* specified max no of redirects (http(s)), this accepts -1 as a
       special condition */
    err = str2num(&config->maxredirs, nextarg);
    if(!err && (config->maxredirs < -1))
      err = PARAM_BAD_NUMERIC;
    break;
#ifndef CURL_DISABLE_IPFS
  case C_IPFS_GATEWAY: /* --ipfs-gateway */
    err = getstr(&config->ipfs_gateway, nextarg, DENY_BLANK);
    break;
#endif
  case C_AWS_SIGV4: /* --aws-sigv4 */
    config->authtype |= CURLAUTH_AWS_SIGV4;
    err = getstr(&config->aws_sigv4, nextarg, ALLOW_BLANK);
    break;
  case C_HTTPSIG_ALGORITHM: /* --httpsig-algo */
    if(!feature_httpsig)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else {
      config->authtype |= CURLAUTH_HTTPSIG;
      err = getstr(&config->httpsig_algorithm, nextarg, DENY_BLANK);
    }
    break;
  case C_HTTPSIG_KEYID: /* --httpsig-keyid */
    if(!feature_httpsig)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      err = getstr(&config->httpsig_keyid, nextarg, DENY_BLANK);
    break;
  case C_HTTPSIG_HEADERS: /* --httpsig-headers */
    if(!feature_httpsig)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      err = getstr(&config->httpsig_headers, nextarg, DENY_BLANK);
    break;
  case C_INTERFACE: /* --interface */
    /* interface */
    err = getstr(&config->iface, nextarg, DENY_BLANK);
    break;
  case C_HAPROXY_CLIENTIP: /* --haproxy-clientip */
    err = getstr(&config->haproxy_clientip, nextarg, DENY_BLANK);
    break;
  case C_MAX_FILESIZE: /* --max-filesize */
    err = GetSizeParameter(nextarg, &value);
    if(!err)
      config->max_filesize = value;
    break;
  case C_URL: /* --url */
    err = parse_url(config, nextarg);
    break;
  case C_SOCKS5: /* --socks5 */
    /*  socks5 proxy to use, and resolves the name locally and passes on the
        resolved address */
    err = getstr(&config->proxy, nextarg, DENY_BLANK);
    config->proxyver = CURLPROXY_SOCKS5;
    break;
  case C_SOCKS4: /* --socks4 */
    err = getstr(&config->proxy, nextarg, DENY_BLANK);
    config->proxyver = CURLPROXY_SOCKS4;
    break;
  case C_SOCKS4A: /* --socks4a */
    err = getstr(&config->proxy, nextarg, DENY_BLANK);
    config->proxyver = CURLPROXY_SOCKS4A;
    break;
  case C_SOCKS5_HOSTNAME: /* --socks5-hostname */
    err = getstr(&config->proxy, nextarg, DENY_BLANK);
    config->proxyver = CURLPROXY_SOCKS5_HOSTNAME;
    break;
  case C_IP_TOS: { /* --ip-tos */
    struct TOSEntry find;
    const struct TOSEntry *entry;
    find.name = nextarg;
    entry = bsearch(&find, tos_entries,
                    CURL_ARRAYSIZE(tos_entries),
                    sizeof(*tos_entries), find_tos);
    if(entry)
      config->ip_tos = entry->value;
    else /* numeric tos value */
      err = str2unummax(&config->ip_tos, nextarg, 0xFF);
    break;
  }
  case C_FTP_ACCOUNT: /* --ftp-account */
    err = getstr(&config->ftp_account, nextarg, DENY_BLANK);
    break;
  case C_FTP_METHOD: /* --ftp-method */
    config->ftp_filemethod = ftpfilemethod(nextarg);
    break;
  case C_LOCAL_PORT: /* --local-port */
    err = parse_localport(config, nextarg);
    break;
  case C_FTP_ALTERNATIVE_TO_USER: /* --ftp-alternative-to-user */
    err = getstr(&config->ftp_alternative_to_user, nextarg, DENY_BLANK);
    break;
  case C_LIBCURL: /* --libcurl */
#ifdef CURL_DISABLE_LIBCURL_OPTION
    warnf("--libcurl option was disabled at build-time");
    err = PARAM_OPTION_UNKNOWN;
#else
    err = getstr(&global->libcurl, nextarg, DENY_BLANK);
#endif
    break;
  case C_NOPROXY: /* --noproxy */
    /* This specifies the noproxy list */
    err = getstr(&config->noproxy, nextarg, ALLOW_BLANK);
    break;
  case C_PROXY1_0: /* --proxy1.0 */
    /* http 1.0 proxy */
    err = getstr(&config->proxy, nextarg, DENY_BLANK);
    config->proxyver = CURLPROXY_HTTP_1_0;
    break;
  case C_MAIL_FROM: /* --mail-from */
    err = getstr(&config->mail_from, nextarg, DENY_BLANK);
    break;
  case C_MAIL_RCPT: /* --mail-rcpt */
    /* append receiver to a list */
    err = add2list(&config->mail_rcpt, nextarg);
    break;
  case C_PROTO: /* --proto */
    config->proto_present = TRUE;
    err = proto2num(built_in_protos, &config->proto_str, nextarg);
    break;
  case C_PROTO_REDIR: /* --proto-redir */
    config->proto_redir_present = TRUE;
    if(proto2num(redir_protos, &config->proto_redir_str, nextarg))
      err = PARAM_BAD_USE;
    break;
  case C_RESOLVE: /* --resolve */
    err = add2list(&config->resolve, nextarg);
    break;
  case C_DELEGATION: /* --delegation */
    config->gssapi_delegation = delegation(nextarg);
    break;
  case C_MAIL_AUTH: /* --mail-auth */
    err = getstr(&config->mail_auth, nextarg, DENY_BLANK);
    break;
  case C_SASL_AUTHZID: /* --sasl-authzid */
    err = getstr(&config->sasl_authzid, nextarg, DENY_BLANK);
    break;
  case C_PROXY_SERVICE_NAME: /* --proxy-service-name */
    err = getstr(&config->proxy_service_name, nextarg, DENY_BLANK);
    break;
  case C_SERVICE_NAME: /* --service-name */
    err = getstr(&config->service_name, nextarg, DENY_BLANK);
    break;
  case C_PROTO_DEFAULT: /* --proto-default */
    err = getstr(&config->proto_default, nextarg, DENY_BLANK);
    if(!err)
      err = check_protocol(config->proto_default);
    break;
  case C_CONNECT_TO: /* --connect-to */
    err = add2list(&config->connect_to, nextarg);
    break;
  case C_TLS_MAX: /* --tls-max */
    err = str2tls_max(&config->ssl_version_max, nextarg);
    if(!err && (config->ssl_version_max < config->ssl_version)) {
      errorf("--tls-max set lower than minimum accepted version");
      err = PARAM_BAD_USE;
    }
    break;
  case C_TRACE_CONFIG: /* --trace-config */
    global->trace_set = TRUE;
    if(set_trace_config(nextarg))
      err = PARAM_NO_MEM;
    break;
  case C_VARIABLE: /* --variable */
    err = setvariable(nextarg);
    break;
  case C_TLS13_CIPHERS: /* --tls13-ciphers */
    err = getstr(&config->cipher13_list, nextarg, DENY_BLANK);
    break;
  case C_PROXY_TLS13_CIPHERS: /* --proxy-tls13-ciphers */
    err = getstr(&config->proxy_cipher13_list, nextarg, DENY_BLANK);
    break;
  case C_USER_AGENT: /* --user-agent */
    err = getstr(&config->useragent, nextarg, ALLOW_BLANK);
    break;
  case C_ALT_SVC: /* --alt-svc */
    if(!feature_altsvc)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      err = getstr(&config->altsvc, nextarg, ALLOW_BLANK);
    break;
  case C_HSTS: /* --hsts */
    if(!feature_hsts)
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
    else
      err = getstr(&config->hsts, nextarg, ALLOW_BLANK);
    break;
  case C_COOKIE: /* --cookie */
    if(strchr(nextarg, '=')) {
      /* A cookie string must have a =-letter */
      err = add2list(&config->cookies, nextarg);
      break;
    }
    else {
      /* We have a cookie file to read from! */
      err = add2list(&config->cookiefiles, nextarg);
    }
    break;
  case C_COOKIE_JAR: /* --cookie-jar */
    err = getstr(&config->cookiejar, nextarg, DENY_BLANK);
    break;
  case C_CONTINUE_AT: /* --continue-at */
    err = parse_continue_at(config, nextarg);
    break;
  case C_DATA: /* --data */
  case C_DATA_ASCII:  /* --data-ascii */
  case C_DATA_BINARY:  /* --data-binary */
  case C_DATA_URLENCODE:  /* --data-urlencode */
  case C_JSON:  /* --json */
  case C_DATA_RAW:  /* --data-raw */
    err = set_data((cmdline_t)a->cmd, nextarg, config);
    break;
  case C_URL_QUERY:  /* --url-query */
    err = url_query(nextarg, config);
    break;
  case C_REFERER: { /* --referer */
    size_t len = strlen(nextarg);
    /* does it end with ;auto ? */
    if(len >= 5 && !strcmp(";auto", &nextarg[len - 5])) {
      /* Automatic referer requested, this may be combined with a set initial
         one */
      config->autoreferer = TRUE;
      len -= 5;
    }
    else
      config->autoreferer = FALSE;

    if(len)
      err = getstrn(&config->referer, nextarg, len, ALLOW_BLANK);
    else
      curlx_safefree(config->referer);
  }
    break;
  case C_CERT_TYPE: /* --cert-type */
    err = getstr(&config->cert_type, nextarg, DENY_BLANK);
    break;
  case C_KEY_TYPE: /* --key-type */
    err = getstr(&config->key_type, nextarg, DENY_BLANK);
    break;
  case C_PASS: /* --pass */
    err = getstr(&config->key_passwd, nextarg, DENY_BLANK);
    break;
  case C_ENGINE: /* --engine */
    err = getstr(&config->engine, nextarg, DENY_BLANK);
    if(!err &&
       config->engine && !strcmp(config->engine, "list")) {
      err = PARAM_ENGINES_REQUESTED;
    }
    break;
  case C_ECH: /* --ech */
    err = parse_ech(config, nextarg);
    break;
  case C_PUBKEY: /* --pubkey */
    err = getstr(&config->pubkey, nextarg, DENY_BLANK);
    break;
  case C_HOSTPUBMD5: /* --hostpubmd5 */
    err = getstr(&config->hostpubmd5, nextarg, DENY_BLANK);
    if(!err) {
      if(!config->hostpubmd5 || strlen(config->hostpubmd5) != 32)
        err = PARAM_BAD_USE;
    }
    break;
  case C_HOSTPUBSHA256: /* --hostpubsha256 */
    err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK);
    break;
  case C_PINNEDPUBKEY: /* --pinnedpubkey */
    err = getstr(&config->pinnedpubkey, nextarg, DENY_BLANK);
    break;
  case C_PROXY_PINNEDPUBKEY: /* --proxy-pinnedpubkey */
    err = getstr(&config->proxy_pinnedpubkey, nextarg, DENY_BLANK);
    break;
  case C_PROXY_CERT_TYPE: /* --proxy-cert-type */
    err = getstr(&config->proxy_cert_type, nextarg, DENY_BLANK);
    break;
  case C_PROXY_KEY_TYPE: /* --proxy-key-type */
    err = getstr(&config->proxy_key_type, nextarg, DENY_BLANK);
    break;
  case C_PROXY_PASS: /* --proxy-pass */
    err = getstr(&config->proxy_key_passwd, nextarg, ALLOW_BLANK);
    break;
  case C_PROXY_CIPHERS: /* --proxy-ciphers */
    err = getstr(&config->proxy_cipher_list, nextarg, DENY_BLANK);
    break;
  case C_LOGIN_OPTIONS: /* --login-options */
    err = getstr(&config->login_options, nextarg, ALLOW_BLANK);
    break;
  case C_CURVES: /* --curves */
    err = getstr(&config->ssl_ec_curves, nextarg, DENY_BLANK);
    break;
  case C_SIGNATURE_ALGORITHMS: /* --sigalgs */
    err = getstr(&config->ssl_signature_algorithms, nextarg, DENY_BLANK);
    break;
  case C_FORM: /* --form */
  case C_FORM_STRING: /* --form-string */
    /* "form data" simulation, this is a little advanced so lets do our best
       to sort this out slowly and carefully */
    if(formparse(nextarg, &config->mimeroot, &config->mimecurrent,
                 (a->cmd == C_FORM_STRING))) /* literal string */
      err = PARAM_BAD_USE;
    else if(SetHTTPrequest(TOOL_HTTPREQ_MIMEPOST, &config->httpreq))
      err = PARAM_BAD_USE;
    break;
  case C_REQUEST_TARGET: /* --request-target */
    err = getstr(&config->request_target, nextarg, DENY_BLANK);
    break;
  case C_HEADER: /* --header */
  case C_PROXY_HEADER: /* --proxy-header */
    err = parse_header(config, (cmdline_t)a->cmd, nextarg);
    break;
  case C_OUTPUT_DIR: /* --output-dir */
    err = getstr(&config->output_dir, nextarg, DENY_BLANK);
    break;
  case C_FTP_PORT: /* --ftp-port */
    /* This makes the FTP sessions use PORT instead of PASV */
    /* use <eth0> or <192.168.10.10> style addresses. Anything except
       this makes us try to get the "default" address.
       NOTE: this is a changed behavior since the released 4.1!
     */
    err = getstr(&config->ftpport, nextarg, DENY_BLANK);
    break;
  case C_FTP_SSL_CCC_MODE: /* --ftp-ssl-ccc-mode */
    config->ftp_ssl_ccc = TRUE;
    config->ftp_ssl_ccc_mode = ftpcccmethod(nextarg);
    break;
  case C_QUOTE: /* --quote */
    err = parse_quote(config, nextarg);
    break;
  case C_RANGE: /* --range */
    err = parse_range(config, nextarg);
    break;
  case C_TELNET_OPTION: /* --telnet-option */
    /* Telnet options */
    err = add2list(&config->telnet_options, nextarg);
    break;
  case C_USER: /* --user */
    /* user:password */
    err = getstr(&config->userpwd, nextarg, ALLOW_BLANK);
    break;
  case C_PROXY_USER: /* --proxy-user */
    /* Proxy user:password */
    err = getstr(&config->proxyuserpwd, nextarg, ALLOW_BLANK);
    break;
  case C_WRITE_OUT: /* --write-out */
    err = parse_writeout(config, nextarg);
    break;
  case C_PREPROXY: /* --preproxy */
    err = getstr(&config->preproxy, nextarg, DENY_BLANK);
    break;
  case C_PROXY: /* --proxy */
    /* --proxy */
    err = getstr(&config->proxy, nextarg, ALLOW_BLANK);
    if(config->proxyver != CURLPROXY_HTTPS2 &&
                config->proxyver != CURLPROXY_HTTPS3)
      config->proxyver = CURLPROXY_HTTP;
    break;
  case C_REQUEST: /* --request */
    /* set custom request */
    err = getstr(&config->customrequest, nextarg, DENY_BLANK);
    break;
  case C_TIME_COND: /* --time-cond */
    err = parse_time_cond(config, nextarg);
    break;
  case C_UPLOAD_FLAGS: /* --upload-flags */
    err = parse_upload_flags(config, nextarg);
    break;
  }
  return err;
}

/* detect e2 80 80 - e2 80 ff */
static bool has_leading_unicode(const unsigned char *arg)
{
  return (arg[0] == 0xe2) && (arg[1] == 0x80) && (arg[2] & 0x80);
}

/* the longest command line option, excluding the leading -- */
#define MAX_OPTION_LEN 26

ParameterError getparameter(const char *flag, /* f or -long-flag */
                            const char *nextarg,    /* NULL if unset */
                            bool *usedarg,    /* set to TRUE if the arg
                                                 has been used */
                            struct OperationConfig *config,
                            int max_recursive)
{
  const char *parse = NULL;
  bool longopt = FALSE;
  bool singleopt = FALSE; /* when true means '-o foo' used '-ofoo' */
  ParameterError err = PARAM_OK;
  bool toggle = TRUE; /* how to switch boolean options, on or off. Controlled
                         by using --OPTION or --no-OPTION */
  bool nextalloc = FALSE; /* if nextarg is allocated */
  bool consumearg = TRUE; /* the argument comes separate */
  const struct LongShort *a = NULL;
  verbose_nopts = 0; /* options processed in `flag`*/

  *usedarg = FALSE; /* default is that we do not use the arg */

  if(('-' != flag[0]) || ('-' == flag[1])) {
    /* this should be a long name */
    const char *word = ('-' == flag[0]) ? flag + 2 : flag;
    bool noflagged = FALSE;
    bool expand = FALSE;
    const char *p;
    struct Curl_str out;

    if(!strncmp(word, "no-", 3)) {
      /* disable this option but ignore the "no-" part when looking for it */
      word += 3;
      toggle = FALSE;
      noflagged = TRUE;
    }
    else if(!strncmp(word, "expand-", 7)) {
      /* variable expansions is to be done on the argument */
      word += 7;
      expand = TRUE;
    }

    p = word;
    /* is there an '=' ? */
    if(!curlx_str_until(&p, &out, MAX_OPTION_LEN, '=') &&
       !curlx_str_single(&p, '=')) {
      /* there is an equal sign */
      char tempword[MAX_OPTION_LEN + 1];
      memcpy(tempword, curlx_str(&out), curlx_strlen(&out));
      tempword[curlx_strlen(&out)] = 0;
      a = findlongopt(tempword);
      nextarg = p;
      consumearg = FALSE; /* it is not separate */
    }
    else
      a = findlongopt(word);

    if(a) {
      longopt = TRUE;
    }
    else {
      err = PARAM_OPTION_UNKNOWN;
      goto error;
    }
    if(noflagged && (ARGTYPE(a->desc) != ARG_BOOL)) {
      /* --no- prefixed an option that is not boolean! */
      err = PARAM_NO_PREFIX;
      goto error;
    }
    else if(expand && nextarg) {
      struct dynbuf nbuf;
      bool replaced;

      if(ARGTYPE(a->desc) < ARG_STRG) {
        /* --expand on an option that does not take an argument */
        err = PARAM_EXPAND_ERROR;
        goto error;
      }
      err = varexpand(nextarg, &nbuf, &replaced);
      if(err) {
        curlx_dyn_free(&nbuf);
        goto error;
      }
      if(replaced) {
        nextarg = curlx_dyn_ptr(&nbuf);
        nextalloc = TRUE;
      }
    }
  }
  else {
    flag++; /* prefixed with one dash, pass it */
    parse = flag;
  }

  do {
    /* we can loop here if we have multiple single-letters */
    if(!longopt) {
      a = findshortopt(*parse);
      if(!a) {
        err = PARAM_OPTION_UNKNOWN;
        break;
      }
      toggle = !(a->desc & ARG_NO);
    }
    if((a->desc & ARG_TLS) && !feature_ssl) {
      err = PARAM_LIBCURL_DOESNT_SUPPORT;
      break;
    }
    else if(ARGTYPE(a->desc) >= ARG_STRG) {
      /* this option requires an extra parameter */
      if(!longopt && parse[1]) {
        nextarg = &parse[1]; /* this is the actual extra parameter */
        singleopt = TRUE;   /* do not loop anymore after this */
      }
      else if(a->cmd == C_HELP) {
        /* --help is special */
        tool_help((nextarg && *nextarg) ? nextarg : NULL);
        err = PARAM_HELP_REQUESTED;
        break;
      }
      else if(!nextarg) {
        err = PARAM_REQUIRES_PARAMETER;
        break;
      }
      else {
        *usedarg = consumearg; /* mark it as used */
      }
      if(a->desc & ARG_DEPR) {
        if(a->desc & ARG_CLEAR)
          cleanarg(CURL_UNCONST(nextarg));
        opt_depr(a);
        break;
      }

      if(has_leading_unicode((const unsigned char *)nextarg)) {
        warnf("The argument '%s' starts with a Unicode character. "
              "Maybe ASCII was intended?", nextarg);
      }
      switch(ARGTYPE(a->desc)) {
      case ARG_FILE:
        err = opt_file(config, a, nextarg, max_recursive);
        break;
      case ARG_STRG:
        err = opt_string(config, a, nextarg);
        break;
      case ARG_SECS:
        err = opt_secs(config, a, nextarg);
        break;
      case ARG_UNUM:
        err = opt_unum(config, a, nextarg);
        break;
      default:
        DEBUGASSERT(0);
        err = PARAM_OPTION_UNKNOWN;
        break;
      }
      if(a->desc & ARG_CLEAR)
        cleanarg(CURL_UNCONST(nextarg));
    }
    else {
      if(a->desc & ARG_DEPR) {
        opt_depr(a);
        break;
      }
      /* ARG_NONE | ARG_BOOL */
      if(ARGTYPE(a->desc) == ARG_BOOL)
        err = opt_bool(config, a, toggle);
      else
        err = opt_none(config, a);
    }

    ++verbose_nopts; /* processed one option from `flag` input, loop for
                        more */
  } while(!longopt && !singleopt && *++parse && !*usedarg && !err);

error:
  if(nextalloc)
    curlx_free(CURL_UNCONST(nextarg));
  return err;
}

ParameterError parse_args(int argc, argv_item_t argv[])
{
  int i;
  bool stillflags;
  const char *orig_opt = NULL;
  ParameterError err = PARAM_OK;
  struct OperationConfig *config = global->first;

  stillflags = TRUE;
  for(i = 1; i < argc && !err; i++) {
    orig_opt = convert_tchar_to_UTF8(argv[i]);
    if(!orig_opt)
      return PARAM_NO_MEM;

    if(stillflags && ('-' == orig_opt[0])) {
      bool passarg;

      if(!strcmp("--", orig_opt))
        /* This indicates the end of the flags and thus enables the
           following (URL) argument to start with -. */
        stillflags = FALSE;
      else {
        const char *nextarg = NULL;
        if(i < (argc - 1)) {
          nextarg = convert_tchar_to_UTF8(argv[i + 1]);
          if(!nextarg) {
            unicodefree(CURL_UNCONST(orig_opt));
            return PARAM_NO_MEM;
          }
        }

        err = getparameter(orig_opt, nextarg, &passarg, config,
                           CONFIG_MAX_LEVELS);

        unicodefree(CURL_UNCONST(nextarg));
        config = global->last;
        if(err == PARAM_NEXT_OPERATION) {
          /* Reset err as PARAM_NEXT_OPERATION is only used here and not
             returned from this function */
          err = PARAM_OK;

          if(config->url_list && config->url_list->url) {
            /* Allocate the next config */
            config->next = config_alloc();
            if(config->next) {
              /* Update the last config pointer */
              global->last = config->next;

              /* Move onto the new config */
              config->next->prev = config;
              config = config->next;
            }
            else
              err = PARAM_NO_MEM;
          }
          else {
            errorf("missing URL before --next");
            err = PARAM_BAD_USE;
          }
        }
        else if(!err && passarg)
          i++; /* we are supposed to skip this */
      }
    }
    else {
      bool used;

      /* add the URL please */
      err = getparameter("--url", orig_opt, &used, config, 0);
    }

    if(!err) {
      unicodefree(CURL_UNCONST(orig_opt));
      orig_opt = NULL;
    }
  }

  if(!err && config->content_disposition) {
    if(config->resume_from_current)
      err = PARAM_CONTDISP_RESUME_FROM;
  }

  if(err &&
     err != PARAM_HELP_REQUESTED &&
     err != PARAM_MANUAL_REQUESTED &&
     err != PARAM_VERSION_INFO_REQUESTED &&
     err != PARAM_ENGINES_REQUESTED &&
     err != PARAM_CA_EMBED_REQUESTED) {
    const char *reason = param2text(err);

    if(orig_opt && strcmp(":", orig_opt))
      helpf("option %s: %s", orig_opt, reason);
    else
      helpf("%s", reason);
  }

  unicodefree(CURL_UNCONST(orig_opt));
  return err;
}
