root/usr/src/lib/libsip/common/sip_parse_uri.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "sip_parse_uri.h"

/*
 * SIP-URI          =  "sip:" [ userinfo ] hostport uri-parameters [ headers ]
 * SIPS-URI         =  "sips:" [ userinfo ] hostport uri-parameters [ headers ]
 * userinfo         =  ( user / telephone-subscriber ) [ ":" password ] "@"
 * user             =  1*( unreserved / escaped / user-unreserved )
 * user-unreserved  =  "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
 * password         =  *( unreserved / escaped / "&" / "=" / "+" / "$" / "," )
 * hostport         =  host [ ":" port ]
 * host             =  hostname / IPv4address / IPv6reference
 * hostname         =  *( domainlabel "." ) toplabel [ "." ]
 * domainlabel      =  alphanum / alphanum *( alphanum / "-" ) alphanum
 * toplabel         =  ALPHA / ALPHA *( alphanum / "-" ) alphanum
 * IPv4address    =  1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
 * IPv6reference  =  "[" IPv6address "]"
 * IPv6address    =  hexpart [ ":" IPv4address ]
 * hexpart        =  hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
 * hexseq         =  hex4 *( ":" hex4)
 * hex4           =  1*4HEXDIG
 * port           =  1*DIGIT
 *
 * The BNF for telephone-subscriber can be found in RFC 2806 [9].  Note,
 * however, that any characters allowed there that are not allowed in
 * the user part of the SIP URI MUST be escaped.
 *
 * uri-parameters    =  *( ";" uri-parameter)
 * uri-parameter     =  transport-param / user-param / method-param
 *                      / ttl-param / maddr-param / lr-param / other-param
 * transport-param   =  "transport="( "udp" / "tcp" / "sctp" / "tls"
 *                     / other-transport)
 * other-transport   =  token
 * user-param        =  "user=" ( "phone" / "ip" / other-user)
 * other-user        =  token
 * method-param      =  "method=" Method
 * ttl-param         =  "ttl=" ttl
 * maddr-param       =  "maddr=" host
 * lr-param          =  "lr"
 * other-param       =  pname [ "=" pvalue ]
 * pname             =  1*paramchar
 * pvalue            =  1*paramchar
 * paramchar         =  param-unreserved / unreserved / escaped
 * param-unreserved  =  "[" / "]" / "/" / ":" / "&" / "+" / "$"
 * headers         =  "?" header *( "&" header )
 * header          =  hname "=" hvalue
 * hname           =  1*( hnv-unreserved / unreserved / escaped )
 * hvalue          =  *( hnv-unreserved / unreserved / escaped )
 * hnv-unreserved  =  "[" / "]" / "/" / "?" / ":" / "+" / "$"
 *
 */

#define SIP_URI_MSG_BUF_SZ      100

#define SIP_URI_ISHEX(c)                                        \
        (((int)(c) >= 0x30 && (int)(c) <= 0x39) ||      \
        ((int)(c) >= 0x41 && (int)(c) <= 0x46) ||       \
        ((int)(c) >= 0x61 && (int)(c) <= 0x66))

#define SIP_URI_ISURLESCAPE(scan, end)                  \
        ((scan) + 2 < (end) && (scan)[0] == '%' &&      \
        SIP_URI_ISHEX((scan)[1]) && SIP_URI_ISHEX((scan[2])))

/*
 * URL character classes
 *  mark        - _ . ! ~ * ' ()
 *  reserved    ; / ? : @ & = + $ ,    also [] for IPv6
 *  unreserved  alphanum mark
 *  pchar       : @ & = + $ , unreserved
 *  userinfo    ; : & = + $ , unreserved escaped
 *  relsegment  ; @ & = + $ , unreserved escaped
 *  reg_name    ; : @ & = + $ , unreserved escaped
 *  token       - _ . ! ~ * ' %  + `
 *  param-unreserved  [ ] / : + $ &
 *  hnv-unreserved    [ ] / : + $ ?
 */
#define SIP_URI_ALPHA_BIT               0x0001
#define SIP_URI_DIGIT_BIT               0x0002
#define SIP_URI_ALNUM_BITS              0x0003
#define SIP_URI_SCHEME_BIT              0x0004  /* for - + . */
#define SIP_URI_TOKEN_BIT               0x0008  /* for - _ . ! ~ * ' % + ` */
#define SIP_URI_QUEST_BIT               0x0010  /* for ? */
#define SIP_URI_AT_BIT                  0x0020  /* for @ */
#define SIP_URI_COLON_BIT               0x0040  /* for : */
#define SIP_URI_SEMI_BIT                0x0080  /* for ; */
#define SIP_URI_DASH_BIT                0x0100  /* for - */
#define SIP_URI_MARK_BIT                0x0200  /* for - _ . ! ~ * ' ( ) */
#define SIP_URI_AND_BIT                 0x0400  /* for & */
#define SIP_URI_PHCOMM_BIT              0x0800  /* for [ ] / : + $ */
#define SIP_URI_OTHER_BIT               0x1000  /* for = + $ , */
#define SIP_URI_SLASH_BIT               0x2000  /* for / */
#define SIP_URI_VISUALSEP_BIT           0x4000  /* for -.() */
#define SIP_URI_DTMFURI_DIGIT_BIT       0x8000  /* for *ABCD */

#define a                       SIP_URI_ALPHA_BIT
#define d                       SIP_URI_DIGIT_BIT
#define s                       SIP_URI_SCHEME_BIT
#define t                       SIP_URI_TOKEN_BIT
#define q                       SIP_URI_QUEST_BIT
#define m                       SIP_URI_AT_BIT
#define c                       SIP_URI_COLON_BIT
#define i                       SIP_URI_SEMI_BIT
#define h                       SIP_URI_DASH_BIT
#define k                       SIP_URI_MARK_BIT
#define n                       SIP_URI_AND_BIT
#define o                       SIP_URI_PHCOMM_BIT
#define r                       SIP_URI_OTHER_BIT
#define l                       SIP_URI_SLASH_BIT
#define v                       SIP_URI_VISUALSEP_BIT
#define f                       SIP_URI_DTMFURI_DIGIT_BIT

static const unsigned short sip_uri_table[256] = {
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      t|k,    0,      0,      o|r,    t,      n,      t|k,
        k|v,    k|v,    t|k|f, s|t|r|o, r,  h|s|t|k|v, s|t|k|v, o|l,
        d,      d,      d,      d,      d,      d,      d,      d,
        d,      d,      c|o,    i,      0,      r,      0,      q,
        m,      a|f,    a|f,    a|f,    a|f,    a,      a,      a,
        a,      a,      a,      a,      a,      a,      a,      a,
        a,      a,      a,      a,      a,      a,      a,      a,
        a,      a,      a,      o,      0,      o,      0,      t|k,
        t,      a,      a,      a,      a,      a,      a,      a,
        a,      a,      a,      a,      a,      a,      a,      a,
        a,      a,      a,      a,      a,      a,      a,      a,
        a,      a,      a,      0,      0,      0,      t|k,    0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
        0,      0,      0,      0,      0,      0,      0,      0,
};

