Commit message: CVE-2014-4877: Arbitrary Symlink Access

Wget was susceptible to a symlink attack which could create arbitrary
files, directories or symbolic links and set their permissions when
retrieving a directory recursively through FTP. This commit changes the
default settings in Wget such that Wget no longer creates local symbolic
links, but rather traverses them and retrieves the pointed-to file in
such a retrieval.

The old behaviour can be attained by passing the --retr-symlinks=no
option to the Wget invokation command.
CVE: CVE-2014-4877
CWE: CWE-22
Vulnerability description: Absolute path traversal vulnerability in GNU Wget before 1.16, when recursion is enabled, allows remote FTP servers to write to arbitrary files, and consequently execute arbitrary code, via a LIST response that references the same filename within two entries, one of which indicates that the filename is for a symlink.

Project: wget

cmd_directory (const char *com, const char *val, void *place)
{
  char *s, *t;

  /* Call cmd_file() for tilde expansion and separator
     canonicalization (backslash -> slash under Windows).  These
     things should perhaps be in a separate function.  */
  if (!cmd_file (com, val, place))
    return false;

  s = *(char **)place;
  t = s + strlen (s);
  while (t > s && *--t == '/')
    *t = '\0';

  return true;
}

Project: wget

cmd_bytes_sum (const char *com, const char *val, void *place)
{
  double byte_value;
  if (!parse_bytes_helper (val, &byte_value))
    {
      fprintf (stderr, _("%s: %s: Invalid byte value %s\n"),
               exec_name, com, quote (val));
      return false;
    }
  *(SUM_SIZE_INT *) place = (SUM_SIZE_INT) byte_value;
  return true;
}

Project: wget

wgetrc_file_name (void)
{
  char *file = wgetrc_env_file_name ();
  if (file && *file)
    return file;

  file = wgetrc_user_file_name ();

#ifdef WINDOWS
  /* Under Windows, if we still haven't found .wgetrc, look for the file
     `wget.ini' in the directory where `wget.exe' resides; we do this for
     backward compatibility with previous versions of Wget.
     SYSTEM_WGETRC should not be defined under WINDOWS.  */
  if (!file)
    {
      char *home = home_dir ();
      xfree_null (file);
      file = NULL;
      home = ws_mypath ();
      if (home)
        {
          file = aprintf ("%s/wget.ini", home);
          if (!file_exists_p (file))
            {
              xfree (file);
              file = NULL;
            }
          xfree (home);
        }
    }
#endif /* WINDOWS */

  return file;
}

Project: wget

cleanup (void)
{
  /* Free external resources, close files, etc. */

  /* Close WARC file. */
  if (opt.warc_filename != 0)
    warc_close ();

  log_close ();

  if (output_stream)
    if (fclose (output_stream) == EOF)
      inform_exit_status (CLOSEFAILED);

  /* No need to check for error because Wget flushes its output (and
     checks for errors) after any data arrives.  */

  /* We're exiting anyway so there's no real need to call free()
     hundreds of times.  Skipping the frees will make Wget exit
     faster.

     However, when detecting leaks, it's crucial to free() everything
     because then you can find the real leaks, i.e. the allocated
     memory which grows with the size of the program.  */

#ifdef DEBUG_MALLOC
  convert_cleanup ();
  res_cleanup ();
  http_cleanup ();
  cleanup_html_url ();
  spider_cleanup ();
  host_cleanup ();
  log_cleanup ();
  netrc_cleanup (netrc_list);

  for (i = 0; i < nurl; i++)
    xfree (url[i]);

  xfree_null (opt.choose_config);
  xfree_null (opt.lfilename);
  xfree_null (opt.dir_prefix);
  xfree_null (opt.input_filename);
  xfree_null (opt.output_document);
  free_vec (opt.accepts);
  free_vec (opt.rejects);
  free_vec (opt.excludes);
  free_vec (opt.includes);
  free_vec (opt.domains);
  free_vec (opt.follow_tags);
  free_vec (opt.ignore_tags);
  xfree_null (opt.progress_type);
  xfree_null (opt.ftp_user);
  xfree_null (opt.ftp_passwd);
  xfree_null (opt.ftp_proxy);
  xfree_null (opt.https_proxy);
  xfree_null (opt.http_proxy);
  free_vec (opt.no_proxy);
  xfree_null (opt.useragent);
  xfree_null (opt.referer);
  xfree_null (opt.http_user);
  xfree_null (opt.http_passwd);
  free_vec (opt.user_headers);
  free_vec (opt.warc_user_headers);
# ifdef HAVE_SSL
  xfree_null (opt.cert_file);
  xfree_null (opt.private_key);
  xfree_null (opt.ca_directory);
  xfree_null (opt.ca_cert);
  xfree_null (opt.random_file);
  xfree_null (opt.egd_file);
# endif
  xfree_null (opt.bind_address);
  xfree_null (opt.cookies_input);
  xfree_null (opt.cookies_output);
  xfree_null (opt.user);
  xfree_null (opt.passwd);
  xfree_null (opt.base_href);
  xfree_null (opt.method);

#endif /* DEBUG_MALLOC */
}

