/* Copyright libuv contributors. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* Derived from https://github.com/bnoordhuis/punycode
 * but updated to support IDNA 2008.
 */

#include "uv.h"
#include "uv-common.h"
#include "idna.h"
#include <assert.h>
#include <string.h>
#include <limits.h> /* UINT_MAX */


static int32_t uv__wtf8_decode1(const char** input) {
  uint32_t code_point;
  uint8_t b1;
  uint8_t b2;
  uint8_t b3;
  uint8_t b4;

  b1 = **input;
  if (b1 <= 0x7F)
    return b1; /* ASCII code point */
  if (b1 < 0xC2)
    return -1; /* invalid: continuation byte */
  code_point = b1;

  b2 = *++*input;
  if ((b2 & 0xC0) != 0x80)
    return -1; /* invalid: not a continuation byte */
  code_point = (code_point << 6) | (b2 & 0x3F);
  if (b1 <= 0xDF)
    return 0x7FF & code_point; /* two-byte character */

  b3 = *++*input;
  if ((b3 & 0xC0) != 0x80)
    return -1; /* invalid: not a continuation byte */
  code_point = (code_point << 6) | (b3 & 0x3F);
  if (b1 <= 0xEF)
    return 0xFFFF & code_point; /* three-byte character */

  b4 = *++*input;
  if ((b4 & 0xC0) != 0x80)
    return -1; /* invalid: not a continuation byte */
  code_point = (code_point << 6) | (b4 & 0x3F);
  if (b1 <= 0xF4) {
    code_point &= 0x1FFFFF;
    if (code_point <= 0x10FFFF)
      return code_point; /* four-byte character */
  }

  /* code point too large */
  return -1;
}


static unsigned uv__utf8_decode1_slow(const char** p,
                                      const char* pe,
                                      unsigned a) {
  unsigned b;
  unsigned c;
  unsigned d;
  unsigned min;

  if (a > 0xF7)
    return -1;

  switch (pe - *p) {
  default:
    if (a > 0xEF) {
      min = 0x10000;
      a = a & 7;
      b = (unsigned char) *(*p)++;
      c = (unsigned char) *(*p)++;
      d = (unsigned char) *(*p)++;
      break;
    }
    /* Fall through. */
  case 2:
    if (a > 0xDF) {
      min = 0x800;
      b = 0x80 | (a & 15);
      c = (unsigned char) *(*p)++;
      d = (unsigned char) *(*p)++;
      a = 0;
      break;
    }
    /* Fall through. */
  case 1:
    if (a > 0xBF) {
      min = 0x80;
      b = 0x80;
      c = 0x80 | (a & 31);
      d = (unsigned char) *(*p)++;
      a = 0;
      break;
    }
    /* Fall through. */
  case 0:
    return -1;  /* Invalid continuation byte. */
  }

  if (0x80 != (0xC0 & (b ^ c ^ d)))
    return -1;  /* Invalid sequence. */

  b &= 63;
  c &= 63;
  d &= 63;
  a = (a << 18) | (b << 12) | (c << 6) | d;

  if (a < min)
    return -1;  /* Overlong sequence. */

  if (a > 0x10FFFF)
    return -1;  /* Four-byte sequence > U+10FFFF. */

  if (a >= 0xD800 && a <= 0xDFFF)
    return -1;  /* Surrogate pair. */

  return a;
}


unsigned uv__utf8_decode1(const char** p, const char* pe) {
  unsigned a;

  assert(*p < pe);

  a = (unsigned char) *(*p)++;

  if (a < 128)
    return a;  /* ASCII, common case. */

  return uv__utf8_decode1_slow(p, pe, a);
}