#undef  a
#undef  d
#undef  s
#undef  t
#undef  q
#undef  m
#undef  c
#undef  i
#undef  h
#undef  k
#undef  n
#undef  o
#undef  r
#undef  l
#undef  v
#undef  f

#define SIP_URI_UT(c)                   sip_uri_table[(unsigned char)(c)]
#define SIP_URI_ISALPHA(c)              (SIP_URI_UT(c) & SIP_URI_ALPHA_BIT)
#define SIP_URI_ISDIGIT(c)              (SIP_URI_UT(c) & SIP_URI_DIGIT_BIT)
#define SIP_URI_ISALNUM(c)              (SIP_URI_UT(c) & SIP_URI_ALNUM_BITS)
#define SIP_URI_ISSCHEME(c)             \
                (SIP_URI_UT(c) & (SIP_URI_ALNUM_BITS|SIP_URI_SCHEME_BIT))
#define SIP_URI_ISTOKEN(c)              \
                (SIP_URI_UT(c) & (SIP_URI_ALNUM_BITS|SIP_URI_TOKEN_BIT))
#define SIP_URI_ISSIPDELIM(c)           \
                (SIP_URI_UT(c) & (SIP_URI_SEMI_BIT|SIP_URI_QUEST_BIT))
#define SIP_URI_ISSIPHDELIM(c)                                  \
        (SIP_URI_UT(c) & (SIP_URI_COLON_BIT|SIP_URI_SEMI_BIT|SIP_URI_QUEST_BIT))
#define SIP_URI_ISHOST(c)               \
                (SIP_URI_UT(c) & (SIP_URI_ALNUM_BITS|SIP_URI_DASH_BIT))
#define SIP_URI_ISUSER(c)                                               \
        (SIP_URI_UT(c) & (SIP_URI_OTHER_BIT|SIP_URI_SEMI_BIT|           \
        SIP_URI_QUEST_BIT|SIP_URI_SLASH_BIT|SIP_URI_AND_BIT))

#define SIP_URI_ISABSHDELIM(c)                                  \
        (SIP_URI_UT(c) & \
        (SIP_URI_SLASH_BIT|SIP_URI_COLON_BIT|SIP_URI_QUEST_BIT))
#define SIP_URI_ISABSDELIM(c)   \
        (SIP_URI_UT(c) & (SIP_URI_SLASH_BIT|SIP_URI_QUEST_BIT))
#define SIP_URI_ISUNRESERVED(c) \
        (SIP_URI_UT(c) & (SIP_URI_ALNUM_BITS|SIP_URI_MARK_BIT))
#define SIP_URI_ISPARAM(c)                                              \
        (SIP_URI_UT(c) & (SIP_URI_PHCOMM_BIT|SIP_URI_AND_BIT|\
        SIP_URI_ALNUM_BITS|SIP_URI_MARK_BIT))
#define SIP_URI_ISHEADER(c)                                             \
        (SIP_URI_UT(c) & (SIP_URI_PHCOMM_BIT|SIP_URI_QUEST_BIT|\
        SIP_URI_ALNUM_BITS|SIP_URI_MARK_BIT))
#define SIP_URI_ISOTHER(c)              (SIP_URI_UT(c) & SIP_URI_OTHER_BIT)
#define SIP_URI_ISRESERVED(c)                                   \
        (SIP_URI_UT(c) & (SIP_URI_SEMI_BIT|SIP_URI_SLASH_BIT|   \
        SIP_URI_QUEST_BIT| SIP_URI_COLON_BIT|SIP_URI_AT_BIT|    \
        SIP_URI_AND_BIT|SIP_URI_OTHER_BIT))
#define SIP_URI_ISPCHAR(c)      \
        (SIP_URI_UT(c) & (SIP_URI_COLON_BIT|SIP_URI_AT_BIT|     \
        SIP_URI_AND_BIT|SIP_URI_OTHER_BIT))
#define SIP_URI_ISREGNAME(c)                                    \
        (SIP_URI_UT(c) &        \
        (SIP_URI_OTHER_BIT|SIP_URI_SEMI_BIT|SIP_URI_COLON_BIT|  \
        SIP_URI_AT_BIT|SIP_URI_AND_BIT))
#define SIP_URI_ISPHONEDIGIT(c) \
        (SIP_URI_UT(c) & (SIP_URI_DIGIT_BIT|SIP_URI_VISUALSEP_BIT))
#define SIP_URI_ISDTMFDIGIT(c)  (SIP_URI_UT(c) & SIP_URI_DTMFURI_DIGIT_BIT)

static int  sip_uri_url_casecmp(const char *, const char *, unsigned);
static void sip_uri_parse_params(_sip_uri_t *, char *, char *);
static void sip_uri_parse_headers(_sip_uri_t *, char *, char *);
static void sip_uri_parse_abs_opaque(_sip_uri_t *, char *, char *);
static void sip_uri_parse_abs_query(_sip_uri_t *, char *, char *);
static void sip_uri_parse_abs_path(_sip_uri_t *, char *, char *);
static void sip_uri_parse_abs_regname(_sip_uri_t *, char *, char *);
static int  sip_uri_parse_scheme(_sip_uri_t *, char *, char *);
static void sip_uri_parse_password(_sip_uri_t *, char *, char *);
static void sip_uri_parse_user(_sip_uri_t *, char *, char *);
static void sip_uri_parse_port(_sip_uri_t *, char *, char *);
static void sip_uri_parse_netpath(_sip_uri_t *, char **, char *, boolean_t);
static int  sip_uri_parse_ipv6(char *, char *);
static int  sip_uri_parse_ipv4(char *, char *);
static int  sip_uri_parse_hostname(char *, char *);
static int sip_uri_parse_tel(char *, char *);
static int sip_uri_parse_tel_areaspe(char *, char *);
static int sip_uri_parse_tel_servicepro(char *, char *);
static int sip_uri_parse_tel_futureext(char *, char *);
static int sip_uri_isTokenchar(char **, char *);
static int sip_uri_isEscapedPound(char **, char *);
static int sip_uri_hexVal(char *, char *);
static int SIP_URI_HEXVAL(int);

/*
 * get the hex value of a char
 */
static int
SIP_URI_HEXVAL(int c)
{
        if (c >= 0x30 && c <= 0x39)
                return (c - '0');
        if (c >= 0x41 && c <= 0x46)
                return (c - 'A' + 10);
        if (c >= 0x61 && c <= 0x66)
                return (c - 'a' + 10);
        return (c);
}

/*
 * basic ASCII case-insensitive comparison
 */