Project: wget

cmd_string_uppercase (const char *com _GL_UNUSED, const char *val, void *place)
{
  char *q, **pstring;
  pstring = (char **)place;
  xfree_null (*pstring);

  *pstring = xmalloc (strlen (val) + 1);

  for (q = *pstring; *val; val++, q++)
    *q = c_toupper (*val);

  *q = '\0';
  return true;
}

Project: wget

cmd_spec_progress (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  if (!valid_progress_implementation_p (val))
    {
      fprintf (stderr, _("%s: %s: Invalid progress type %s.\n"),
               exec_name, com, quote (val));
      return false;
    }
  xfree_null (opt.progress_type);

  /* Don't call set_progress_implementation here.  It will be called
     in main when it becomes clear what the log output is.  */
  opt.progress_type = xstrdup (val);
  return true;
}

Project: wget

cmd_bytes (const char *com, const char *val, void *place)
{
  double byte_value;
  if (!parse_bytes_helper (val, &byte_value))
    {
      fprintf (stderr, _("%s: %s: Invalid byte value %s\n"),
               exec_name, com, quote (val));
      return false;
    }
  *(wgint *)place = (wgint)byte_value;
  return true;
}

Project: wget

cmd_spec_recursive (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  if (!cmd_boolean (com, val, &opt.recursive))
    return false;
  else
    {
      if (opt.recursive && !opt.no_dirstruct)
        opt.dirstruct = true;
    }
  return true;
}

Project: wget

command_by_name (const char *cmdname)
{
  /* Use binary search for speed.  Wget has ~100 commands, which
     guarantees a worst case performance of 7 string comparisons.  */
  int lo = 0, hi = countof (commands) - 1;

  while (lo <= hi)
    {
      int mid = (lo + hi) >> 1;
      int cmp = strcasecmp (cmdname, commands[mid].name);
      if (cmp < 0)
        hi = mid - 1;
      else if (cmp > 0)
        lo = mid + 1;
      else
        return mid;
    }
  return -1;
}

Project: wget

run_wgetrc (const char *file)
{
  FILE *fp;
  char *line = NULL;
  size_t bufsize = 0;
  int ln;
  int errcnt = 0;

  fp = fopen (file, "r");
  if (!fp)
    {
      fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
               file, strerror (errno));
      return true;                      /* not a fatal error */
    }
  ln = 1;
  while (getline (&line, &bufsize, fp) > 0)
    {
      char *com = NULL, *val = NULL;
      int comind;

      /* Parse the line.  */
      switch (parse_line (line, &com, &val, &comind))
        {
        case line_ok:
          /* If everything is OK, set the value.  */
          if (!setval_internal_tilde (comind, com, val))
            {
              fprintf (stderr, _("%s: Error in %s at line %d.\n"),
                       exec_name, file, ln);
              ++errcnt;
            }
          break;
        case line_syntax_error:
          fprintf (stderr, _("%s: Syntax error in %s at line %d.\n"),
                   exec_name, file, ln);
          ++errcnt;
          break;
        case line_unknown_command:
          fprintf (stderr, _("%s: Unknown command %s in %s at line %d.\n"),
                   exec_name, quote (com), file, ln);
          ++errcnt;
          break;
        case line_empty:
          break;
        default:
          abort ();
        }
      xfree_null (com);
      xfree_null (val);
      ++ln;
    }
  xfree (line);
  fclose (fp);

  return errcnt == 0;
}

Project: wget

cmd_spec_htmlify (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  int flag = cmd_boolean (com, val, &opt.htmlify);
  if (flag && !opt.htmlify)
    opt.remove_listing = false;
  return flag;
}

Project: wget

cmd_spec_verbose (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  bool flag;
  if (cmd_boolean (com, val, &flag))
    {
      opt.verbose = flag;
      return true;
    }
  return false;
}

Project: wget

cmd_spec_report_speed (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  opt.report_bps = strcasecmp (val, "bits") == 0;
  if (!opt.report_bps)
    fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
  return opt.report_bps;
}

Project: wget

