]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
curl: do more command line parsing in sub functions
authorDaniel Stenberg <daniel@haxx.se>
Wed, 4 Dec 2024 09:23:47 +0000 (10:23 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 4 Dec 2024 12:16:29 +0000 (13:16 +0100)
This moves argument parsing logic for a number of options into sub
functions to reduce the overall complexity of the single getparameter()
function. pmccabe says it takes complexity down from 234 to 147.

The command line options that now has dedicated parser funtions are:

 --continue-at, --ech, --header, --localport, --output, --quote, --range
 --remote-name, --time-cond, --upload-file, --url, --verbose, --writeout

These parsers were selected for thise because they had more than 15
lines of logic in the main switch(). Detected like this:

 git grep -hn 'case C_' tool_getparam.c |
  cut -d: -f1 |
  awk '{if(($1 - prev) > 15) { printf "%d\n", prev;} prev = $1;}'

Closes #15680

src/tool_getparam.c

index abae52bd960a31fac67700da4b0b6c26e52a7c7c..7eb08cd097b0da87fbee6f9380657d7cff998253 100644 (file)
@@ -1019,6 +1019,543 @@ const struct LongShort *findlongopt(const char *opt)
                  sizeof(aliases[0]), findarg);
 }
 
+static ParameterError parse_url(struct OperationConfig *config,
+                                const char *nextarg)
+{
+  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->flags & GETOUT_URL))
+      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, nextarg, DENY_BLANK);
+    url->flags |= GETOUT_URL;
+  }
+  return err;
+}
+
+static ParameterError parse_localport(struct OperationConfig *config,
+                                      char *nextarg)
+{
+  int rc;
+  /* 16bit base 10 is 5 digits, but we allow 6 so that this catches
+     overflows, not just truncates */
+  char lrange[7]="";
+  char *p = nextarg;
+  while(ISDIGIT(*p))
+    p++;
+  if(*p) {
+    /* if there is anything more than a plain decimal number */
+    rc = sscanf(p, " - %6s", lrange);
+    *p = 0; /* null-terminate to make str2unum() work below */
+  }
+  else
+    rc = 0;
+
+  if(str2unum(&config->localport, nextarg) ||
+     (config->localport > 65535))
+    return PARAM_BAD_USE;
+  if(!rc)
+    config->localportrange = 1; /* default number of ports to try */
+  else {
+    if(str2unum(&config->localportrange, lrange) ||
+       (config->localportrange > 65535))
+      return PARAM_BAD_USE;
+    else {
+      config->localportrange -= (config->localport-1);
+      if(config->localportrange < 1)
+        return PARAM_BAD_USE;
+    }
+  }
+  return PARAM_OK;
+}
+
+static ParameterError parse_continue_at(struct GlobalConfig *global,
+                                        struct OperationConfig *config,
+                                        const char *nextarg)
+{
+  ParameterError err = PARAM_OK;
+  if(config->range) {
+    errorf(global, "--continue-at is mutually exclusive with --range");
+    return PARAM_BAD_USE;
+  }
+  if(config->rm_partial) {
+    errorf(config->global,
+           "--continue-at is mutually exclusive with --remove-on-error");
+    return PARAM_BAD_USE;
+  }
+  if(config->file_clobber_mode == CLOBBER_NEVER) {
+    errorf(config->global,
+           "--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 GlobalConfig *global,
+                                struct OperationConfig *config,
+                                const char *nextarg)
+{
+  ParameterError err = PARAM_OK;
+  if(!feature_ech)
+    err = PARAM_LIBCURL_DOESNT_SUPPORT;
+  else if(strlen(nextarg) > 4 && strncasecompare("pn:", nextarg, 3)) {
+    /* a public_name */
+    err = getstr(&config->ech_public, nextarg, DENY_BLANK);
+  }
+  else if(strlen(nextarg) > 5 && strncasecompare("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++;        /* skip over '@' */
+      if(!strcmp("-", nextarg)) {
+        file = stdin;
+      }
+      else {
+        file = fopen(nextarg, FOPEN_READTEXT);
+      }
+      if(!file) {
+        warnf(global,
+              "Couldn't read file \"%s\" "
+              "specified for \"--ech ecl:\" option",
+              nextarg);
+        return PARAM_BAD_USE; /*  */
+      }
+      err = file2string(&tmpcfg, file);
+      if(file != stdin)
+        fclose(file);
+      if(err)
+        return err;
+      config->ech_config = aprintf("ecl:%s",tmpcfg);
+      if(!config->ech_config)
+        return PARAM_NO_MEM;
+      free(tmpcfg);
+    } /* file done */
+  }
+  else {
+    /* Simple case: just a string, with a keyword */
+    err = getstr(&config->ech, nextarg, DENY_BLANK);
+  }
+  return err;
+}
+
+static ParameterError parse_header(struct GlobalConfig *global,
+                                   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 */
+    char *string;
+    size_t len;
+    bool use_stdin = !strcmp(&nextarg[1], "-");
+    FILE *file = use_stdin ? stdin : fopen(&nextarg[1], FOPEN_READTEXT);
+    if(!file) {
+      errorf(global, "Failed to open %s", &nextarg[1]);
+      err = PARAM_READ_ERROR;
+    }
+    else {
+      err = file2memory(&string, &len, file);
+      if(!err && string) {
+        /* Allow strtok() here since this is not used threaded */
+        /* !checksrc! disable BANNEDFUNC 2 */
+        char *h = strtok(string, "\r\n");
+        while(h) {
+          if(cmd == C_PROXY_HEADER) /* --proxy-header */
+            err = add2list(&config->proxyheaders, h);
+          else
+            err = add2list(&config->headers, h);
+          if(err)
+            break;
+          h = strtok(NULL, "\r\n");
+        }
+        free(string);
+      }
+      if(!use_stdin)
+        fclose(file);
+    }
+  }
+  else {
+    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->flags & GETOUT_OUTFILE))
+      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 */
+  err = getstr(&url->outfile, nextarg, DENY_BLANK);
+  url->flags &= ~GETOUT_USEREMOTE; /* switch off */
+  url->flags |= GETOUT_OUTFILE;
+  return err;
+}
+
+static ParameterError parse_remote_name(struct OperationConfig *config,
+                                        bool toggle)
+{
+  ParameterError err = PARAM_OK;
+  struct getout *url;
+
+  if(!toggle && !config->default_node_flags)
+    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->flags & GETOUT_OUTFILE))
+      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 */
+  if(toggle)
+    url->flags |= GETOUT_USEREMOTE;  /* switch on */
+  else
+    url->flags &= ~GETOUT_USEREMOTE; /* switch off */
+  url->flags |= GETOUT_OUTFILE;
+  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 a just-before-transfer one */
+    nextarg++;
+    err = add2list(&config->prequote, nextarg);
+        break;
+  default:
+    err = add2list(&config->quote, nextarg);
+    break;
+  }
+  return err;
+}
+
+static ParameterError parse_range(struct GlobalConfig *global,
+                                  struct OperationConfig *config,
+                                  const char *nextarg)
+{
+  ParameterError err = PARAM_OK;
+
+  if(config->use_resume) {
+    errorf(global, "--continue-at is mutually exclusive with --range");
+    return PARAM_BAD_USE;
+  }
+  /* Specifying a range WITHOUT A DASH will create an illegal HTTP range
+     (and will not actually be range by definition). The manpage
+     previously claimed that to be a good way, why this code is added to
+     work-around it. */
+  if(ISDIGIT(*nextarg) && !strchr(nextarg, '-')) {
+    char buffer[32];
+    curl_off_t value;
+    if(curlx_strtoofft(nextarg, NULL, 10, &value)) {
+      warnf(global, "unsupported range point");
+      err = PARAM_BAD_USE;
+    }
+    else {
+      warnf(global,
+            "A specified range MUST include at least one dash (-). "
+            "Appending one for you");
+      msnprintf(buffer, sizeof(buffer), "%" CURL_FORMAT_CURL_OFF_T "-",
+                value);
+      Curl_safefree(config->range);
+      config->range = strdup(buffer);
+      if(!config->range)
+        err = PARAM_NO_MEM;
+    }
+  }
+  else {
+    /* byte range requested */
+    const char *tmp_range = nextarg;
+    while(*tmp_range) {
+      if(!ISDIGIT(*tmp_range) && *tmp_range != '-' && *tmp_range != ',') {
+        warnf(global, "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;
+      }
+      tmp_range++;
+    }
+    err = getstr(&config->range, nextarg, 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->flags & GETOUT_UPLOAD))
+      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->flags |= GETOUT_UPLOAD; /* mark -T used */
+  if(!*nextarg)
+    url->flags |= GETOUT_NOUPLOAD;
+  else {
+    /* "-" equals stdin, but keep the string around for now */
+    err = getstr(&url->infile, nextarg, DENY_BLANK);
+  }
+  return err;
+}
+
+static ParameterError parse_verbose(struct GlobalConfig *global,
+                                    bool toggle,
+                                    size_t nopts)
+{
+  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(global, "-all"))
+      err = PARAM_NO_MEM;
+    global->tracetype = TRACE_NONE;
+    return err;
+  }
+  else if(!nopts) {
+    /* fist `-v` in an argument resets to base verbosity */
+    global->verbosity = 0;
+    if(set_trace_config(global, "-all"))
+      return PARAM_NO_MEM;
+  }
+  /* the '%' thing here will cause the trace get sent to stderr */
+  switch(global->verbosity) {
+  case 0:
+    global->verbosity = 1;
+    Curl_safefree(global->trace_dump);
+    global->trace_dump = strdup("%");
+    if(!global->trace_dump)
+      err = PARAM_NO_MEM;
+    else {
+      if(global->tracetype && (global->tracetype != TRACE_PLAIN))
+        warnf(global,
+              "-v, --verbose overrides an earlier trace option");
+      global->tracetype = TRACE_PLAIN;
+    }
+    break;
+  case 1:
+    global->verbosity = 2;
+    if(set_trace_config(global, "ids,time,protocol"))
+      err = PARAM_NO_MEM;
+    break;
+  case 2:
+    global->verbosity = 3;
+    global->tracetype = TRACE_ASCII;
+    if(set_trace_config(global, "ssl,read,write"))
+      err = PARAM_NO_MEM;
+    break;
+  case 3:
+    global->verbosity = 4;
+    if(set_trace_config(global, "network"))
+      err = PARAM_NO_MEM;
+    break;
+  default:
+    /* no effect for now */
+    break;
+  }
+  return err;
+}
+
+static ParameterError parse_writeout(struct GlobalConfig *global,
+                                     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 = fopen(fname, FOPEN_READTEXT);
+      if(!file) {
+        errorf(global, "Failed to open %s", fname);
+        return PARAM_READ_ERROR;
+      }
+    }
+    Curl_safefree(config->writeout);
+    err = file2string(&config->writeout, file);
+    if(file && (file != stdin))
+      fclose(file);
+    if(err)
+      return err;
+    if(!config->writeout)
+      warnf(global, "Failed to read %s", fname);
+  }
+  else
+    err = getstr(&config->writeout, nextarg, ALLOW_BLANK);
+
+  return err;
+}
+
+static ParameterError parse_time_cond(struct GlobalConfig *global,
+                                      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(-1 == config->condtime) {
+    curl_off_t value;
+    /* now let's see if it is a filename to get the time from instead! */
+    int rc = getfiletime(nextarg, global, &value);
+    if(!rc)
+      /* pull the time out from the file */
+      config->condtime = value;
+    else {
+      /* failed, remove time condition */
+      config->timecond = CURL_TIMECOND_NONE;
+      warnf(global,
+            "Illegal date format for -z, --time-cond (and not "
+            "a filename). Disabling time condition. "
+            "See curl_getdate(3) for valid date syntax.");
+    }
+  }
+  return err;
+}
 
 ParameterError getparameter(const char *flag, /* f or -long-flag */
                             char *nextarg,    /* NULL if unset */
@@ -1028,7 +1565,6 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
                             struct GlobalConfig *global,
                             struct OperationConfig *config)
 {
-  int rc;
   const char *parse = NULL;
   bool longopt = FALSE;
   bool singleopt = FALSE; /* when true means '-o foo' used '-ofoo' */
@@ -1037,7 +1573,6 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
   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 */
-  struct getout *url;
   static const char *redir_protos[] = {
     "http",
     "https",
@@ -1114,7 +1649,6 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
 
   do {
     /* we can loop here if we have multiple single-letters */
-    char letter;
     cmdline_t cmd;
 
     if(!longopt && !a) {
@@ -1124,7 +1658,6 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
         break;
       }
     }
-    letter = a->letter;
     cmd = (cmdline_t)a->cmd;
     if(ARGTYPE(a->desc) >= ARG_STRG) {
       /* this option requires an extra parameter */
@@ -1380,32 +1913,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       config->xattr = toggle;
       break;
     case C_URL: /* --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->flags & GETOUT_URL))
-          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)
-        err = PARAM_NO_MEM;
-      else {
-        /* fill in the URL */
-        err = getstr(&url->url, nextarg, DENY_BLANK);
-        url->flags |= GETOUT_URL;
-      }
+      err = parse_url(config, nextarg);
       break;
     case C_FTP_SSL: /* --ftp-ssl */
     case C_SSL: /* --ssl */
@@ -1509,40 +2017,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
     case C_FTP_METHOD: /* --ftp-method */
       config->ftp_filemethod = ftpfilemethod(config, nextarg);
       break;
-    case C_LOCAL_PORT: { /* --local-port */
-      /* 16bit base 10 is 5 digits, but we allow 6 so that this catches
-         overflows, not just truncates */
-      char lrange[7]="";
-      char *p = nextarg;
-      while(ISDIGIT(*p))
-        p++;
-      if(*p) {
-        /* if there is anything more than a plain decimal number */
-        rc = sscanf(p, " - %6s", lrange);
-        *p = 0; /* null-terminate to make str2unum() work below */
-      }
-      else
-        rc = 0;
-
-      err = str2unum(&config->localport, nextarg);
-      if(err || (config->localport > 65535)) {
-        err = PARAM_BAD_USE;
-        break;
-      }
-      if(!rc)
-        config->localportrange = 1; /* default number of ports to try */
-      else {
-        err = str2unum(&config->localportrange, lrange);
-        if(err || (config->localportrange > 65535))
-          err = PARAM_BAD_USE;
-        else {
-          config->localportrange -= (config->localport-1);
-          if(config->localportrange < 1)
-            err = PARAM_BAD_USE;
-        }
-      }
+    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;
@@ -1845,30 +2322,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       err = getstr(&config->cookiejar, nextarg, DENY_BLANK);
       break;
     case C_CONTINUE_AT: /* --continue-at */
-      if(config->range) {
-        errorf(global, "--continue-at is mutually exclusive with --range");
-        return PARAM_BAD_USE;
-      }
-      if(config->rm_partial) {
-        errorf(config->global,
-               "--continue-at is mutually exclusive with --remove-on-error");
-        return PARAM_BAD_USE;
-      }
-      if(config->file_clobber_mode == CLOBBER_NEVER) {
-        errorf(config->global,
-               "--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;
+      err = parse_continue_at(global, config, nextarg);
       break;
     case C_DATA: /* --data */
     case C_DATA_ASCII:  /* --data-ascii */
@@ -1935,52 +2389,8 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       }
       break;
     case C_ECH: /* --ech */
-      if(!feature_ech)
-        err = PARAM_LIBCURL_DOESNT_SUPPORT;
-      else if(strlen(nextarg) > 4 && strncasecompare("pn:", nextarg, 3)) {
-        /* a public_name */
-        err = getstr(&config->ech_public, nextarg, DENY_BLANK);
-      }
-      else if(strlen(nextarg) > 5 && strncasecompare("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++;        /* skip over '@' */
-          if(!strcmp("-", nextarg)) {
-            file = stdin;
-          }
-          else {
-            file = fopen(nextarg, FOPEN_READTEXT);
-          }
-          if(!file) {
-            warnf(global,
-                  "Couldn't read file \"%s\" "
-                  "specified for \"--ech ecl:\" option",
-                  nextarg);
-            return PARAM_BAD_USE; /*  */
-          }
-          err = file2string(&tmpcfg, file);
-          if(file != stdin)
-            fclose(file);
-          if(err)
-            return err;
-          config->ech_config = aprintf("ecl:%s",tmpcfg);
-          if(!config->ech_config)
-            return PARAM_NO_MEM;
-          free(tmpcfg);
-      } /* file done */
-    }
-    else {
-      /* Simple case: just a string, with a keyword */
-      err = getstr(&config->ech, nextarg, DENY_BLANK);
-    }
-    break;
+      err = parse_ech(global, config, nextarg);
+      break;
     case C_CAPATH: /* --capath */
       err = getstr(&config->capath, nextarg, DENY_BLANK);
       break;
@@ -2219,44 +2629,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       break;
     case C_HEADER: /* --header */
     case C_PROXY_HEADER: /* --proxy-header */
-      /* A custom header to append to a list */
-      if(nextarg[0] == '@') {
-        /* read many headers from a file or stdin */
-        char *string;
-        size_t len;
-        bool use_stdin = !strcmp(&nextarg[1], "-");
-        FILE *file = use_stdin ? stdin : fopen(&nextarg[1], FOPEN_READTEXT);
-        if(!file) {
-          errorf(global, "Failed to open %s", &nextarg[1]);
-          err = PARAM_READ_ERROR;
-        }
-        else {
-          err = file2memory(&string, &len, file);
-          if(!err && string) {
-            /* Allow strtok() here since this is not used threaded */
-            /* !checksrc! disable BANNEDFUNC 2 */
-            char *h = strtok(string, "\r\n");
-            while(h) {
-              if(cmd == C_PROXY_HEADER) /* --proxy-header */
-                err = add2list(&config->proxyheaders, h);
-              else
-                err = add2list(&config->headers, h);
-              if(err)
-                break;
-              h = strtok(NULL, "\r\n");
-            }
-            free(string);
-          }
-          if(!use_stdin)
-            fclose(file);
-        }
-      }
-      else {
-        if(cmd == C_PROXY_HEADER) /* --proxy-header */
-          err = add2list(&config->proxyheaders, nextarg);
-        else
-          err = add2list(&config->headers, nextarg);
-      }
+      err = parse_header(global, config, cmd, nextarg);
       break;
     case C_INCLUDE: /* --include */
     case C_SHOW_HEADERS: /* --show-headers */
@@ -2343,47 +2716,10 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       config->file_clobber_mode = toggle ? CLOBBER_ALWAYS : CLOBBER_NEVER;
       break;
     case C_OUTPUT: /* --output */
+      err = parse_output(config, nextarg);
+      break;
     case C_REMOTE_NAME: /* --remote-name */
-      /* 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->flags & GETOUT_OUTFILE))
-          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 {
-        if(!toggle && !config->default_node_flags)
-          break;
-        /* there was no free node, create one! */
-        config->url_out = url = new_getout(config);
-      }
-
-      if(!url) {
-        err = PARAM_NO_MEM;
-        break;
-      }
-
-      /* fill in the outfile */
-      if('o' == letter) {
-        err = getstr(&url->outfile, nextarg, DENY_BLANK);
-        url->flags &= ~GETOUT_USEREMOTE; /* switch off */
-      }
-      else {
-        url->outfile = NULL; /* leave it */
-        if(toggle)
-          url->flags |= GETOUT_USEREMOTE;  /* switch on */
-        else
-          url->flags &= ~GETOUT_USEREMOTE; /* switch off */
-      }
-      url->flags |= GETOUT_OUTFILE;
+      err = parse_remote_name(config, toggle);
       break;
     case C_FTP_PORT: /* --ftp-port */
       /* This makes the FTP sessions use PORT instead of PASV */
@@ -2403,65 +2739,10 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
          cause an error! */
       break;
     case C_QUOTE: /* --quote */
-      /* 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 a just-before-transfer one */
-        nextarg++;
-        err = add2list(&config->prequote, nextarg);
-        break;
-      default:
-        err = add2list(&config->quote, nextarg);
-        break;
-      }
+      err = parse_quote(config, nextarg);
       break;
     case C_RANGE: /* --range */
-      if(config->use_resume) {
-        errorf(global, "--continue-at is mutually exclusive with --range");
-        return PARAM_BAD_USE;
-      }
-      /* Specifying a range WITHOUT A DASH will create an illegal HTTP range
-         (and will not actually be range by definition). The manpage
-         previously claimed that to be a good way, why this code is added to
-         work-around it. */
-      if(ISDIGIT(*nextarg) && !strchr(nextarg, '-')) {
-        char buffer[32];
-        if(curlx_strtoofft(nextarg, NULL, 10, &value)) {
-          warnf(global, "unsupported range point");
-          err = PARAM_BAD_USE;
-        }
-        else {
-          warnf(global,
-                "A specified range MUST include at least one dash (-). "
-                "Appending one for you");
-          msnprintf(buffer, sizeof(buffer), "%" CURL_FORMAT_CURL_OFF_T "-",
-                    value);
-          Curl_safefree(config->range);
-          config->range = strdup(buffer);
-          if(!config->range)
-            err = PARAM_NO_MEM;
-        }
-      }
-      else {
-        /* byte range requested */
-        const char *tmp_range = nextarg;
-        while(*tmp_range) {
-          if(!ISDIGIT(*tmp_range) && *tmp_range != '-' && *tmp_range != ',') {
-            warnf(global, "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;
-          }
-          tmp_range++;
-        }
-        err = getstr(&config->range, nextarg, DENY_BLANK);
-      }
+      err = parse_range(global, config, nextarg);
       break;
     case C_REMOTE_TIME: /* --remote-time */
       /* use remote file's time */
@@ -2481,37 +2762,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       err = add2list(&config->telnet_options, nextarg);
       break;
     case C_UPLOAD_FILE: /* --upload-file */
-      /* 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->flags & GETOUT_UPLOAD))
-          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) {
-        err = PARAM_NO_MEM;
-        break;
-      }
-
-      url->flags |= GETOUT_UPLOAD; /* mark -T used */
-      if(!*nextarg)
-        url->flags |= GETOUT_NOUPLOAD;
-      else {
-        /* "-" equals stdin, but keep the string around for now */
-        err = getstr(&url->infile, nextarg, DENY_BLANK);
-      }
+      err = parse_upload_file(config, nextarg);
       break;
     case C_USER: /* --user */
       /* user:password  */
@@ -2524,95 +2775,14 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       cleanarg(clearthis);
       break;
     case C_VERBOSE: /* --verbose */
-      /* 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(global, "-all"))
-          err = PARAM_NO_MEM;
-        global->tracetype = TRACE_NONE;
-        break;
-      }
-      else if(!nopts) {
-        /* fist `-v` in an argument resets to base verbosity */
-        global->verbosity = 0;
-        if(set_trace_config(global, "-all")) {
-          err = PARAM_NO_MEM;
-          break;
-        }
-      }
-      /* the '%' thing here will cause the trace get sent to stderr */
-      switch(global->verbosity) {
-      case 0:
-        global->verbosity = 1;
-        Curl_safefree(global->trace_dump);
-        global->trace_dump = strdup("%");
-        if(!global->trace_dump)
-          err = PARAM_NO_MEM;
-        else {
-          if(global->tracetype && (global->tracetype != TRACE_PLAIN))
-            warnf(global,
-                  "-v, --verbose overrides an earlier trace option");
-          global->tracetype = TRACE_PLAIN;
-        }
-        break;
-      case 1:
-        global->verbosity = 2;
-        if(set_trace_config(global, "ids,time,protocol"))
-          err = PARAM_NO_MEM;
-        break;
-      case 2:
-        global->verbosity = 3;
-        global->tracetype = TRACE_ASCII;
-        if(set_trace_config(global, "ssl,read,write"))
-          err = PARAM_NO_MEM;
-        break;
-      case 3:
-        global->verbosity = 4;
-        if(set_trace_config(global, "network"))
-          err = PARAM_NO_MEM;
-        break;
-      default:
-        /* no effect for now */
-        break;
-      }
+      err = parse_verbose(global, toggle, nopts);
       break;
     case C_VERSION: /* --version */
       if(toggle)    /* --no-version yields no output! */
         err = PARAM_VERSION_INFO_REQUESTED;
       break;
     case C_WRITE_OUT: /* --write-out */
-      /* 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 = fopen(fname, FOPEN_READTEXT);
-          if(!file) {
-            errorf(global, "Failed to open %s", fname);
-            err = PARAM_READ_ERROR;
-            break;
-          }
-        }
-        Curl_safefree(config->writeout);
-        err = file2string(&config->writeout, file);
-        if(file && (file != stdin))
-          fclose(file);
-        if(err)
-          break;
-        if(!config->writeout)
-          warnf(global, "Failed to read %s", fname);
-      }
-      else
-        err = getstr(&config->writeout, nextarg, ALLOW_BLANK);
+      err = parse_writeout(global, config, nextarg);
       break;
     case C_PREPROXY: /* --preproxy */
       err = getstr(&config->preproxy, nextarg, DENY_BLANK);
@@ -2659,41 +2829,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
       global->parallel_connect = toggle;
       break;
     case C_TIME_COND: /* --time-cond */
-      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(-1 == config->condtime) {
-        /* now let's see if it is a filename to get the time from instead! */
-        rc = getfiletime(nextarg, global, &value);
-        if(!rc)
-          /* pull the time out from the file */
-          config->condtime = value;
-        else {
-          /* failed, remove time condition */
-          config->timecond = CURL_TIMECOND_NONE;
-          warnf(global,
-                "Illegal date format for -z, --time-cond (and not "
-                "a filename). Disabling time condition. "
-                "See curl_getdate(3) for valid date syntax.");
-        }
-      }
+      err = parse_time_cond(global, config, nextarg);
       break;
     case C_MPTCP: /* --mptcp */
       config->mptcp = TRUE;