root/usr/src/lib/libsip/common/sip_parse_generic.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 <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <strings.h>
#include <ctype.h>
#include <sip.h>

#include "sip_miscdefs.h"
#include "sip_msg.h"
#include "sip_parse_uri.h"

/*
 * atoi function from a header
 */
int
sip_atoi(_sip_header_t *sip_header, int *num)
{
        boolean_t       num_found = B_FALSE;

        *num = 0;
        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (isspace(*sip_header->sip_hdr_current)) {
                        sip_header->sip_hdr_current++;
                        if (num_found)
                                break;
                } else if (isdigit(*sip_header->sip_hdr_current)) {
                        *num = (*num * 10) +
                            (*sip_header->sip_hdr_current - '0');
                        num_found = B_TRUE;
                        sip_header->sip_hdr_current++;
                } else {
                        break;
                }
        }
        if (!num_found)
                return (EINVAL);
        return (0);
}

/*
 * Find the 'token'
 */
int
sip_find_token(_sip_header_t *sip_header, char token)
{
        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (token != SIP_COMMA &&
                    *sip_header->sip_hdr_current == SIP_COMMA) {
                        sip_header->sip_hdr_current--;
                        return (1);
                }
                if (*sip_header->sip_hdr_current++ == token) {
                        /*
                         * sip_hdr_current points to the char
                         * after the token
                         */
                        return (0);
                }
        }
        return (1);
}

/*
 * Find a carriage-return
 */
int
sip_find_cr(_sip_header_t *sip_header)
{
        sip_header->sip_hdr_current = sip_header->sip_hdr_end;
        while (*sip_header->sip_hdr_current-- != '\n') {
                if (sip_header->sip_hdr_current == sip_header->sip_hdr_start)
                        return (1);
        }
        return (0);
}

/*
 * Find one of the separator provided, i.e. separator_1st or separator_2nd or
 * separator_3rd.
 */
int
sip_find_separator(_sip_header_t *sip_header, char separator_1st,
    char separator_2nd, char separator_3rd, boolean_t ignore_space)
{
        assert(separator_1st != (char)NULL || separator_2nd != (char)NULL);
        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (ignore_space && (*sip_header->sip_hdr_current == SIP_SP)) {
                        sip_header->sip_hdr_current++;
                        continue;
                }
                if (isspace(*sip_header->sip_hdr_current) ||
                    (separator_1st != 0 &&
                    (*sip_header->sip_hdr_current == separator_1st)) ||
                    (separator_2nd != 0 &&
                    (*sip_header->sip_hdr_current == separator_2nd)) ||
                    (separator_3rd != 0 &&
                    (*sip_header->sip_hdr_current == separator_3rd))) {
                        return (0);
                }
                /*
                 * If we have escape character, go to the next char
                 */
                if (*sip_header->sip_hdr_current == '\\')
                        sip_header->sip_hdr_current++;
                sip_header->sip_hdr_current++;
        }
        return (1);
}

/*
 * Return when we hit a white space
 */
int
sip_find_white_space(_sip_header_t *sip_header)
{
        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (isspace(*sip_header->sip_hdr_current))
                        return (0);
                sip_header->sip_hdr_current++;
        }
        return (1);
}

/*
 * Skip to the next non-whitespace
 */
int
sip_skip_white_space(_sip_header_t *sip_header)
{
        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (!isspace(*sip_header->sip_hdr_current))
                        return (0);
                sip_header->sip_hdr_current++;
        }
        return (1);
}


/*
 * Skip to the non-white space in the reverse direction
 */
int
sip_reverse_skip_white_space(_sip_header_t *sip_header)
{
        while (sip_header->sip_hdr_current >= sip_header->sip_hdr_start) {
                if (!isspace(*sip_header->sip_hdr_current))
                        return (0);
                sip_header->sip_hdr_current--;
        }
        return (1);
}

/*
 * get to the first non space after ':'
 */
int
sip_parse_goto_values(_sip_header_t *sip_header)
{
        if (sip_find_token(sip_header, SIP_HCOLON) !=  0)
                return (1);
        if (sip_skip_white_space(sip_header) != 0)
                return (1);

        return (0);
}

/*
 * Skip the current value.
 */
int
sip_goto_next_value(_sip_header_t *sip_header)
{
        boolean_t       quoted = B_FALSE;

        while (sip_header->sip_hdr_current < sip_header->sip_hdr_end) {
                if (*sip_header->sip_hdr_current == SIP_QUOTE) {
                        if (quoted)
                                quoted = B_FALSE;
                        else
                                quoted = B_TRUE;
                } else if (!quoted &&
                    *sip_header->sip_hdr_current == SIP_COMMA) {
                        /*
                         * value ends before the COMMA
                         */
                        sip_header->sip_hdr_current--;
                        return (0);
                }
                sip_header->sip_hdr_current++;
        }
        if (quoted)
                return (1);
        return (0);
}