cmd_number (const char *com, const char *val, void *place)
{
  if (!simple_atoi (val, val + strlen (val), place)
      || *(int *) place < 0)
    {
      fprintf (stderr, _("%s: %s: Invalid number %s.\n"),
               exec_name, com, quote (val));
      return false;
    }
  return true;
}

Project: wget

cmd_spec_warc_header (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  /* Empty value means reset the list of headers. */
  if (*val == '\0')
    {
      free_vec (opt.warc_user_headers);
      opt.warc_user_headers = NULL;
      return true;
    }

  if (!check_user_specified_header (val))
    {
      fprintf (stderr, _("%s: %s: Invalid WARC header %s.\n"),
               exec_name, com, quote (val));
      return false;
    }
  opt.warc_user_headers = vec_append (opt.warc_user_headers, val);
  return true;
}

Project: wget

setval_internal_tilde (int comind, const char *com, const char *val)
{
  bool ret;
  int homelen;
  char *home;
  char **pstring;
  ret = setval_internal (comind, com, val);

  /* We make tilde expansion for cmd_file and cmd_directory */
  if (((commands[comind].action == cmd_file) ||
       (commands[comind].action == cmd_directory))
      && ret && (*val == '~' && ISSEP (val[1])))
    {
      pstring = commands[comind].place;
      home = home_dir ();
      if (home)
        {
          homelen = strlen (home);
          while (homelen && ISSEP (home[homelen - 1]))
                 home[--homelen] = '\0';

          /* Skip the leading "~/". */
          for (++val; ISSEP (*val); val++)
            ;
          *pstring = concat_strings (home, "/", val, (char *)0);
        }
    }
  return ret;
}

Project: wget

check_user_specified_header (const char *s)
{
  const char *p;

  for (p = s; *p && *p != ':' && !c_isspace (*p); p++)
    ;
  /* The header MUST contain `:' preceded by at least one
     non-whitespace character.  */
  if (*p != ':' || p == s)
    return false;
  /* The header MUST NOT contain newlines.  */
  if (strchr (s, '\n'))
    return false;
  return true;
}

Project: wget

decode_string (const char *val, const struct decode_item *items, int itemcount,
               int *place)
{
  int i;
  for (i = 0; i < itemcount; i++)
    if (0 == strcasecmp (val, items[i].name))
      {
        *place = items[i].code;
        return true;
      }
  return false;
}

Project: wget

cmd_spec_regex_type (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  static const struct decode_item choices[] = {
    { "posix", regex_type_posix },
#ifdef HAVE_LIBPCRE
    { "pcre",  regex_type_pcre },
#endif
  };
  int regex_type = regex_type_posix;
  int ok = decode_string (val, choices, countof (choices), &regex_type);
  if (!ok)
    fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
  opt.regex_type = regex_type;
  return ok;
}

Project: wget

setoptval (const char *com, const char *val, const char *optname)
{
  /* Prepend "--" to OPTNAME. */
  char *dd_optname = (char *) alloca (2 + strlen (optname) + 1);
  dd_optname[0] = '-';
  dd_optname[1] = '-';
  strcpy (dd_optname + 2, optname);

  assert (val != NULL);
  if (!setval_internal (command_by_name (com), dd_optname, val))
    exit (WGET_EXIT_PARSE_ERROR);
}

Project: wget

cmd_spec_restrict_file_names (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  int restrict_os = opt.restrict_files_os;
  int restrict_ctrl = opt.restrict_files_ctrl;
  int restrict_case = opt.restrict_files_case;
  int restrict_nonascii = opt.restrict_files_nonascii;

  const char *end;

#define VAL_IS(string_literal) BOUNDED_EQUAL (val, end, string_literal)

  do
    {
      end = strchr (val, ',');
      if (!end)
        end = val + strlen (val);

      if (VAL_IS ("unix"))
        restrict_os = restrict_unix;
      else if (VAL_IS ("windows"))
        restrict_os = restrict_windows;
      else if (VAL_IS ("lowercase"))
        restrict_case = restrict_lowercase;
      else if (VAL_IS ("uppercase"))
        restrict_case = restrict_uppercase;
      else if (VAL_IS ("nocontrol"))
        restrict_ctrl = false;
      else if (VAL_IS ("ascii"))
        restrict_nonascii = true;
      else
        {
          fprintf (stderr, _("\
%s: %s: Invalid restriction %s,\n\
    use [unix|windows],[lowercase|uppercase],[nocontrol],[ascii].\n"),
                   exec_name, com, quote (val));
          return false;
        }

      if (*end)
        val = end + 1;
    }
  while (*val && *end);

#undef VAL_IS

  opt.restrict_files_os = restrict_os;
  opt.restrict_files_ctrl = restrict_ctrl;
  opt.restrict_files_case = restrict_case;
  opt.restrict_files_nonascii = restrict_nonascii;

  return true;
}