static int
sip_uri_url_casecmp(const char *str1, const char *str2, unsigned len)
{
        unsigned        j;

        for (j = 0; j < len && tolower(str1[j]) == tolower(str2[j]) &&
            str1[j] != '\0'; ++j) {
                ;
        }
        return (j == len ? 0 : tolower(str2[j]) - tolower(str1[j]));
}

/*
 * telephone-subscriber  = global-phone-number / local-phone-number
 * Please refer to RFC 2806
 */
static int
sip_uri_parse_tel(char *scan, char *uend)
{
        char    *mark = (char *)0;
        int     ret = 0;
        int     isGlobal = 0;
        int     quote = 0;

        if (scan == uend)
                return (0);
        if (*scan == '+') {
                ++scan;
                isGlobal = 1;
        }
        mark = scan;
        if (isGlobal) {
                while (scan < uend && SIP_URI_ISPHONEDIGIT(*scan))
                        ++scan;
        } else {
                while (scan < uend &&
                    (SIP_URI_ISPHONEDIGIT(*scan) ||
                    SIP_URI_ISDTMFDIGIT(*scan) ||
                    sip_uri_isEscapedPound(&scan, uend) ||
                    *scan == 'p' || *scan == 'w')) {
                        ++scan;
                }
        }
        if (mark == scan || (scan < uend && *scan != ';'))
                return (0);

        /*
         * parse isdn-subaddress
         */
        if (uend - scan > 6 && !sip_uri_url_casecmp(scan, ";isub=", 6)) {
                scan += 6;
                mark = scan;
                while (scan < uend && SIP_URI_ISPHONEDIGIT(*scan))
                        ++scan;
                if (mark == scan || (scan < uend && *scan != ';'))
                        return (0);
        }

        /*
         * parse post-dial
         */
        if (uend - scan > 7 && !sip_uri_url_casecmp(scan, ";postd=", 7)) {
                scan += 7;
                mark = scan;
                while (scan < uend &&
                    (SIP_URI_ISPHONEDIGIT(*scan) ||
                    SIP_URI_ISDTMFDIGIT(*scan) ||
                    sip_uri_isEscapedPound(&scan, uend) ||
                    *scan == 'p' || *scan == 'w')) {
                        ++scan;
                }
                if (mark == scan || (scan < uend && *scan != ';'))
                        return (0);
        }

        if (!isGlobal) {
                /*
                 * parse area-specifier
                 */
                if (uend - scan > 15 &&
                    !sip_uri_url_casecmp(scan, ";phone-context=", 15)) {
                        scan += 15;
                        mark = scan;
                        while (scan < uend && *scan != ';')
                                ++scan;
                        ret = sip_uri_parse_tel_areaspe(mark, scan);
                }
        } else {
                ret = 1;
        }

        /*
         * parse area-specifier, service-provider, future-extension
         */
        while (scan < uend && ret) {
                if (uend - scan > 15 &&
                        !sip_uri_url_casecmp(scan, ";phone-context=", 15)) {
                        scan += 15;
                        mark = scan;
                        while (scan < uend && *scan != ';')
                                ++scan;
                        ret = sip_uri_parse_tel_areaspe(mark, scan);
                } else if (uend - scan > 5 &&
                    !sip_uri_url_casecmp(scan, ";tsp=", 5)) {
                        scan += 5;
                        mark = scan;
                        while (scan < uend && *scan != ';')
                                ++scan;
                        ret = sip_uri_parse_tel_servicepro(mark, scan);
                } else {
                        ++scan;
                        mark = scan;
                        while (scan < uend && (*scan != ';' || quote)) {
                                if (sip_uri_hexVal(scan, uend) == 0x22) {
                                        quote = !quote;
                                        scan += 3;
                                } else {
                                        ++scan;
                                }
                        }
                        ret = sip_uri_parse_tel_futureext(mark, scan);
                }
        }
        return (ret && scan == uend);
}

/*
 * area-specifier        = ";" phone-context-tag "=" phone-context-ident
 * phone-context-tag     = "phone-context"
 * phone-context-ident   = network-prefix / private-prefix
 * network-prefix        = global-network-prefix / local-network-prefix
 * global-network-prefix = "+" 1*phonedigit
 * local-network-prefix  = 1*(phonedigit / dtmf-digit / pause-character)
 * private-prefix        = (%x21-22 / %x24-27 / %x2C / %x2F / %x3A /
 *                          %x3C-40 / %x45-4F / %x51-56 / %x58-60 /
 *                          %x65-6F / %x71-76 / %x78-7E)
 *                          *(%x21-3A / %x3C-7E)
 * phonedigit            = DIGIT / visual-separator
 * visual-separator      = "-" / "." / "(" / ")"
 * pause-character       = one-second-pause / wait-for-dial-tone
 * one-second-pause      = "p"
 * wait-for-dial-tone    = "w"
 * dtmf-digit            = "*" / "#" / "A" / "B" / "C" / "D"
 */
static int
sip_uri_parse_tel_areaspe(char *scan, char *uend)
{
        int     uri_hexValue;

        if (scan == uend)
                return (0);

        /*
         * parse global-network-prefix
         */
        if (*scan == '+') {
                ++scan;
                if (scan == uend)
                        return (0);
                while (scan < uend && SIP_URI_ISPHONEDIGIT(*scan))
                        ++scan;
        /*
         * parse local-network-prefix
         */
        } else if (SIP_URI_ISPHONEDIGIT(*scan) || SIP_URI_ISDTMFDIGIT(*scan) ||
            sip_uri_isEscapedPound(&scan, uend) ||
            *scan == 'p' || *scan == 'w') {
                ++scan;
                while (scan < uend &&
                    (SIP_URI_ISPHONEDIGIT(*scan) ||
                    SIP_URI_ISDTMFDIGIT(*scan) ||
                    sip_uri_isEscapedPound(&scan, uend) ||
                    *scan == 'p' || *scan == 'w')) {
                        ++scan;
                }
        } else {
        /*
         * parse private-prefix
         *
         * any characters allowed in RFC 2806 that are not allowed in
         * the user part of the SIP URI MUST be escaped
         *
         * private-prefix       = (! $ & ', / = ? _
         *                      EFGHIJKLMNOQRSTUVXYZ efghijklmnoqrstuvxyz
         *                      { } | ~ [ ] \ ^  ` " % : < > @)
         *                      *(%x21-3A / %x3C-7E)
         *
         * following characters are allowed in RFC 2806 and
         * the user part of SIP URI
         *  ! $ & ', / = ? _ EFGHIJKLMNOQRSTUVXYZ efghijklmnoqrstuvxyz
         */
                if (*scan == '!' || *scan == '$' || *scan == '&' ||
                    *scan == '\'' || *scan == ',' || *scan == '/' ||
                    *scan == '=' || *scan == '?' || *scan == '_' ||
                    (*scan >= 'E' && *scan <= 'Z' &&
                    *scan != 'P' && *scan != 'W') ||
                    (*scan >= 'e' && *scan <= 'z' &&
                    *scan != 'p' && *scan != 'w')) {
                        ++scan;
                } else {
                        uri_hexValue = sip_uri_hexVal(scan, uend);
                        if (uri_hexValue == 0x21 || uri_hexValue == 0x22 ||
                            (uri_hexValue >= 0x24 && uri_hexValue <= 0x27) ||
                            uri_hexValue == 0x2c || uri_hexValue == 0x2f ||
                            uri_hexValue == 0x3a ||
                            (uri_hexValue >= 0x3c && uri_hexValue <= 0x40) ||
                            (uri_hexValue >= 0x45 && uri_hexValue <= 0x4f) ||
                            (uri_hexValue >= 0x51 && uri_hexValue <= 0x56) ||
                            (uri_hexValue >= 0x58 && uri_hexValue <= 0x60) ||
                            (uri_hexValue >= 0x65 && uri_hexValue <= 0x6f) ||
                            (uri_hexValue >= 0x71 && uri_hexValue <= 0x76) ||
                            (uri_hexValue >= 0x78 && uri_hexValue <= 0x7e)) {
                                scan += 3;
                        } else {
                                return (0);
                        }
                }
                /*
                 * parse *(%x21-3A / %x3C-7E)
                 */
                while (scan < uend) {
                        if (SIP_URI_ISUNRESERVED(*scan) ||
                            (SIP_URI_ISUSER(*scan) && *scan != ';')) {
                                ++scan;
                        } else {
                                uri_hexValue = sip_uri_hexVal(scan, uend);
                                if (uri_hexValue >= 0x21 &&
                                    uri_hexValue <= 0x7e &&
                                    uri_hexValue != 0x3b) {
                                        scan += 3;
                                } else {
                                        return (0);
                                }
                        }
                }
        }
        if (scan < uend)
                return (0);
        return (1);
}