/*
 * Parse the header into parameter list. Parameters start with a ';'
 */
int
sip_parse_params(_sip_header_t *sip_header, sip_param_t **parsed_list)
{
        sip_param_t     *param = NULL;
        sip_param_t     *new_param;
        char            *tmp_ptr;

        if (parsed_list == NULL)
                return (0);

        *parsed_list = NULL;
        for (;;) {
                boolean_t       quoted_name = B_FALSE;

                /*
                 * First check if there are any params
                 */
                if (sip_skip_white_space(sip_header) != 0)
                        return (0);
                if (*sip_header->sip_hdr_current != SIP_SEMI)
                        return (0);

                sip_header->sip_hdr_current++;

                new_param = calloc(1, sizeof (sip_param_t));
                if (new_param == NULL)
                        return (ENOMEM);

                if (param != NULL)
                        param->param_next = new_param;
                else
                        *parsed_list = new_param;

                param = new_param;

                /*
                 * Let's get to the start of the param name
                 */
                if (sip_skip_white_space(sip_header) != 0)
                        return (EPROTO);
                /*
                 * start of param name
                 */
                tmp_ptr = sip_header->sip_hdr_current;
                param->param_name.sip_str_ptr = tmp_ptr;

                if (sip_find_separator(sip_header, SIP_EQUAL, SIP_SEMI,
                    SIP_COMMA, B_FALSE) != 0) {
                        param->param_name.sip_str_len =
                            sip_header->sip_hdr_current - tmp_ptr;
                        param->param_value.sip_str_ptr = NULL;
                        param->param_value.sip_str_len = 0;
                        return (0);
                }

                /*
                 * End of param name
                 */
                param->param_name.sip_str_len =
                    sip_header->sip_hdr_current - tmp_ptr;

                if (sip_skip_white_space(sip_header) != 0 ||
                    *sip_header->sip_hdr_current == SIP_COMMA) {
                        param->param_value.sip_str_ptr = NULL;
                        param->param_value.sip_str_len = 0;
                        return (0);
                }
                if (*sip_header->sip_hdr_current == SIP_SEMI) {
                        param->param_value.sip_str_ptr = NULL;
                        param->param_value.sip_str_len = 0;
                        continue;
                }
                assert(*sip_header->sip_hdr_current == SIP_EQUAL);

                /*
                 * We are at EQUAL, lets go beyond that
                 */
                sip_header->sip_hdr_current++;

                if (sip_skip_white_space(sip_header) != 0)
                        return (EPROTO);

                if (*sip_header->sip_hdr_current == SIP_QUOTE) {
                        sip_header->sip_hdr_current++;
                        quoted_name = B_TRUE;
                }

                /*
                 * start of param value
                 */
                param->param_value.sip_str_ptr = sip_header->sip_hdr_current;
                tmp_ptr = sip_header->sip_hdr_current;

                if (quoted_name && sip_find_token(sip_header, SIP_QUOTE) != 0) {
                        return (EPROTO);
                } else if (sip_find_separator(sip_header, SIP_SEMI, SIP_COMMA,
                    0, B_FALSE) != 0) {
                        return (EPROTO);
                }
                param->param_value.sip_str_len = sip_header->sip_hdr_current -
                    tmp_ptr;
                if (quoted_name)
                        param->param_value.sip_str_len--;
        }
}

/*
 * a header that only has "header_name : " is an empty header
 * ":" must exist
 * sip_hdr_current resets to sip_hdr_start before exit
 */
boolean_t
sip_is_empty_hdr(_sip_header_t *sip_header)
{
        if (sip_find_token(sip_header, SIP_HCOLON) != 0) {
                sip_header->sip_hdr_current = sip_header->sip_hdr_start;
                return (B_FALSE);
        }

        if (sip_skip_white_space(sip_header) == 0) {
                sip_header->sip_hdr_current = sip_header->sip_hdr_start;
                return (B_FALSE);
        }

        sip_header->sip_hdr_current = sip_header->sip_hdr_start;
        return (B_TRUE);
}

/*
 * Parsing an empty header, i.e. only has a ":"
 */
int
sip_parse_hdr_empty(_sip_header_t *hdr, sip_parsed_header_t **phdr)
{
        sip_parsed_header_t     *parsed_header;

        if (hdr == NULL || phdr == NULL)
                return (EINVAL);

        /*
         * check if already parsed
         */
        if (hdr->sip_hdr_parsed != NULL) {
                *phdr = hdr->sip_hdr_parsed;
                return (0);
        }

        *phdr = NULL;

        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_header = hdr;

        parsed_header->value = NULL;

        *phdr = parsed_header;
        return (0);
}

/*
 * validate uri str and parse uri using uri_parse()
 */
