msg_parser_util.c 46.8 KB
Newer Older
Pekka Pessi's avatar
Pekka Pessi committed
1 2 3 4 5 6 7
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
8
 * This library is free software; you can redistribute it and/or
Pekka Pessi's avatar
Pekka Pessi committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
 * 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>
46
#include <sofia-sip/su_tagarg.h>
Pekka Pessi's avatar
Pekka Pessi committed
47

48 49
#include <sofia-sip/su.h>
#include <sofia-sip/su_alloc.h>
50
#include <sofia-sip/su_string.h>
Pekka Pessi's avatar
Pekka Pessi committed
51 52

#include "msg_internal.h"
53 54
#include "sofia-sip/msg_parser.h"
#include "sofia-sip/bnf.h"
Pekka Pessi's avatar
Pekka Pessi committed
55

56
#include "sofia-sip/url.h"
Pekka Pessi's avatar
Pekka Pessi committed
57

58
static issize_t msg_comma_scanner(char *start);
Pekka Pessi's avatar
Pekka Pessi committed
59 60 61 62 63 64 65 66 67 68

/**
 * 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;
69
  size_t n;
Pekka Pessi's avatar
Pekka Pessi committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

  /* 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.
 */
106
issize_t msg_token_d(char **ss, char const **return_token)
Pekka Pessi's avatar
Pekka Pessi committed
107 108
{
  char *s = *ss;
109
  size_t n = span_token(s);
Pekka Pessi's avatar
Pekka Pessi committed
110 111 112 113 114
  if (n) {
    for (; IS_LWS(s[n]); n++)
      s[n] = '\0';
    *return_token = s;
    *ss = s + n;
115
    return n;
Pekka Pessi's avatar
Pekka Pessi committed
116 117 118 119 120 121 122 123 124 125
  }
  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.
126 127
 *
 * @retval length of parsed integer, or -1 upon an error.
Pekka Pessi's avatar
Pekka Pessi committed
128
 */
129
issize_t msg_uint32_d(char **ss, uint32_t *return_value)
Pekka Pessi's avatar
Pekka Pessi committed
130 131 132 133 134
{
  char const *s = *ss, *s0 = s;
  uint32_t value;
  unsigned digit;

135
  if (!IS_DIGIT(*s))
Pekka Pessi's avatar
Pekka Pessi committed
136 137 138 139 140 141 142 143 144 145 146 147 148
    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))
149
      return (issize_t)-1;
Pekka Pessi's avatar
Pekka Pessi committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    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.
 *
167 168 169
 * 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
Pekka Pessi's avatar
Pekka Pessi committed
170 171
 * list.
 *
172
 * The function @b must be passed a scanning function @a scanner. The
173
 * scanning function scans for a legitimate list item, for example, a token.
174 175 176 177
 * 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.
Pekka Pessi's avatar
Pekka Pessi committed
178
 *
179 180
 * @param[in]     home    memory home for allocating list pointers
 * @param[in,out] ss      pointer to pointer to string to be parsed
181 182
 * @param[in,out] append_list  pointer to list
 *                             where parsed list items are appended
183
 * @param[in]     sep     separator character
184
 * @param[in]     scanner pointer to function for scanning a single item
185
 *
Pekka Pessi's avatar
Pekka Pessi committed
186 187 188
 * @retval 0  if successful.
 * @retval -1 upon an error.
 */
189 190
issize_t msg_any_list_d(su_home_t *home,
			char **ss,
191
			msg_param_t **append_list,
192
			issize_t (*scanner)(char *s),
193
			int sep)
Pekka Pessi's avatar
Pekka Pessi committed
194
{
195 196 197
  char const *stack[MSG_N_PARAMS];
  char const **list = stack, **re_list;
  size_t N = MSG_N_PARAMS, n = 0;
198
  issize_t tlen;
Pekka Pessi's avatar
Pekka Pessi committed
199 200 201 202 203 204
  char *s = *ss;
  char const **start;

  if (!scanner)
    return -1;

205 206
  if (*append_list) {
    list = *append_list;
Pekka Pessi's avatar
Pekka Pessi committed
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    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);
225
	if (list == stack || list == *append_list) {
Pekka Pessi's avatar
Pekka Pessi committed
226 227 228 229 230 231 232 233 234 235
	  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;
      }
236

Pekka Pessi's avatar
Pekka Pessi committed
237 238 239 240 241 242 243
      list[n++] = s;
      s += tlen;
    }

    if (*s == sep) {
      *s++ = '\0';
      skip_lws(&s);
244
    }
Pekka Pessi's avatar
Pekka Pessi committed
245 246 247 248 249 250
    else if (*s)
      break;
  }

  *ss = s;

