Commit message: Fix a buffer overflow vulnerability

* src/iri.c(do_conversion): Reallocate the output buffer to a larger
  size if it is already full
CVE: CVE-2019-5953
CWE: CWE-787
Vulnerability description: Buffer overflow in GNU Wget 1.20.1 and earlier allows remote attackers to cause a denial-of-service (DoS) or may execute an arbitrary code via unspecified vectors.

Project: wget

check_encoding_name (const char *encoding)
{
  const char *s = encoding;

  while (*s)
    {
      if (!c_isascii (*s) || c_isspace (*s))
        {
          logprintf (LOG_VERBOSE, _("Encoding %s isn't valid\n"), quote (encoding));
          return false;
        }

      s++;
    }

  return true;
}

Project: wget

locale_to_utf8 (const char *str)
{
  char *new;

  /* That shouldn't happen, just in case */
  if (!opt.locale)
    {
      logprintf (LOG_VERBOSE, _("locale_to_utf8: locale is unset\n"));
      opt.locale = find_locale ();
    }

  if (!opt.locale || !c_strcasecmp (opt.locale, "utf-8"))
    return str;

  if (do_conversion ("UTF-8", opt.locale, (char *) str, strlen ((char *) str), &new))
    return (const char *) new;

  xfree (new);
  return str;
}

Project: wget

set_uri_encoding (struct iri *i, const char *charset, bool force)
{
  DEBUGP (("URI encoding = %s\n", charset ? quote (charset) : "None"));
  if (!force && opt.encoding_remote)
    return;
  if (i->uri_encoding)
    {
      if (charset && !c_strcasecmp (i->uri_encoding, charset))
        return;
      xfree (i->uri_encoding);
    }

  i->uri_encoding = charset ? xstrdup (charset) : NULL;
}

Project: wget

idn_encode (const struct iri *i, const char *host)
{
  int ret;
  char *ascii_encoded;
  char *utf8_encoded = NULL;
  const char *src;
#if IDN2_VERSION_NUMBER < 0x00140000
  uint8_t *lower;
  size_t len = 0;
#endif

  /* Encode to UTF-8 if not done */
  if (!i->utf8_encode)
    {
      if (!remote_to_utf8 (i, host, &utf8_encoded))
          return NULL;  /* Nothing to encode or an error occurred */
      src = utf8_encoded;
    }
  else
    src = host;

#if IDN2_VERSION_NUMBER >= 0x00140000
  /* IDN2_TRANSITIONAL implies input NFC encoding */
  ret = idn2_lookup_u8 ((uint8_t *) src, (uint8_t **) &ascii_encoded, IDN2_NONTRANSITIONAL);
  if (ret != IDN2_OK)
    /* fall back to TR46 Transitional mode, max IDNA2003 compatibility */
    ret = idn2_lookup_u8 ((uint8_t *) src, (uint8_t **) &ascii_encoded, IDN2_TRANSITIONAL);

  if (ret != IDN2_OK)
    logprintf (LOG_VERBOSE, _("idn_encode failed (%d): %s\n"), ret,
               quote (idn2_strerror (ret)));
#else
  /* we need a conversion to lowercase */
  lower = u8_tolower ((uint8_t *) src, u8_strlen ((uint8_t *) src) + 1, 0, UNINORM_NFKC, NULL, &len);
  if (!lower)
    {
      logprintf (LOG_VERBOSE, _("Failed to convert to lower: %d: %s\n"),
                 errno, quote (src));
      xfree (utf8_encoded);
      return NULL;
    }

  if ((ret = idn2_lookup_u8 (lower, (uint8_t **) &ascii_encoded, IDN2_NFC_INPUT)) != IDN2_OK)
    {
      logprintf (LOG_VERBOSE, _("idn_encode failed (%d): %s\n"), ret,
                 quote (idn2_strerror (ret)));
    }

  xfree (lower);
#endif

  xfree (utf8_encoded);

  if (ret == IDN2_OK && ascii_encoded)
    {
      char *tmp = xstrdup (ascii_encoded);
      idn2_free (ascii_encoded);
      ascii_encoded = tmp;
    }

  return ret == IDN2_OK ? ascii_encoded : NULL;
}