static void
sip_parse_uri_str(sip_str_t *sip_str, sip_hdr_value_t *value)
{
        int             error;

        /*
         * Parse uri
         */
        if (sip_str->sip_str_len > 0) {
                value->sip_value_parsed_uri = sip_parse_uri(sip_str, &error);
                if (value->sip_value_parsed_uri == NULL)
                        return;
                if (error != 0 ||
                    value->sip_value_parsed_uri->sip_uri_errflags != 0) {
                        value->sip_value_state = SIP_VALUE_BAD;
                }
        }
}

/*
 * Some basic common checks before parsing the headers
 */
int
sip_prim_parsers(_sip_header_t *sip_header, sip_parsed_header_t **header)
{
        if (sip_header == NULL || header == NULL)
                return (EINVAL);

        /*
         * check if already parsed
         */
        if (sip_header->sip_hdr_parsed != NULL) {
                *header = sip_header->sip_hdr_parsed;
                return (0);
        }
        *header = NULL;

        assert(sip_header->sip_hdr_start == sip_header->sip_hdr_current);

        if (sip_parse_goto_values(sip_header) != 0)
                return (EPROTO);

        return (0);
}

/*
 * Parse SIP/2.0 string
 */
int
sip_get_protocol_version(_sip_header_t *sip_header,
    sip_proto_version_t *sip_proto_version)
{
        if (sip_skip_white_space(sip_header) != 0)
                return (1);

        if (strncasecmp(sip_header->sip_hdr_current, SIP, strlen(SIP)) == 0) {
                sip_proto_version->name.sip_str_ptr =
                    sip_header->sip_hdr_current;
                sip_proto_version->name.sip_str_len = strlen(SIP);

                if (sip_find_token(sip_header, SIP_SLASH) != 0)
                        return (1);
                if (sip_skip_white_space(sip_header) != 0)
                        return (1);

                sip_proto_version->version.sip_str_ptr =
                    sip_header->sip_hdr_current;
                while (isdigit(*sip_header->sip_hdr_current)) {
                        sip_header->sip_hdr_current++;
                        if (sip_header->sip_hdr_current >=
                            sip_header->sip_hdr_end) {
                                return (1);
                        }
                }
                if (*sip_header->sip_hdr_current != SIP_PERIOD)
                        return (1);
                sip_header->sip_hdr_current++;

                if (!isdigit(*sip_header->sip_hdr_current))
                        return (1);
                while (isdigit(*sip_header->sip_hdr_current)) {
                        sip_header->sip_hdr_current++;
                        if (sip_header->sip_hdr_current >=
                            sip_header->sip_hdr_end) {
                                return (1);
                        }
                }

                sip_proto_version->version.sip_str_len =
                    sip_header->sip_hdr_current -
                    sip_proto_version->version.sip_str_ptr;
                return (0);
        }
        return (1);
}

/*
 * parser1 parses hdr format
 *      header_name: val1[; par1=pval1;par2=pval2 ..][, val2[;parlist..] ]
 *      val can be str1/str2 or str
 * headers: Accept, Accept-Encode, Accept-lang, Allow, Content-disp,
 *          Content-Encode, Content-Lang, In-reply-to,
 *          Priority, Require, Supported, Unsupported
 *          Allow-Events, Event, Subscription-State
 */
