/**
 * @brief Checks if a buffer points to a valid short IPv6 range-expressed
 * network. "::200:ff:1-fee5" is valid.
 *
 * @param   str String to check in.
 *
 * @return 1 if str points to a valid short-range IPv6 network, 0 otherwise.
 */
static int
is_short_range6_network (const char *str)
{
  char *ip_str, *end_str, *p;

  ip_str = g_strdup (str);
  end_str = strchr (ip_str, '-');
  if (end_str == NULL)
    {
      g_free (ip_str);
      return 0;
    }

  /* Separate the addresses. */
  *end_str = '\0';
  end_str++;

  if (!is_ipv6_address (ip_str) || *end_str == '\0')
    {
      g_free (ip_str);
      return 0;
    }

  p = end_str;
  /* Check that the 2nd part is at most 4 hexadecimal characters. */
  while (isxdigit (*p) && p++);
  if (*p || p - end_str > 4)
    {
      g_free (ip_str);
      return 0;
    }

  g_free (ip_str);
  return 1;
}
/**
 * @brief Checks if a buffer points to an IPv6 CIDR-exprpessed block.
 * "2620:0:2d0:200::7/120" is valid, "2620:0:2d0:200::7/129" is not.
 *
 * @param[in]   str Buffer to check in.
 *
 * @return 1 if valid IPv6 CIDR-expressed block, 0 otherwise.
 */
static int
is_cidr6_block (const char *str)
{
  long block;
  char *addr6_str, *block_str, *p;

  addr6_str = g_strdup (str);
  block_str = strchr (addr6_str, '/');
  if (block_str == NULL)
    {
      g_free (addr6_str);
      return 0;
    }

  /* Separate the address from the block value. */
  *block_str = '\0';
  block_str++;

  if (!is_ipv6_address (addr6_str) || !isdigit (*block_str))
    {
      g_free (addr6_str);
      return 0;
    }

  p = NULL;
  block = strtol (block_str, &p, 10);
  g_free (addr6_str);

  if (*p || block <= 0 || block > 128)
    return 0;

  return 1;
}
/**
 * @brief Determines the host type in a buffer.
 *
 * @param[in] str   Buffer that contains host definition, could a be hostname,
 *                  single IPv4 or IPv6, CIDR-expressed block etc,.
 *
 * @return Host_TYPE_*, -1 if error.
 */
static int
determine_host_type (const gchar *str_stripped)
{
  /*
   * We have a single element with no leading or trailing
   * white spaces. This element could represent different host
   * definitions: single IPs, host names, CIDR-expressed blocks,
   * range-expressed networks, IPv6 addresses.
   */

  /* Null or empty string. */
  if (str_stripped == NULL || *str_stripped == '\0')
    return -1;

  /* Check for regular single IPv4 address. */
  if (is_ipv4_address (str_stripped))
    return HOST_TYPE_IPV4;

  /* Check for regular single IPv6 address. */
  if (is_ipv6_address (str_stripped))
    return HOST_TYPE_IPV6;

  /* Check for regular IPv4 CIDR-expressed block like "192.168.12.0/24" */
  if (is_cidr_block (str_stripped))
    return HOST_TYPE_CIDR_BLOCK;

  /* Check for short range-expressed networks "192.168.12.5-40" */
  if (is_short_range_network (str_stripped))
    return HOST_TYPE_RANGE_SHORT;

  /* Check for long range-expressed networks "192.168.1.0-192.168.3.44" */
  if (is_long_range_network (str_stripped))
    return HOST_TYPE_RANGE_LONG;

  /* Check for regular IPv6 CIDR-expressed block like "2620:0:2d0:200::7/120" */
  if (is_cidr6_block (str_stripped))
    return HOST_TYPE_CIDR6_BLOCK;

  /* Check for short range-expressed networks "::1-ef12" */
  if (is_short_range6_network (str_stripped))
    return HOST_TYPE_RANGE6_SHORT;

  /* Check for long IPv6 range-expressed networks like "::1:20:7-::1:25:3" */
  if (is_long_range6_network (str_stripped))
    return HOST_TYPE_RANGE6_LONG;

  /* Check for hostname. */
  if (is_hostname (str_stripped))
    return HOST_TYPE_NAME;

  /* @todo: If everything else fails, fallback to hostname ? */
  return -1;
}
/**
 * @brief Checks if a buffer points to a valid long IPv6 range-expressed
 * network. "::fee5-::1:530" is valid.
 *
 * @param[in]   str Buffer to check in.
 *
 * @return 1 if valid long range-expressed network, 0 otherwise.
 */