static int
sip_uri_hexVal(char *scan, char *uend)
{
        int     ret = -1;

        if (SIP_URI_ISURLESCAPE(scan, uend)) {
                ret = (SIP_URI_ISDIGIT(scan[1]) ? (scan[1] - '0') :
                    (tolower(scan[1]) - 'a' + 10)) * 16 +
                    (SIP_URI_ISDIGIT(scan[2]) ? (scan[2] - '0') :
                    (tolower(scan[2]) - 'a' + 10));
        }
        return (ret);
}

/*
 * service-provider  = ";" provider-tag "=" provider-hostname
 * provider-tag      = "tsp"
 * provider-hostname = domain
 */
static int
sip_uri_parse_tel_servicepro(char *scan, char *uend)
{
        char    *mark = (char *)0;

        if (scan == uend)
                return (0);

        /*
         * parse domain=" "
         */
        if (sip_uri_hexVal(scan, uend) == 0x20 && scan + 3 == uend)
                return (1);
        while (scan < uend) {
                mark = scan;
                while (scan < uend && (*scan == '-'|| SIP_URI_ISALNUM(*scan)))
                        ++scan;
                if ((scan < uend && *scan != '.') ||
                    !SIP_URI_ISALPHA(*mark) || !SIP_URI_ISALNUM(*(scan - 1))) {
                        return (0);
                }
                if (scan < uend)
                        ++scan;
        }

        if (scan < uend)
                return (0);
        return (1);
}

/*
 * future-extension = ";" 1*(token-char) ["=" ((1*(token-char)
 *                    ["?" 1*(token-char)]) / quoted-string )]
 * token-char       = (%x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39
 *                     / %x41-5A / %x5E-7A / %x7C / %x7E)
 */
static int
sip_uri_parse_tel_futureext(char *scan, char *uend)
{
        char    *mark;
        int     uri_hexValue = 0;

        if (scan == uend)
                return (0);

        /*
         * parse 1*(token-char)
         */
        mark = scan;
        while (scan < uend && sip_uri_isTokenchar(&scan, uend))
                ;
        if (mark == scan ||
            (scan < uend && (*scan != '=' || scan + 1 == uend))) {
                return (0);
        }
        if (scan == uend)
                return (1);
        ++scan;

        /*
         * parse 1*token-char ["?" 1*token-char]
         */
        if (sip_uri_isTokenchar(&scan, uend)) {
                while (sip_uri_isTokenchar(&scan, uend))
                        ;
                if (scan < uend) {
                        if (*scan != '?')
                                return (0);
                        ++scan;
                        mark = scan;
                        while (sip_uri_isTokenchar(&scan, uend))
                                ;
                        if (mark == scan)
                                return (0);
                }
        } else { /* parse quoted-string */
                uri_hexValue = sip_uri_hexVal(scan, uend);
                if (uri_hexValue != 0x22)
                        return (0);
                scan += 3;
                while (scan < uend && sip_uri_hexVal(scan, uend) != 0x22) {
                        /*
                         * parse "\" CHAR
                         */
                        if (sip_uri_hexVal(scan, uend) == 0x5c) {
                                scan += 3;
                                if (scan < uend) {
                                        if (SIP_URI_ISUNRESERVED(*scan) ||
                                            SIP_URI_ISUSER(*scan)) {
                                                ++scan;
                                        } else if (sip_uri_hexVal(scan, uend) >=
                                            0x00 &&
                                            sip_uri_hexVal(scan, uend) <=
                                            0x7f) {
                                                scan += 3;
                                        } else {
                                                return (0);
                                        }
                                } else {
                                        return (0);
                                }
                        } else {
                                if (SIP_URI_ISUNRESERVED(*scan) ||
                                    SIP_URI_ISUSER(*scan)) {
                                        ++scan;
                                } else {
                                        uri_hexValue =
                                            sip_uri_hexVal(scan, uend);
                                        if ((uri_hexValue >= 0x20 &&
                                                uri_hexValue <= 0x21) ||
                                                (uri_hexValue >= 0x23 &&
                                                uri_hexValue <= 0x7e) ||
                                                (uri_hexValue >= 0x80 &&
                                                uri_hexValue <= 0xff)) {
                                                scan += 3;
                                        } else {
                                                return (0);
                                        }
                                }
                        }
                }
                if (scan == uend ||
                    (scan < uend && sip_uri_hexVal(scan, uend) != 0x22)) {
                        return (0);
                }
                scan += 3;
        }

        if (scan < uend)
                return (0);
        return (1);
}

/*
 * Any characters allowed in RFC2806 tel URL that are not allowed in
 * the user part of the SIP URI MUST be escaped.
 * token-char = - _ . ! ~ * ' $ &  + DIGIT ALPHA #  % ^ ` |
 */