Project: wget

cmd_spec_dirstruct (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  if (!cmd_boolean (com, val, &opt.dirstruct))
    return false;
  /* Since dirstruct behaviour is explicitly changed, no_dirstruct
     must be affected inversely.  */
  if (opt.dirstruct)
    opt.no_dirstruct = false;
  else
    opt.no_dirstruct = true;
  return true;
}

Project: wget

simple_atof (const char *beg, const char *end, double *dest)
{
  double result = 0;

  bool negative = false;
  bool seen_dot = false;
  bool seen_digit = false;
  double divider = 1;

  const char *p = beg;

  while (p < end && c_isspace (*p))
    ++p;
  if (p < end && (*p == '-' || *p == '+'))
    {
      negative = (*p == '-');
      ++p;
    }

  for (; p < end; p++)
    {
      char ch = *p;
      if (c_isdigit (ch))
        {
          if (!seen_dot)
            result = (10 * result) + (ch - '0');
          else
            result += (ch - '0') / (divider *= 10);
          seen_digit = true;
        }
      else if (ch == '.')
        {
          if (!seen_dot)
            seen_dot = true;
          else
            return false;
        }
      else
        return false;
    }
  if (!seen_digit)
    return false;
  if (negative)
    result = -result;

  *dest = result;
  return true;
}

Project: wget

cmd_number_inf (const char *com, const char *val, void *place)
{
  if (!strcasecmp (val, "inf"))
    {
      *(int *) place = 0;
      return true;
    }
  return cmd_number (com, val, place);
}

Project: wget

cmd_directory_vector (const char *com _GL_UNUSED, const char *val, void *place)
{
  char ***pvec = (char ***)place;

  if (*val)
    {
      /* Strip the trailing slashes from directories.  */
      char **t, **seps;

      seps = sepstring (val);
      for (t = seps; t && *t; t++)
        {
          int len = strlen (*t);
          /* Skip degenerate case of root directory.  */
          if (len > 1)
            {
              if ((*t)[len - 1] == '/')
                (*t)[len - 1] = '\0';
            }
        }
      *pvec = merge_vecs (*pvec, seps);
    }
  else
    {
      free_vec (*pvec);
      *pvec = NULL;
    }
  return true;
}

Project: wget

cmd_spec_header (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  /* Empty value means reset the list of headers. */
  if (*val == '\0')
    {
      free_vec (opt.user_headers);
      opt.user_headers = NULL;
      return true;
    }

  if (!check_user_specified_header (val))
    {
      fprintf (stderr, _("%s: %s: Invalid header %s.\n"),
               exec_name, com, quote (val));
      return false;
    }
  opt.user_headers = vec_append (opt.user_headers, val);
  return true;
}

Project: wget

cmd_cert_type (const char *com, const char *val, void *place)
{
  static const struct decode_item choices[] = {
    { "pem", keyfile_pem },
    { "der", keyfile_asn1 },
    { "asn1", keyfile_asn1 },
  };
  int ok = decode_string (val, choices, countof (choices), place);
  if (!ok)
    fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
  return ok;
}

Project: wget

cmd_spec_prefer_family (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  static const struct decode_item choices[] = {
    { "IPv4", prefer_ipv4 },
    { "IPv6", prefer_ipv6 },
    { "none", prefer_none },
  };
  int prefer_family = prefer_none;
  int ok = decode_string (val, choices, countof (choices), &prefer_family);
  if (!ok)
    fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
  opt.prefer_family = prefer_family;
  return ok;
}

Project: wget

cmd_spec_secure_protocol (const char *com, const char *val, void *place)
{
  static const struct decode_item choices[] = {
    { "auto", secure_protocol_auto },
    { "sslv2", secure_protocol_sslv2 },
    { "sslv3", secure_protocol_sslv3 },
    { "tlsv1", secure_protocol_tlsv1 },
    { "tlsv1_1", secure_protocol_tlsv1_1 },
    { "tlsv1_2", secure_protocol_tlsv1_2 },
    { "pfs", secure_protocol_pfs },
  };
  int ok = decode_string (val, choices, countof (choices), place);
  if (!ok)
    fprintf (stderr, _("%s: %s: Invalid value %s.\n"), exec_name, com, quote (val));
  return ok;
}

Project: wget

cmd_spec_mirror (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  int mirror;

  if (!cmd_boolean (com, val, &mirror))
    return false;
  if (mirror)
    {
      opt.recursive = true;
      if (!opt.no_dirstruct)
        opt.dirstruct = true;
      opt.timestamping = true;
      opt.reclevel = INFINITE_RECURSION;
      opt.remove_listing = false;
    }
  return true;
}