int
sip_parse_hdr_parser1(_sip_header_t *hdr, sip_parsed_header_t **phdr, char sep)
{
        sip_parsed_header_t     *parsed_header;
        int                     ret;
        sip_hdr_value_t         *value = NULL;
        sip_hdr_value_t         *last_value = NULL;

        if ((ret = sip_prim_parsers(hdr, phdr)) != 0)
                return (ret);

        /*
         * check if previously parsed
         */
        if (*phdr != NULL) {
                hdr->sip_hdr_parsed = *phdr;
                return (0);
        }

        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_parsed_header_version = SIP_PARSED_HEADER_VERSION_1;
        parsed_header->sip_header = hdr;

        while (hdr->sip_hdr_current < hdr->sip_hdr_end) {
                value = calloc(1, sizeof (sip_hdr_value_t));
                if (value == NULL) {
                        sip_free_phdr(parsed_header);
                        return (ENOMEM);
                }
                if (last_value != NULL)
                        last_value->sip_next_value = value;
                else
                        parsed_header->value = (sip_value_t *)value;

                value->sip_value_start = hdr->sip_hdr_current;
                value->sip_value_header = parsed_header;

                if (sip_find_separator(hdr, sep, SIP_COMMA, SIP_SEMI,
                    B_FALSE) == 0) {
                        char    c = *hdr->sip_hdr_current;

                        if (isspace(c) && sep == 0) {
                                value->str_val_ptr = value->sip_value_start;
                                value->str_val_len = hdr->sip_hdr_current -
                                    value->sip_value_start;
                                /*
                                 * nothing at the end except space
                                 */
                                if (sip_skip_white_space(hdr) != 0) {
                                        value->sip_value_end =
                                            hdr->sip_hdr_current;
                                        goto end;
                                }
                                /*
                                 * white space skipped
                                 */
                                c = *(hdr->sip_hdr_current);
                        }

                        /*
                         * only one string until COMMA, use sip_str_t
                         */
                        if (c == SIP_COMMA) {
                                char    *t = hdr->sip_hdr_current;

                                hdr->sip_hdr_current--;
                                (void) sip_reverse_skip_white_space(hdr);
                                value->str_val_ptr = value->sip_value_start;
                                value->str_val_len = hdr->sip_hdr_current -
                                    value->sip_value_start + 1;
                                hdr->sip_hdr_current = t;
                                goto get_next_val;
                        }

                        /*
                         * two strings, use sip_2strs_t
                         */
                        if ((sep != 0) && (c == sep)) {
                                value->strs1_val_ptr = value->sip_value_start;
                                value->strs1_val_len = hdr->sip_hdr_current -
                                    value->sip_value_start;

                                value->strs2_val_ptr =
                                    (++hdr->sip_hdr_current);
                                if (sip_find_separator(hdr, SIP_SEMI, SIP_COMMA,
                                    0, B_FALSE) == 0) {
                                        char t = *(hdr->sip_hdr_current);
                                        value->strs2_val_len =
                                            hdr->sip_hdr_current -
                                            value->strs2_val_ptr;
                                        /*
                                         * if COMMA, no param list, get next val
                                         * if SEMI, need to set params list
                                         */
                                        if (t == SIP_COMMA)
                                                goto get_next_val;
                                } else { /* the last part */
                                        value->strs2_val_len =
                                            hdr->sip_hdr_current -
                                            value->strs2_val_ptr;
                                        value->sip_value_end =
                                            hdr->sip_hdr_current;
                                        goto end;
                                }
                        } else if (sep != 0) {
                                value->sip_value_state = SIP_VALUE_BAD;
                                goto get_next_val;
                        }

                        /*
                         * c == SEMI, value contains single string
                         * only one string until SEMI, use sip_str_t
                         */
                        if (c == SIP_SEMI) {
                                char    *t = hdr->sip_hdr_current;

                                hdr->sip_hdr_current--;
                                /*
                                 * get rid of SP at end of value field
                                 */
                                (void) sip_reverse_skip_white_space(hdr);
                                value->str_val_ptr = value->sip_value_start;
                                value->str_val_len = hdr->sip_hdr_current -
                                    value->str_val_ptr + 1;
                                hdr->sip_hdr_current = t;
                        }

                        /*
                         * if SEMI exists in the value, set params list
                         * two situations, there is or not SLASH before SEMI
                         */
                        ret = sip_parse_params(hdr, &value->sip_param_list);
                        if (ret == EPROTO) {
                                value->sip_value_state = SIP_VALUE_BAD;
                        } else if (ret != 0) {
                                sip_free_phdr(parsed_header);
                                return (ret);
                        }
                        goto get_next_val;
                } else {
                        value->str_val_ptr = value->sip_value_start;
                        value->str_val_len = hdr->sip_hdr_current -
                            value->sip_value_start;
                        value->sip_value_end = hdr->sip_hdr_current;
                        goto end;
                }
get_next_val:
                if (sip_find_token(hdr, SIP_COMMA) != 0) {
                        value->sip_value_end = hdr->sip_hdr_current;
                        break;
                }
                value->sip_value_end = hdr->sip_hdr_current - 1;
                last_value = value;
                (void) sip_skip_white_space(hdr);
        }

end:
        *phdr = parsed_header;
        hdr->sip_hdr_parsed = *phdr;
        return (0);
}

/*
 * header_name: int
 * headers: Expires, Min-Expires
 */
/* ARGSUSED */
int
sip_parse_hdr_parser2(_sip_header_t *hdr, sip_parsed_header_t **phdr,
    int val_type)
{
        sip_parsed_header_t     *parsed_header;
        int                     ret = 0;
        sip_hdr_value_t         *value = NULL;

        if ((ret = sip_prim_parsers(hdr, phdr)) != 0)
                return (ret);

        /*
         * check if previously parsed
         */
        if (*phdr != NULL) {
                hdr->sip_hdr_parsed = *phdr;
                return (0);
        }
        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_parsed_header_version = SIP_PARSED_HEADER_VERSION_1;
        parsed_header->sip_header = hdr;

        value = calloc(1, sizeof (sip_hdr_value_t));
        if (value == NULL) {
                sip_free_phdr(parsed_header);
                return (ENOMEM);
        }

        parsed_header->value = (sip_value_t *)value;

        value->sip_value_start = hdr->sip_hdr_current;
        value->sip_value_header = parsed_header;

        ret = sip_atoi(hdr, &value->int_val);
        if (ret != 0) {
                value->int_val = 0;
                value->sip_value_state = SIP_VALUE_BAD;
        }

        value->sip_value_end = hdr->sip_hdr_current - 1;

        *phdr = parsed_header;
        hdr->sip_hdr_parsed = *phdr;
        return (0);
}