static int
sip_uri_isTokenchar(char **pscan, char *uend)
{
        char    *scan = *pscan;
        int     uri_hexValue = 0;

        if (scan == uend)
                return (0);

        /*
         * for ALPAH DIGIT - _ . ! ~ * ' $ & +
         */
        if ((SIP_URI_ISUNRESERVED(*scan) && *scan != '(' && *scan != ')') ||
            *scan == '$' || *scan == '&' || *scan == '+') {
                ++scan;
                *pscan = scan;
                return (1);
        }

        uri_hexValue = sip_uri_hexVal(scan, uend);
        if (uri_hexValue == 0x21 || uri_hexValue == 0x7c ||
            uri_hexValue == 0x7e ||
            (uri_hexValue >= 0x23 && uri_hexValue <= 0x27) ||
            (uri_hexValue >= 0x2a && uri_hexValue <= 0x2b) ||
            (uri_hexValue >= 0x2d && uri_hexValue <= 0x2e) ||
            (uri_hexValue >= 0x30 && uri_hexValue <= 0x39) ||
            (uri_hexValue >= 0x41 && uri_hexValue <= 0x5a) ||
            (uri_hexValue >= 0x5e && uri_hexValue <= 0x7a)) {
                scan += 3;
                *pscan = scan;
                return (1);
        }
        return (0);
}

/*
 * '#' is not allowed in the telephone-subscriber part of SIP URI
 * it must be escaped
 */
static int
sip_uri_isEscapedPound(char **pscan, char *uend)
{
        char    *scan = *pscan;

        if (scan == uend)
                return (0);
        if (*scan == '%' && scan + 2 < uend && scan[1] == '2' &&
            scan[2] == '3') {
                scan += 2;
                *pscan = scan;
                return (1);
        }
        return (0);
}

/*
 * scheme =  ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
 */
static int
sip_uri_parse_scheme(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_SCHEME;
                return (0);
        }
        outurl->sip_uri_scheme.sip_str_ptr = scan;
        outurl->sip_uri_scheme.sip_str_len = uend - scan;

        if (scan < uend && SIP_URI_ISALPHA(*scan)) {
                ++scan;
                while (scan < uend && SIP_URI_ISSCHEME(*scan))
                        ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_SCHEME;
        return (1);
}

/*
 * The format of params is supposed to be;XXX;XXX;XXX
 * uri-parameters       = *(";" uri-parameter)
 * uri-parameter        = transport-param / user-param / method-param
 *                      / ttl-param / maddr-param / lr-param / other-param
 * transport-param      =  "transport="
 *                      ("udp" / "tcp" / "sctp" / "tls" / other-transport)
 * other-transport              =  token
 * user-param           =  "user=" ("phone" / "ip" / other-user)
 * other-user           =  token
 * method-param         =  "method=" Method
 * ttl-param            =  "ttl=" ttl
 * maddr-param          =  "maddr=" host
 * lr-param             =  "lr"
 * other-param          =  pname [ "=" pvalue ]
 * pname                =  1*paramchar
 * pvalue               =  1*paramchar
 * paramchar            =  param-unreserved / unreserved / escaped
 * param-unreserved     =  "[" / "]" / "/" / ":" / "&" / "+" / "$"
 */
static void
sip_uri_parse_params(_sip_uri_t *outurl, char *scan, char *uend)
{
        char            *mark = (char *)0;
        char            *equal = (char *)0;
        int             i = 0;
        int             ttl = 0;
        int             paramleftlen = 0;
        int             gothost = 0;
        sip_param_t     *param = NULL;
        sip_param_t     *new_param = NULL;

        if (scan == uend || *scan != ';' || scan + 1 == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_PARAM;
                return;
        }

        while (scan < uend) {
                mark = ++scan;
                while (scan < uend && *scan != ';')
                        ++scan;
                if (scan == mark) {
                        outurl->sip_uri_errflags |= SIP_URIERR_PARAM;
                        return;
                }

                new_param = calloc(1, sizeof (sip_param_t));
                if (new_param == NULL) {
                        outurl->sip_uri_errflags |= SIP_URIERR_MEMORY;
                        return;
                }

                if (param == NULL)
                        outurl->sip_uri_params = new_param;
                else
                        param->param_next = new_param;

                param = new_param;

                param->param_name.sip_str_ptr = mark;
                equal = memchr(mark, '=', scan - mark);
                if (equal == (char *)0) {
                        param->param_name.sip_str_len = scan - mark;
                        param->param_value.sip_str_ptr = NULL;
                        param->param_value.sip_str_len = 0;
                        while (mark < scan && (SIP_URI_ISPARAM(*mark) ||
                            SIP_URI_ISURLESCAPE(mark, scan))) {
                                ++mark;
                        }
                } else {
                        param->param_name.sip_str_len = equal - mark;
                        param->param_value.sip_str_ptr = equal + 1;
                        param->param_value.sip_str_len = scan - equal - 1;

                        if (mark == equal || equal + 1 == scan) {
                                outurl->sip_uri_errflags |= SIP_URIERR_PARAM;
                                return;
                        }
                        paramleftlen = equal - mark + 1;
                        if ((paramleftlen == 10 &&
                            !sip_uri_url_casecmp(mark, "transport=", 10)) ||
                            (paramleftlen == 5 &&
                            !sip_uri_url_casecmp(mark, "user=", 5)) ||
                            (paramleftlen == 7 &&
                            !sip_uri_url_casecmp(mark, "method=", 7))) {
                                if (scan - equal == 1) {
                                        outurl->sip_uri_errflags |=
                                            SIP_URIERR_PARAM;
                                        return;
                                }
                                mark = equal + 1;
                                while (mark < scan && SIP_URI_ISTOKEN(*mark))
                                        ++mark;
                        } else if (paramleftlen == 4 &&
                            !sip_uri_url_casecmp(mark, "ttl=", 4)) {
                                if (scan - equal == 1) {
                                        outurl->sip_uri_errflags |=
                                            SIP_URIERR_PARAM;
                                        return;
                                }
                                mark = equal;
                                for (i = 0; i < 3; ++i) {
                                        ++mark;
                                        if (mark < scan &&
                                            SIP_URI_ISDIGIT(*mark)) {
                                                ttl = ttl * 10 + (*mark - '0');
                                        }
                                        if (ttl > 255) {
                                                outurl->sip_uri_errflags |=
                                                        SIP_URIERR_PARAM;
                                                return;
                                        }
                                }
                        } else if (paramleftlen == 6 &&
                            !sip_uri_url_casecmp(mark, "maddr=", 6)) {
                                gothost = 0;
                                mark = equal + 1;
                                if (mark < scan && SIP_URI_ISDIGIT(*mark)) {
                                        gothost = sip_uri_parse_ipv4(mark,
                                            scan);
                                }
                                /*
                                 * not valid syntax for a host or user name,
                                 * try IPv6 literal
                                 */
                                if (!gothost && mark < scan && *mark == '[') {
                                        gothost = sip_uri_parse_ipv6(mark,
                                            scan);
                                }
                                /*
                                 * look for a valid host name:
                                 * *(domainlabel ".") toplabel ["."]
                                 */
                                if (!gothost && mark < scan) {
                                        if (!(gothost =
                                            sip_uri_parse_hostname(mark,
                                            scan))) {
                                                outurl->sip_uri_errflags |=
                                                        SIP_URIERR_PARAM;
                                        }
                                }
                                if (gothost)
                                        mark = scan;
                        } else if (paramleftlen == 3 &&
                            !sip_uri_url_casecmp(mark, "lr=", 3)) {
                                outurl->sip_uri_errflags |= SIP_URIERR_PARAM;
                                return;
                        } else {
                                while (mark < scan && (SIP_URI_ISPARAM(*mark) ||
                                    SIP_URI_ISURLESCAPE(mark, scan) ||
                                    mark == equal)) {
                                        ++mark;
                                }
                        }
                }
                if (mark < scan) {
                        outurl->sip_uri_errflags |= SIP_URIERR_PARAM;
                        return;
                }
        }
}

