msg_parser_util.c 45.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>
Pekka Pessi's avatar
Pekka Pessi committed
50 51

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

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

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

/**
 * 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;
68
  size_t n;
Pekka Pessi's avatar
Pekka Pessi committed
69 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

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

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

  if (!scanner)
    return -1;

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

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

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

  *ss = s;

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

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

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

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

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

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

  if (!*s)
    return -1;

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

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

    /* 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 == '"') {
395
	size_t qlen = span_quoted(s);
Pekka Pessi's avatar
Pekka Pessi committed
396 397 398 399 400
	if (!qlen)
	  goto error;
	v = s; s += qlen;
      }
      else {
401
	v = s;
Pekka Pessi's avatar
Pekka Pessi committed
402
	skip_param(&s);
403
	if (s == v)
Pekka Pessi's avatar
Pekka Pessi committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
	  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) {
436
    size_t size = sizeof(*params) * MSG_PARAMS_NUM(n + 1);
Pekka Pessi's avatar
Pekka Pessi committed
437 438 439 440 441 442
    params = su_alloc(home, size);
    if (!params) return -1;
    memcpy((void *)params, stack, n * sizeof(*params));
  }
  else if (n == N) {
    /* Reallocate params */
443
    char **nparams = su_alloc(home,
Pekka Pessi's avatar
Pekka Pessi committed
444 445 446 447 448 449 450 451 452
			      (N = MSG_PARAMS_NUM(N + 1)) * sizeof(*params));
    if (!nparams) {
      goto error;
    }
    params = memcpy(nparams, params, n * sizeof(*params));
  }

  params[n] = NULL;

453
  *append_list = params;
Pekka Pessi's avatar
Pekka Pessi committed
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469

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

490
  if (IS_LWS(**ss)) {
Pekka Pessi's avatar
Pekka Pessi committed
491 492 493 494 495 496 497
    *(*ss)++ = '\0'; skip_lws(ss);
  }

  return 0;
}

/** Encode a list of parameters */
498
isize_t msg_params_e(char b[], isize_t bsiz, msg_param_t const pparams[])
Pekka Pessi's avatar
Pekka Pessi committed
499 500 501 502 503
{
  int i;
  char *end = b + bsiz, *b0 = b;
  msg_param_t p;

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

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

541
  assert(b <= end); (void)end;
542

Pekka Pessi's avatar
Pekka Pessi committed
543 544 545
  *d = (msg_param_t const *)pp;

  return b;
546
}
Pekka Pessi's avatar
Pekka Pessi committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567


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

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

Pekka Pessi's avatar
Pekka Pessi committed
607 608 609 610 611 612 613 614 615
  s = p = start;

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

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

616
    if (IS_TOKEN(c))
Pekka Pessi's avatar
Pekka Pessi committed
617 618 619 620 621
      tlen = span_token(s);
    else if (c == '"')
      tlen = span_quoted(s);
    else /* if (IS_SEPARATOR(c)) */
      tlen = 1;
622

Pekka Pessi's avatar
Pekka Pessi committed
623 624 625 626 627 628
    if (tlen == 0)
      return -1;

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

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

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

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

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

  *s++ = '\0';

  if (return_comment)
    *return_comment = s;
664

Pekka Pessi's avatar
Pekka Pessi committed
665 666 667 668 669
  while (level) switch (*s++) {
  case '(': level++; break;
  case ')': level--; break;
  case '\0': /* ERROR */ return -1;
  }
670

Pekka Pessi's avatar
Pekka Pessi committed
671
  assert(s[-1] == ')');
672

Pekka Pessi's avatar
Pekka Pessi committed
673 674 675 676 677 678 679 680
  s[-1] = '\0';
  skip_lws(&s);
  *ss = s;

  return 0;
}

/** Parse a quoted string */
681
issize_t msg_quoted_d(char **ss, char **return_quoted)
Pekka Pessi's avatar
Pekka Pessi committed
682
{
683 684
  char *s= *ss, *s0 = s;
  ssize_t n = span_quoted(s);
Pekka Pessi's avatar
Pekka Pessi committed
685 686 687 688 689 690 691 692 693 694 695 696

  if (n <= 0)
    return -1;

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

  *ss = s;
697 698

  return s - s0;
Pekka Pessi's avatar
Pekka Pessi committed
699 700 701 702 703 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
}

#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).
733
 *
Pekka Pessi's avatar
Pekka Pessi committed
734 735 736 737
 * @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

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

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