static int
is_long_range6_network (const char *str)
{
  char *first_str, *second_str;
  int ret;

  first_str = g_strdup (str);
  second_str = strchr (first_str, '-');
  if (second_str == NULL)
    {
      g_free (first_str);
      return 0;
    }

  /* Separate the addreses. */
  *second_str = '\0';
  second_str++;

  ret = is_ipv6_address (first_str) && is_ipv6_address (second_str);
  g_free (first_str);

  return ret;
}
struct url_breakout *parse_url_string( char *url)

{
    int rc = RC_NORMAL, stlen, port_number = NO_PORT;
    char *copy = 0, *protocol = 0, *user = 0, *domain = 0, *port = 0,
      *uri = 0, *query = 0, *pos = 0, *en_protocol = 0, *en_user = 0, 
      *en_domain = 0, *st_port = 0, *st_query = 0, *next = 0, *scan_from = 0,
      *st_ipv6_port = 0, hold;
    struct url_breakout *parts = 0;

    copy = strdup( url);
    if( !copy) rc = ERR_MALLOC_FAILED;

    if( rc == RC_NORMAL)
    {
        parts = alloc_url_breakout();
        if( !parts) rc = ERR_MALLOC_FAILED;
    }

    if( rc == RC_NORMAL)
    {
        scan_from = pos = copy;
        next = copy + strlen( copy);

        en_protocol = strstr( scan_from, PROTOCOL_DELIM);
        if( en_protocol)
        {
            scan_from = en_protocol + strlen( PROTOCOL_DELIM);
            next = en_protocol;
	}

        en_user = strstr( scan_from, USER_DELIM);
        en_domain = strstr( scan_from, DOMAIN_DELIM);

        if( en_user)
        {
            if( !en_domain) scan_from = en_user + 1;
            else if( en_user < en_domain) scan_from = en_user + 1;
	}

        st_port = strstr( scan_from, PORT_DELIM);
        st_query = strstr( scan_from, QUERY_DELIM);

        st_ipv6_port = strstr( scan_from, IPV6_PORT_DELIM);
        if( st_ipv6_port)
        {
            if( en_domain)
            {
/* fixes the case:  http://2601:9:5480:7d:59a8:fa5e:a8d2:c72d#8080/something.html */
/* also allows:     http://www.foo.com#8080/something.html */
                if( st_ipv6_port < en_domain) st_port = st_ipv6_port;
	    }
            else if( st_query)
            {
/* fixes the case:  http://2601:9:5480:7d:59a8:fa5e:a8d2:c72d#8080?data=some */
/* also allows:     http://www.foo.com#8080?data=some */
                if( st_ipv6_port < st_query) st_port = st_ipv6_port;
	    }
/* fixes the case:  http://2601:9:5480:7d:59a8:fa5e:a8d2:c72d#8080 */
/* also allows:     http://www.foo.com#8080 */
            else st_port = st_ipv6_port;
	}

        if( en_user) if( en_user < next) next = en_user;
        if( en_domain) if( en_domain < next) next = en_domain;
        if( st_port) if( st_port < next) next = st_port;
	if( st_query) if( st_query < next) next = st_query;

    }

    if( rc == RC_NORMAL && next == en_protocol)
    {
        hold = *en_protocol;
        *en_protocol = '\0';
        protocol = strdup( pos);
        *en_protocol = hold;
        if( !protocol) rc = ERR_MALLOC_FAILED;
        else
        {
            pos = en_protocol + strlen( PROTOCOL_DELIM);

            next = pos + strlen( pos);
            if( en_user) if( en_user < next) next = en_user;
            if( en_domain) if( en_domain < next) next = en_domain;
            if( st_port) if( st_port < next) next = st_port;
            if( st_query) if( st_query < next) next = st_query;
        }
    }

    if( rc == RC_NORMAL && next == en_user)
    {
        hold = *en_user;
        *en_user = '******';
        user = strdup( pos);
        *en_user = hold;
        if( !user) rc = ERR_MALLOC_FAILED;
        pos = en_user + 1;

        next = next + strlen( next);
        if( st_port) if( st_port < next) next = st_port;
        if( en_domain) if( en_domain < next) next = en_domain;
        if( st_query) if( st_query < next) next = st_query;
    }