Project: wget

dehyphen (char *s)
{
  char *t = s;                  /* t - tortoise */
  char *h = s;                  /* h - hare     */
  while (*h)
    if (*h == '_' || *h == '-')
      ++h;
    else
      *t++ = *h++;
  *t = '\0';
}

Project: wget

wgetrc_env_file_name (void)
{
  char *env = getenv ("WGETRC");
  if (env && *env)
    {
      if (!file_exists_p (env))
        {
          fprintf (stderr, _("%s: WGETRC points to %s, which doesn't exist.\n"),
                   exec_name, env);
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      return xstrdup (env);
    }
  return NULL;
}

Project: wget

parse_line (const char *line, char **com, char **val, int *comind)
{
  const char *p;
  const char *end = line + strlen (line);
  const char *cmdstart, *cmdend;
  const char *valstart, *valend;

  char *cmdcopy;
  int ind;

  /* Skip leading and trailing whitespace.  */
  while (*line && c_isspace (*line))
    ++line;
  while (end > line && c_isspace (end[-1]))
    --end;

  /* Skip empty lines and comments.  */
  if (!*line || *line == '#')
    return line_empty;

  p = line;

  cmdstart = p;
  while (p < end && (c_isalnum (*p) || *p == '_' || *p == '-'))
    ++p;
  cmdend = p;

  /* Skip '=', as well as any space before or after it. */
  while (p < end && c_isspace (*p))
    ++p;
  if (p == end || *p != '=')
    return line_syntax_error;
  ++p;
  while (p < end && c_isspace (*p))
    ++p;

  valstart = p;
  valend   = end;

  /* The syntax is valid (even though the command might not be).  Fill
     in the command name and value.  */
  *com = strdupdelim (cmdstart, cmdend);
  *val = strdupdelim (valstart, valend);

  /* The line now known to be syntactically correct.  Check whether
     the command is valid.  */
  BOUNDED_TO_ALLOCA (cmdstart, cmdend, cmdcopy);
  dehyphen (cmdcopy);
  ind = command_by_name (cmdcopy);
  if (ind == -1)
    return line_unknown_command;

  /* Report success to the caller. */
  *comind = ind;
  return line_ok;
}

Project: wget

parse_bytes_helper (const char *val, double *result)
{
  double number, mult;
  const char *end = val + strlen (val);

  /* Check for "inf".  */
  if (0 == strcmp (val, "inf"))
    {
      *result = 0;
      return true;
    }

  /* Strip trailing whitespace.  */
  while (val < end && c_isspace (end[-1]))
    --end;
  if (val == end)
    return false;

  switch (c_tolower (end[-1]))
    {
    case 'k':
      --end, mult = 1024.0;
      break;
    case 'm':
      --end, mult = 1048576.0;
      break;
    case 'g':
      --end, mult = 1073741824.0;
      break;
    case 't':
      --end, mult = 1099511627776.0;
      break;
    default:
      /* Not a recognized suffix: assume it's a digit.  (If not,
         simple_atof will raise an error.)  */
      mult = 1;
    }

  /* Skip leading and trailing whitespace. */
  while (val < end && c_isspace (*val))
    ++val;
  while (val < end && c_isspace (end[-1]))
    --end;
  if (val == end)
    return false;

  if (!simple_atof (val, end, &number) || number < 0)
    return false;

  *result = number * mult;
  return true;
}

Project: wget

test_cmd_spec_restrict_file_names(void)
{
  unsigned i;
  static const struct {
    const char *val;
    int expected_restrict_files_os;
    int expected_restrict_files_ctrl;
    int expected_restrict_files_case;
    bool result;
  } test_array[] = {
    { "windows", restrict_windows, true, restrict_no_case_restriction, true },
    { "windows,", restrict_windows, true, restrict_no_case_restriction, true },
    { "windows,lowercase", restrict_windows, true, restrict_lowercase, true },
    { "unix,nocontrol,lowercase,", restrict_unix, false, restrict_lowercase, true },
  };

  for (i = 0; i < countof(test_array); ++i)
    {
      bool res;

      defaults();
      res = cmd_spec_restrict_file_names ("dummy", test_array[i].val, NULL);

      /*
      fprintf (stderr, "test_cmd_spec_restrict_file_names: TEST %d\n", i); fflush (stderr);
      fprintf (stderr, "opt.restrict_files_os: %d\n",   opt.restrict_files_os); fflush (stderr);
      fprintf (stderr, "opt.restrict_files_ctrl: %d\n", opt.restrict_files_ctrl); fflush (stderr);
      fprintf (stderr, "opt.restrict_files_case: %d\n", opt.restrict_files_case); fflush (stderr);
      */
      mu_assert ("test_cmd_spec_restrict_file_names: wrong result",
                 res == test_array[i].result
                 && opt.restrict_files_os   == test_array[i].expected_restrict_files_os
                 && opt.restrict_files_ctrl == test_array[i].expected_restrict_files_ctrl
                 && opt.restrict_files_case == test_array[i].expected_restrict_files_case);
    }

  return NULL;
}

Project: wget

cmd_file (const char *com _GL_UNUSED, const char *val, void *place)
{
  char **pstring = (char **)place;

  xfree_null (*pstring);

  /* #### If VAL is empty, perhaps should set *PLACE to NULL.  */

  *pstring = xstrdup (val);

#if defined(WINDOWS) || defined(MSDOS)
  /* Convert "\" to "/". */
  {
    char *s;
    for (s = *pstring; *s; s++)
      if (*s == '\\')
        *s = '/';
  }
#endif
  return true;
}

Project: wget

setval_internal (int comind, const char *com, const char *val)
{
  assert (0 <= comind && ((size_t) comind) < countof (commands));
  DEBUGP (("Setting %s (%s) to %s\n", com, commands[comind].name, val));
  return commands[comind].action (com, val, commands[comind].place);
}

Project: wget

simple_atoi (const char *beg, const char *end, int *dest)
{
  int result = 0;
  bool negative = false;
  const char *p = beg;

  while (p < end && c_isspace (*p))
    ++p;
  if (p < end && (*p == '-' || *p == '+'))
    {
      negative = (*p == '-');
      ++p;
    }
  if (p == end)
    return false;

  /* Read negative numbers in a separate loop because the most
     negative integer cannot be represented as a positive number.  */

  if (!negative)
    for (; p < end && c_isdigit (*p); p++)
      {
        int next = (10 * result) + (*p - '0');
        if (next < result)
          return false;         /* overflow */
        result = next;
      }
  else
    for (; p < end && c_isdigit (*p); p++)
      {
        int next = (10 * result) - (*p - '0');
        if (next > result)
          return false;         /* underflow */
        result = next;
      }

  if (p != end)
    return false;

  *dest = result;
  return true;
}

Project: wget

home_dir (void)
{
  static char *buf = NULL;
  static char *home, *ret;

  if (!home)
    {
      home = getenv ("HOME");
      if (!home)
        {
#if defined(MSDOS)
          int len;

          /* Under MSDOS, if $HOME isn't defined, use the directory where
             `wget.exe' resides.  */
          const char *_w32_get_argv0 (void); /* in libwatt.a/pcconfig.c */
          char *p;

          buff = _w32_get_argv0 ();

          p = strrchr (buf, '/');            /* djgpp */
          if (!p)
            p = strrchr (buf, '\\');          /* others */
          assert (p);

          len = p - buff + 1;
          buff = strdup (_w32_get_argv0 ());

          home = buf;
#elif !defined(WINDOWS)
          /* If HOME is not defined, try getting it from the password
             file.  */
          struct passwd *pwd = getpwuid (getuid ());
          if (!pwd || !pwd->pw_dir)
            return NULL;
          home = pwd->pw_dir;
#else  /* !WINDOWS */
          /* Under Windows, if $HOME isn't defined, use the directory where
             `wget.exe' resides.  */
          home = ws_mypath ();
#endif /* WINDOWS */
        }
    }

  ret = home ? xstrdup (home) : NULL;
  free (buf);

  return ret;
}

Project: wget

wgetrc_user_file_name (void)
{
  char *home;
  char *file = NULL;
  /* If that failed, try $HOME/.wgetrc (or equivalent).  */

#ifdef __VMS
  file = "SYS$LOGIN:.wgetrc";
#else /* def __VMS */
  home = home_dir ();
  if (home)
    file = aprintf ("%s/.wgetrc", home);
  xfree_null (home);
#endif /* def __VMS [else] */

  if (!file)
    return NULL;
  if (!file_exists_p (file))
    {
      xfree (file);
      return NULL;
    }
  return file;
}

Project: wget

cmd_string (const char *com _GL_UNUSED, const char *val, void *place)
{
  char **pstring = (char **)place;

  xfree_null (*pstring);
  *pstring = xstrdup (val);
  return true;
}

Project: wget

cmd_spec_useragent (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  /* Disallow embedded newlines.  */
  if (strchr (val, '\n'))
    {
      fprintf (stderr, _("%s: %s: Invalid value %s.\n"),
               exec_name, com, quote (val));
      return false;
    }
  xfree_null (opt.useragent);
  opt.useragent = xstrdup (val);
  return true;
}

Project: wget

test_commands_sorted(void)
{
  unsigned i;

  for (i = 1; i < countof(commands); ++i)
    {
      if (strcasecmp (commands[i - 1].name, commands[i].name) > 0)
        {
          mu_assert ("FAILED", false);
          break;
        }
    }
  return NULL;
}

Project: wget

cmd_spec_timeout (const char *com, const char *val, void *place_ignored _GL_UNUSED)
{
  double value;
  if (!cmd_time (com, val, &value))
    return false;
  opt.read_timeout = value;
  opt.connect_timeout = value;
  opt.dns_timeout = value;
  return true;
}

Project: wget

initialize (void)
{
  char *file, *env_sysrc;
  bool ok = true;

  /* Run a non-standard system rc file when the according environment
     variable has been set. For internal testing purposes only!  */
  env_sysrc = getenv ("SYSTEM_WGETRC");
  if (env_sysrc && file_exists_p (env_sysrc))
    {
      ok &= run_wgetrc (env_sysrc);
      /* If there are any problems parsing the system wgetrc file, tell
         the user and exit */
      if (! ok)
        {
          fprintf (stderr, _("\
Parsing system wgetrc file (env SYSTEM_WGETRC) failed.  Please check\n\
'%s',\n\
or specify a different file using --config.\n"), env_sysrc);
          exit (WGET_EXIT_PARSE_ERROR);
        }
    }
  /* Otherwise, if SYSTEM_WGETRC is defined, use it.  */
#ifdef SYSTEM_WGETRC
  else if (file_exists_p (SYSTEM_WGETRC))
    ok &= run_wgetrc (SYSTEM_WGETRC);
  /* If there are any problems parsing the system wgetrc file, tell
     the user and exit */
  if (! ok)
    {
      fprintf (stderr, _("\
Parsing system wgetrc file failed.  Please check\n\
'%s',\n\
or specify a different file using --config.\n"), SYSTEM_WGETRC);
      exit (WGET_EXIT_PARSE_ERROR);
    }
#endif
  /* Override it with your own, if one exists.  */
  file = wgetrc_file_name ();
  if (!file)
    return;
  /* #### We should canonicalize `file' and SYSTEM_WGETRC with
     something like realpath() before comparing them with `strcmp'  */
#ifdef SYSTEM_WGETRC
  if (!strcmp (file, SYSTEM_WGETRC))
    {
      fprintf (stderr, _("\
%s: Warning: Both system and user wgetrc point to %s.\n"),
               exec_name, quote (file));
    }
  else
#endif
    ok &= run_wgetrc (file);

  /* If there were errors processing either `.wgetrc', abort. */
  if (!ok)
    exit (WGET_EXIT_PARSE_ERROR);

  xfree (file);
  return;
}

Project: wget

defaults (void)
{
  char *tmp;

  /* Most of the default values are 0 (and 0.0, NULL, and false).
     Just reset everything, and fill in the non-zero values.  Note
     that initializing pointers to NULL this way is technically
     illegal, but porting Wget to a machine where NULL is not all-zero
     bit pattern will be the least of the implementors' worries.  */
  xzero (opt);

  opt.cookies = true;
  opt.verbose = -1;
  opt.ntry = 20;
  opt.reclevel = 5;
  opt.add_hostdir = true;
  opt.netrc = true;
  opt.ftp_glob = true;
  opt.htmlify = true;
  opt.http_keep_alive = true;
  opt.use_proxy = true;
  tmp = getenv ("no_proxy");
  if (tmp)
    opt.no_proxy = sepstring (tmp);
  opt.prefer_family = prefer_none;
  opt.allow_cache = true;

  opt.read_timeout = 900;
  opt.use_robots = true;

  opt.remove_listing = true;

  opt.dot_bytes = 1024;
  opt.dot_spacing = 10;
  opt.dots_in_line = 50;

  opt.dns_cache = true;
  opt.ftp_pasv = true;
  /* 2014-09-07  Darshit Shah  <darnir@gmail.com>
   * opt.retr_symlinks is set to true by default. Creating symbolic links on the
   * local filesystem pose a security threat by malicious FTP Servers that
   * server a specially crafted .listing file akin to this:
   *
   * lrwxrwxrwx   1 root     root           33 Dec 25  2012 JoCxl6d8rFU -> /
   * drwxrwxr-x  15 1024     106          4096 Aug 28 02:02 JoCxl6d8rFU
   *
   * A .listing file in this fashion makes Wget susceptiple to a symlink attack
   * wherein the attacker is able to create arbitrary files, directories and
   * symbolic links on the target system and even set permissions.
   *
   * Hence, by default Wget attempts to retrieve the pointed-to files and does
   * not create the symbolic links locally.
   */
  opt.retr_symlinks = true;

#ifdef HAVE_SSL
  opt.check_cert = true;
#endif

  /* The default for file name restriction defaults to the OS type. */
#if defined(WINDOWS) || defined(MSDOS) || defined(__CYGWIN__)
  opt.restrict_files_os = restrict_windows;
#else
  opt.restrict_files_os = restrict_unix;
#endif
  opt.restrict_files_ctrl = true;
  opt.restrict_files_nonascii = false;
  opt.restrict_files_case = restrict_no_case_restriction;

  opt.regex_type = regex_type_posix;

  opt.max_redirect = 20;

  opt.waitretry = 10;

#ifdef ENABLE_IRI
  opt.enable_iri = true;
#else
  opt.enable_iri = false;
#endif
  opt.locale = NULL;
  opt.encoding_remote = NULL;

  opt.useservertimestamps = true;
  opt.show_all_dns_entries = false;

  opt.warc_maxsize = 0; /* 1024 * 1024 * 1024; */
#ifdef HAVE_LIBZ
  opt.warc_compression_enabled = true;
#else
  opt.warc_compression_enabled = false;
#endif
  opt.warc_digests_enabled = true;
  opt.warc_cdx_enabled = false;
  opt.warc_cdx_dedup_filename = NULL;
  opt.warc_tempdir = NULL;
  opt.warc_keep_log = true;

  /* Use a negative value to mark the absence of --start-pos option */
  opt.start_pos = -1;
  opt.show_progress = false;
  opt.noscroll = false;
}

Project: wget

run_command (const char *cmdopt)
{
  char *com, *val;
  int comind;
  switch (parse_line (cmdopt, &com, &val, &comind))
    {
    case line_ok:
      if (!setval_internal (comind, com, val))
        exit (WGET_EXIT_PARSE_ERROR);
      xfree (com);
      xfree (val);
      break;
    default:
      fprintf (stderr, _("%s: Invalid --execute command %s\n"),
               exec_name, quote (cmdopt));
      exit (WGET_EXIT_PARSE_ERROR);
    }
}

Project: wget

cmd_boolean (const char *com, const char *val, void *place)
{
  bool value;

  if (CMP2 (val, 'o', 'n') || CMP3 (val, 'y', 'e', 's') || CMP1 (val, '1'))
    /* "on", "yes" and "1" mean true. */
    value = true;
  else if (CMP3 (val, 'o', 'f', 'f') || CMP2 (val, 'n', 'o') || CMP1 (val, '0'))
    /* "off", "no" and "0" mean false. */
    value = false;
  else
    {
      fprintf (stderr,
               _("%s: %s: Invalid boolean %s; use `on' or `off'.\n"),
               exec_name, com, quote (val));
      return false;
    }

  *(bool *) place = value;
  return true;
}

Project: wget

cmd_time (const char *com, const char *val, void *place)
{
  double number, mult;
  const char *end = val + strlen (val);

  /* Strip trailing whitespace.  */
  while (val < end && c_isspace (end[-1]))
    --end;

  if (val == end)
    {
    err:
      fprintf (stderr, _("%s: %s: Invalid time period %s\n"),
               exec_name, com, quote (val));
      return false;
    }

  switch (c_tolower (end[-1]))
    {
    case 's':
      --end, mult = 1;          /* seconds */
      break;
    case 'm':
      --end, mult = 60;         /* minutes */
      break;
    case 'h':
      --end, mult = 3600;       /* hours */
      break;
    case 'd':
      --end, mult = 86400.0;    /* days */
      break;
    case 'w':
      --end, mult = 604800.0;   /* weeks */
      break;
    default:
      /* Not a recognized suffix: assume it belongs to the number.
         (If not, simple_atof will raise an error.)  */
      mult = 1;
    }

  /* Skip leading and trailing whitespace. */
  while (val < end && c_isspace (*val))
    ++val;
  while (val < end && c_isspace (end[-1]))
    --end;
  if (val == end)
    goto err;

  if (!simple_atof (val, end, &number))
    goto err;

  *(double *)place = number * mult;
  return true;
}

Project: wget

cmd_vector (const char *com _GL_UNUSED, const char *val, void *place)
{
  char ***pvec = (char ***)place;

  if (*val)
    *pvec = merge_vecs (*pvec, sepstring (val));
  else
    {
      free_vec (*pvec);
      *pvec = NULL;
    }
  return true;
}