Pekka Pessi's avatar
Pekka Pessi committed
789 790 791 792 793
  *ss = s;

  return 0;
}

794 795 796 797 798 799 800
/** 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.
 *
801
 * @param h     pointer to header structure
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
 * @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;
}

818
/**Modify a parameter value or list item in a header.
819
 *
820 821 822 823
 * 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.
824
 *
825 826 827 828 829 830 831 832 833 834 835 836
 * @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
837
 * @retval 0 if parameter was added successfully,
838
 *           or there was nothing to remove
839 840
 * @retval -1 upon an error
 */
841
static
842 843 844 845
int msg_header_param_modify(su_home_t *home, msg_common_t *h,
			    char const *param,
			    int is_item,
			    int remove_replace_add)
846
{
847 848
  msg_param_t *params, **pointer_to_params;
  size_t plen, n;
849

850 851
  if (!h || !h->h_class->hc_params || !param)
    return -1;
852

853 854
  pointer_to_params = (msg_param_t **)((char *)h + h->h_class->hc_params);
  params = *pointer_to_params;
855

856 857
  plen = is_item > 0 ? strlen(param) : strcspn(param, "=");
  n = 0;
858

859 860 861 862
  if (params) {
    /* Existing list, try to replace or remove  */
    for (; params[n]; n++) {
      char const *maybe = params[n];
863

864 865
      if (remove_replace_add > 0)
	continue;
866

867 868 869 870 871 872 873 874 875 876 877 878 879
      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;
      }
    }
  }
880

881 882 883 884 885 886 887
  /* 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 */
  }
888

889
  if (remove_replace_add < 0) { /* Remove */
890
    for (; params[n]; n++)
891 892 893 894 895 896
      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);
897

898
      assert(!params || !params[n]);
899

900 901 902
      if (m_before != m_after || !params) {
	msg_param_t *p;
	/* XXX - we should know when to do realloc */
903
	p = su_alloc(home, m_after * sizeof(*p));
904 905 906 907 908 909
	if (!p) return -1;
	if (n > 0)
	  memcpy(p, params, n * sizeof(p[0]));
	*pointer_to_params = params = p;
      }
      params[n + 1] = NULL;
910
    }
911

912 913
    params[n] = param;	/* Add .. or replace */
  }
914

915
  msg_fragment_clear(h);
916

917 918 919 920
  if (h->h_class->hc_update) {
    /* Update shortcuts */
    size_t namelen;
    char const *name, *value;
921

922 923
    name = param;
    namelen = strcspn(name, "=");
924

925 926 927 928
    if (remove_replace_add < 0)
      value = NULL;
    else
      value = param + namelen + (name[namelen] == '=');
929

930
    h->h_class->hc_update(h, name, namelen, value);
931 932
  }

933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
  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
 *
961
 * @sa msg_header_replace_param(), msg_header_remove_param(),
962
 * msg_header_update_params(),
963
 * #msg_common_t, #msg_header_t,
964 965 966 967
 * #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)
{
968 969
  return msg_header_param_modify(home, h, param,
				 0 /* case-insensitive name=value */,
970
				 1 /* add */);
971 972
}

973

974

975
/** Replace or add a parameter to a header.
976
 *
977 978 979
 * 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.
980
 *
981 982 983
 * @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.
984
 *
985 986 987 988 989
 * 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
990 991 992 993 994 995
 * @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
996
 *
997
 * @sa msg_header_add_param(), msg_header_remove_param(),
998
 * msg_header_update_params(),
999
 * #msg_common_t, #msg_header_t,
1000
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1001
 */
1002 1003
int msg_header_replace_param(su_home_t *home,
			     msg_common_t *h,
1004 1005
			     char const *param)
{
1006 1007
  return msg_header_param_modify(home, h, param,
				 0 /* case-insensitive name=value */,
1008
				 0 /* replace */);
1009 1010
}

1011 1012 1013
/** Remove a parameter from header.
 *
 * The parameter name is given as token optionally followed by "=" sign and
1014 1015 1016 1017 1018 1019 1020
 * 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.
1021 1022
 *
 * @param h         pointer to a header
1023
 * @param name      name of parameter to be removed
1024 1025 1026 1027
 *
 * @retval 1 if a parameter was removed
 * @retval 0 if no parameter was not removed
 * @retval -1 upon an error
1028 1029 1030
 *
 * @sa msg_header_add_param(), msg_header_replace_param(),
 * msg_header_update_params(),
1031
 * #msg_common_t, #msg_header_t,
1032
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1033
 */