static int uv__idna_toascii_label(const char* s, const char* se,
                                  char** d, char* de) {
  static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789";
  const char* ss;
  unsigned c;
  unsigned h;
  unsigned k;
  unsigned n;
  unsigned m;
  unsigned q;
  unsigned t;
  unsigned x;
  unsigned y;
  unsigned bias;
  unsigned delta;
  unsigned todo;
  int first;

  h = 0;
  ss = s;
  todo = 0;

  /* Note: after this loop we've visited all UTF-8 characters and know
   * they're legal so we no longer need to check for decode errors.
   */
  while (s < se) {
    c = uv__utf8_decode1(&s, se);

    if (c == UINT_MAX)
      return UV_EINVAL;

    if (c < 128)
      h++;
    else
      todo++;
  }

  /* Only write "xn--" when there are non-ASCII characters. */
  if (todo > 0) {
    if (*d < de) *(*d)++ = 'x';
    if (*d < de) *(*d)++ = 'n';
    if (*d < de) *(*d)++ = '-';
    if (*d < de) *(*d)++ = '-';
  }

  /* Write ASCII characters. */
  x = 0;
  s = ss;
  while (s < se) {
    c = uv__utf8_decode1(&s, se);
    assert(c != UINT_MAX);

    if (c > 127)
      continue;

    if (*d < de)
      *(*d)++ = c;

    if (++x == h)
      break;  /* Visited all ASCII characters. */
  }

  if (todo == 0)
    return h;

  /* Only write separator when we've written ASCII characters first. */
  if (h > 0)
    if (*d < de)
      *(*d)++ = '-';

  n = 128;
  bias = 72;
  delta = 0;
  first = 1;

  while (todo > 0) {
    m = -1;
    s = ss;

    while (s < se) {
      c = uv__utf8_decode1(&s, se);
      assert(c != UINT_MAX);

      if (c >= n)
        if (c < m)
          m = c;
    }

    x = m - n;
    y = h + 1;

    if (x > ~delta / y)
      return UV_E2BIG;  /* Overflow. */

    delta += x * y;
    n = m;

    s = ss;
    while (s < se) {
      c = uv__utf8_decode1(&s, se);
      assert(c != UINT_MAX);

      if (c < n)
        if (++delta == 0)
          return UV_E2BIG;  /* Overflow. */

      if (c != n)
        continue;

      for (k = 36, q = delta; /* empty */; k += 36) {
        t = 1;

        if (k > bias)
          t = k - bias;

        if (t > 26)
          t = 26;

        if (q < t)
          break;

        /* TODO(bnoordhuis) Since 1 <= t <= 26 and therefore
         * 10 <= y <= 35, we can optimize the long division
         * into a table-based reciprocal multiplication.
         */
        x = q - t;
        y = 36 - t;  /* 10 <= y <= 35 since 1 <= t <= 26. */
        q = x / y;
        t = t + x % y;  /* 1 <= t <= 35 because of y. */

        if (*d < de)
          *(*d)++ = alphabet[t];
      }

      if (*d < de)
        *(*d)++ = alphabet[q];

      delta /= 2;

      if (first) {
        delta /= 350;
        first = 0;
      }

      /* No overflow check is needed because |delta| was just
       * divided by 2 and |delta+delta >= delta + delta/h|.
       */
      h++;
      delta += delta / h;

      for (bias = 0; delta > 35 * 26 / 2; bias += 36)
        delta /= 35;

      bias += 36 * delta / (delta + 38);
      delta = 0;
      todo--;
    }

    delta++;
    n++;
  }

  return 0;
}


ssize_t uv__idna_toascii(const char* s, const char* se, char* d, char* de) {
  const char* si;
  const char* st;
  unsigned c;
  char* ds;
  int rc;

  if (s == se)
    return UV_EINVAL;

  ds = d;

  si = s;
  while (si < se) {
    st = si;
    c = uv__utf8_decode1(&si, se);

    if (c == UINT_MAX)
      return UV_EINVAL;

    if (c != '.')
      if (c != 0x3002)  /* 。 */
        if (c != 0xFF0E)  /* ． */
          if (c != 0xFF61)  /* ｡ */
            continue;

    rc = uv__idna_toascii_label(s, st, &d, de);

    if (rc < 0)
      return rc;

    if (d < de)
      *d++ = '.';

    s = si;
  }

  if (s < se) {
    rc = uv__idna_toascii_label(s, se, &d, de);

    if (rc < 0)
      return rc;
  }

  if (d >= de)
    return UV_EINVAL;

  *d++ = '\0';
  return d - ds;  /* Number of bytes written. */
}


ssize_t uv_wtf8_length_as_utf16(const char* source_ptr) {
  size_t w_target_len = 0;
  int32_t code_point;

  do {
    code_point = uv__wtf8_decode1(&source_ptr);
    if (code_point < 0)
      return -1;
    if (code_point > 0xFFFF)
      w_target_len++;
    w_target_len++;
  } while (*source_ptr++);

  return w_target_len;
}


void uv_wtf8_to_utf16(const char* source_ptr,
                      uint16_t* w_target,
                      size_t w_target_len) {
  int32_t code_point;

  do {
    code_point = uv__wtf8_decode1(&source_ptr);
    /* uv_wtf8_length_as_utf16 should have been called and checked first. */
    assert(code_point >= 0);
    if (code_point > 0x10000) {
      assert(code_point < 0x10FFFF);
      *w_target++ = (((code_point - 0x10000) >> 10) + 0xD800);
      *w_target++ = ((code_point - 0x10000) & 0x3FF) + 0xDC00;
      w_target_len -= 2;
    } else {
      *w_target++ = code_point;
      w_target_len -= 1;
    }
  } while (*source_ptr++);

  (void)w_target_len;
  assert(w_target_len == 0);
}