    if( rc == RC_NORMAL && next == st_port)
    {
        /* This is annoying, but we need to see if the ":" we found was part
         * of an IPV6 address and not a ":port" suffixs on a domain or IPV4
         * address.
         */
        if( en_domain)
        {
            hold = *en_domain;
            *en_domain = '\0';
	}

        if( is_ipv6_address( pos))
        {
            st_port = 0;

            domain = strdup( pos);
            if( !domain) rc = ERR_MALLOC_FAILED;

            if( en_domain)
            {
                *en_domain = hold;
                pos = en_domain;
	    }
            else pos = pos + strlen( pos);

            next = pos;
	}
        else
        {
            if( en_domain) *en_domain = hold;

            hold = *st_port;
            *st_port = '\0';
            domain = strdup( pos);
            *st_port = hold;
            if( !domain) rc = ERR_MALLOC_FAILED;
            else
            {
                stlen = strlen( PORT_DELIM);
                pos = st_port + stlen;

                next = next + strlen( next);
                if( en_domain) if( en_domain < next) next = en_domain;
                if( st_query) if( st_query < next) next = st_query;

                hold = *next;
                *next = '\0';
                port = strdup( pos);
                *next = hold;
                if( !port) rc = ERR_MALLOC_FAILED;

                pos = next;
            }
	}
    }

    if( rc == RC_NORMAL && next == en_domain)
    {
        if( !domain)
        {
            hold = *en_domain;
            *en_domain = '\0';
            domain = strdup( pos);
            *en_domain = hold;
            if( !domain) rc = ERR_MALLOC_FAILED;
            pos = en_domain;
        }

        if( rc == RC_NORMAL)
        {
            if( !st_query)
            {
                uri = strdup( pos);
                pos = pos + strlen( pos);
                next = pos;
	    }
            else
            {
                hold = *st_query;
                *st_query = '\0';
                uri = strdup( pos);
                *st_query = hold;
                next = pos = st_query;
	    }

            if( !uri) rc = ERR_MALLOC_FAILED;
	}
    }
            
    if( rc == RC_NORMAL && next == st_query)
    {
        if( !domain)
        {
            hold = *st_query;
            *st_query = '\0';
            domain = strdup( pos);
            *st_query = hold;
            if( !domain) rc = ERR_MALLOC_FAILED;
	}

        if( rc == RC_NORMAL)
        {
            query = strdup( st_query + 1);
            if( !query) rc = ERR_MALLOC_FAILED;
            pos = pos + strlen( pos);
            next = pos;
	}
    }

    if( rc == RC_NORMAL && !domain && !*next && *pos)
    {
        domain = strdup( pos);
        if( !domain) rc = ERR_MALLOC_FAILED;
        pos = next;
    }

    if( rc == RC_NORMAL && port)
    {
        if( !isdigit( *port)) rc = ERR_INVALID_DATA;
        else
        {
            errno = 0;
            port_number = strtoul( port, &pos, BASE10);
            if( errno) rc = ERR_INVALID_DATA;
            else if( *pos) rc = ERR_INVALID_DATA;
	}
    }

    if( rc == RC_NORMAL && domain)
    {
        pos = strdup( domain);
        if( !pos) rc = ERR_MALLOC_FAILED;
        else if( is_ipv4_address( domain)) parts->ip4 = pos;
        else if( is_ipv6_address( domain)) parts->ip6 = pos;
        else parts->host = pos;
    }

    if( rc == RC_NORMAL)
    {
        if( !domain) parts->status = URL_ERROR;
        else if( !*domain) parts->status = URL_ERROR;
        else
        {
            parts->status = URL_VALID;
            parts->port = port_number;
            parts->protocol = protocol;
            parts->user = user;
            parts->target = domain;
            parts->uri = uri;
            parts->query = query;
            if( protocol) parts->use_ssl = !strcasecmp( protocol, SSL_PROT_PREFIX);

            protocol = user = domain = uri = query = 0;
	}
    }
    else if( parts)
    {
        parts->status = URL_ERROR;
    }

    if( protocol) free( protocol);
    if( user) free( user);
    if( domain) free( domain);
    if( uri) free( uri);
    if( query) free( query);
    if( port) free( port);
    if( copy) free( copy);

    return( parts);
}