251 252 253 254 255 256
  if (n == 0) {
    *append_list = NULL;
    return 0;
  }

  if (list == stack) {
257
    size_t size = sizeof(*list) * MSG_PARAMS_NUM(n + 1);
Pekka Pessi's avatar
Pekka Pessi committed
258 259
    list = su_alloc(home, size);
    if (!list) return -1;
260
    memcpy((void *)list, stack, n * sizeof(*list));
Pekka Pessi's avatar
Pekka Pessi committed
261 262 263
  }

  list[n] = NULL;
264
  *append_list = list;
Pekka Pessi's avatar
Pekka Pessi committed
265 266 267 268
  return 0;

 error:
  *start = NULL;
269
  if (list != stack && list != *append_list)
Pekka Pessi's avatar
Pekka Pessi committed
270 271 272 273
    su_free(home, list);
  return -1;
}

274 275 276 277 278 279 280 281
/** 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.
282 283
 *
 * If there is whitespace within the scanned value or after it,
284 285
 * NUL-terminates the scanned attribute.
 *
286
 * @retval > 0 number of characters scanned,
287 288 289
 *             including the whitespace within the value
 * @retval -1 upon an error
 */
290
issize_t msg_attribute_value_scanner(char *s)
Pekka Pessi's avatar
Pekka Pessi committed
291 292
{
  char *p = s;
293
  size_t tlen;
Pekka Pessi's avatar
Pekka Pessi committed
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

  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 == '"') {
311
      size_t qlen = span_quoted(s);
Pekka Pessi's avatar
Pekka Pessi committed
312 313 314 315 316
      if (!qlen)
	return -1;
      v = s; s += qlen;
    }
    else {
317
      v = s;
Pekka Pessi's avatar
Pekka Pessi committed
318
      skip_param(&s);
319
      if (s == v)
Pekka Pessi's avatar
Pekka Pessi committed
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
	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
 *
343 344
 * @param[in]     home      pointer to a memory home
 * @param[in,out] ss        pointer to string at the start of parameter list
345 346
 * @param[in,out] append_list  pointer to list
 *                             where parsed list items are appended
Pekka Pessi's avatar
Pekka Pessi committed
347
 *
348
 * @retval >= 0 if successful
Pekka Pessi's avatar
Pekka Pessi committed
349 350
 * @retval -1 upon an error
 */
351 352
issize_t msg_avlist_d(su_home_t *home,
		      char **ss,
353
		      msg_param_t const **append_list)
Pekka Pessi's avatar
Pekka Pessi committed
354 355 356
{
  char const *stack[MSG_N_PARAMS];
  char const **params;
357
  size_t n = 0, N;
Pekka Pessi's avatar
Pekka Pessi committed
358 359 360 361 362
  char *s = *ss;

  if (!*s)
    return -1;

363 364
  if (*append_list) {
    params = (char const **)*append_list;
Pekka Pessi's avatar
Pekka Pessi committed
365 366 367
    for (n = 0; params[n]; n++)
      ;
    N = MSG_PARAMS_NUM(n + 1);
368 369
  }
  else {
Pekka Pessi's avatar
Pekka Pessi committed
370 371 372
    params = stack;
    N = MSG_PARAMS_NUM(1);
  }
373

Pekka Pessi's avatar
Pekka Pessi committed
374
  for (;;) {
375 376
    char *p;
    size_t tlen;
Pekka Pessi's avatar
Pekka Pessi committed
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

    /* 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 == '"') {
396
	size_t qlen = span_quoted(s);
Pekka Pessi's avatar
Pekka Pessi committed
397 398 399 400 401
	if (!qlen)
	  goto error;
	v = s; s += qlen;
      }
      else {
402
	v = s;
Pekka Pessi's avatar
Pekka Pessi committed
403
	skip_param(&s);
404
	if (s == v)
Pekka Pessi's avatar
Pekka Pessi committed
405 406 407 408 409 410 411 412 413 414 415 416 417
	  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 */
Kai Samposalo's avatar
Kai Samposalo committed
418
      char const **nparams = su_realloc(home, (void*)(params != stack ? params : NULL),
419
					(N = MSG_PARAMS_NUM(N + 1)) * sizeof(*params));
Pekka Pessi's avatar
Pekka Pessi committed
420 421 422
      if (!nparams) {
	goto error;
      }
423 424 425
      if (params == stack)
	memcpy(nparams, stack, n * sizeof(*params));
      params = nparams;
Pekka Pessi's avatar
Pekka Pessi committed
426 427 428 429 430 431 432 433 434 435 436 437 438
    }

    params[n++] = p;

    if (*s != ';')
      break;

    *s++ = '\0';
  }

  *ss = s;

  if (params == stack) {
439
    size_t size = sizeof(*params) * MSG_PARAMS_NUM(n + 1);
Pekka Pessi's avatar
Pekka Pessi committed
440 441 442 443 444 445
    params = su_alloc(home, size);
    if (!params) return -1;
    memcpy((void *)params, stack, n * sizeof(*params));
  }
  else if (n == N) {
    /* Reallocate params */
Kai Samposalo's avatar
Kai Samposalo committed
446
    char const **nparams = su_realloc(home, (void*)(params != stack ? params : NULL),
447
				      (N = MSG_PARAMS_NUM(N + 1)) * sizeof(*params));
Pekka Pessi's avatar
Pekka Pessi committed
448 449 450
    if (!nparams) {
      goto error;
    }
451 452 453
    if (params == stack)
      memcpy(nparams, stack, n * sizeof(*params));
    params = nparams;
Pekka Pessi's avatar
Pekka Pessi committed
454 455 456 457
  }

  params[n] = NULL;

458
  *append_list = params;
Pekka Pessi's avatar
Pekka Pessi committed
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474

  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
 *
475 476
 * @param[in]     home      pointer to a memory home
 * @param[in,out] ss        pointer to string at the start of parameter list
477 478
 * @param[in,out] append_list  pointer to list
 *                             where parsed list items are appended
Pekka Pessi's avatar
Pekka Pessi committed
479
 *
480
 * @retval >= 0 if successful
Pekka Pessi's avatar
Pekka Pessi committed
481 482 483 484
 * @retval -1 upon an error
 *
 * @sa msg_avlist_d()
 */
485 486
issize_t msg_params_d(su_home_t *home,
		      char **ss,
487
		      msg_param_t const **append_list)
Pekka Pessi's avatar
Pekka Pessi committed
488 489 490
{
  if (**ss == ';') {
    *(*ss)++ = '\0';
491 492
    *append_list = NULL;
    return msg_avlist_d(home, ss, append_list);
Pekka Pessi's avatar
Pekka Pessi committed
493 494
  }

495
  if (IS_LWS(**ss)) {
Pekka Pessi's avatar
Pekka Pessi committed
496 497 498 499 500 501 502
    *(*ss)++ = '\0'; skip_lws(ss);
  }

  return 0;
}

/** Encode a list of parameters */
503
isize_t msg_params_e(char b[], isize_t bsiz, msg_param_t const pparams[])
Pekka Pessi's avatar
Pekka Pessi committed
504 505 506 507 508
{
  int i;
  char *end = b + bsiz, *b0 = b;
  msg_param_t p;

509
  if (pparams)
Pekka Pessi's avatar
Pekka Pessi committed
510 511 512 513 514 515 516 517 518 519 520 521
    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[],
522
		     char *b, isize_t xtra)
Pekka Pessi's avatar
Pekka Pessi committed
523 524 525
{
  char *end = b + xtra;
  char **pp;
Michael Jerris's avatar
Michael Jerris committed
526 527
  int i;
  isize_t n;
Pekka Pessi's avatar
Pekka Pessi committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544

  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;
545

546
  assert(b <= end); (void)end;
547

Pekka Pessi's avatar
Pekka Pessi committed
548 549 550
  *d = (msg_param_t const *)pp;

  return b;
551
}
Pekka Pessi's avatar
Pekka Pessi committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572


/** 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).
 *
573 574
 * @param[in]     home    memory home for allocating list pointers
 * @param[in,out] ss      pointer to pointer to string to be parsed
575 576
 * @param[in,out] append_list  pointer to list
 *                             where parsed list items are appended
577
 * @param[in]     scanner pointer to function scanning a single item
578
 *                        (optional)
579
 *
Pekka Pessi's avatar
Pekka Pessi committed
580 581 582
 * @retval 0  if successful.
 * @retval -1 upon an error.
 */
583 584
issize_t msg_commalist_d(su_home_t *home,
			 char **ss,
585
			 msg_param_t **append_list,
586
			 issize_t (*scanner)(char *s))
Pekka Pessi's avatar
Pekka Pessi committed
587 588
{
  scanner = scanner ? scanner : msg_comma_scanner;
589
  return msg_any_list_d(home, ss, append_list, scanner, ',');
Pekka Pessi's avatar
Pekka Pessi committed
590 591
}

Pekka Pessi's avatar
Pekka Pessi committed
592
/** Token scanner for msg_commalist_d() accepting also empty entries. */
593
issize_t msg_token_scan(char *start)
Pekka Pessi's avatar
Pekka Pessi committed
594 595 596 597 598 599 600 601 602 603 604 605 606
{
  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
607
issize_t msg_comma_scanner(char *start)
Pekka Pessi's avatar
Pekka Pessi committed
608
{
609
  size_t tlen;
Pekka Pessi's avatar
Pekka Pessi committed
610
  char *s, *p;
611

Pekka Pessi's avatar
Pekka Pessi committed
612 613 614 615 616 617 618 619 620
  s = p = start;

  if (s[0] == ',')
    return 0;

  for (;;) {
    /* Grab next section - token, quoted string, or separator character */
    char c = *s;

621
    if (IS_TOKEN(c))
Pekka Pessi's avatar
Pekka Pessi committed
622 623 624 625 626
      tlen = span_token(s);
    else if (c == '"')
      tlen = span_quoted(s);
    else /* if (IS_SEPARATOR(c)) */
      tlen = 1;
627

Pekka Pessi's avatar
Pekka Pessi committed
628 629 630 631 632 633
    if (tlen == 0)
      return -1;

    if (p != s)
      memmove(p, s, tlen);	/* Move section to end of paramexter */
    p += tlen; s += tlen;
634

Pekka Pessi's avatar
Pekka Pessi committed
635
    skip_lws(&s);		/* Skip possible LWS */
636

Pekka Pessi's avatar
Pekka Pessi committed
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
    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.
 */
654
issize_t msg_comment_d(char **ss, char const **return_comment)
Pekka Pessi's avatar
Pekka Pessi committed
655 656 657 658 659 660 661 662 663 664 665 666 667 668
{
  /* skip comment */
  int level = 1;
  char *s = *ss;

  assert(s[0] == '(');

  if (*s != '(')
    return -1;

  *s++ = '\0';

  if (return_comment)
    *return_comment = s;
669

Pekka Pessi's avatar
Pekka Pessi committed
670 671 672 673 674
  while (level) switch (*s++) {
  case '(': level++; break;
  case ')': level--; break;
  case '\0': /* ERROR */ return -1;
  }
675

Pekka Pessi's avatar
Pekka Pessi committed
676
  assert(s[-1] == ')');
677

Pekka Pessi's avatar
Pekka Pessi committed
678 679 680 681 682 683 684 685
  s[-1] = '\0';
  skip_lws(&s);
  *ss = s;

  return 0;
}

/** Parse a quoted string */
686
issize_t msg_quoted_d(char **ss, char **return_quoted)
Pekka Pessi's avatar
Pekka Pessi committed
687
{
688 689
  char *s= *ss, *s0 = s;
  ssize_t n = span_quoted(s);
Pekka Pessi's avatar
Pekka Pessi committed
690 691 692 693 694 695 696 697 698 699 700 701

  if (n <= 0)
    return -1;

  *return_quoted = s;
  s += n;
  if (IS_LWS(*s)) {
    *s++ = '\0';
    skip_lws(&s);		/* skip linear whitespace */
  }

  *ss = s;
702 703

  return s - s0;
Pekka Pessi's avatar
Pekka Pessi committed
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
}

#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).
738
 *
Pekka Pessi's avatar
Pekka Pessi committed
739 740 741 742
 * @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

743
 * @return
Pekka Pessi's avatar
Pekka Pessi committed
744 745 746 747 748 749 750 751 752 753 754
 * 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.
 */
755 756
int msg_hostport_d(char **ss,
		   char const **return_host,
Pekka Pessi's avatar
Pekka Pessi committed
757 758 759 760 761 762
		   char const **return_port)
{
  char *host, *s = *ss;
  char *port = NULL;

  /* Host name */
763
  host = s;
Pekka Pessi's avatar
Pekka Pessi committed
764 765 766 767 768
  if (s[0] != '[') {
    skip_token(&s); if (host == s) return -1;
  }
  else {
    /* IPv6 */
Michael Jerris's avatar
Michael Jerris committed
769
    size_t n = strspn(++s, HEX ":.");
Pekka Pessi's avatar
Pekka Pessi committed
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
    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;
793

Pekka Pessi's avatar
Pekka Pessi committed
794 795 796 797 798
  *ss = s;

  return 0;
}

799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
/** Clear encoded data from header fields.
 *
 * Clear encoded or cached unencoded headers from header fields.
 *
 * @param h pointer to header structure
 */
void msg_fragment_clear_chain(msg_header_t *h)
{
  char const *data;
  msg_header_t *prev, *succ;

  if (h == NULL || h->sh_data == NULL)
    return;

  data = (char *)h->sh_data + h->sh_len;

  /* Find first field of header */
  for (prev = (msg_header_t *)h->sh_prev;
       prev && (void *)prev->sh_next == (void *)h;) {
    if (!prev->sh_data)
      break;
    if ((char *)prev->sh_data + prev->sh_len != data)
      break;
    h = prev, prev = (msg_header_t *)h->sh_prev;
  }

  for (h = h; h; h = succ) {
    succ = h->sh_succ;

    h->sh_data = NULL, h->sh_len = 0;

    if (!data ||
	!succ ||
	h->sh_next != succ ||
	succ->sh_data != (void *)data ||
	succ->sh_len)
      return;
  }
}


840 841 842 843 844 845 846
/** 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.
 *
847
 * @param h     pointer to header structure
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
 * @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;
}

864
/**Modify a parameter value or list item in a header.
865
 *
866 867 868 869
 * 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.
870
 *
871 872 873 874 875 876 877 878 879 880 881 882
 * @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
883
 * @retval 0 if parameter was added successfully,
884
 *           or there was nothing to remove
885 886
 * @retval -1 upon an error
 */
887
static
888 889 890 891
int msg_header_param_modify(su_home_t *home, msg_common_t *h,
			    char const *param,
			    int is_item,
			    int remove_replace_add)
892
{
893 894
  msg_param_t *params, **pointer_to_params;
  size_t plen, n;
895

896 897
  if (!h || !h->h_class->hc_params || !param)
    return -1;
898

899 900
  pointer_to_params = (msg_param_t **)((char *)h + h->h_class->hc_params);
  params = *pointer_to_params;
901

902 903
  plen = is_item > 0 ? strlen(param) : strcspn(param, "=");
  n = 0;
904

905 906 907 908
  if (params) {
    /* Existing list, try to replace or remove  */
    for (; params[n]; n++) {
      char const *maybe = params[n];
909

910 911
      if (remove_replace_add > 0)
	continue;
912

913 914 915 916 917 918 919
      if (is_item > 0) {
	if (strcmp(maybe, param) == 0) {
	  if (remove_replace_add == 0)
	    return 1;
	}
      }
      else {
920
	if (su_casenmatch(maybe, param, plen) &&
921 922 923 924 925
	    (maybe[plen] == '=' || maybe[plen] == 0))
	  break;
      }
    }
  }
926

927 928 929 930 931 932 933
  /* 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 */
  }
934

935
  if (remove_replace_add < 0) { /* Remove */
936
    for (; params[n]; n++)
937 938 939 940 941 942
      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);
943

944
      assert(!params || !params[n]);
945

946 947 948
      if (m_before != m_after || !params) {
	msg_param_t *p;
	/* XXX - we should know when to do realloc */
949
	p = su_alloc(home, m_after * sizeof(*p));
950 951 952 953 954 955
	if (!p) return -1;
	if (n > 0)
	  memcpy(p, params, n * sizeof(p[0]));
	*pointer_to_params = params = p;
      }
      params[n + 1] = NULL;
956
    }
957

958 959
    params[n] = param;	/* Add .. or replace */
  }
960

961 962
  if (h->h_data)
    msg_fragment_clear_chain((msg_header_t *)h);
963

964 965 966 967
  if (h->h_class->hc_update) {
    /* Update shortcuts */
    size_t namelen;
    char const *name, *value;
968

969 970
    name = param;
    namelen = strcspn(name, "=");
971

972 973 974 975
    if (remove_replace_add < 0)
      value = NULL;
    else
      value = param + namelen + (name[namelen] == '=');
976

977
    h->h_class->hc_update(h, name, namelen, value);
978 979
  }

980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
  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
 *
1008
 * @sa msg_header_replace_param(), msg_header_remove_param(),
1009
 * msg_header_update_params(),
1010
 * #msg_common_t, #msg_header_t,
1011 1012 1013 1014
 * #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)
{
1015 1016
  return msg_header_param_modify(home, h, param,
				 0 /* case-insensitive name=value */,
1017
				 1 /* add */);
1018 1019
}

1020

1021

1022
/** Replace or add a parameter to a header.
1023
 *
1024 1025 1026
 * 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.
1027
 *
1028 1029 1030
 * @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.
1031
 *
1032 1033 1034 1035 1036
 * 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
1037 1038 1039 1040 1041 1042
 * @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
1043
 *
1044
 * @sa msg_header_add_param(), msg_header_remove_param(),
1045
 * msg_header_update_params(),
1046
 * #msg_common_t, #msg_header_t,
1047
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1048
 */
1049 1050
int msg_header_replace_param(su_home_t *home,
			     msg_common_t *h,
1051 1052
			     char const *param)
{
1053 1054
  return msg_header_param_modify(home, h, param,
				 0 /* case-insensitive name=value */,
1055
				 0 /* replace */);
1056 1057
}

1058 1059 1060
/** Remove a parameter from header.
 *
 * The parameter name is given as token optionally followed by "=" sign and
1061 1062 1063 1064 1065 1066 1067
 * 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.
1068 1069
 *
 * @param h         pointer to a header
1070
 * @param name      name of parameter to be removed
1071 1072 1073 1074
 *
 * @retval 1 if a parameter was removed
 * @retval 0 if no parameter was not removed
 * @retval -1 upon an error
1075 1076 1077
 *
 * @sa msg_header_add_param(), msg_header_replace_param(),
 * msg_header_update_params(),
1078
 * #msg_common_t, #msg_header_t,
1079
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1080
 */
1081 1082
int msg_header_remove_param(msg_common_t *h, char const *name)
{
1083 1084
  return msg_header_param_modify(NULL, h, name,
				 0 /* case-insensitive name=value */,
1085
				 -1 /* remove */);
1086 1087
}

1088 1089 1090 1091 1092 1093 1094 1095 1096
/** 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, "".
1097 1098 1099
 *
 * @retval 0 when successful
 * @retval -1 upon an error
1100 1101 1102
 *
 * @sa msg_header_add_param(), msg_header_replace_param(),
 * msg_header_update_params(),
1103
 * #msg_common_t, #msg_header_t,
1104
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1105
 */
1106 1107
int msg_header_update_params(msg_common_t *h, int clear)
{
1108 1109 1110
  msg_hclass_t *hc;
  unsigned char offset;
  msg_update_f *update;
1111 1112 1113 1114
  int retval;
  msg_param_t const *params;
  size_t n;
  char const *p, *v;
1115

1116 1117 1118
  if (h == NULL)
    return errno = EFAULT, -1;

1119 1120 1121
  hc = h->h_class; offset = hc->hc_params; update = hc->hc_update;

  if (offset == 0 || update == NULL)
1122 1123 1124
    return 0;

  if (clear)
1125
    update(h, NULL, 0, NULL);
1126

1127
  params = *(msg_param_t **)((char *)h + offset);
1128 1129 1130 1131 1132 1133 1134 1135
  if (params == NULL)
    return 0;

  retval = 0;

  for (p = *params; p; p = *++params) {
    n = strcspn(p, "=");
    v = p + n + (p[n] == '=');
1136
    if (update(h, p, n, v) < 0)
1137 1138 1139 1140 1141 1142 1143
      retval = -1;
  }

  return retval;
}


1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
/** 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.
 *
1155 1156
 * @since New in @VERSION_1_12_4
 *
1157
 * @sa msg_header_replace_item(), msg_header_remove_item(),
1158 1159 1160 1161 1162
 * @Allow, @AllowEvents
 */
char const *msg_header_find_item(msg_common_t const *h, char const *item)
{
  if (h && h->h_class->hc_params) {
1163
    char const * const * items =
1164
      *(char const * const * const *)
1165 1166
      ((char *)h + h->h_class->hc_params);

1167 1168 1169 1170 1171
    if (items)
      for (; *items; items++) {
	if (strcmp(item, *items) == 0) {
	  return *items;
	}
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
      }
  }

  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
Pekka Pessi's avatar
Pekka Pessi committed
1189
 * @param item      item to be removed
1190 1191 1192 1193 1194
 *
 * @retval 0 if item was added
 * @retval 1 if item was replaced
 * @retval -1 upon an error
 *
1195
 * @since New in @VERSION_1_12_4.
1196
 *
1197
 * @sa msg_header_remove_item(), @Allow, @AllowEvents,
1198
 * msg_header_replace_param(), msg_header_remove_param(),
1199 1200 1201
 * #msg_common_t, #msg_header_t, #msg_list_t
 * #msg_hclass_t, msg_hclass_t::hc_params
 */
1202 1203
int msg_header_replace_item(su_home_t *home,
			    msg_common_t *h,
1204 1205
			    char const *item)
{
1206 1207
  return msg_header_param_modify(home, h, item,
				 1 /* string item */,
1208 1209 1210 1211 1212
				 0 /* replace */);
}

/**Remove an item from a header.
 *
1213
 * This function treats a #msg_header_t as set of C strings. The @a item is a
1214 1215 1216 1217
 * C string. If identical string is found from the list, it is removed.
 *
 * The shortcuts, if any, to item values are updated accordingly.
 *
Pekka Pessi's avatar
Pekka Pessi committed
1218 1219
 * @param h        pointer to a header
 * @param name     item to be removed
1220 1221 1222 1223 1224
 *
 * @retval 0 if item was added
 * @retval 1 if item was replaced
 * @retval -1 upon an error
 *
1225
 * @since New in @VERSION_1_12_4.
1226
 *
1227
 * @sa msg_header_replace_item(), @Allow, @AllowEvents,
1228
 * msg_header_replace_param(), msg_header_remove_param(),
1229 1230 1231 1232 1233
 * #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)
{
1234 1235
  return msg_header_param_modify(NULL, h, name,
				 1 /* item */,
1236 1237 1238 1239
				 -1 /* remove */);
}


Pekka Pessi's avatar
Pekka Pessi committed
1240 1241 1242
/** Find a parameter from a parameter list.
 *
 * Searches for given parameter @a token from the parameter list. If
1243
 * parameter is found, it returns a non-NULL pointer to the parameter value.
Pekka Pessi's avatar
Pekka Pessi committed
1244 1245 1246
 * If there is no value for the parameter (the parameter is of form "name"
 * or "name="), the returned pointer points to a NUL character.
 *
1247
 * @param params list (or vector) of parameters
Pekka Pessi's avatar
Pekka Pessi committed
1248 1249 1250 1251 1252 1253 1254 1255
 * @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) {
1256
    size_t i, n = strcspn(token, "=");
Pekka Pessi's avatar
Pekka Pessi committed
1257 1258 1259 1260 1261

    assert(n > 0);

    for (i = 0; params[i]; i++) {
      msg_param_t param = params[i];
1262
      if (su_casenmatch(param, token, n)) {
Pekka Pessi's avatar
Pekka Pessi committed
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
	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.
 *
1280
 * @param params list (or vector) of parameters
Pekka Pessi's avatar
Pekka Pessi committed
1281 1282 1283 1284 1285 1286 1287 1288
 * @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) {
Michael Jerris's avatar
Michael Jerris committed
1289 1290
    int i;
	size_t n = strlen(token);
Pekka Pessi's avatar
Pekka Pessi committed
1291 1292 1293 1294 1295

    assert(n > 0);

    for (i = 0; params[i]; i++) {
      msg_param_t param = params[i];
1296
      if (su_casenmatch(param, token, n)) {
Pekka Pessi's avatar
Pekka Pessi committed
1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308
	if (param[n] == '=')
	  return params + i;
        else if (param[n] == 0 || token[n - 1] == '=')
	  return params + i;
      }
    }

  }

  return NULL;
}

1309
/** Replace or add a parameter from a list.
Pekka Pessi's avatar
Pekka Pessi committed
1310
 *
1311 1312
 * A non-NULL parameter list must have been created by msg_params_d()
 * or by msg_params_dup().
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
 *
 * @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
Pekka Pessi's avatar
Pekka Pessi committed
1323 1324
 */
int msg_params_replace(su_home_t *home,
1325
		       msg_param_t **inout_params,
Pekka Pessi's avatar
Pekka Pessi committed
1326 1327 1328
		       msg_param_t param)
{
  msg_param_t *params;
1329
  size_t i, n;
Pekka Pessi's avatar
Pekka Pessi committed
1330

1331
  assert(inout_params);
Pekka Pessi's avatar
Pekka Pessi committed
1332 1333 1334 1335

  if (param == NULL || param[0] == '=' || param[0] == '\0')
    return -1;

1336
  params = *inout_params;
Pekka Pessi's avatar
Pekka Pessi committed
1337 1338 1339 1340 1341 1342 1343 1344

  n = strcspn(param, "=");

  if (params) {
    /* Existing list, try to replace or remove  */
    for (i = 0; params[i]; i++) {
      msg_param_t maybe = params[i];

1345
      if (su_casenmatch(maybe, param, n)) {
Pekka Pessi's avatar
Pekka Pessi committed
1346
	if (maybe[n] == '=' || maybe[n] == 0) {
1347
	  params[i] = param;
1348
	  return 1;
Pekka Pessi's avatar
Pekka Pessi committed
1349 1350 1351 1352 1353
	}
      }
    }
  }

1354 1355
  /* Not found on list */
  return msg_params_add(home, inout_params, param);
Pekka Pessi's avatar
Pekka Pessi committed
1356 1357
}

1358
/** Remove a parameter from a list.
Pekka Pessi's avatar
Pekka Pessi committed
1359
 *
1360 1361 1362
 * @retval 1 if parameter was removed
 * @retval 0 if parameter was not found
 * @retval -1 upon an error
Pekka Pessi's avatar
Pekka Pessi committed
1363 1364 1365
 */
int msg_params_remove(msg_param_t *params, msg_param_t param)
{
1366
  size_t i, n;
Pekka Pessi's avatar
Pekka Pessi committed
1367 1368 1369 1370

  if (!params || !param || !param[0])
    return -1;

1371
  n = strcspn(param, "=");
Pekka Pessi's avatar
Pekka Pessi committed
1372 1373 1374 1375 1376
  assert(n > 0);

  for (i = 0; params[i]; i++) {
    msg_param_t maybe = params[i];

1377
    if (su_casenmatch(maybe, param, n)) {
Pekka Pessi's avatar
Pekka Pessi committed
1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390
      if (maybe[n] == '=' || maybe[n] == 0) {
	/* Remove */
	do {
	  params[i] = params[i + 1];
	} while (params[i++]);
	return 1;
      }
    }
  }

  return 0;
}

1391 1392
/** Calculate number of parameters in a parameter list */
size_t msg_params_length(char const * const * params)
Pekka Pessi's avatar
Pekka Pessi committed
1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
{
  size_t len;

  if (!params)
    return 0;

  for (len = 0; params[len]; len++)
    ;

  return len;
}


/**
 * Add a parameter to a list.
 *
1409 1410 1411
 * 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).
Pekka Pessi's avatar
Pekka Pessi committed
1412 1413 1414 1415
 *
 * @note This function does not duplicate @p param.
 *
 * @param home      memory home
1416
 * @param inout_params   pointer to pointer to parameter list
Pekka Pessi's avatar
Pekka Pessi committed
1417 1418
 * @param param     parameter to be added
 *
1419
 * @retval 0 if parameter was added
1420
 * @retval -1 upon an error
Pekka Pessi's avatar
Pekka Pessi committed
1421 1422
 */
int msg_params_add(su_home_t *home,
1423
		   msg_param_t **inout_params,
Pekka Pessi's avatar
Pekka Pessi committed
1424 1425
		   msg_param_t param)
{
1426
  size_t n, m_before, m_after;
1427
  msg_param_t *p = *inout_params;
Pekka Pessi's avatar
Pekka Pessi committed
1428 1429 1430 1431 1432 1433 1434 1435 1436 1437

  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);
1438

Pekka Pessi's avatar
Pekka Pessi committed
1439
  if (m_before != m_after || !p) {
1440
    p = su_alloc(home, m_after * sizeof(*p));
Pekka Pessi's avatar
Pekka Pessi committed
1441 1442
    assert(p); if (!p) return -1;
    if (n)
1443 1444
      memcpy(p, *inout_params, n * sizeof(*p));
    *inout_params = p;
Pekka Pessi's avatar
Pekka Pessi committed
1445 1446 1447 1448 1449 1450 1451 1452
  }

  p[n] = param;
  p[n + 1] = NULL;

  return 0;
}

1453
static
Pekka Pessi's avatar
Pekka Pessi committed
1454 1455
int msg_param_prune(msg_param_t const d[], msg_param_t p, unsigned prune)
{
1456
  size_t i, nlen;
Pekka Pessi's avatar
Pekka Pessi committed
1457 1458 1459 1460 1461 1462 1463

  if (prune == 1)
    nlen = strcspn(p, "=");
  else
    nlen = 0;

  for (i = 0; d[i]; i++) {
1464
    if ((prune == 1 &&
1465
	 su_casenmatch(p, d[i], nlen)
Pekka Pessi's avatar
Pekka Pessi committed
1466
	 && (d[i][nlen] == '=' || d[i][nlen] == '\0'))
1467
	||
1468
	(prune == 2 && su_casematch(p, d[i]))
Pekka Pessi's avatar
Pekka Pessi committed
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
	||
	(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
1486
 * @param prune   prune duplicates
Pekka Pessi's avatar
Pekka Pessi committed
1487 1488
 * @param dup     duplicate parameters in src list
 *
1489
 * @par Pruning
Pekka Pessi's avatar
Pekka Pessi committed
1490 1491 1492 1493 1494 1495 1496 1497
 * <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
1498 1499
 * @retval >= 0 when successful
 * @retval -1 upon an error
Pekka Pessi's avatar
Pekka Pessi committed
1500
 */
1501 1502 1503 1504 1505
issize_t msg_params_join(su_home_t *home,
			 msg_param_t **dst,
			 msg_param_t const *src,
			 unsigned prune,
			 int dup)
Pekka Pessi's avatar
Pekka Pessi committed
1506
{
1507
  size_t n, m, n_before, n_after, pruned, total = 0;
1508
  msg_param_t *d = *dst;
Pekka Pessi's avatar
Pekka Pessi committed
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532

  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);
1533

Pekka Pessi's avatar
Pekka Pessi committed
1534
  if (n_before != n_after || !d) {
1535
    d = su_alloc(home, n_after * sizeof(*d));
Pekka Pessi's avatar
Pekka Pessi committed
1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552
    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];
1553
  }
Pekka Pessi's avatar
Pekka Pessi committed
1554 1555 1556 1557 1558 1559

  d[n] = NULL;

  return 0;
}

1560 1561
/**Join header item lists.
 *
1562 1563 1564 1565
 * 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.
1566
 *
1567 1568 1569 1570
 * @param home       memory home
 * @param dst        destination header
 * @param src        source header
 * @param duplicate  if true, allocate and copy items that are added
1571 1572 1573 1574
 *
 * @return
 * @retval >= 0 when successful
 * @retval -1 upon an error
1575 1576
 *
 * @NEW_1_12_5.
1577 1578
 */
int msg_header_join_items(su_home_t *home,
1579 1580 1581
			  msg_common_t *dst,
			  msg_common_t const *src,
			  int duplicate)
1582
{
1583 1584 1585 1586 1587
  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])];
1588 1589
  msg_update_f *update = NULL;

1590 1591
  if (dst == NULL || dst->h_class->hc_params == 0 ||
      src == NULL || src->h_class->hc_params == 0)
1592 1593
    return -1;

1594 1595 1596 1597
  s = *(msg_param_t **)((char *)src + src->h_class->hc_params);
  if (s == NULL)
    return 0;

Kai Samposalo's avatar
Kai Samposalo committed
1598 1599
  for (M = 0; s[M]; M++)
    {}
1600

1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611
  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);
  }