/*
 * The format of headers is supposed to be ?XXX&XXX&XXX
 * headers         =  "?" header *("&" header
 * header          =  hname "=" hvalue
 * hname           =  1*(hnv-unreserved / unreserved / escaped
 * hvalue          =  *(hnv-unreserved / unreserved / escaped
 * hnv-unreserved  =  "[" / "]" / "/" / "?" / ":" / "+" / "$"
 */
static void
sip_uri_parse_headers(_sip_uri_t *outurl, char *scan, char *uend)
{
        char    *mark = NULL;
        char    *equal = NULL;

        if (scan == uend || *scan != '?' || scan + 1 == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_HEADER;
                return;
        }
        outurl->sip_uri_headers.sip_str_ptr = scan + 1;
        outurl->sip_uri_headers.sip_str_len = uend - (scan + 1);

        while (scan < uend) {
                mark = ++scan;
                while (scan < uend && *scan != '&')
                        ++scan;
                if (scan == mark) {
                        outurl->sip_uri_errflags |= SIP_URIERR_HEADER;
                        return;
                }
                equal = memchr(mark, '=', scan - mark);
                if (equal == mark || equal == (char *)0) {
                        outurl->sip_uri_errflags |= SIP_URIERR_HEADER;
                        return;
                }
                while (mark < scan &&
                    (SIP_URI_ISHEADER(*mark) ||
                    SIP_URI_ISURLESCAPE(mark, scan) || mark == equal)) {
                        ++mark;
                }
                if (mark < scan) {
                        outurl->sip_uri_errflags |= SIP_URIERR_HEADER;
                        return;
                }
        }
}

/*
 * opaque-part   =  uric-no-slash *uric
 * uric          =  reserved / unreserved / escaped
 * uric-no-slash =  unreserved / escaped / ";" / "?" / ":" / "@"
 *                  / "&" / "=" / "+" / "$" / ","
 */
static void
sip_uri_parse_abs_opaque(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_OPAQUE;
                return;
        }
        outurl->sip_uri_opaque.sip_str_ptr = scan;
        outurl->sip_uri_opaque.sip_str_len = uend - scan;

        if (SIP_URI_ISUNRESERVED(*scan) || SIP_URI_ISURLESCAPE(scan, uend) ||
            SIP_URI_ISOTHER(*scan) || *scan == ';' || *scan == '?' ||
            *scan == ':' || *scan == '@' || *scan == '&') {
                ++scan;
        } else {
                outurl->sip_uri_errflags |= SIP_URIERR_OPAQUE;
                return;
        }
        while (scan < uend && (SIP_URI_ISRESERVED(*scan) ||
            SIP_URI_ISUNRESERVED(*scan) || SIP_URI_ISURLESCAPE(scan, uend))) {
                ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_OPAQUE;
}

/*
 * format of query is supposed to be ?XXX
 * query =  *uric
 * uric  =  reserved / unreserved / escaped
 */
static void
sip_uri_parse_abs_query(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (uend == scan || *scan != '?' || scan + 1 == uend)
                return;
        ++scan;
        outurl->sip_uri_query.sip_str_ptr = scan;
        outurl->sip_uri_query.sip_str_len = uend - scan;

        while (scan < uend && (SIP_URI_ISRESERVED(*scan) ||
            SIP_URI_ISUNRESERVED(*scan) || SIP_URI_ISURLESCAPE(scan, uend))) {
                ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_QUERY;
}

/*
 * the format of path is supposed to be /XXX;XXX/XXX;
 * abs-path       =  "/" path-segments
 * path-segments  =  segment *( "/" segment )
 * segment        =  *pchar *( ";" param )
 * param          =  *pchar
 * pchar          =  unreserved / escaped /
 *                   ":" / "@" / "&" / "=" / "+" / "$" / ","
 */
static void
sip_uri_parse_abs_path(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend || *scan != '/')
                return;
        outurl->sip_uri_path.sip_str_ptr = scan;
        outurl->sip_uri_path.sip_str_len = uend - scan;

        ++scan;
        while (scan < uend && (SIP_URI_ISPCHAR(*scan) ||
            SIP_URI_ISUNRESERVED(*scan) || SIP_URI_ISURLESCAPE(scan, uend) ||
            *scan == '/' || *scan == ';')) {
                ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_PATH;
}
/*
 * reg-name =  1*( unreserved / escaped / "$" / "," / ";"
 *             / ":" / "@" / "&" / "=" / "+" )
 */
static void
sip_uri_parse_abs_regname(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend)
                return;
        outurl->sip_uri_regname.sip_str_ptr = scan;
        outurl->sip_uri_regname.sip_str_len = uend - scan;

        while (scan < uend && (SIP_URI_ISUNRESERVED(*scan) ||
            SIP_URI_ISURLESCAPE(scan, uend) || SIP_URI_ISREGNAME(*scan))) {
                ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_REGNAME;
}

/*
 * The format of the password is supposed to be :XXX
 * password =  *( unreserved / escaped / "&" / "=" / "+" / "$" / "," )
 */
static void
sip_uri_parse_password(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend || *scan != ':' || scan + 1 == uend)
                return;
        ++scan;
        outurl->sip_uri_password.sip_str_ptr = scan;
        outurl->sip_uri_password.sip_str_len = uend - scan;

        while (scan < uend && (SIP_URI_ISUNRESERVED(*scan) ||
            SIP_URI_ISURLESCAPE(scan, uend) || SIP_URI_ISOTHER(*scan) ||
            *scan == '&')) {
                ++scan;
        }
        if (scan < uend)
                outurl->sip_uri_errflags |= SIP_URIERR_PASS;
}

/*
 * user =  1*( unreserved / escaped / user-unreserved )
 * user-unreserved  =  "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
 */
static void
sip_uri_parse_user(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_USER;
                return;
        }
        outurl->sip_uri_user.sip_str_ptr = scan;
        outurl->sip_uri_user.sip_str_len = uend - scan;

        if (sip_uri_parse_tel(scan, uend)) {
                outurl->sip_uri_isteluser = B_TRUE;
        } else {
                while (scan < uend && (SIP_URI_ISUNRESERVED(*scan) ||
                    SIP_URI_ISURLESCAPE(scan, uend) || SIP_URI_ISUSER(*scan))) {
                        ++scan;
                }
                if (scan < uend)
                        outurl->sip_uri_errflags |= SIP_URIERR_USER;
        }
}