Project: wget

idn_decode (const char *host)
{
/*
  char *new;
  int ret;

  ret = idn2_register_u8 (NULL, host, (uint8_t **) &new, 0);
  if (ret != IDN2_OK)
    {
      logprintf (LOG_VERBOSE, _("idn2_register_u8 failed (%d): %s: %s\n"), ret,
                 quote (idn2_strerror (ret)), host);
      return NULL;
    }

  return new;
*/
  /* idn2_register_u8() just works label by label.
   * That is pretty much overhead for just displaying the original ulabels.
   * To keep at least the debug output format, return a cloned host. */
  return xstrdup(host);
}

Project: wget

do_conversion (const char *tocode _GL_UNUSED, const char *fromcode _GL_UNUSED,
               char const *in_org _GL_UNUSED, size_t inlen _GL_UNUSED, char **out)
{
  *out = NULL;
  return false;
}

Project: wget

iri_new (void)
{
  struct iri *i = xmalloc (sizeof *i);
  i->uri_encoding = opt.encoding_remote ? xstrdup (opt.encoding_remote) : NULL;
  i->content_encoding = NULL;
  i->orig_url = NULL;
  i->utf8_encode = opt.enable_iri;
  return i;
}

Project: wget

iri_free (struct iri *i)
{
  if (i)
    {
      xfree (i->uri_encoding);
      xfree (i->content_encoding);
      xfree (i->orig_url);
      xfree (i);
    }
}

Project: wget

find_locale (void)
{
	const char *encoding = nl_langinfo(CODESET);

	if (!encoding || !*encoding)
		return xstrdup("ASCII");

   return xstrdup(encoding);
}

Project: wget

struct iri *iri_dup (const struct iri *src)
{
  struct iri *i = xmalloc (sizeof *i);
  i->uri_encoding = src->uri_encoding ? xstrdup (src->uri_encoding) : NULL;
  i->content_encoding = (src->content_encoding ?
                         xstrdup (src->content_encoding) : NULL);
  i->orig_url = src->orig_url ? xstrdup (src->orig_url) : NULL;
  i->utf8_encode = src->utf8_encode;
  return i;
}

Project: wget

parse_charset (const char *str)
{
  const char *end;
  char *charset;

  if (!str || !*str)
    return NULL;

  str = c_strcasestr (str, "charset=");
  if (!str)
    return NULL;

  str += 8;
  end = str;

  /* sXXXav: which chars should be banned ??? */
  while (*end && !c_isspace (*end))
    end++;

  /* sXXXav: could strdupdelim return NULL ? */
  charset = strdupdelim (str, end);

  /* Do a minimum check on the charset value */
  if (!check_encoding_name (charset))
    {
      xfree (charset);
      return NULL;
    }

  /*logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));*/

  return charset;
}

Project: wget

remote_to_utf8 (const struct iri *iri, const char *str, char **new)
{
  bool ret = false;

  if (!iri->uri_encoding)
    return false;

  /* When `i->uri_encoding' == "UTF-8" there is nothing to convert.  But we must
     test for non-ASCII symbols for correct hostname processing in `idn_encode'
     function. */
  if (!c_strcasecmp (iri->uri_encoding, "UTF-8"))
    {
      const unsigned char *p;
      for (p = (unsigned char *) str; *p; p++)
        if (*p > 127)
          {
            *new = strdup (str);
            return true;
          }
      return false;
    }

  if (do_conversion ("UTF-8", iri->uri_encoding, str, strlen (str), new))
    ret = true;

  /* Test if something was converted */
  if (*new && !strcmp (str, *new))
    {
      xfree (*new);
      return false;
    }

  return ret;
}

Project: wget

set_content_encoding (struct iri *i, const char *charset)
{
  DEBUGP (("URI content encoding = %s\n", charset ? quote (charset) : "None"));
  if (opt.encoding_remote)
    return;
  if (i->content_encoding)
    {
      if (charset && !c_strcasecmp (i->content_encoding, charset))
        return;
      xfree (i->content_encoding);
    }

  i->content_encoding = charset ? xstrdup (charset) : NULL;
}