/*
 * parser3 parses hdr format
 * header_name: <val1>[, <val2>]
 * Alert-Info, Call-Info, Error-Info, reply-to
 */
int
sip_parse_hdr_parser3(_sip_header_t *hdr, sip_parsed_header_t **phdr, int type,
    boolean_t parse_uri)
{
        sip_parsed_header_t     *parsed_header;
        sip_hdr_value_t         *value = NULL;
        sip_hdr_value_t         *last_value = NULL;
        int                     ret;

        if ((ret = sip_prim_parsers(hdr, phdr)) != 0)
                return (ret);

        /*
         * check if previously parsed
         */
        if (*phdr != NULL) {
                hdr->sip_hdr_parsed = *phdr;
                return (0);
        }
        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_parsed_header_version = SIP_PARSED_HEADER_VERSION_1;
        parsed_header->sip_header = hdr;
        while (hdr->sip_hdr_current < hdr->sip_hdr_end) {
                int             r;

                value = calloc(1, sizeof (sip_hdr_value_t));
                if (value == NULL) {
                        sip_free_phdr(parsed_header);
                        return (ENOMEM);
                }

                if (last_value != NULL)
                        last_value->sip_next_value = value;
                else
                        parsed_header->value = (sip_value_t *)value;

                value->sip_value_start = hdr->sip_hdr_current;
                value->sip_value_header = parsed_header;

                if (type == SIP_STRS_VAL) {
                        if (sip_find_token(hdr, SIP_LAQUOT) == 0) {
                                char    *cur;

                                /*
                                 * record the position after LAQUOT
                                 */
                                cur = hdr->sip_hdr_current;
                                /*
                                 * get display name and store in str1
                                 */
                                hdr->sip_hdr_current = value->sip_value_start;
                                if (*(hdr->sip_hdr_current) != SIP_LAQUOT) {
                                        /*
                                         * record start pos of display name
                                         */
                                        char    *tmp = hdr->sip_hdr_current;

                                        if (*hdr->sip_hdr_current ==
                                            SIP_QUOTE) {
                                                hdr->sip_hdr_current++;
                                                tmp++;
                                                if (sip_find_token(hdr,
                                                    SIP_QUOTE) != 0) {
                                                        value->sip_value_state =
                                                            SIP_VALUE_BAD;
                                                        goto get_next_val;
                                                }
                                                hdr->sip_hdr_current -= 2;
                                        } else {
                                                hdr->sip_hdr_current = cur - 2;
                                                (void)
                                                    sip_reverse_skip_white_space
                                                    (hdr);
                                        }
                                        value->strs1_val_ptr = tmp;
                                        value->strs1_val_len =
                                            hdr->sip_hdr_current - tmp + 1;
                                } else {
                                        value->strs1_val_ptr = NULL;
                                        value->strs1_val_len = 0;
                                }

                                /*
                                 * set current to the char after LAQUOT
                                 */
                                hdr->sip_hdr_current = cur;
                                value->strs2_val_ptr = hdr->sip_hdr_current;
                                if (sip_find_token(hdr, SIP_RAQUOT)) {
                                        /*
                                         * no RAQUOT
                                         */
                                        value->strs1_val_ptr = NULL;
                                        value->strs1_val_len = 0;
                                        value->strs2_val_ptr = NULL;
                                        value->strs2_val_len = 0;
                                        value->sip_value_state = SIP_VALUE_BAD;
                                        goto get_next_val;
                                }
                                value->strs2_val_len = hdr->sip_hdr_current -
                                    value->strs2_val_ptr - 1;
                        } else {
                                char    *cur;

                                /*
                                 * No display name - Only URI.
                                 */
                                value->strs1_val_ptr = NULL;
                                value->strs1_val_len = 0;
                                cur = value->sip_value_start;
                                hdr->sip_hdr_current = cur;
                                if (sip_find_separator(hdr, SIP_COMMA,
                                    0, 0, B_FALSE) != 0) {
                                        value->strs2_val_ptr = cur;
                                        value->strs2_val_len =
                                            hdr->sip_hdr_current -
                                            value->strs2_val_ptr - 1;
                                } else if (*hdr->sip_hdr_current == SIP_SP) {
                                        value->strs2_val_ptr = cur;
                                        cur = hdr->sip_hdr_current - 1;
                                        if (sip_skip_white_space(hdr) != 0) {
                                                value->strs2_val_len = cur -
                                                    value->strs2_val_ptr - 1;
                                        } else if (*hdr->sip_hdr_current ==
                                            SIP_COMMA) {
                                                value->strs2_val_len = cur -
                                                    value->strs2_val_ptr - 1;
                                        } else {
                                                value->sip_value_state =
                                                    SIP_VALUE_BAD;
                                                goto get_next_val;
                                        }
                                } else {
                                        value->strs2_val_ptr = cur;
                                        value->strs2_val_len =
                                            hdr->sip_hdr_current -
                                            value->strs2_val_ptr;
                                }
                        }
                        if (parse_uri)
                                sip_parse_uri_str(&value->strs_s2, value);
                }

                if (type == SIP_STR_VAL) {
                        /*
                         * alert-info, error-info, call-info
                         */
                        if (sip_find_token(hdr, SIP_LAQUOT) == 0) {
                                value->str_val_ptr = hdr->sip_hdr_current;
                                if (sip_find_token(hdr, SIP_RAQUOT) == 0) {
                                        value->str_val_len =
                                            hdr->sip_hdr_current -
                                            value->str_val_ptr - 1;
                                } else {
                                        value->str_val_ptr = NULL;
                                        value->str_val_len = 0;
                                        value->sip_value_state = SIP_VALUE_BAD;
                                        goto get_next_val;
                                }
                                hdr->sip_hdr_current--;
                        } else {
                                value->str_val_ptr = NULL;
                                value->str_val_len = 0;
                                value->sip_value_state = SIP_VALUE_BAD;
                                goto get_next_val;
                        }
                        if (parse_uri)
                                sip_parse_uri_str(&value->str_val, value);
                }

                r = sip_find_separator(hdr, SIP_COMMA, SIP_SEMI, 0,
                    B_FALSE);
                if (r != 0) {
                        value->sip_value_end = hdr->sip_hdr_current;
                        goto end;
                }
                if (*hdr->sip_hdr_current == SIP_SEMI) {
                        (void) sip_parse_params(hdr,
                            &(value->sip_param_list));
                        goto get_next_val;
                }

                if (*hdr->sip_hdr_current == SIP_COMMA) {
                        hdr->sip_hdr_current--;
                        goto get_next_val;
                }
get_next_val:
                if (sip_find_token(hdr, SIP_COMMA) != 0) {
                        value->sip_value_end = hdr->sip_hdr_current;
                        break;
                }
                value->sip_value_end = hdr->sip_hdr_current - 1;
                last_value = value;
                (void) sip_skip_white_space(hdr);
        }

end:
        *phdr = parsed_header;
        hdr->sip_hdr_parsed = *phdr;
        return (0);
}