1034 1035
int msg_header_remove_param(msg_common_t *h, char const *name)
{
1036 1037
  return msg_header_param_modify(NULL, h, name,
				 0 /* case-insensitive name=value */,
1038
				 -1 /* remove */);
1039 1040
}

1041 1042 1043 1044 1045 1046 1047 1048 1049
/** 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, "".
1050 1051 1052
 *
 * @retval 0 when successful
 * @retval -1 upon an error
1053 1054 1055
 *
 * @sa msg_header_add_param(), msg_header_replace_param(),
 * msg_header_update_params(),
1056
 * #msg_common_t, #msg_header_t,
1057
 * #msg_hclass_t, msg_hclass_t::hc_params, msg_hclass_t::hc_update
1058
 */
1059 1060
int msg_header_update_params(msg_common_t *h, int clear)
{
1061 1062 1063
  msg_hclass_t *hc;
  unsigned char offset;
  msg_update_f *update;
1064 1065 1066 1067
  int retval;
  msg_param_t const *params;
  size_t n;
  char const *p, *v;
1068

1069 1070 1071
  if (h == NULL)
    return errno = EFAULT, -1;

1072 1073 1074
  hc = h->h_class; offset = hc->hc_params; update = hc->hc_update;

  if (offset == 0 || update == NULL)
1075 1076 1077
    return 0;

  if (clear)
1078
    update(h, NULL, 0, NULL);
1079

1080
  params = *(msg_param_t **)((char *)h + offset);
1081 1082 1083 1084 1085 1086 1087 1088
  if (params == NULL)
    return 0;

  retval = 0;

  for (p = *params; p; p = *++params) {
    n = strcspn(p, "=");
    v = p + n + (p[n] == '=');
1089
    if (update(h, p, n, v) < 0)
1090 1091 1092 1093 1094 1095 1096
      retval = -1;
  }

  return retval;
}


1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
/** 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.
 *
1108 1109
 * @since New in @VERSION_1_12_4
 *
1110
 * @sa msg_header_replace_item(), msg_header_remove_item(),
1111 1112 1113 1114 1115
 * @Allow, @AllowEvents
 */
char const *msg_header_find_item(msg_common_t const *h, char const *item)
{
  if (h && h->h_class->hc_params) {
1116
    char const * const * items =
1117
      *(char const * const * const *)
1118 1119
      ((char *)h + h->h_class->hc_params);

1120 1121 1122 1123 1124
    if (items)
      for (; *items; items++) {
	if (strcmp(item, *items) == 0) {
	  return *items;
	}
1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
      }
  }

  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
1142
 * @param item      item to be removed
1143 1144 1145 1146 1147
 *
 * @retval 0 if item was added
 * @retval 1 if item was replaced
 * @retval -1 upon an error
 *
1148
 * @since New in @VERSION_1_12_4.
1149
 *
1150
 * @sa msg_header_remove_item(), @Allow, @AllowEvents,
1151
 * msg_header_replace_param(), msg_header_remove_param(),
1152 1153 1154
 * #msg_common_t, #msg_header_t, #msg_list_t
 * #msg_hclass_t, msg_hclass_t::hc_params
 */
1155 1156
int msg_header_replace_item(su_home_t *home,
			    msg_common_t *h,
1157 1158
			    char const *item)
{
1159 1160
  return msg_header_param_modify(home, h, item,
				 1 /* string item */,
1161 1162 1163 1164 1165
				 0 /* replace */);
}

/**Remove an item from a header.
 *
1166
 * This function treats a #msg_header_t as set of C strings. The @a item is a
1167 1168 1169 1170
 * 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
1171 1172
 * @param h        pointer to a header
 * @param name     item to be removed
1173 1174 1175 1176 1177
 *
 * @retval 0 if item was added
 * @retval 1 if item was replaced
 * @retval -1 upon an error
 *
1178
 * @since New in @VERSION_1_12_4.
1179
 *
1180
 * @sa msg_header_replace_item(), @Allow, @AllowEvents,
1181
 * msg_header_replace_param(), msg_header_remove_param(),
1182 1183 1184 1185 1186
 * #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)
{
1187 1188
  return msg_header_param_modify(NULL, h, name,
				 1 /* item */,
1189 1190 1191 1192
				 -1 /* remove */);
}