/*
 * the format of port is supposed to be :XXX
 * port =  1*DIGIT
 */
static void
sip_uri_parse_port(_sip_uri_t *outurl, char *scan, char *uend)
{
        if (scan == uend || *scan != ':' || scan + 1 == uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_PORT;
                return;
        }
        ++scan;
        /*
         * parse numeric port number
         */
        if (SIP_URI_ISDIGIT(*scan)) {
                outurl->sip_uri_port = *scan - '0';
                while (++scan < uend && SIP_URI_ISDIGIT(*scan)) {
                    outurl->sip_uri_port =
                        outurl->sip_uri_port * 10 + (*scan - '0');
                        if (outurl->sip_uri_port > 0xffff) {
                                outurl->sip_uri_errflags |= SIP_URIERR_PORT;
                                outurl->sip_uri_port = 0;
                                break;
                        }
                }
        }
        if (scan < uend) {
                outurl->sip_uri_errflags |= SIP_URIERR_PORT;
                outurl->sip_uri_port = 0;
        }
}

/*
 * parse an IPv4 address
 *    1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
 *  advances pscan to end of IPv4 address, or after last "." that was
 *  a valid IPv4 or domain name.
 * returns 1 if ipv4 found, 0 otherwise
 */
static int
sip_uri_parse_ipv4(char *scan, char *uend)
{
        int     j = 0;
        int     val = 0;

        for (j = 0; j < 4; ++j) {
                if (!SIP_URI_ISDIGIT(*scan))
                        break;
                val = *scan - '0';
                while (++scan < uend && SIP_URI_ISDIGIT(*scan)) {
                        val = val * 10 + (*scan - '0');
                        if (val > 255)
                                return (0);
                }
                if (j < 3) {
                        if (*scan != '.')
                                break;
                        ++scan;
                }
        }

        if (j == 4 && scan == uend)
                return (1);

        return (0);
}

/*
 * parse an IPv6 address
 *  IPv6address = hexpart [ ":" IPv4address ]
 *  IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
 *  hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
 *  hexseq  = hex4 *( ":" hex4)
 *  hex4    = 1*4HEXDIG
 *  if not found, leaves pscan unchanged, otherwise advances to end
 *  returns 1 if valid,
 *  0 if invalid
 */
static int
sip_uri_parse_ipv6(char *scan, char *uend)
{
        char            *mark;
        unsigned        j = 0;                  /* index for addr */
        unsigned        val = 0;                /* hex value */
        int             zpad = 0;               /* index of :: delimiter */

        if (*scan != '[')
                return (0);
        ++scan;
        j = 0;

        /*
         * check for leading "::", set zpad to the position of the "::"
         */
        if (scan + 1 < uend && scan[0] == ':' && scan[1] == ':') {
                zpad = 0;
                scan += 2;
        } else {
                zpad = -1;
        }

        /*
         * loop through up to 16 bytes of IPv6 address
         */
        while (scan < uend && j < 15) {
                if (!SIP_URI_ISHEX(*scan))
                        break;
                mark = scan;
                val = SIP_URI_HEXVAL(*scan);
                while (++scan < uend && SIP_URI_ISHEX(*scan)) {
                        val = val * 16 + SIP_URI_HEXVAL(*scan);
                        if (val > 0xffff)
                                return (0);
                }

                /*
                 * always require a delimiter or ]
                 */
                if (scan == uend)
                        return (0);

                if (*scan == '.' && (j == 12 || (zpad != -1 && j < 12)) &&
                    mark < uend && sip_uri_parse_ipv4(mark, uend - 1) &&
                    *(uend - 1) == ']') {
                        mark = uend - 1;
                        j += 4;
                        scan = mark + 1;
                        break;
                }

                /*
                 * set address
                 */
                j += 2;

                /*
                 * check for delimiter or ]
                 */
                if (*scan == ':') {
                        /*
                         * found ":" delimiter, check for "::"
                         */
                        if (++scan < uend && *scan == ':') {
                                if (zpad != -1)
                                        return (0);
                                zpad = j;
                                if (++scan < uend && *scan == ']') {
                                        ++scan;
                                        break;
                                }
                        }
                } else if (*scan == ']' && (j == 16 || zpad != -1)) {
                        ++scan;
                        break;
                } else {
                        /*
                         * not a valid delimiter
                         */
                        return (0);
                }
        }
        if (zpad == -1 && j < 16)
                return (0);
        if (zpad != -1) {
                if (j > 15)
                        return (0);
        }

        if (scan == uend)
                return (1);

        return (0);
}

/*
 * hostname         =  *( domainlabel "." ) toplabel [ "." ]
 * domainlabel      =  alphanum / alphanum *( alphanum / "-" ) alphanum
 * toplabel         =  ALPHA / ALPHA *( alphanum / "-" ) alphanum
 */
static int
sip_uri_parse_hostname(char *scan, char *uend)
{
        int     sawalpha = 0;

        if (scan < uend && SIP_URI_ISALNUM(*scan)) {
                do {
                        sawalpha = SIP_URI_ISALPHA(*scan);
                        while (SIP_URI_ISHOST(*scan))
                                ++scan;
                        if (*scan != '.')
                                break;
                        ++scan;
                } while (scan < uend && SIP_URI_ISALNUM(*scan));
        }

        if (sawalpha && scan == uend)
                return (1);
        return (0);
}


/*
 * parse the network path portion of a full URL
 */