/*
 * parser4 parses hdr format, the whole field is one single str
 * header: Subject, MIME-Version, Organization, Server, User-Agent
 */
int
sip_parse_hdr_parser4(_sip_header_t *hdr, sip_parsed_header_t **phdr)
{
        sip_parsed_header_t     *parsed_header;
        sip_hdr_value_t         *value = NULL;
        int                     ret;

        if ((ret = sip_prim_parsers(hdr, phdr)) != 0)
                return (ret);

        /*
         * check if previously parsed
         */
        if (*phdr != NULL) {
                hdr->sip_hdr_parsed = *phdr;
                return (0);
        }
        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_parsed_header_version = SIP_PARSED_HEADER_VERSION_1;
        parsed_header->sip_header = hdr;

        value = calloc(1, sizeof (sip_hdr_value_t));
        if (value == NULL) {
                sip_free_phdr(parsed_header);
                return (ENOMEM);
        }

        parsed_header->value = (sip_value_t *)value;

        value->sip_value_start = hdr->sip_hdr_current;
        value->sip_value_header = parsed_header;

        value->str_val_ptr = hdr->sip_hdr_current;
        /*
         * get rid of CRLF at end
         */
        value->str_val_len = hdr->sip_hdr_end - value->str_val_ptr - 2;
        value->sip_value_end = hdr->sip_hdr_end;

        *phdr = parsed_header;
        hdr->sip_hdr_parsed = *phdr;
        return (0);
}