Pekka Pessi's avatar
Pekka Pessi committed
1193 1194 1195
/** Find a parameter from a parameter list.
 *
 * Searches for given parameter @a token from the parameter list. If
1196
 * parameter is found, it returns a non-NULL pointer to the parameter value.
Pekka Pessi's avatar
Pekka Pessi committed
1197 1198 1199
 * If there is no value for the parameter (the parameter is of form "name"
 * or "name="), the returned pointer points to a NUL character.
 *
1200
 * @param params list (or vector) of parameters
Pekka Pessi's avatar
Pekka Pessi committed
1201 1202 1203 1204 1205 1206 1207 1208
 * @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) {
1209
    size_t i, n = strcspn(token, "=");
Pekka Pessi's avatar
Pekka Pessi committed
1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232

    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.
 *
1233
 * @param params list (or vector) of parameters
Pekka Pessi's avatar
Pekka Pessi committed
1234 1235 1236 1237 1238 1239 1240 1241
 * @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
1242 1243
    int i;
	size_t n = strlen(token);
Pekka Pessi's avatar
Pekka Pessi committed
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261

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

1262
/** Replace or add a parameter from a list.
Pekka Pessi's avatar
Pekka Pessi committed
1263
 *
1264 1265
 * A non-NULL parameter list must have been created by msg_params_d()
 * or by msg_params_dup().
1266 1267 1268 1269 1270 1271 1272 1273 1274 1275
 *
 * @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
1276 1277
 */
int msg_params_replace(su_home_t *home,
1278
		       msg_param_t **inout_params,
Pekka Pessi's avatar
Pekka Pessi committed
1279 1280 1281
		       msg_param_t param)
{
  msg_param_t *params;
1282
  size_t i, n;
Pekka Pessi's avatar
Pekka Pessi committed
1283

1284
  assert(inout_params);
Pekka Pessi's avatar
Pekka Pessi committed
1285 1286 1287 1288

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

1289
  params = *inout_params;
Pekka Pessi's avatar
Pekka Pessi committed
1290 1291 1292 1293 1294 1295 1296 1297

  n = strcspn(param, "=");

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

1298
      if (!(strncasecmp(maybe, param, n))) {
Pekka Pessi's avatar
Pekka Pessi committed
1299
	if (maybe[n] == '=' || maybe[n] == 0) {
1300
	  params[i] = param;
1301
	  return 1;
Pekka Pessi's avatar
Pekka Pessi committed
1302 1303 1304 1305 1306
	}
      }
    }
  }

1307 1308
  /* Not found on list */
  return msg_params_add(home, inout_params, param);
Pekka Pessi's avatar
Pekka Pessi committed
1309 1310
}

1311
/** Remove a parameter from a list.
Pekka Pessi's avatar
Pekka Pessi committed
1312
 *
1313 1314 1315
 * @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
1316 1317 1318
 */
int msg_params_remove(msg_param_t *params, msg_param_t param)
{
1319
  size_t i, n;
Pekka Pessi's avatar
Pekka Pessi committed
1320 1321 1322 1323

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

1324
  n = strcspn(param, "=");
Pekka Pessi's avatar
Pekka Pessi committed
1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
  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;
}

1344 1345
/** Calculate number of parameters in a parameter list */
size_t msg_params_length(char const * const * params)
Pekka Pessi's avatar
Pekka Pessi committed
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361
{
  size_t len;

  if (!params)
    return 0;

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

  return len;
}


/**
 * Add a parameter to a list.
 *
1362 1363 1364
 * 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
1365 1366 1367 1368
 *
 * @note This function does not duplicate @p param.
 *
 * @param home      memory home
1369
 * @param inout_params   pointer to pointer to parameter list
Pekka Pessi's avatar
Pekka Pessi committed
1370 1371
 * @param param     parameter to be added
 *
1372
 * @retval 0 if parameter was added
1373
 * @retval -1 upon an error
Pekka Pessi's avatar
Pekka Pessi committed
1374 1375
 */