static void
sip_uri_parse_netpath(_sip_uri_t *outurl, char **pscan, char *uend,
    boolean_t issip)
{
        char    *mark = (char *)0;
        char    *mark2 = (char *)0;
        char    *scan = *pscan;
        int     gothost = 0;

        /*
         * look for the first high-level delimiter
         */
        mark = scan;
        while (scan < uend && *scan != '@')
                ++scan;
        /*
         * handle userinfo section of URL
         */
        if (scan < uend && *scan == '@') {
                /*
                 * parse user
                 */
                mark2 = mark;
                while (mark < scan && *mark != ':')
                        ++mark;
                sip_uri_parse_user(outurl, mark2, mark);
                /*
                 * parse password
                 */
                if (*mark == ':')
                        sip_uri_parse_password(outurl, mark, scan);
                mark = ++scan;
        }

        scan = mark;
        if (scan < uend && *scan == '[') {      /* look for an IPv6 address */
                while (scan < uend && *scan != ']')
                        ++scan;
                if (scan < uend) {
                        ++scan;
                        if (sip_uri_parse_ipv6(mark, scan))
                                gothost = 1;
                }
        } else {
                while (scan < uend && ((issip && !SIP_URI_ISSIPHDELIM(*scan)) ||
                    (!issip && !SIP_URI_ISABSHDELIM(*scan)))) {
                        ++scan;
                }

                /*
                 * look for an IPv4 address
                 */
                if (mark < scan && SIP_URI_ISDIGIT(*mark) &&
                    sip_uri_parse_ipv4(mark, scan)) {
                        gothost = 1;
                }

                /*
                 * look for a valid host name
                 */
                if (!gothost && mark < scan &&
                    sip_uri_parse_hostname(mark, scan)) {
                        gothost = 1;
                }
        }
        /*
         * handle invalid host name
         */
        if (!gothost)
                outurl->sip_uri_errflags |= SIP_URIERR_HOST;
        /*
         * save host name
         */
        outurl->sip_uri_host.sip_str_ptr = mark;
        outurl->sip_uri_host.sip_str_len = scan - mark;

        mark = scan;
        /*
         * parse the port number
         */
        if (scan < uend && *scan == ':') {
                while (scan < uend && ((issip && !SIP_URI_ISSIPDELIM(*scan)) ||
                    (!issip && !SIP_URI_ISABSDELIM(*scan)))) {
                        ++scan;
                }
                sip_uri_parse_port(outurl, mark, scan);
        }

        /*
         * set return pointer
         */
        *pscan = scan;
}

/*
 * parse a URL
 * URL = SIP-URI / SIPS-URI / absoluteURI
 */
void
sip_uri_parse_it(_sip_uri_t *outurl, sip_str_t *uri_str)
{
        char            *mark;
        char            *scan;
        char            *uend;
        char            *str = uri_str->sip_str_ptr;
        unsigned        urlen = uri_str->sip_str_len;

        /*
         * reset output parameters
         */
        (void) memset(outurl, 0, sizeof (sip_uri_t));

        /*
         * strip enclosing angle brackets
         */
        if (urlen > 1 && str[0] == '<' && str[urlen-1] == '>') {
                urlen -= 2;
                ++str;
        }
        uend = str + urlen;

        /*
         * strip off space prefix and trailing spaces
         */
        while (str < uend && isspace(*str)) {
                ++str;
                --urlen;
        }
        while (str < uend && isspace(*(uend - 1))) {
                --uend;
                --urlen;
        }

        /*
         * strip off "URL:" prefix
         */
        if (urlen > 4 && sip_uri_url_casecmp(str, "URL:", 4) == 0) {
                str += 4;
                urlen -= 4;
        }

        /*
         * parse the scheme name
         */
        mark = scan = str;
        while (scan < uend && *scan != ':')
                ++scan;
        if (scan == uend || !sip_uri_parse_scheme(outurl, mark, scan)) {
                outurl->sip_uri_errflags |= SIP_URIERR_SCHEME;
                return;
        }

        if ((outurl->sip_uri_scheme.sip_str_len == SIP_SCHEME_LEN &&
            !memcmp(outurl->sip_uri_scheme.sip_str_ptr, SIP_SCHEME,
            SIP_SCHEME_LEN)) ||
            (outurl->sip_uri_scheme.sip_str_len == SIPS_SCHEME_LEN &&
            !memcmp(outurl->sip_uri_scheme.sip_str_ptr, SIPS_SCHEME,
            SIPS_SCHEME_LEN))) {
                outurl->sip_uri_issip = B_TRUE;
        } else {
                outurl->sip_uri_issip = B_FALSE;
        }
        ++scan; /* skip ':' */

        if (outurl->sip_uri_issip) {
                /*
                 * parse SIP URL
                 */
                sip_uri_parse_netpath(outurl, &scan, uend, B_TRUE);

                /*
                 * parse parameters
                 */
                if (scan < uend && *scan == ';') {
                        mark = scan;
                        while (scan < uend && *scan != '?')
                                ++scan;
                        sip_uri_parse_params(outurl, mark, scan);
                }

                /*
                 * parse headers
                 */
                if (scan < uend && *scan == '?')
                        sip_uri_parse_headers(outurl, scan, uend);
        } else if (scan < uend && scan[0] == '/') {      /* parse absoluteURL */
                ++scan;
                /*
                 * parse authority
                 * authority    = srvr / reg-name
                 * srvr         = [ [ userinfo "@" ] hostport ]
                 * reg-name     = 1*(unreserved / escaped / "$" / ","
                 *                      / ";" / ":" / "@" / "&" / "=" / "+")
                 */
                if (scan < uend && *scan == '/') {
                        ++scan;
                        mark = scan;
                        /*
                         * take authority as srvr
                         */
                        sip_uri_parse_netpath(outurl, &scan, uend, B_FALSE);

                        /*
                         * if srvr failed, take it as reg-name
                         * parse reg-name
                         */
                        if (outurl->sip_uri_errflags & SIP_URIERR_USER ||
                            outurl->sip_uri_errflags & SIP_URIERR_PASS ||
                            outurl->sip_uri_errflags & SIP_URIERR_HOST ||
                            outurl->sip_uri_errflags & SIP_URIERR_PORT) {
                                scan = mark;
                                while (scan < uend && *scan != '/' &&
                                        *scan != '?') {
                                        ++scan;
                                }
                                sip_uri_parse_abs_regname(outurl, mark, scan);
                                if (!(outurl->sip_uri_errflags &
                                    SIP_URIERR_REGNAME)) {
                                        /*
                                         * remove error info of user,
                                         * password, host, port
                                         */
                                        outurl->sip_uri_user.sip_str_ptr = NULL;
                                        outurl->sip_uri_user.sip_str_len = 0;
                                        outurl->sip_uri_errflags &=
                                            ~SIP_URIERR_USER;
                                        outurl->sip_uri_password.sip_str_ptr =
                                            NULL;
                                        outurl->sip_uri_password.sip_str_len =
                                            0;
                                        outurl->sip_uri_errflags &=
                                            ~SIP_URIERR_PASS;
                                        outurl->sip_uri_host.sip_str_ptr = NULL;
                                        outurl->sip_uri_host.sip_str_len = 0;
                                        outurl->sip_uri_errflags &=
                                            ~SIP_URIERR_HOST;
                                        outurl->sip_uri_port = 0;
                                        outurl->sip_uri_errflags &=
                                            ~SIP_URIERR_PORT;
                                }
                        }
                } else {
                        /*
                         * there is no net-path
                         */
                        --scan;
                }
                /*
                 * parse abs-path
                 */
                if (scan < uend && *scan == '/') {
                        mark = scan;
                        while (scan < uend && *scan != '?')
                                ++scan;
                        sip_uri_parse_abs_path(outurl, mark, scan);
                }

                /*
                 * parse query
                 */
                if (scan < uend && *scan == '?')
                        sip_uri_parse_abs_query(outurl, scan, uend);
        } else {
                /*
                 * parse opaque-part
                 */
                sip_uri_parse_abs_opaque(outurl, scan, uend);
        }
}