int
sip_parse_hdr_parser5(_sip_header_t *hdr, sip_parsed_header_t **phdr,
    boolean_t parse_uri)
{
        sip_parsed_header_t     *parsed_header;
        sip_hdr_value_t         *value = NULL;
        sip_param_t             *tmp_param;
        boolean_t               first_param = B_TRUE;
        int                     ret;

        if ((ret = sip_prim_parsers(hdr, phdr)) != 0)
                return (ret);

        /*
         * check if previously parsed
         */
        if (*phdr != NULL) {
                hdr->sip_hdr_parsed = *phdr;
                return (0);
        }
        parsed_header = calloc(1, sizeof (sip_parsed_header_t));
        if (parsed_header == NULL)
                return (ENOMEM);
        parsed_header->sip_parsed_header_version = SIP_PARSED_HEADER_VERSION_1;
        parsed_header->sip_header = hdr;

        value = calloc(1, sizeof (sip_hdr_value_t));
        if (value == NULL) {
                sip_free_phdr(parsed_header);
                return (ENOMEM);
        }

        parsed_header->value = (sip_value_t *)value;

        value->sip_value_start = hdr->sip_hdr_current;
        value->auth_scheme_ptr = value->sip_value_start;
        value->sip_value_header = parsed_header;
        /*
         * get auth_scheme
         */
        if (sip_find_white_space(hdr)) {
                value->sip_value_state = SIP_VALUE_BAD;
                return (EINVAL);
        }
        value->auth_scheme_len = hdr->sip_hdr_current - value->auth_scheme_ptr;

        tmp_param = value->auth_param;

        /*
         * parse auth_param
         */
        for (;;) {
                char            *tmp_cur;
                boolean_t       quoted_name = B_FALSE;
                char            quoted_char = (char)0;
                sip_param_t     *new_param;
                boolean_t       pval_is_uri = B_FALSE;

                if (sip_skip_white_space(hdr) != 0) {
                        value->sip_value_state = SIP_VALUE_BAD;
                        return (EPROTO);
                }
                tmp_cur = hdr->sip_hdr_current;

                new_param = calloc(1, sizeof (sip_param_t));
                if (new_param == NULL)
                        return (ENOMEM);

                if (first_param == B_FALSE)
                        tmp_param->param_next = new_param;
                else
                        value->auth_param = new_param;

                tmp_param = new_param;
                tmp_param->param_name.sip_str_ptr = tmp_cur;

                if (sip_find_separator(hdr, SIP_EQUAL, SIP_COMMA, 0,
                    B_FALSE) != 0) {
                        tmp_param->param_name.sip_str_len =
                            hdr->sip_hdr_current - tmp_cur;
                        tmp_param->param_value.sip_str_ptr = NULL;
                        tmp_param->param_value.sip_str_len = 0;
                        value->sip_value_end = hdr->sip_hdr_current;
                        goto end;
                }

                /*
                 * End of param name
                 */
                tmp_param->param_name.sip_str_len = hdr->sip_hdr_current -
                    tmp_cur;

                if (sip_skip_white_space(hdr) != 0 ||
                    *hdr->sip_hdr_current == SIP_COMMA) {
                        tmp_param->param_value.sip_str_ptr = NULL;
                        tmp_param->param_value.sip_str_len = 0;
                        continue;
                }

                /*
                 * We are at EQUAL
                 */
                hdr->sip_hdr_current++;

                if (sip_skip_white_space(hdr) != 0) {
                        value->sip_value_state = SIP_VALUE_BAD;
                        free(tmp_param);
                        return (EPROTO);
                }

                if (*hdr->sip_hdr_current == SIP_QUOTE ||
                    *hdr->sip_hdr_current == SIP_LAQUOT) {
                        if (*hdr->sip_hdr_current == SIP_QUOTE)
                                quoted_char = SIP_QUOTE;
                        else {
                                quoted_char = SIP_RAQUOT;
                                pval_is_uri = B_TRUE;
                        }
                        hdr->sip_hdr_current++;
                        quoted_name = B_TRUE;
                }

                /*
                 * start of param value
                 */
                tmp_cur = hdr->sip_hdr_current;
                tmp_param->param_value.sip_str_ptr = tmp_cur;
                if (quoted_name) {
                        if (sip_find_token(hdr, quoted_char) != 0) {
                                value->sip_value_state = SIP_VALUE_BAD;
                                free(tmp_param);
                                return (EPROTO);
                        }
                        tmp_param->param_value.sip_str_len =
                            hdr->sip_hdr_current - tmp_cur - 1;
                }

                if (sip_find_token(hdr, SIP_COMMA) != 0) {
                        value->sip_value_end = hdr->sip_hdr_current;
                        goto end;
                } else {
                        if (!quoted_name) {
                                char *t = hdr->sip_hdr_current;
                                hdr->sip_hdr_current--;
                                (void) sip_reverse_skip_white_space(hdr);
                                tmp_param->param_value.sip_str_len =
                                    hdr->sip_hdr_current - tmp_cur;
                                hdr->sip_hdr_current = t;
                        }
                }

                if (first_param == B_TRUE)
                        first_param = B_FALSE;

                /*
                 * Parse uri
                 */
                if (pval_is_uri && parse_uri)
                        sip_parse_uri_str(&tmp_param->param_value, value);

        }

end:
        *phdr = parsed_header;
        hdr->sip_hdr_parsed = *phdr;
        return (0);
}

/*
 * Return the URI in the request startline
 */