int msg_params_add(su_home_t *home,
1376
		   msg_param_t **inout_params,
Pekka Pessi's avatar
Pekka Pessi committed
1377 1378
		   msg_param_t param)
{
1379
  size_t n, m_before, m_after;
1380
  msg_param_t *p = *inout_params;
Pekka Pessi's avatar
Pekka Pessi committed
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390

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

Pekka Pessi's avatar
Pekka Pessi committed
1392
  if (m_before != m_after || !p) {
1393
    p = su_alloc(home, m_after * sizeof(*p));
Pekka Pessi's avatar
Pekka Pessi committed
1394 1395
    assert(p); if (!p) return -1;
    if (n)
1396 1397
      memcpy(p, *inout_params, n * sizeof(*p));
    *inout_params = p;
Pekka Pessi's avatar
Pekka Pessi committed
1398 1399 1400 1401 1402 1403 1404 1405
  }

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

  return 0;
}

1406
static
Pekka Pessi's avatar
Pekka Pessi committed
1407 1408
int msg_param_prune(msg_param_t const d[], msg_param_t p, unsigned prune)
{
1409
  size_t i, nlen;
Pekka Pessi's avatar
Pekka Pessi committed
1410 1411 1412 1413 1414 1415 1416

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

  for (i = 0; d[i]; i++) {
1417 1418
    if ((prune == 1 &&
	 strncasecmp(p, d[i], nlen) == 0
Pekka Pessi's avatar
Pekka Pessi committed
1419
	 && (d[i][nlen] == '=' || d[i][nlen] == '\0'))
1420
	||
Pekka Pessi's avatar
Pekka Pessi committed
1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438
	(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
1439
 * @param prune   prune duplicates
Pekka Pessi's avatar
Pekka Pessi committed
1440 1441
 * @param dup     duplicate parameters in src list
 *
1442
 * @par Pruning
Pekka Pessi's avatar
Pekka Pessi committed
1443 1444 1445 1446 1447 1448 1449 1450
 * <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
1451 1452
 * @retval >= 0 when successful
 * @retval -1 upon an error
Pekka Pessi's avatar
Pekka Pessi committed
1453
 */
1454 1455 1456 1457 1458
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
1459
{
1460
  size_t n, m, n_before, n_after, pruned, total = 0;
1461
  msg_param_t *d = *dst;
Pekka Pessi's avatar
Pekka Pessi committed
1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485

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

Pekka Pessi's avatar
Pekka Pessi committed
1487
  if (n_before != n_after || !d) {
1488
    d = su_alloc(home, n_after * sizeof(*d));
Pekka Pessi's avatar
Pekka Pessi committed
1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505
    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];
1506
  }
Pekka Pessi's avatar
Pekka Pessi committed
1507 1508 1509 1510 1511 1512

  d[n] = NULL;

  return 0;
}

1513 1514
/**Join header item lists.
 *
1515 1516 1517 1518
 * 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.
1519
 *
1520 1521 1522 1523
 * @param home       memory home
 * @param dst        destination header
 * @param src        source header
 * @param duplicate  if true, allocate and copy items that are added
1524 1525 1526 1527
 *
 * @return
 * @retval >= 0 when successful
 * @retval -1 upon an error
1528 1529
 *
 * @NEW_1_12_5.
1530 1531
 */
int msg_header_join_items(su_home_t *home,
1532 1533 1534
			  msg_common_t *dst,
			  msg_common_t const *src,
			  int duplicate)
1535
{
1536 1537 1538 1539 1540
  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])];
1541 1542
  msg_update_f *update = NULL;

1543 1544
  if (dst == NULL || dst->h_class->hc_params == 0 ||
      src == NULL || src->h_class->hc_params == 0)
1545 1546
    return -1;

1547 1548 1549 1550 1551
  s = *(msg_param_t **)((char *)src + src->h_class->hc_params);
  if (s == NULL)
    return 0;

  for (M = 0; s[M]; M++);
1552

1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563
  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);
  }
1564

1565
  dd = (msg_param_t **)((char *)dst + dst->h_class->hc_params);
1566 1567
  d = *dd;

1568
  for (N = 0; d && d[N]; N++);
1569

1570 1571 1572 1573
  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)
1574
	break;
1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586
    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;
1587 1588
  }

1589 1590
  if (M == 0)
    goto success;
1591

1592
  dup = su_alloc(home, total); if (!dup) goto error;
1593

1594 1595
  n_before = MSG_PARAMS_NUM(N + 1);
  n_after = MSG_PARAMS_NUM(N + M + 1);
1596

1597 1598 1599 1600
  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));
1601 1602 1603
    *dd = d;
  }

1604
  update = dst->h_class->hc_update;
1605

1606 1607
  for (m = 0; m < M; m++) {
    d[N++] = memcpy(dup, temp[m], len[m] + 1);