text: /*
   * This file is part of the Sofia-SIP package
   *
   * Copyright (C) 2005,2006 Nokia Corporation.
   *
   * Contact: Pekka Pessi <pekka.pessi@nokia.com>
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public License
   * as published by the Free Software Foundation; either version 2.1 of
   * the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
   * 02110-1301 USA
   *
   */
  
  /**@CFILE bnf.c
   * @brief Character syntax table for HTTP-like protocols.
   *
   * @author Pekka Pessi <Pekka.Pessi@nokia.com>
   * @author Kai Vehmanen <Kai.Vehmanen@nokia.com>
   *
   * @date Created: Thu Jun  8 19:28:55 2000 ppessi
   */
  
  #include "config.h"
  
  #include "sofia-sip/bnf.h"
  #include "sofia-sip/su_string.h"
  
  #include <stdio.h>
  #include <assert.h>
  
  #define ws    bnf_ws
  #define crlf  bnf_crlf
  #define alpha bnf_alpha
  #define digit bnf_mark|bnf_token0|bnf_safe
  #define sep   bnf_separator
  #define msep  bnf_mark|bnf_separator
  #define psep  bnf_param0|bnf_separator
  #define tok   bnf_token0
  #define mtok  bnf_mark|bnf_token0
  #define smtok bnf_mark|bnf_token0|bnf_safe
  #define safe  bnf_safe
  
  /** Table for determining class of a character */
  unsigned char const _bnf_table[256] = {
    0,     0,     0,     0,     0,     0,     0,     0,
    0,     ws,    crlf,  0,     0,     crlf,  0,     0,
    0,     0,     0,     0,     0,     0,     0,     0,
    0,     0,     0,     0,     0,     0,     0,     0,
    ws,    mtok,  sep,   0,     safe,  mtok,  0,     mtok,  /*  !"#$%&' */
    msep,  msep,  mtok,  tok,   sep,   smtok, smtok, psep,  /* ()*+,-./ */
    digit, digit, digit, digit, digit, digit, digit, digit, /* 01234567 */
    digit, digit, psep,  sep,   sep,   sep,   sep,   sep,   /* 89:;<=>? */
    sep,   alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* @ABCDEFG */
    alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* HIJKLMNO */
    alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* PQRSTUVW */
    alpha, alpha, alpha, psep,  sep,   psep,  0,     smtok, /* XYZ[\]^_ */
    tok,   alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* `abcdefg */
    alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* hijklmno */
    alpha, alpha, alpha, alpha, alpha, alpha, alpha, alpha, /* pqrstuvw */
    alpha, alpha, alpha, sep,   0,     sep,   mtok,  0,     /* xyz{|}~  */
  };
  
  #if 0				/* This escaped lab */
  
  #define BM(c, m00, m32, m64, m96)			   \
    ((c < 64)						   \
     ? ((c < 32)						   \
        ? (m00 & (1 << (31 - c)))				   \
        : (m32 & (1 << (63 - c))))			   \
     : ((c < 96)						   \
        ? (m64 & (1 << (95 - c)))				   \
        : (m96 & (1 << (127 - c)))))
  
  /** Span of a token */
  size_t bnf_span_token(char const *s)
  {
    char const *e = s;
    unsigned const m32 = 0x4536FFC0U, m64 = 0x7FFFFFE1U, m96 = 0xFFFFFFE2U;
  
    while (BM(*e, 0, m32, m64, m96))
      e++;
  
    return e - s;
  }
  
  /** Span of a token */
  size_t bnf_span_token4(char const *s)
  {
    char const *e = s;
    while (_bnf_table[(unsigned char)(*e)] & bnf_token)
      e++;
    return e - s;
  }
  
  char * bnf_span_token_end(char const *s)
  {
    return (char *)s;
  }
  #endif
  
  /** Return length of decimal-octet */
  su_inline int span_ip4_octet(char const *host)
  {
    /*
        decimal-octet =       DIGIT
                              / DIGIT DIGIT
                              / (("0"/"1") 2*(DIGIT))
                              / ("2" ("0"/"1"/"2"/"3"/"4") DIGIT)
                              / ("2" "5" ("0"/"1"/"2"/"3"/"4"/"5"))
    */
  
    if (!IS_DIGIT(host[0]))
      return 0;
  
    /* DIGIT */
    if (!IS_DIGIT(host[1]))
      return 1;
  
    if (host[0] == '2') {
      /* ("2" "5" ("0"/"1"/"2"/"3"/"4"/"5")) */
      if (host[1] == '5' && host[2] >= '0' && host[2] <= '5')
        return 3;
  
      /* ("2" ("0"/"1"/"2"/"3"/"4") DIGIT) */
      if (host[1] >= '0' && host[1] <= '4' &&
  	host[2] >= '0' && host[2] <= '9')
        return 3;
    }
    else if (host[0] == '0' || host[0] == '1') {
      if (IS_DIGIT(host[2]))
        /* ("1" 2*(DIGIT)) ... or "0" 2*(DIGIT) */
        return 3;
    }
  
    /* POS-DIGIT DIGIT */
    return 2;
  }
  
  /** Return length of valid IP4 address */
  static
  int span_canonic_ip4_address(char const *host, int *return_canonize)
  {
    int n, len, canonize = 0;
  
    if (host == NULL)
      return 0;
  
    /* IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet */
    len = span_ip4_octet(host);
    if (len == 0 || host[len] != '.')
      return 0;
    if (len > 1 && host[0] == '0')
      canonize = 1;
    n = len + 1;
  
    len = span_ip4_octet(host + n);
    if (len == 0 || host[n + len] != '.')
      return 0;
    if (len > 1 && host[n] == '0')
      canonize = 1;
    n += len + 1;
  
    len = span_ip4_octet(host + n);
    if (len == 0 || host[n + len] != '.')
      return 0;
    if (len > 1 && host[n] == '0')
      canonize = 1;
    n += len + 1;
  
    len = span_ip4_octet(host + n);
    if (len == 0 || IS_DIGIT(host[n + len]) || host[n + len] == '.')
      return 0;
    if (len > 1 && host[n] == '0')
      canonize = 1;
    n += len;
  
    if (canonize && return_canonize)
      *return_canonize = 1;
  
    return n;
  }
  
  /** Return length of valid IP4 address.
   *
   * Note that we accept here up to two leading zeroes
   * which makes "dotted decimal" notation ambiguous:
   * 127.000.000.001 is interpreted same as 127.0.0.1
   *
   * Note that traditionally IP address octets starting
   * with zero have been interpreted as octal:
   * 172.055.055.001 has been same as 172.45.45.1
   *
   * @b However, we interpret them as @b decimal,
   * 172.055.055.001 is same as 172.55.55.1.
   */
  int span_ip4_address(char const *host)
  {
    return span_canonic_ip4_address(host, NULL);
  }
  
  /** Scan and canonize a valid IP4 address. */
  int scan_ip4_address(char **inout_host)
  {
    char *src = *inout_host, *dst = src;
    issize_t n;
    int canonize = 0;
  
    if (src == NULL)
      return -1;
  
    n = span_canonic_ip4_address(src, &canonize);
    if (n == 0)
      return -1;
  
    *inout_host += n;
  
    if (!canonize)
      return n;
  
    for (;;) {
      char c = *dst++ = *src++;
  
      if (IS_DIGIT(*src)) {
        if (canonize && c == '0')
  	dst--;
        else if (c == '.')
  	canonize = 1;
        else
  	canonize = 0;
      }
      else if (*src != '.') {
        break;
      }
    }
    *dst = '\0';
  
    return n;
  }
  
  /** Return length of hex4 */
  su_inline int span_hex4(char const *host)
  {
    if (!IS_HEX(host[0]))
      return 0;
    if (!IS_HEX(host[1]))
      return 1;
    if (!IS_HEX(host[2]))
      return 2;
    if (!IS_HEX(host[3]))
      return 3;
    return 4;
  }
  
  /** Return length of valid IP6 address */
  su_inline
  int span_canonic_ip6_address(char const *host,
  			     int *return_canonize,
  			     char *hexparts[9])
  {
    int n = 0, len, hex4, doublecolon = 0, canonize = 0;
  
    /*
       IPv6address    =  hexpart [ ":" IPv4address ]
       hexpart        =  hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
       hexseq         =  hex4 *( ":" hex4)
       hex4           =  1*4HEXDIG
  
       There is at most 8 hex4, 6 hex4 if IPv4address is included.
    */
  
    if (host == NULL)
      return 0;
  
    for (hex4 = 0; hex4 < 8; ) {
      len = span_hex4(host + n);
  
      if (return_canonize) {
        if ((len > 1 && host[n + 1] == '0') || host[n] == '0')
  	canonize = 1;
        if (hexparts)
  	hexparts[hex4 + doublecolon] = (char *)(host + n);
      }
  
      if (host[n + len] == ':') {
        if (len != 0) {
  	hex4++;
  	n += len + 1;
  	if (!doublecolon && host[n] == ':') {
  	  if (return_canonize && hexparts) {
  	    hexparts[hex4] = (char *)(host + n - 1);
  	  }
  	  doublecolon++, n++;
  	}
        }
        else if (n == 0 && host[1] == ':') {
  	doublecolon++, n = 2;
        }
        else
  	break;
      }
      else if (host[n + len] == '.') {
        len = span_canonic_ip4_address(host + n, return_canonize);
  
        if (len == 0 || hex4 > 6 || !(doublecolon || hex4 == 6))
  	return 0;
  
        if (canonize && return_canonize)
  	*return_canonize = 1;
  
        return n + len;
      }
      else {
        if (len != 0)
  	hex4++;
        n += len;
        break;
      }
    }
  
    if (hex4 != 8 && !doublecolon)
      return 0;
  
    if (IS_HEX(host[n]) || host[n] == ':')
      return 0;
  
    if (canonize && return_canonize)
      *return_canonize = canonize;
  
    return n;
  }
  
  /** Canonize scanned IP6 address.
   *
   * @retval Length of canonized IP6 address.
   */
  su_inline
  int canonize_ip6_address(char *host, char *hexparts[9])
  {
    char *dst, *hex, *ip4 = NULL;
    int i, doublecolon, j, maxparts, maxspan, span, len;
  
    char buf[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
  
    /*
      Canonic representation has fewest chars
       - except for mapped/compatible IP4 addresses, like
         ::15.21.117.42 or ::ffff:15.21.117.42 which have non-canonic forms of
         ::f15:752a or ::ffff:f15:752a
      => we just canonize hexparts and ip4part separately
         and select optimal place for doublecolon
         (with expection of ::1 and ::, which are canonized)
    */
    for (i = 0, doublecolon = -1; i < 9; i++) {
      hex = hexparts[i];
      if (!hex)
        break;
      if (hex[0] == ':')
        doublecolon = i;
      while (hex[0] == '0' && IS_HEX(hex[1]))
        hex++;
      hexparts[i] = hex;
    }
    assert(i > 0);
  
    if (hexparts[i - 1][span_hex4(hexparts[i - 1])] == '.')
      ip4 = hexparts[--i];
  
    maxparts = ip4 ? 6 : 8;
  
    if (doublecolon >= 0) {
      /* Order at most 8 (or 6) hexparts */
      assert(i <= maxparts + 1);
  
      if (i == maxparts + 1) {
        /* There is an extra doublecolon */
        for (j = doublecolon; j + 1 < i; j++)
  	hexparts[j] = hexparts[j + 1];
        i--;
      }
      else {
        for (j = maxparts; i > doublecolon + 1; )
  	hexparts[--j] = hexparts[--i];
        for (;j > doublecolon;)
  	hexparts[--j] = "0:";
        i = maxparts;
      }
    }
    assert(i == maxparts);
  
    /* Scan for optimal place for "::" */
    for (i = 0, maxspan = 0, span = 0, doublecolon = 0; i < maxparts; i++) {
      if (hexparts[i][0] == '0')
        span++;
      else if (span > maxspan)
        doublecolon = i - span, maxspan = span, span = 0;
      else
        span = 0;
    }
  
    if (span > maxspan)
      doublecolon = i - span, maxspan = span;
  
    dst = buf;
  
    for (i = 0; i < maxparts; i++) {
      if (i == doublecolon)
        hex = i == 0 ? "::" : ":", len = 1;
      else if (i > doublecolon && i < doublecolon + maxspan)
        continue;
      else
        hex = hexparts[i], len = span_hex4(hex);
      if (hex[len] == ':')
        len++;
      memcpy(dst, hex, len);
      dst += len;
    }
  
    if (ip4) {
      hex = ip4;
      len = scan_ip4_address(&hex); assert(len > 0);
  
      /* Canonize :: and ::1 */
      if (doublecolon == 0 && maxspan == 6) {
        if (len == 7 && strncmp(ip4, "0.0.0.0", len) == 0)
  	ip4 = "", len = 0;
        else if (len == 7 && strncmp(ip4, "0.0.0.1", len) == 0)
  	ip4 = "1", len = 1;
      }
  
      memcpy(dst, ip4, len);
      dst += len;
    }
  
    len = dst - buf;
  
    memcpy(host, buf, len);
  
    return len;
  }
  
  /** Return length of valid IP6 address */
  int span_ip6_address(char const *host)
  {
    return span_canonic_ip6_address(host, NULL, NULL);
  }
  
  /** Scan and canonize valid IP6 address.
   *
   * @param inout_host input pointer to string to scan
   *                   output pointer to first character after valid IP6 address
   *
   * @retval Length of valid IP6 address or -1 upon an error.
   *
   * @note Scanned IP6 is not always NUL-terminated.
   */
  int scan_ip6_address(char **inout_host)
  {
    int n, canonize = 0;
    char *host = *inout_host;
    char *hexparts[9] = { NULL };
  
    n = span_canonic_ip6_address(host, &canonize, hexparts);
  
    if (n == 0)
      return -1;
  
    *inout_host += n;
  
    if (canonize) {
      int len = canonize_ip6_address(host, hexparts);
      assert(len <= n);
      if (len < n)
        host[len] = '\0';
    }
  
    return n;
  }
  
  /** Return length of valid IP6 reference. */
  int span_ip6_reference(char const *host)
  {
    /* IPv6reference  =  "[" IPv6address "]" */
  
    if (host && host[0] == '[') {
      int n = span_ip6_address(host + 1);
      if (n > 0 && host[n + 1] == ']')
        return n + 2;
    }
  
    return 0;
  }
  
  /** Scan valid IP6 reference. */
  int scan_ip6_reference(char **inout_host)
  {
    int n, canonize = 0;
    char *host = *inout_host;
    char *hexparts[9] = { NULL };
  
    /* IPv6reference  =  "[" IPv6address "]" */
  
    if (host == NULL ||
        host[0] != '[' ||
        (n = span_canonic_ip6_address(host + 1, &canonize, hexparts)) == 0 ||
        host[n + 1] != ']')
      return -1;
  
    *inout_host += n + 2;
  
    if (canonize) {
      int len = canonize_ip6_address(host + 1, hexparts);
  
      assert(len <= n);
  
      host[len + 1] = ']';
      if (len + 2 < n + 2)
        host[len + 2] = '\0';
    }
  
    return n + 2;
  }
  
  /** Return length of valid IP4 or IP6 address. */
  int span_ip_address(char const *host)
  {
    if (!host || !host[0])
      return 0;
  
    /* IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet */
    if (IS_DIGIT(host[0])) {
      int n = span_ip4_address(host);
      if (n)
        return n;
    }
  
    if (host[0] == '[')
      return span_ip6_reference(host);
    else
      return span_ip6_address(host);
  }
  
  /** Scan valid IP4/IP6 address. */
  int scan_ip_address(char **inout_host)
  {
    char *host = *inout_host;
  
    if (host == NULL)
      return -1;
  
    /* IPv6reference  =  "[" IPv6address "]" */
    if (host[0] == '[')
      return scan_ip6_reference(inout_host);
  
    if (IS_DIGIT(host[0])) {
      int n = scan_ip4_address(inout_host);
      if (n > 0)
        return n;
    }
  
    return scan_ip6_address(inout_host);
  }
  
  /** Return length of a valid domain label */
  su_inline
  size_t span_domain_label(char const *label)
  {
    /* domainlabel =  alphanum / alphanum *( alphanum / "-" ) alphanum */
    if (IS_ALPHANUM(*label)) {
      size_t n;
      for (n = 1; IS_ALPHANUM(label[n]) || label[n] == '-'; n++)
        ;
      if (IS_ALPHANUM(label[n - 1]))
        return n;
    }
  
    return 0;
  }
  
  /** Scan valid domain name and count number of labels in it. */
  su_inline
  size_t span_domain_labels(char const *host, size_t *return_labels)
  {
    size_t len, n, labels;
    int c;
  
    if (!host || !host[0])
      return 0;
  
    for (n = 0, labels = 0; ; n += len) {
      len = span_domain_label(host + n);
      if (len == 0)
        return 0;
  
      labels++;
  
      if (host[n + len] != '.')
        break;
      len++;
      if (!IS_ALPHANUM(host[n + len]))
        break;
    }
  
    /* Check that last label does not start with number */
    if (!IS_ALPHA(host[n]))
      return 0;
  
    c = host[n + len];
    if (IS_ALPHANUM(c) || c == '-' || c == '.')
      return 0;
  
    if (return_labels)
      *return_labels = labels;
  
    return n + len;
  }
  
  /** Return length of a valid domain name.
   *
   * @code
   * hostname         =  *( domainlabel "." ) toplabel [ "." ]
   * domainlabel      =  alphanum
   *                     / alphanum *( alphanum / "-" ) alphanum
   * toplabel         =  ALPHA / ALPHA *( alphanum / "-" ) alphanum
   * @endcode
   */
  isize_t span_domain(char const *host)
  {
    return span_domain_labels(host, NULL);
  }
  
  /** Scan valid domain name. */
  issize_t scan_domain(char **inout_host)
  {
    char *host;
    size_t n, labels;
  
    n = span_domain_labels(host = *inout_host, &labels);
    if (n == 0)
      return -1;
  
    /* Remove extra dot at the end of hostname */
    if (labels > 1 && host[n - 1] == '.')
      host[n - 1] = '\0';
  
    *inout_host += n;
  
    return n;
  }
  
  /** Return length of a valid domain name or IP address. */
  isize_t span_host(char const *host)
  {
   if (!host || !host[0])
      return 0;
  
    if (host[0] == '[')
      return span_ip6_reference(host);
  
    if (IS_DIGIT(host[0])) {
      int n = span_ip4_address(host);
      if (n)
        return (isize_t)n;
    }
  
    return span_domain(host);
  }
  
  /** Scan valid domain name or IP address. */
  issize_t scan_host(char **inout_host)
  {
    char *host = *inout_host;
  
    if (host == NULL)
      return -1;
  
    /* IPv6reference  =  "[" IPv6address "]" */
    if (host[0] == '[')
      return scan_ip6_reference(inout_host);
  
    if (IS_DIGIT(host[0])) {
      int n = scan_ip4_address(inout_host);
      if (n > 0)
        return (issize_t)n;
    }
  
    return scan_domain(inout_host);
  }
  
  #include <sofia-sip/hostdomain.h>
  
  /** Return true if @a string is valid IP4 address in dot-notation.
   *
   * @note Only 4-octet form is accepted, e.g., @c 127.1 is not considered
   * valid IP4 address.
   */
  int host_is_ip4_address(char const *string)
  {
    int n = span_ip4_address(string);
    return n > 0 && string[n] == '\0';
  }
  
  /** Return true if @a string is valid IP6 address in hex notation.
   *
   * E.g., fe80::1 is a valid IP6 address.
   */
  int host_is_ip6_address(char const *string)
  {
    int n = span_ip6_address(string);
    return n > 0 && string[n] == '\0';
  }
  
  int host_ip6_reference(char const *string)
  {
    return host_is_ip6_reference(string);
  }
  
  /** Return true if @a string is valid IP6 reference,
   *  i.e. hex notation in square brackets.
   *
   * E.g., [::1] is a valid IP6 reference.
   */
  int host_is_ip6_reference(char const *string)
  {
    int n = span_ip6_reference(string);
    return n > 0 && string[n] == '\0';
  }
  
  /** Return true if @a string is valid IP address.
   *
   * Valid IP address is either a IP4 adddress in quad-octet notation,
   * IP6 hex address or IP6 reference in square brackets ([]).
   */
  int host_is_ip_address(char const *string)
  {
    int n = span_ip_address(string);
    return n > 0 && string[n] == '\0';
  }
  
  /** Return true if @a string is valid a domain name.
   *
   * Valid domain name consists of alphanumeric labels separated with
   * dot ("."). There can be a "-" in the middle of label.
   * The last label must start with a letter.
   *
   * @code
   * hostname         =  *( domainlabel "." ) toplabel [ "." ]
   * domainlabel      =  alphanum
   *                     / alphanum *( alphanum / "-" ) alphanum
   * toplabel         =  ALPHA / ALPHA *( alphanum / "-" ) alphanum
   * @endcode
   */
  int host_is_domain(char const *string)
  {
    size_t n = string ? span_domain(string) : 0;
    return string && n > 0 && string[n] == '\0';
  }
  
  /** Return true if @a string is valid a host name.
   *
   * Check if the @a string is a domain name, IP address or IP6 reference.
   */
  int host_is_valid(char const *string)
  {
    size_t n = span_host(string);
    return n > 0 && string[n] == '\0';
  }
  
  /** Returns true if @a string is describing a local address.
   *
   * Uses the definitions of local addresses found in RFC1700 and
   * RFC4291.
   */
  int host_is_local(char const *host)
  {
    size_t n;
  
    if (host_is_ip6_reference(host))
      return (strcmp(host, "[::1]") == 0);
    else if (host_is_ip6_address(host))
      return (strcmp(host, "::1") == 0);
    else if (host_is_ip4_address(host))
      return (strncmp(host, "127.", 4) == 0);
  
    n = span_domain(host);
  
    return
      n >= 9 /* strlen("localhost") */ &&
      su_casenmatch(host, "localhost", 9) &&
      (n == 9 ||
       ((n == 10 || /* localhost. */
         n == 21 || /* strlen("localhost.localdomain") */
         n == 22) && /* strlen("localhost.localdomain.") */
        su_casenmatch(host + 9, ".localdomain.", n - 9)));
  }
  
  /** Return true if @a string has domain name in "invalid." domain.
   *
   */
  int host_has_domain_invalid(char const *string)
  {
    size_t n = span_domain(string);
  
    if (n >= 7 && string[n] == '\0') {
      static char const invalid[] = ".invalid";
      if (string[n - 1] == '.')	/* .invalid. perhaps? */
        n--;
      if (n == 7 /* strlen("invalid") */)
        return su_casenmatch(string, invalid + 1, 7);
      else
        return su_casenmatch(string + n - 8, invalid, 8);
    }
  
    return 0;
  }
  
  #include <sofia-sip/su.h>
  
  static size_t convert_ip_address(char const *s,
  				 uint8_t addr[16],
  				 size_t *return_addrlen)
  {
    size_t len;
    int canonize = 0;
    char buf[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
  
  #if SU_HAVE_IN6
  
    len = span_ip6_reference(s);
    if (len) {
      assert(len - 2 < sizeof buf); assert(len > 2);
  
      if (s[len])
        return 0;
  
      len = len - 2;
      s = memcpy(buf, s + 1, len), buf[len] = '\0';
    }
    else
      len = span_ip6_address(s);
  
    if (len) {
      if (s[len] == '\0' && su_inet_pton(AF_INET6, s, addr) == 1) {
        if (SU_IN6_IS_ADDR_V4MAPPED(addr) ||
  	  SU_IN6_IS_ADDR_V4COMPAT(addr)) {
  	memcpy(addr, addr + 12, 4);
  	return (void)(*return_addrlen = 4), len;
        }
        return (void)(*return_addrlen = 16), len;
      }
    }
    else
  #endif
    len = span_canonic_ip4_address(s, &canonize);
  
    if (len) {
      if (canonize) {
        char *tmp = buf;
        s = memcpy(tmp, s, len + 1);
        scan_ip4_address(&tmp);
      }
      if (s[len] == '\0' && su_inet_pton(AF_INET, s, addr) == 1)
        return (void)(*return_addrlen = 4), len;
    }
  
    return 0;
  }
  
  /** Compare two host names or IP addresses
   *
   * Converts valid IP addresses to the binary format before comparing them.
   * Note that IP6-mapped IP4 addresses and IP6-compatible IP4 addresses are
   * compared as IP4 addresses; that is, ::%ffff:127.0.0.1, ::127.0.0.1 and
   * 127.0.0.1 all are all equal.
   *
   * @param a IP address or domain name
   * @param b IP address or domain name
   *
   * @retval -1 if a < b
   * @retval 0 if a == b
   * @retval 1 if a > b
   *
   * @since New in @VERSION_1_12_4.
   */
  int host_cmp(char const *a, char const *b)
  {
    uint8_t a6[16], b6[16];
    size_t alen, blen, asize = 0, bsize = 0;
    int retval;
  
    if (a == NULL || b == NULL) {
      retval = (a != NULL) - (b != NULL);
    }
    else {
      alen = convert_ip_address(a, a6, &asize);
      blen = convert_ip_address(b, b6, &bsize);
  
      if (alen > 0 && blen > 0) {
        if (asize < bsize)
  	retval = -1;
        else if (asize > bsize)
  	retval = 1;
        else
  	retval = memcmp(a6, b6, asize);
      }
      else {
        retval = su_strcasecmp(a, b);
      }
    }
  
    return retval;
  }
  