static int32_t uv__get_surrogate_value(const uint16_t* w_source_ptr,
                                       ssize_t w_source_len) {
  uint16_t u;
  uint16_t next;

  u = w_source_ptr[0];
  if (u >= 0xD800 && u <= 0xDBFF && w_source_len != 1) {
    next = w_source_ptr[1];
    if (next >= 0xDC00 && next <= 0xDFFF)
      return 0x10000 + ((u - 0xD800) << 10) + (next - 0xDC00);
  }
  return u;
}


size_t uv_utf16_length_as_wtf8(const uint16_t* w_source_ptr,
                               ssize_t w_source_len) {
  size_t target_len;
  int32_t code_point;

  target_len = 0;
  while (w_source_len) {
    code_point = uv__get_surrogate_value(w_source_ptr, w_source_len);
    /* Can be invalid UTF-8 but must be valid WTF-8. */
    assert(code_point >= 0);
    if (w_source_len < 0 && code_point == 0)
      break;
    if (code_point < 0x80)
      target_len += 1;
    else if (code_point < 0x800)
      target_len += 2;
    else if (code_point < 0x10000)
      target_len += 3;
    else {
      target_len += 4;
      w_source_ptr++;
      if (w_source_len > 0)
        w_source_len--;
    }
    w_source_ptr++;
    if (w_source_len > 0)
      w_source_len--;
  }

  return target_len;
}


int uv_utf16_to_wtf8(const uint16_t* w_source_ptr,
                     ssize_t w_source_len,
                     char** target_ptr,
                     size_t* target_len_ptr) {
  size_t target_len;
  char* target;
  char* target_end;
  int32_t code_point;

  /* If *target_ptr is provided, then *target_len_ptr must be its length
   * (excluding space for NUL), otherwise we will compute the target_len_ptr
   * length and may return a new allocation in *target_ptr if target_ptr is
   * provided. */
  if (target_ptr == NULL || *target_ptr == NULL) {
    target_len = uv_utf16_length_as_wtf8(w_source_ptr, w_source_len);
    if (target_len_ptr != NULL)
      *target_len_ptr = target_len;
  } else {
    target_len = *target_len_ptr;
  }

  if (target_ptr == NULL)
    return 0;

  if (*target_ptr == NULL) {
    target = uv__malloc(target_len + 1);
    if (target == NULL) {
      return UV_ENOMEM;
    }
    *target_ptr = target;
  } else {
    target = *target_ptr;
  }

  target_end = target + target_len;

  while (target != target_end && w_source_len) {
    code_point = uv__get_surrogate_value(w_source_ptr, w_source_len);
    /* Can be invalid UTF-8 but must be valid WTF-8. */
    assert(code_point >= 0);
    if (w_source_len < 0 && code_point == 0) {
      w_source_len = 0;
      break;
    }
    if (code_point < 0x80) {
      *target++ = code_point;
    } else if (code_point < 0x800) {
      *target++ = 0xC0 | (code_point >> 6);
      if (target == target_end)
        break;
      *target++ = 0x80 | (code_point & 0x3F);
    } else if (code_point < 0x10000) {
      *target++ = 0xE0 | (code_point >> 12);
      if (target == target_end)
        break;
      *target++ = 0x80 | ((code_point >> 6) & 0x3F);
      if (target == target_end)
        break;
      *target++ = 0x80 | (code_point & 0x3F);
    } else {
      *target++ = 0xF0 | (code_point >> 18);
      if (target == target_end)
        break;
      *target++ = 0x80 | ((code_point >> 12) & 0x3F);
      if (target == target_end)
        break;
      *target++ = 0x80 | ((code_point >> 6) & 0x3F);
      if (target == target_end)
        break;
      *target++ = 0x80 | (code_point & 0x3F);
      /* uv__get_surrogate_value consumed 2 input characters */
      w_source_ptr++;
      if (w_source_len > 0)
        w_source_len--;
    }
    target_len = target - *target_ptr;
    w_source_ptr++;
    if (w_source_len > 0)
      w_source_len--;
  }

  if (target != target_end && target_len_ptr != NULL)
    /* Did not fill all of the provided buffer, so update the target_len_ptr
     * output with the space used. */
    *target_len_ptr = target - *target_ptr;

  /* Check if input fit into target exactly. */
  if (w_source_len < 0 && target == target_end && w_source_ptr[0] == 0)
    w_source_len = 0;

  *target++ = '\0';

  /* Characters remained after filling the buffer, compute the remaining length now. */
  if (w_source_len) {
    if (target_len_ptr != NULL)
      *target_len_ptr = target_len + uv_utf16_length_as_wtf8(w_source_ptr, w_source_len);
    return UV_ENOBUFS;
  }

  return 0;
}