static int
_sip_get_request_uri(_sip_header_t *sip_header, sip_message_type_t *msg_info)
{
        int     size = 0;
        char    *start_ptr;

        if (sip_skip_white_space(sip_header) != 0)
                return (EINVAL);
        start_ptr = sip_header->sip_hdr_current;

        while (!isspace(*sip_header->sip_hdr_current)) {
                if (sip_header->sip_hdr_current >= sip_header->sip_hdr_end)
                        return (EINVAL);
                sip_header->sip_hdr_current++;
        }

        size = sip_header->sip_hdr_current - start_ptr;

        msg_info->U.sip_request.sip_request_uri.sip_str_ptr = start_ptr;
        msg_info->U.sip_request.sip_request_uri.sip_str_len = size;
        if (size > 0) { /* Parse uri */
                int             error;

                msg_info->U.sip_request.sip_parse_uri = sip_parse_uri(
                    &msg_info->U.sip_request.sip_request_uri, &error);
                if (msg_info->U.sip_request.sip_parse_uri == NULL)
                        return (error);
        }
        return (0);
}

/*
 * Parse the start line into request/response
 */
int
sip_parse_first_line(_sip_header_t *sip_header, sip_message_type_t **msg_info)
{
        sip_message_type_t      *sip_msg_info;
        boolean_t               sip_is_request = B_TRUE;
        int                     ret;

        if (sip_header == NULL || msg_info == NULL)
                return (EINVAL);

        if (sip_skip_white_space(sip_header) != 0)
                return (EPROTO);

        /*
         * There is nothing, return
         */
        if (sip_header->sip_hdr_current + strlen(SIP_VERSION) >=
            sip_header->sip_hdr_end) {
                return (EPROTO);
        }
#ifdef  __solaris__
        assert(mutex_held(&sip_header->sip_hdr_sipmsg->sip_msg_mutex));
#endif
        sip_msg_info = malloc(sizeof (sip_message_type_t));
        if (sip_msg_info == NULL)
                return (ENOMEM);

        /*
         * let's see if it's a request or a response
         */
        ret = sip_get_protocol_version(sip_header,
            &sip_msg_info->sip_proto_version);
        if (ret == 0) {
                sip_is_request = B_FALSE;
        } else if (ret == 2) {
                free(sip_msg_info);
                return (EPROTO);
        }

        if (sip_skip_white_space(sip_header) != 0) {
                free(sip_msg_info);
                return (EPROTO);
        }

        if (!sip_is_request) {
                /*
                 * check for status code.
                 */
                if (sip_skip_white_space(sip_header) != 0) {
                        free(sip_msg_info);
                        return (EPROTO);
                }
                if (sip_header->sip_hdr_current + SIP_SIZE_OF_STATUS_CODE >=
                    sip_header->sip_hdr_end) {
                        free(sip_msg_info);
                        return (EPROTO);
                }

                if (sip_atoi(sip_header,
                    &sip_msg_info->U.sip_response.sip_response_code)) {
                        free(sip_msg_info);
                        return (EPROTO);
                }

                if (sip_msg_info->U.sip_response.sip_response_code < 100 ||
                    sip_msg_info->U.sip_response.sip_response_code > 700) {
                        free(sip_msg_info);
                        return (EPROTO);
                }

                /*
                 * get reason phrase.
                 */
                if (sip_skip_white_space(sip_header) != 0) {
                        sip_msg_info->sip_resp_phrase_len = 0;
                        sip_msg_info->sip_resp_phrase_ptr = NULL;
                } else {
                        sip_msg_info->sip_resp_phrase_ptr =
                            sip_header->sip_hdr_current;
                        if (sip_find_cr(sip_header) != 0) {
                                free(sip_msg_info);
                                return (EPROTO);
                        }
                        sip_msg_info->sip_resp_phrase_len =
                            sip_header->sip_hdr_current -
                            sip_msg_info->sip_resp_phrase_ptr;
                }
                sip_msg_info->is_request = B_FALSE;
        } else {
                int i;
                /*
                 * It's a request.
                 */
                sip_msg_info->is_request = B_TRUE;
                for (i = 1; i < MAX_SIP_METHODS; i++) {
                        if (strncmp(sip_methods[i].name,
                            sip_header->sip_hdr_current,
                            sip_methods[i].len) == 0) {
                                sip_msg_info->sip_req_method = i;
                                sip_header->sip_hdr_current +=
                                    sip_methods[i].len;
                                if (!isspace(*sip_header->sip_hdr_current++) ||
                                    !isalpha(*sip_header->sip_hdr_current)) {
                                        free(sip_msg_info);
                                        return (EPROTO);
                                }

                                if ((ret = _sip_get_request_uri(sip_header,
                                    sip_msg_info)) != 0) {
                                        free(sip_msg_info);
                                        return (ret);
                                }

                                /*
                                 * Get SIP version
                                 */
                                ret = sip_get_protocol_version(sip_header,
                                    &sip_msg_info->sip_proto_version);
                                if (ret != 0) {
                                        free(sip_msg_info);
                                        return (EPROTO);
                                }
                                goto done;
                        }
                }
                free(sip_msg_info);
                return (EPROTO);
        }
done:
        sip_msg_info->sip_next = *msg_info;
        *msg_info = sip_msg_info;
        return (0);
}