freeswitch/libs/sofia-sip/libsofia-sip-ua/msg/msg_parser_util.c

1994 lines
46 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@ingroup msg_parser
* @CFILE msg_parser_util.c
*
* Text-message parser helper functions.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Tue Aug 28 16:26:34 2001 ppessi
*
*/
#include "config.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <stdarg.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su.h>
#include <sofia-sip/su_alloc.h>
#include "msg_internal.h"
#include "sofia-sip/msg_parser.h"
#include "sofia-sip/bnf.h"
#include "sofia-sip/url.h"
static issize_t msg_comma_scanner(char *start);
/**
* Parse first line.
*
* Splits the first line from a message into three whitespace-separated
* parts.
*/
int msg_firstline_d(char *s, char **return_part2, char **return_part3)
{
char *s1 = s, *s2, *s3;
size_t n;
/* Split line into three segments separated by whitespace */
if (s1[n = span_non_ws(s1)]) {
s1[n] = '\0';
s2 = s1 + n + 1;
while (IS_WS(*s2))
s2++;
}
else {
/* Hopeless - no WS in first line */
return -1;
}
n = span_non_ws(s2);
if (s2[n]) {
s2[n++] = '\0';
while (IS_WS(s2[n]))
n++;
}
s3 = s2 + n;
*return_part2 = s2;
*return_part3 = s3;
return 0;
}
/**Parse a token.
*
* Parses a token from string pointed by @a *ss. It stores the token value
* in @a return_token, and updates the @a ss to the end of token and
* possible whitespace.
*/
issize_t msg_token_d(char **ss, char const **return_token)
{
char *s = *ss;
size_t n = span_token(s);
if (n) {
for (; IS_LWS(s[n]); n++)
s[n] = '\0';
*return_token = s;
*ss = s + n;
return n;
}
else
return -1;
}
/** Parse a 32-bit unsigned int.
*
* The function msg_uint32_d() parses a 32-bit unsigned integer in string
* pointed by @a *ss. It stores the value in @a return_token and updates the
* @a ss to the end of integer and possible whitespace.
*
* @retval length of parsed integer, or -1 upon an error.
*/
issize_t msg_uint32_d(char **ss, uint32_t *return_value)
{
char const *s = *ss, *s0 = s;
uint32_t value;
unsigned digit;
if (!IS_DIGIT(*s))
return -1;
for (value = 0; IS_DIGIT(*s); s++) {
digit = *s - '0';
if (value > 429496729U)
return -1;
else if (value == 429496729U && digit > 5)
return -1;
value = 10 * value + digit;
}
if (*s) {
if (!IS_LWS(*s))
return (issize_t)-1;
skip_lws(&s);
}
*ss = (char *)s;
*return_value = value;
return s - s0;
}
/** Parse any list.
*
* Parses a list of items, separated by @a sep. The parsed string is passed
* in @a *ss, which is updated to point to the first non-linear-whitespace
* character after the list. The function modifies the string as it parses
* it.
*
* The parsed items are appended to the list @a *append_list. If there the
* list in @a *append_list is NULL, allocate a new list and return it in @a
* *append_list. Empty list items are ignored, and are not appended to the
* list.
*
* The function @b must be passed a scanning function @a scanner. The
* scanning function scans for a legitimate list item, for example, a token.
* It should also compact the list item, for instance, if the item consists
* of @c name=value parameter definitions it should remove whitespace around
* "=" sign. The scanning function returns the length of the scanned item,
* including any linear whitespace after it.
*
* @param[in] home memory home for allocating list pointers
* @param[in,out] ss pointer to pointer to string to be parsed
* @param[in,out] append_list pointer to list
* where parsed list items are appended
* @param[in] sep separator character
* @param[in] scanner pointer to function for scanning a single item
*
* @retval 0 if successful.
* @retval -1 upon an error.
*/
issize_t msg_any_list_d(su_home_t *home,
char **ss,
msg_param_t **append_list,
issize_t (*scanner)(char *s),
int sep)
{
char const *auto_list[MSG_N_PARAMS];
char const **list = auto_list, **re_list;
int N = MSG_N_PARAMS, n = 0;
issize_t tlen;
char *s = *ss;
char const **start;
if (!scanner)
return -1;
if (*append_list) {
list = *append_list;
while (list[n])
n++;
N = MSG_PARAMS_NUM(n + 1);
}
start = &list[n];
skip_lws(&s);
while (*s) {
tlen = scanner(s);
if (tlen < 0 || (s[tlen] && s[tlen] != sep && s[tlen] != ','))
goto error;
if (tlen > 0) {
if (n + 1 == N) { /* Reallocate list? */
N = MSG_PARAMS_NUM(N + 1);
if (list == auto_list || list == *append_list) {
re_list = su_alloc(home, N * sizeof(*list));
if (re_list)
memcpy(re_list, list, n * sizeof(*list));
}
else
re_list = su_realloc(home, list, N * sizeof(*list));
if (!re_list)
goto error;
list = re_list;
}
list[n++] = s;
s += tlen;
}
if (*s == sep) {
*s++ = '\0';
skip_lws(&s);
}
else if (*s)
break;
}
*ss = s;
if (n > 0 && list == auto_list) {
size_t size = sizeof(*list) * MSG_PARAMS_NUM(n + 1);
list = su_alloc(home, size);
if (!list) return -1;
memcpy((void *)list, auto_list, n * sizeof(*list));
}
list[n] = NULL;
if (n == 0)
list = NULL;
*append_list = list;
return 0;
error:
*start = NULL;
if (list != auto_list && list != *append_list)
su_free(home, list);
return -1;
}
/** Scan an attribute (name [= value]) pair.
*
* The attribute consists of name (a token) and optional value, separated by
* equal sign. The value can be a token or quoted string.
*
* This function compacts the scanned value. It removes the
* whitespace around equal sign "=" by moving the equal sign character and
* value towards name.
*
* If there is whitespace within the scanned value or after it,
* NUL-terminates the scanned attribute.
*
* @retval > 0 number of characters scanned,
* including the whitespace within the value
* @retval -1 upon an error
*/
issize_t msg_attribute_value_scanner(char *s)
{
char *p = s;
size_t tlen;
skip_token(&s);
if (s == p) /* invalid parameter name */
return -1;
tlen = s - p;
if (IS_LWS(*s)) { *s++ = '\0'; skip_lws(&s); }
if (*s == '=') {
char *v;
s++;
skip_lws(&s);
/* get value */
if (*s == '"') {
size_t qlen = span_quoted(s);
if (!qlen)
return -1;
v = s; s += qlen;
}
else {
v = s;
skip_param(&s);
if (s == v)
return -1;
}
if (p + tlen + 1 != v) {
memmove(p + tlen + 1, v, s - v);
p[tlen] = '=';
p[tlen + 1 + (s - v)] = '\0';
}
}
if (IS_LWS(*s)) { *s++ = '\0'; skip_lws(&s); }
return s - p;
}
/**Parse an attribute-value list.
*
* Parses an attribute-value list, which has syntax as follows:
* @code
* av-list = (av-pair *(";" av-pair)
* av-pair = token ["=" ( value / quoted-string) ] ; optional value
* @endcode
*
* @param[in] home pointer to a memory home
* @param[in,out] ss pointer to string at the start of parameter list
* @param[in,out] append_list pointer to list
* where parsed list items are appended
*
* @retval >= 0 if successful
* @retval -1 upon an error
*/
issize_t msg_avlist_d(su_home_t *home,
char **ss,
msg_param_t const **append_list)
{
char const *stack[MSG_N_PARAMS];
char const **params;
size_t n = 0, N;
char *s = *ss;
if (!*s)
return -1;
if (*append_list) {
params = (char const **)*append_list;
for (n = 0; params[n]; n++)
;
N = MSG_PARAMS_NUM(n + 1);
} else {
params = stack;
N = MSG_PARAMS_NUM(1);
}
for (;;) {
char *p;
size_t tlen;
/* XXX - we should handle also quoted parameters */
skip_lws(&s);
p = s;
skip_token(&s);
tlen = s - p;
if (!tlen) /* invalid parameter name */
goto error;
if (IS_LWS(*s)) { *s++ = '\0'; skip_lws(&s); }
if (*s == '=') {
char *v;
s++;
skip_lws(&s);
/* get value */
if (*s == '"') {
size_t qlen = span_quoted(s);
if (!qlen)
goto error;
v = s; s += qlen;
}
else {
v = s;
skip_param(&s);
if (s == v)
goto error;
}
if (p + tlen + 1 != v) {
p = memmove(v - tlen - 1, p, tlen);
p[tlen] = '=';
}
}
if (IS_LWS(*s)) { *s++ = '\0'; skip_lws(&s); }
if (n == N) {
/* Reallocate params */
char **nparams = su_alloc(home,
(N = MSG_PARAMS_NUM(N + 1)) * sizeof(*params));
if (!nparams) {
goto error;
}
params = memcpy(nparams, params, n * sizeof(*params));
}
params[n++] = p;
if (*s != ';')
break;
*s++ = '\0';
}
*ss = s;
if (params == stack) {
size_t size = sizeof(*params) * MSG_PARAMS_NUM(n + 1);
params = su_alloc(home, size);
if (!params) return -1;
memcpy((void *)params, stack, n * sizeof(*params));
}
else if (n == N) {
/* Reallocate params */
char **nparams = su_alloc(home,
(N = MSG_PARAMS_NUM(N + 1)) * sizeof(*params));
if (!nparams) {
goto error;
}
params = memcpy(nparams, params, n * sizeof(*params));
}
params[n] = NULL;
*append_list = params;
return 0;
error:
if (params != stack)
su_free(home, params);
return -1;
}
/**Parse a semicolon-separated parameter list starting with semicolon.
*
* Parse a parameter list, which has syntax as follows:
* @code
* *(";" token [ "=" (token | quoted-string)]).
* @endcode
*
* @param[in] home pointer to a memory home
* @param[in,out] ss pointer to string at the start of parameter list
* @param[in,out] append_list pointer to list
* where parsed list items are appended
*
* @retval >= 0 if successful
* @retval -1 upon an error
*
* @sa msg_avlist_d()
*/
issize_t msg_params_d(su_home_t *home,
char **ss,
msg_param_t const **append_list)
{
if (**ss == ';') {
*(*ss)++ = '\0';
*append_list = NULL;
return msg_avlist_d(home, ss, append_list);
}
if (IS_LWS(**ss)) {
*(*ss)++ = '\0'; skip_lws(ss);
}
return 0;
}
/** Encode a list of parameters */
isize_t msg_params_e(char b[], isize_t bsiz, msg_param_t const pparams[])
{
int i;
char *end = b + bsiz, *b0 = b;
msg_param_t p;
if (pparams)
for (i = 0; (p = pparams[i]); i++) {
if (p[0]) {
MSG_CHAR_E(b, end, ';');
MSG_STRING_E(b, end, p);
}
}
return b - b0;
}
/** Duplicate a parameter list */
char *msg_params_dup(msg_param_t const **d, msg_param_t const s[],
char *b, isize_t xtra)
{
char *end = b + xtra;
char **pp;
int i;
isize_t n;
n = msg_params_count(s);
if (n == 0) {
*d = NULL;
return b;
}
MSG_STRUCT_ALIGN(b);
pp = (char **)b;
b += sizeof(*pp) * MSG_PARAMS_NUM(n + 1);
for (i = 0; s[i]; i++) {
MSG_STRING_DUP(b, pp[i], s[i]);
}
pp[i] = NULL;
assert(b <= end); (void)end;
*d = (msg_param_t const *)pp;
return b;
}
/** Parse a comma-separated list.
*
* Parses a comma-separated list. The parsed string is passed in @a *ss,
* which is updated to point to the first non-linear-whitespace character
* after the list. The function modifies the string as it parses it.
*
* A pointer to the resulting list is returned in @a *retval. If there
* already is a list in @a *retval, new items are appended. Empty list items
* are ignored, and are not included in the list.
*
* The function can be passed an optional scanning function. The scanning
* function scans for a legitimate list item, for example, a token. It also
* compacts the list item, for instance, if the item consists of @c
* name=value parameter definitions. The scanning function returns the
* length of the scanned item, including any linear whitespace after it.
*
* By default, the scanning function accepts tokens, quoted strings or
* separators (except comma, of course).
*
* @param[in] home memory home for allocating list pointers
* @param[in,out] ss pointer to pointer to string to be parsed
* @param[in,out] append_list pointer to list
* where parsed list items are appended
* @param[in] scanner pointer to function scanning a single item
* (optional)
*
* @retval 0 if successful.
* @retval -1 upon an error.
*/
issize_t msg_commalist_d(su_home_t *home,
char **ss,
msg_param_t **append_list,
issize_t (*scanner)(char *s))
{
scanner = scanner ? scanner : msg_comma_scanner;
return msg_any_list_d(home, ss, append_list, scanner, ',');
}
/** Token scanner for msg_commalist_d() accepting also empty entries. */
issize_t msg_token_scan(char *start)
{
char *s = start;
skip_token(&s);
if (IS_LWS(*s))
*s++ = '\0';
skip_lws(&s);
return s - start;
}
/** Scan and compact a comma-separated item */
static
issize_t msg_comma_scanner(char *start)
{
size_t tlen;
char *s, *p;
s = p = start;
if (s[0] == ',')
return 0;
for (;;) {
/* Grab next section - token, quoted string, or separator character */
char c = *s;
if (IS_TOKEN(c))
tlen = span_token(s);
else if (c == '"')
tlen = span_quoted(s);
else /* if (IS_SEPARATOR(c)) */
tlen = 1;
if (tlen == 0)
return -1;
if (p != s)
memmove(p, s, tlen); /* Move section to end of paramexter */
p += tlen; s += tlen;
skip_lws(&s); /* Skip possible LWS */
if (*s == '\0' || *s == ',') { /* Test for possible end */
if (p != s) *p = '\0';
return s - start;
}
if (IS_TOKEN(c) && IS_TOKEN(*s))
*p++ = ' '; /* Two tokens must be separated by LWS */
}
}
/** Parse a comment.
*
* Parses a multilevel comment. The comment assigned to return-value
* parameter @a return_comment is NUL-terminated. The string at return-value
* parameter @a ss is updated to point to first non-linear-whitespace
* character after the comment.
*/
issize_t msg_comment_d(char **ss, char const **return_comment)
{
/* skip comment */
int level = 1;
char *s = *ss;
assert(s[0] == '(');
if (*s != '(')
return -1;
*s++ = '\0';
if (return_comment)
*return_comment = s;
while (level) switch (*s++) {
case '(': level++; break;
case ')': level--; break;
case '\0': /* ERROR */ return -1;
}
assert(s[-1] == ')');
s[-1] = '\0';
skip_lws(&s);
*ss = s;
return 0;
}
/** Parse a quoted string */
issize_t msg_quoted_d(char **ss, char **return_quoted)
{
char *s= *ss, *s0 = s;
ssize_t n = span_quoted(s);
if (n <= 0)
return -1;
*return_quoted = s;
s += n;
if (IS_LWS(*s)) {
*s++ = '\0';
skip_lws(&s); /* skip linear whitespace */
}
*ss = s;
return s - s0;
}
#if 0
/** Calculate length of string when quoted. */
int msg_quoted_len(char const *u)
{
int rv;
if (!u)
return 0;
rv = span_token_lws(u);
if (u[rv]) {
/* We need to quote string */
int n;
int extra = 2; /* quote chars */
/* Find all characters to quote */
for (n = strcspn(u + rv, "\\\""); u[rv + n]; rv += n)
extra++;
rv += extra;
}
return rv;
}
#endif
/**Parse @e host[":"port] pair.
*
* Parses a @e host[":"port] pair. The caller passes function a pointer to a
* string via @a ss, and pointers to which parsed host and port are assigned
* via @a hhost and @a pport, respectively. The value-result parameter @a
* *pport must be initialized to default value (e.g., NULL).
*
* @param ss pointer to pointer to string to be parsed
* @param return_host value-result parameter for @e host
* @param return_port value-result parameter for @e port
* @return
* Returns zero when successful, and a negative value upon an error. The
* parsed values for host and port are assigned via @a return_host and @a
* return_port, respectively. The function also updates the pointer @a *ss,
* so if call is successful, the @a *ss points to first
* non-linear-whitespace character after @e host[":"port] pair.
*
* @note
* If there is no whitespace after @e port, the value in @a *pport may not be
* NUL-terminated. The calling function @b must NUL terminate the value by
* setting the @a **ss to NUL after first examining its value.
*/
int msg_hostport_d(char **ss,
char const **return_host,
char const **return_port)
{
char *host, *s = *ss;
char *port = NULL;
/* Host name */
host = s;
if (s[0] != '[') {
skip_token(&s); if (host == s) return -1;
}
else {
/* IPv6 */
size_t n = strspn(++s, HEX ":.");
if (s[n] != ']') return -1;
s += n + 1;
}
if (IS_LWS(*s)) { *s++ = '\0'; skip_lws(&s); }
if (s[0] == ':') {
unsigned long nport;
*s++ = '\0'; skip_lws(&s);
if (!IS_DIGIT(*s))
return -1;
port = s;
nport = strtoul(s, &s, 10);
if (nport > 65535)
return -1;
if (IS_LWS(*s)) {
*s++ = '\0';
skip_lws(&s);
}
}
*return_host = host;
*return_port = port;
*ss = s;
return 0;
}
/** Find a header parameter.
*
* Searches for given parameter @a name from the header. If parameter is
* found, it returns a non-NULL pointer to the parameter value. If there is
* no value for the name (in form "name" or "name=value"), the returned pointer
* points to a NUL character.
*
* @param h pointer to header structure
* @param name parameter name (with or without "=" sign)
*
* @return
* A pointer to parameter value, or NULL if parameter was not found.
*/
char const *msg_header_find_param(msg_common_t const *h, char const *name)
{
if (h && h->h_class->hc_params) {
msg_param_t const **params = (msg_param_t const **)
((char *)h + h->h_class->hc_params);
return msg_params_find(*params, name);
}
return NULL;
}
/**Modify a parameter value or list item in a header.
*
* A header parameter @a param can be just a C-string (@a is_item > 0), or
* it can have internal format <i>name [ "=" value]</i>. In the latter case,
* the value part following = is ignored when replacing or removing the
* parameter.
*
* @param home memory home used to re-allocate parameter list in header
* @param h pointer to a header
* @param param parameter to be replaced or added
* @param is_item how to interpret @a param:
* - 1 case-sensitive, no structure
* - 0 case-insensitive, <i>name [ "=" value ]</i>
* @param remove_replace_add what operation to do:
* - -1 remove
* - 0 replace
* - 1 add
*
* @retval 1 if parameter was replaced or removed successfully
* @retval 0 if parameter was added successfully,
* or there was nothing to remove
* @retval -1 upon an error
*/
static
int msg_header_param_modify(su_home_t *home, msg_common_t *h,
char const *param,
int is_item,
int remove_replace_add)
{
msg_param_t *params, **pointer_to_params;
size_t plen, n;
if (!h || !h->h_class->hc_params || !param)
return -1;
pointer_to_params = (msg_param_t **)((char *)h + h->h_class->hc_params);
params = *pointer_to_params;
plen = is_item > 0 ? strlen(param) : strcspn(param, "=");
n = 0;
if (params) {
/* Existing list, try to replace or remove */
for (; params[n]; n++) {
char const *maybe = params[n];
if (remove_replace_add > 0)
continue;
if (is_item > 0) {
if (strcmp(maybe, param) == 0) {
if (remove_replace_add == 0)
return 1;
}
}
else {
if (strncasecmp(maybe, param, plen) == 0 &&
(maybe[plen] == '=' || maybe[plen] == 0))
break;
}
}
}
/* Not found? */
if (!params || !params[n]) {
if (remove_replace_add < 0)
return 0; /* Nothing to remove */
else
remove_replace_add = 1; /* Add instead of replace */
}
if (remove_replace_add < 0) { /* Remove */
for (; params[n]; n++)
params[n] = params[n + 1];
}
else {
if (remove_replace_add > 0) { /* Add */
size_t m_before = MSG_PARAMS_NUM(n + 1);
size_t m_after = MSG_PARAMS_NUM(n + 2);
assert(!params || !params[n]);
if (m_before != m_after || !params) {
msg_param_t *p;
/* XXX - we should know when to do realloc */
p = su_alloc(home, m_after * sizeof(*p));
if (!p) return -1;
if (n > 0)
memcpy(p, params, n * sizeof(p[0]));
*pointer_to_params = params = p;
}
params[n + 1] = NULL;
}
params[n] = param; /* Add .. or replace */
}
msg_fragment_clear(h);
if (h->h_class->hc_update) {
/* Update shortcuts */
size_t namelen;
char const *name, *value;
name = param;
namelen = strcspn(name, "=");
if (remove_replace_add < 0)
value = NULL;
else
value = param + namelen + (name[namelen] == '=');
h->h_class->hc_update(h, name, namelen, value);
}
return remove_replace_add <= 0; /* 0 when added, 1 otherwise */
}
/** Add a parameter to a header.
*
* You should use this function only when the header accepts multiple
* parameters (or list items) with the same name. If that is not the case,
* you should use msg_header_replace_param().
*
* @note This function @b does @b not duplicate @p param. The caller should
* have allocated the @a param from the memory home associated with header
* @a h.
*
* The possible shortcuts to parameter values are updated. For example, the
* "received" parameter in @Via header has shortcut in structure #sip_via_t,
* the @ref sip_via_s::v_received "v_received" field. The shortcut is usully
* a pointer to the parameter value. If the parameter was
* "received=127.0.0.1" the @ref sip_via_s::v_received "v_received" field
* would be a pointer to "127.0.0.1". If the parameter was "received=" or
* "received", the shortcut would be a pointer to an empty string, "".
*
* @param home memory home used to re-allocate parameter list in header
* @param h pointer to a header
* @param param parameter to be replaced or added
*
* @retval 0 if parameter was added
* @retval -1 upon an error
*
* @sa msg_header_replace_param(), msg_header_remove_param(),
* msg_header_update_params(),
* #msg_common_t, #msg_header_t,
* #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
*/
int msg_header_add_param(su_home_t *home, msg_common_t *h, char const *param)
{
return msg_header_param_modify(home, h, param,
0 /* case-insensitive name=value */,
1 /* add */);
}
/** Replace or add a parameter to a header.
*
* A header parameter @a param is a string of format <i>name "=" value</i>
* or just name. The value part following "=" is ignored when selecting a
* parameter to replace.
*
* @note This function @b does @b not duplicate @p param. The caller should
* have allocated the @a param from the memory home associated with header
* @a h.
*
* The possible shortcuts to parameter values are updated. For example, the
* "received" parameter in @Via header has shortcut in structure #sip_via_t,
* the @ref sip_via_s::v_received "v_received" field.
*
* @param home memory home used to re-allocate parameter list in header
* @param h pointer to a header
* @param param parameter to be replaced or added
*
* @retval 0 if parameter was added
* @retval 1 if parameter was replaced
* @retval -1 upon an error
*
* @sa msg_header_add_param(), msg_header_remove_param(),
* msg_header_update_params(),
* #msg_common_t, #msg_header_t,
* #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
*/
int msg_header_replace_param(su_home_t *home,
msg_common_t *h,
char const *param)
{
return msg_header_param_modify(home, h, param,
0 /* case-insensitive name=value */,
0 /* replace */);
}
/** Remove a parameter from header.
*
* The parameter name is given as token optionally followed by "=" sign and
* value. The "=" and value after it are ignored when selecting a parameter
* to remove.
*
* The possible shortcuts to parameter values are updated. For example, the
* "received" parameter in @Via header has shortcut in structure #sip_via_t,
* the @ref sip_via_s::v_received "v_received" field. The shortcut to
* removed parameter would be set to NULL.
*
* @param h pointer to a header
* @param name name of parameter to be removed
*
* @retval 1 if a parameter was removed
* @retval 0 if no parameter was not removed
* @retval -1 upon an error
*
* @sa msg_header_add_param(), msg_header_replace_param(),
* msg_header_update_params(),
* #msg_common_t, #msg_header_t,
* #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
*/
int msg_header_remove_param(msg_common_t *h, char const *name)
{
return msg_header_param_modify(NULL, h, name,
0 /* case-insensitive name=value */,
-1 /* remove */);
}
/** Update shortcuts to parameter values.
*
* Update the shortcuts to parameter values in parameter list. For example,
* the "received" parameter in @Via header has shortcut in structure
* #sip_via_t, the @ref sip_via_s::v_received "v_received" field. The
* shortcut is usully a pointer to the parameter value. If the parameter was
* "received=127.0.0.1" the @ref sip_via_s::v_received "v_received" field
* would be a pointer to "127.0.0.1". If the parameter was "received=" or
* "received", the shortcut would be a pointer to an empty string, "".
*
* @retval 0 when successful
* @retval -1 upon an error
*
* @sa msg_header_add_param(), msg_header_replace_param(),
* msg_header_update_params(),
* #msg_common_t, #msg_header_t,
* #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
*/
int msg_header_update_params(msg_common_t *h, int clear)
{
msg_hclass_t *hc;
unsigned char offset;
msg_update_f *update;
int retval;
msg_param_t const *params;
size_t n;
char const *p, *v;
if (h == NULL)
return errno = EFAULT, -1;
hc = h->h_class; offset = hc->hc_params; update = hc->hc_update;
if (offset == 0 || update == NULL)
return 0;
if (clear)
update(h, NULL, 0, NULL);
params = *(msg_param_t **)((char *)h + offset);
if (params == NULL)
return 0;
retval = 0;
for (p = *params; p; p = *++params) {
n = strcspn(p, "=");
v = p + n + (p[n] == '=');
if (update(h, p, n, v) < 0)
retval = -1;
}
return retval;
}
/** Find a header item.
*
* Searches for given item @a name from the header. If item is found, the
* function returns a non-NULL pointer to the item.
*
* @param h pointer to header structure
* @param item item
*
* @return
* A pointer to item, or NULL if it was not found.
*
* @since New in @VERSION_1_12_4
*
* @sa msg_header_replace_item(), msg_header_remove_item(),
* @Allow, @AllowEvents
*/
char const *msg_header_find_item(msg_common_t const *h, char const *item)
{
if (h && h->h_class->hc_params) {
char const * const * items =
*(char const * const * const *)
((char *)h + h->h_class->hc_params);
if (items)
for (; *items; items++) {
if (strcmp(item, *items) == 0) {
return *items;
}
}
}
return NULL;
}
/**Add an item to a header.
*
* This function treats a #msg_header_t as set of C strings. The @a item is
* a C string. If no identical string is found from the list, it is added to
* the list.
*
* The shortcuts, if any, to item values are updated accordingly.
*
* @param home memory home used to re-allocate list in header
* @param h pointer to a header
* @param item item to be removed
*
* @retval 0 if item was added
* @retval 1 if item was replaced
* @retval -1 upon an error
*
* @since New in @VERSION_1_12_4.
*
* @sa msg_header_remove_item(), @Allow, @AllowEvents,
* msg_header_replace_param(), msg_header_remove_param(),
* #msg_common_t, #msg_header_t, #msg_list_t
* #msg_hclass_t, msg_hclass_t::hc_params
*/
int msg_header_replace_item(su_home_t *home,
msg_common_t *h,
char const *item)
{
return msg_header_param_modify(home, h, item,
1 /* string item */,
0 /* replace */);
}
/**Remove an item from a header.
*
* This function treats a #msg_header_t as set of C strings. The @a item is a
* C string. If identical string is found from the list, it is removed.
*
* The shortcuts, if any, to item values are updated accordingly.
*
* @param h pointer to a header
* @param name item to be removed
*
* @retval 0 if item was added
* @retval 1 if item was replaced
* @retval -1 upon an error
*
* @since New in @VERSION_1_12_4.
*
* @sa msg_header_replace_item(), @Allow, @AllowEvents,
* msg_header_replace_param(), msg_header_remove_param(),
* #msg_common_t, #msg_header_t, #msg_list_t
* #msg_hclass_t, msg_hclass_t::hc_params
*/
int msg_header_remove_item(msg_common_t *h, char const *name)
{
return msg_header_param_modify(NULL, h, name,
1 /* item */,
-1 /* remove */);
}
/** Find a parameter from a parameter list.
*
* Searches for given parameter @a token from the parameter list. If
* parameter is found, it returns a non-NULL pointer to the parameter value.
* If there is no value for the parameter (the parameter is of form "name"
* or "name="), the returned pointer points to a NUL character.
*
* @param params list (or vector) of parameters
* @param token parameter name (with or without "=" sign)
*
* @return
* A pointer to parameter value, or NULL if parameter was not found.
*/
msg_param_t msg_params_find(msg_param_t const params[], msg_param_t token)
{
if (params && token) {
size_t i, n = strcspn(token, "=");
assert(n > 0);
for (i = 0; params[i]; i++) {
msg_param_t param = params[i];
if (strncasecmp(param, token, n) == 0) {
if (param[n] == '=')
return param + n + 1;
else if (param[n] == 0)
return param + n;
}
}
}
return NULL;
}
/** Find a slot for parameter from a parameter list.
*
* Searches for given parameter @a token from the parameter list. If
* parameter is found, it returns a non-NULL pointer to the item containing
* the parameter.
*
* @param params list (or vector) of parameters
* @param token parameter name (with or without "=" sign)
*
* @return
* A pointer to parameter slot, or NULL if parameter was not found.
*/
msg_param_t *msg_params_find_slot(msg_param_t params[], msg_param_t token)
{
if (params && token) {
int i;
size_t n = strlen(token);
assert(n > 0);
for (i = 0; params[i]; i++) {
msg_param_t param = params[i];
if (strncasecmp(param, token, n) == 0) {
if (param[n] == '=')
return params + i;
else if (param[n] == 0 || token[n - 1] == '=')
return params + i;
}
}
}
return NULL;
}
/** Replace or add a parameter from a list.
*
* A non-NULL parameter list must have been created by msg_params_d()
* or by msg_params_dup().
*
* @note This function does not duplicate @p param.
*
* @param home memory home
* @param inout_params pointer to pointer to parameter list
* @param param parameter to be replaced or added
*
* @retval 0 if parameter was added
* @retval 1 if parameter was replaced
* @retval -1 upon an error
*/
int msg_params_replace(su_home_t *home,
msg_param_t **inout_params,
msg_param_t param)
{
msg_param_t *params;
size_t i, n;
assert(inout_params);
if (param == NULL || param[0] == '=' || param[0] == '\0')
return -1;
params = *inout_params;
n = strcspn(param, "=");
if (params) {
/* Existing list, try to replace or remove */
for (i = 0; params[i]; i++) {
msg_param_t maybe = params[i];
if (!(strncasecmp(maybe, param, n))) {
if (maybe[n] == '=' || maybe[n] == 0) {
params[i] = param;
return 1;
}
}
}
}
/* Not found on list */
return msg_params_add(home, inout_params, param);
}
/** Remove a parameter from a list.
*
* @retval 1 if parameter was removed
* @retval 0 if parameter was not found
* @retval -1 upon an error
*/
int msg_params_remove(msg_param_t *params, msg_param_t param)
{
size_t i, n;
if (!params || !param || !param[0])
return -1;
n = strcspn(param, "=");
assert(n > 0);
for (i = 0; params[i]; i++) {
msg_param_t maybe = params[i];
if (strncasecmp(maybe, param, n) == 0) {
if (maybe[n] == '=' || maybe[n] == 0) {
/* Remove */
do {
params[i] = params[i + 1];
} while (params[i++]);
return 1;
}
}
}
return 0;
}
/** Calculate number of parameters in a parameter list */
size_t msg_params_length(char const * const * params)
{
size_t len;
if (!params)
return 0;
for (len = 0; params[len]; len++)
;
return len;
}
/**
* Add a parameter to a list.
*
* Add a parameter to the list; the list must have been created by @c
* msg_params_d() or by @c msg_params_dup() (or it may contain only @c
* NULL).
*
* @note This function does not duplicate @p param.
*
* @param home memory home
* @param inout_params pointer to pointer to parameter list
* @param param parameter to be added
*
* @retval 0 if parameter was added
* @retval -1 upon an error
*/
int msg_params_add(su_home_t *home,
msg_param_t **inout_params,
msg_param_t param)
{
size_t n, m_before, m_after;
msg_param_t *p = *inout_params;
if (param == NULL)
return -1;
/* Count number of parameters */
for (n = 0; p && p[n]; n++)
;
m_before = MSG_PARAMS_NUM(n + 1);
m_after = MSG_PARAMS_NUM(n + 2);
if (m_before != m_after || !p) {
p = su_alloc(home, m_after * sizeof(*p));
assert(p); if (!p) return -1;
if (n)
memcpy(p, *inout_params, n * sizeof(*p));
*inout_params = p;
}
p[n] = param;
p[n + 1] = NULL;
return 0;
}
static
int msg_param_prune(msg_param_t const d[], msg_param_t p, unsigned prune)
{
size_t i, nlen;
if (prune == 1)
nlen = strcspn(p, "=");
else
nlen = 0;
for (i = 0; d[i]; i++) {
if ((prune == 1 &&
strncasecmp(p, d[i], nlen) == 0
&& (d[i][nlen] == '=' || d[i][nlen] == '\0'))
||
(prune == 2 && strcasecmp(p, d[i]) == 0)
||
(prune == 3 && strcmp(p, d[i]) == 0))
return 1;
}
return 0;
}
/**Join two parameter lists.
*
* The function @c msg_params_join() joins two parameter lists; the
* first list must have been created by @c msg_params_d() or by @c
* msg_params_dup() (or it may contain only @c NULL).
*
* @param home memory home
* @param dst pointer to pointer to destination parameter list
* @param src source list
* @param prune prune duplicates
* @param dup duplicate parameters in src list
*
* @par Pruning
* <table>
* <tr><td>0<td>do not prune</tr>
* <tr><td>1<td>prune parameters with identical names</tr>
* <tr><td>2<td>case-insensitive values</tr>
* <tr><td>3<td>case-sensitive values</tr>
* </table>
*
* @return
* @retval >= 0 when successful
* @retval -1 upon an error
*/
issize_t msg_params_join(su_home_t *home,
msg_param_t **dst,
msg_param_t const *src,
unsigned prune,
int dup)
{
size_t n, m, n_before, n_after, pruned, total = 0;
msg_param_t *d = *dst;
if (prune > 3)
return -1;
if (src == NULL || *src == NULL)
return 0;
/* Count number of parameters */
for (n = 0; d && d[n]; n++)
;
n_before = MSG_PARAMS_NUM(n + 1);
for (m = 0, pruned = 0; src[m]; m++) {
if (n > 0 && prune > 0 && msg_param_prune(d, src[m], prune)) {
pruned++;
if (prune > 1)
continue;
}
if (dup)
total += strlen(src[m]) + 1;
}
n_after = MSG_PARAMS_NUM(n + m - pruned + 1);
if (n_before != n_after || !d) {
d = su_alloc(home, n_after * sizeof(*d));
assert(d); if (!d) return -1;
if (n)
memcpy(d, *dst, n * sizeof(*d));
*dst = d;
}
for (m = 0; src[m]; m++) {
if (pruned && msg_param_prune(d, src[m], prune)) {
pruned--;
if (prune > 1)
continue;
}
if (dup)
d[n++] = su_strdup(home, src[m]); /* XXX */
else
d[n++] = src[m];
}
d[n] = NULL;
return 0;
}
/**Join header item lists.
*
* Join items from a source header to the destination header. The item are
* compared with the existing ones. If a match is found, it is not added to
* the list. If @a duplicate is true, the entries are duplicated while they
* are added to the list.
*
* @param home memory home
* @param dst destination header
* @param src source header
* @param duplicate if true, allocate and copy items that are added
*
* @return
* @retval >= 0 when successful
* @retval -1 upon an error
*
* @NEW_1_12_5.
*/
int msg_header_join_items(su_home_t *home,
msg_common_t *dst,
msg_common_t const *src,
int duplicate)
{
size_t N, m, M, i, n_before, n_after, total;
char *dup = NULL;
msg_param_t *d, **dd, *s;
msg_param_t t, *temp, temp0[16];
size_t *len, len0[(sizeof temp0)/(sizeof temp0[0])];
msg_update_f *update = NULL;
if (dst == NULL || dst->h_class->hc_params == 0 ||
src == NULL || src->h_class->hc_params == 0)
return -1;
s = *(msg_param_t **)((char *)src + src->h_class->hc_params);
if (s == NULL)
return 0;
for (M = 0; s[M]; M++);
if (M == 0)
return 0;
if (M <= (sizeof temp0) / (sizeof temp0[0])) {
temp = temp0, len = len0;
}
else {
temp = malloc(M * (sizeof *temp) + M * (sizeof *len));
if (!temp) return -1;
len = (void *)(temp + M);
}
dd = (msg_param_t **)((char *)dst + dst->h_class->hc_params);
d = *dd;
for (N = 0; d && d[N]; N++);
for (m = 0, M = 0, total = 0; s[m]; m++) {
t = s[m];
for (i = 0; i < N; i++)
if (strcmp(t, d[i]) == 0)
break;
if (i < N)
continue;
for (i = 0; i < M; i++)
if (strcmp(t, temp[i]) == 0)
break;
if (i < M)
continue;
temp[M] = t;
len[M] = strlen(t);
total += len[M++] + 1;
}
if (M == 0)
goto success;
dup = su_alloc(home, total); if (!dup) goto error;
n_before = MSG_PARAMS_NUM(N + 1);
n_after = MSG_PARAMS_NUM(N + M + 1);
if (d == NULL || n_before != n_after) {
d = su_alloc(home, n_after * sizeof(*d)); if (!d) goto error;
if (N)
memcpy(d, *dd, N * sizeof(*d));
*dd = d;
}
update = dst->h_class->hc_update;
for (m = 0; m < M; m++) {
d[N++] = memcpy(dup, temp[m], len[m] + 1);
if (update)
update(dst, dup, len[m], dup + len[m]);
dup += len[m] + 1;
}
d[N] = NULL;
success:
if (temp != temp0)
free(temp);
return 0;
error:
if (temp != temp0)
free(temp);
su_free(home, dup);
return -1;
}
/**Compare parameter lists.
*
* Compares parameter lists.
*
* @param a pointer to a parameter list
* @param b pointer to a parameter list
*
* @retval an integer less than zero if @a is less than @a b
* @retval an integer zero if @a match with @a b
* @retval an integer greater than zero if @a is greater than @a b
*/
int msg_params_cmp(char const * const a[], char const * const b[])
{
int c;
size_t nlen;
if (a == NULL || b == NULL)
return (a != NULL) - (b != NULL);
for (;;) {
if (*a == NULL || *b == NULL)
return (*a != NULL) - (*b != NULL);
nlen = strcspn(*a, "=");
if ((c = strncasecmp(*a, *b, nlen)))
return c;
if ((c = strcmp(*a + nlen, *b + nlen)))
return c;
a++, b++;
}
}
/** Unquote string
*
* Duplicates the string @a q in unquoted form.
*/
char *msg_unquote_dup(su_home_t *home, char const *q)
{
char *d;
size_t total, n, m;
/* First, easy case */
if (q[0] == '"')
q++;
n = strcspn(q, "\"\\");
if (q[n] == '\0' || q[n] == '"')
return su_strndup(home, q, n);
/* Hairy case - backslash-escaped chars */
total = n;
for (;;) {
if (q[n] == '\0' || q[n] == '"' || q[n + 1] == '\0')
break;
m = strcspn(q + n + 2, "\"\\");
total += m + 1;
n += m + 2;
}
if (!(d = su_alloc(home, total + 1)))
return NULL;
for (n = 0;;) {
m = strcspn(q, "\"\\");
memcpy(d + n, q, m);
n += m, q += m;
if (q[0] == '\0' || q[0] == '"' || q[1] == '\0')
break;
d[n++] = q[1];
q += 2;
}
assert(total == n);
d[n] = '\0';
return d;
}
/** Unquote string */
char *msg_unquote(char *dst, char const *s)
{
int copy = dst != NULL;
char *d = dst;
if (*s++ != '"')
return NULL;
for (;;) {
size_t n = strcspn(s, "\"\\");
if (copy)
memmove(d, s, n);
s += n;
d += n;
if (*s == '\0')
return NULL;
else if (*s == '"') {
if (copy) *d = '\0';
return dst;
}
else {
/* Copy quoted char */
if ((copy ? (*d++ = *++s) : *++s) == '\0')
return NULL;
s++;
}
}
}
/** Quote string */
issize_t msg_unquoted_e(char *b, isize_t bsiz, char const *s)
{
char *begin = b;
char *end = b + bsiz;
if (b && b + 1 < end)
*b = '"';
b++;
for (;*s;) {
size_t n = strcspn(s, "\"\\");
if (n == 0) {
if (b && b + 2 < end)
b[0] = '\\', b[1] = s[0];
b += 2;
s++;
}
else {
if (b && b + n < end)
memcpy(b, s, n);
b += n;
s += n;
}
}
if (b && b + 1 < end)
*b = '"';
b++;
return b - begin;
}
/** Calculate a simple hash over a string. */
unsigned long msg_hash_string(char const *id)
{
unsigned long hash = 0;
if (id)
for (; *id; id++) {
hash += (unsigned)*id;
hash *= 38501U;
}
else
hash *= 38501U;
if (hash == 0)
hash = (unsigned long)-1;
return hash;
}
/** Calculate the size of a duplicate of a header structure. */
isize_t msg_header_size(msg_header_t const *h)
{
if (h == NULL || h == MSG_HEADER_NONE)
return 0;
else
return h->sh_class->hc_dxtra(h, h->sh_class->hc_size);
}
/** Encode a message to the buffer.
*
* The function msg_encode_e encodes a message to a given buffer.
* It returns the length of the message to be encoded, even if the
* buffer is too small (just like snprintf() is supposed to do).
*
* @param b buffer (may be NULL)
* @param size size of buffer
* @param mo public message structure (#sip_t, #http_t)
* @param flags see #
*/
issize_t msg_object_e(char b[], isize_t size, msg_pub_t const *mo, int flags)
{
size_t rv = 0;
ssize_t n;
msg_header_t const *h;
if (mo->msg_request)
h = mo->msg_request;
else
h = mo->msg_status;
for (; h; h = h->sh_succ) {
n = msg_header_e(b, size, h, flags);
if (n < 0)
return -1;
if ((size_t)n < size)
b += n, size -= n;
else
b = NULL, size = 0;
rv += n;
}
return rv;
}
/** Encode header contents. */
issize_t msg_header_field_e(char b[], isize_t bsiz, msg_header_t const *h, int flags)
{
assert(h); assert(h->sh_class);
return h->sh_class->hc_print(b, bsiz, h, flags);
}
/** Get offset of header @a h from structure @a mo. */
msg_header_t **
msg_header_offset(msg_t const *msg, msg_pub_t const *mo, msg_header_t const *h)
{
if (h == NULL || h->sh_class == NULL)
return NULL;
return msg_hclass_offset(msg->m_class, mo, h->sh_class);
}
/**Get a header from the public message structure.
*
* Gets a pointer to header from a message structure.
*
* @param pub public message structure from which header is obtained
* @param hc header class
*/
msg_header_t *
msg_header_access(msg_pub_t const *pub, msg_hclass_t *hc)
{
msg_header_t * const * hh;
if (pub == NULL || hc == NULL)
return NULL;
hh = msg_hclass_offset((void *)pub->msg_ident, (msg_pub_t *)pub, hc);
if (hh)
return *hh;
else
return NULL;
}
#include <sofia-sip/su_uniqueid.h>
/** Generates a random token.
*
*/
issize_t msg_random_token(char token[], isize_t tlen,
void const *rmemp, isize_t rsize)
{
uint32_t random = 0, rword;
uint8_t rbyte;
uint8_t const *rmem = rmemp;
size_t i;
ssize_t n;
static char const token_chars[33] =
/* Create aesthetically pleasing raNDom capS LooK */
"aBcDeFgHjKmNpQrStUvXyZ0123456789";
if (rmem == NULL && rsize == 0)
rsize = UINT_MAX;
if (rsize == 0) {
if (token && tlen > 0)
strcpy(token, "+");
return 1;
}
if (token == NULL) {
if (rsize >= tlen * 5 / 8)
return tlen;
else
return rsize / 5 * 8;
}
for (i = 0, n = 0; i < tlen;) {
if (n < 5) {
if (rsize == 0)
;
else if (rmem) {
rbyte = *rmem++, rsize--;
random = random | (rbyte << n);
n += 8;
} else {
rword = su_random();
random = (rword >> 13) & 31;
n = 6;
}
}
token[i] = token_chars[random & 31];
random >>= 5;
i++, n -= 5;
if (n < 0 || (n == 0 && rsize == 0))
break;
}
token[i] = 0;
return i;
}
/** Parse a message.
*
* Parse a text message with parser @a mc. The @a data is copied and it is
* not modified or referenced by the parsed message.
*
* @par Example
* Parse a SIP message fragment (e.g., payload of NOTIFY sent in response to
* REFER):
* @code
* msg_t *m = msg_make(sip_default_mclass(), 0, pl->pl_data, pl->pl_len);
* sip_t *frag = sip_object(m);
* @endcode
*
* @param mc message class (parser table)
* @param flags message flags (see #msg_flg_user)
* @param data message text
* @param len size of message text (if -1, use strlen(data))
*
* @retval A pointer to a freshly allocated and parsed message.
*
* Upon parsing error, the header structure may be left incomplete. The
* #MSG_FLG_ERROR is set in @a msg_object(msg)->msg_flags.
*
* @since New in @VERSION_1_12_4
*
* @sa msg_as_string(), msg_extract()
*/
msg_t *msg_make(msg_mclass_t const *mc, int flags,
void const *data, ssize_t len)
{
msg_t *msg;
msg_iovec_t iovec[2];
if (len == -1)
len = strlen(data);
if (len == 0)
return NULL;
msg = msg_create(mc, flags);
if (msg == NULL)
return NULL;
su_home_preload(msg_home(msg), 1, len + 1024);
if (msg_recv_iovec(msg, iovec, 2, len, 1) < 0) {
perror("msg_recv_iovec");
}
assert((ssize_t)iovec->mv_len == len);
memcpy(iovec->mv_base, data, len);
msg_recv_commit(msg, len, 1);
if (msg_extract(msg) < 0)
msg->m_object->msg_flags |= MSG_FLG_ERROR;
return msg;
}