sip_tag_class.c 12.4 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
 * 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
 *
 */

/**@SIP_TAG
26
 *
Pekka Pessi's avatar
Pekka Pessi committed
27 28 29 30 31 32 33 34 35
 * @CFILE sip_tag_class.c  SIP Tag classes
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>.
 *
 * @date Created: Fri Feb 23 12:46:42 2001 ppessi
 */

#include "config.h"

36
#include "sofia-sip/sip_parser.h"
Pekka Pessi's avatar
Pekka Pessi committed
37

38 39 40 41 42
#include <sofia-sip/su_tag_class.h>
#include <sofia-sip/su_tag_inline.h>
#include <sofia-sip/sip_tag_class.h>
#include <sofia-sip/sip_tag.h>
#include <sofia-sip/su_tagarg.h>
43 44
#include <sofia-sip/su_strlst.h>

45
#include <stdio.h>
46 47 48 49
#include <ctype.h>
#include <assert.h>
#include <stddef.h>
#include <string.h>
Pekka Pessi's avatar
Pekka Pessi committed
50
#include <limits.h>
Pekka Pessi's avatar
Pekka Pessi committed
51

52 53 54 55 56
/** Tag class for tags containing SIP headers. @HIDE
 *
 * Tags in this class are not automatically added to the message with
 * sip_add_tl() or sip_add_tagis().
 */
57
tag_class_t sipexthdrtag_class[1] =
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
  {{
    sizeof(siphdrtag_class),
    /* tc_next */     NULL,
    /* tc_len */      NULL,
    /* tc_move */     NULL,
    /* tc_xtra */     msghdrtag_xtra,
    /* tc_dup */      msghdrtag_dup,
    /* tc_free */     NULL,
    /* tc_find */     NULL,
    /* tc_snprintf */ msghdrtag_snprintf,
    /* tc_filter */   siptag_filter,
    /* tc_ref_set */  t_ptr_ref_set,
    /* tc_scan */     msghdrtag_scan,
  }};


Pekka Pessi's avatar
Pekka Pessi committed
74
/** Tag class for SIP header tags. @HIDE */
75
tag_class_t siphdrtag_class[1] =
Pekka Pessi's avatar
Pekka Pessi committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
  {{
    sizeof(siphdrtag_class),
    /* tc_next */     NULL,
    /* tc_len */      NULL,
    /* tc_move */     NULL,
    /* tc_xtra */     msghdrtag_xtra,
    /* tc_dup */      msghdrtag_dup,
    /* tc_free */     NULL,
    /* tc_find */     NULL,
    /* tc_snprintf */ msghdrtag_snprintf,
    /* tc_filter */   siptag_filter,
    /* tc_ref_set */  t_ptr_ref_set,
    /* tc_scan */     msghdrtag_scan,
  }};

/** Tag class for SIP header string tags. @HIDE */
92
tag_class_t sipstrtag_class[1] =
Pekka Pessi's avatar
Pekka Pessi committed
93 94 95 96 97 98 99 100 101 102 103 104
  {{
    sizeof(sipstrtag_class),
    /* tc_next */     NULL,
    /* tc_len */      NULL,
    /* tc_move */     NULL,
    /* tc_xtra */     t_str_xtra,
    /* tc_dup */      t_str_dup,
    /* tc_free */     NULL,
    /* tc_find */     NULL,
    /* tc_snprintf */ t_str_snprintf,
    /* tc_filter */   NULL /* msgtag_str_filter */,
    /* tc_ref_set */  t_ptr_ref_set,
105
    /* tc_scan */     t_str_scan
Pekka Pessi's avatar
Pekka Pessi committed
106 107 108
  }};

/** Tag class for SIP message tags. @HIDE */
109
tag_class_t sipmsgtag_class[1] =
Pekka Pessi's avatar
Pekka Pessi committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123
  {{
    sizeof(sipmsgtag_class),
    /* tc_next */     NULL,
    /* tc_len */      NULL,
    /* tc_move */     NULL,
    /* tc_xtra */     msgobjtag_xtra,
    /* tc_dup */      msgobjtag_dup,
    /* tc_free */     NULL,
    /* tc_find */     NULL,
    /* tc_snprintf */ msgobjtag_snprintf,
    /* tc_filter */   NULL /* siptag_sip_filter */,
    /* tc_ref_set */  t_ptr_ref_set,
  }};

124 125 126 127

/** Filter a for SIP header tag.
 *
 * @param[in] dst tag list for filtering result. May be NULL.
128 129 130
 * @param[in] f   filter tag
 * @param[in] src tag item from source list.
 * @param[in,out] bb pointer to pointer of mempory area used to dup
131 132 133 134
 *                   the filtering result
 *
 * This function is also used to calculate size for filtering result.
 */
Pekka Pessi's avatar
Pekka Pessi committed
135 136
tagi_t *siptag_filter(tagi_t *dst,
		      tagi_t const f[],
137
		      tagi_t const *src,
Pekka Pessi's avatar
Pekka Pessi committed
138 139 140
		      void **bb)
{
  tagi_t stub[2] = {{ NULL }};
141
  tag_type_t srctt, tt = f->t_tag;
Pekka Pessi's avatar
Pekka Pessi committed
142 143 144 145
  msg_hclass_t *hc = (msg_hclass_t *)tt->tt_magic;

  assert(src);

146
  srctt = src->t_tag;
Pekka Pessi's avatar
Pekka Pessi committed
147

148 149
  /* Match filtered header with a header from a SIP message */
  if (srctt && srctt->tt_class == sipmsgtag_class) {
Pekka Pessi's avatar
Pekka Pessi committed
150
    sip_t const *sip = (sip_t const *)src->t_value;
Pekka Pessi's avatar
Pekka Pessi committed
151 152 153 154 155 156
    sip_header_t const **hh, *h;

    if (sip == NULL)
      return dst;

    hh = (sip_header_t const **)
157
      msg_hclass_offset((msg_mclass_t *)sip->sip_common->h_class,
Pekka Pessi's avatar
Pekka Pessi committed
158
			(msg_pub_t *)sip, hc);
Pekka Pessi's avatar
Pekka Pessi committed
159

160
    /* Is header present in the SIP message? */
161 162
    if (hh == NULL ||
	(char *)hh >= ((char *)sip + sip->sip_size) ||
163
	(char *)hh < (char *)&sip->sip_request)
Pekka Pessi's avatar
Pekka Pessi committed
164 165 166 167 168 169 170 171
      return dst;

    h = *hh;

    if (h == NULL)
      return dst;

    stub[0].t_tag = tt;
172
    stub[0].t_value = (tag_value_t)h;
173
    src = stub; srctt = tt;
Pekka Pessi's avatar
Pekka Pessi committed
174 175
  }

176
  if (tt != srctt)
Pekka Pessi's avatar
Pekka Pessi committed
177 178 179 180 181 182 183 184 185 186 187 188 189
    return dst;

  if (!src->t_value)
    return dst;
  else if (dst) {
    return t_dup(dst, src, bb);
  }
  else {
    *bb = (char *)*bb + t_xtra(src, (size_t)*bb);
    return dst + 1;
  }
}

190
/** Duplicate headers from taglist and add them to the SIP message. */
191
int sip_add_tl(msg_t *msg, sip_t *sip,
192
	       tag_type_t tag, tag_value_t value, ...)
193 194 195 196
{
  tagi_t const *t;
  ta_list ta;
  int retval;
197

198
  ta_start(ta, tag, value);
199

200
  t = ta_args(ta);
201

202
  retval = sip_add_tagis(msg, sip, &t);
203

204 205 206
  ta_end(ta);
  return retval;
}
207

208 209 210 211 212 213
/** Add duplicates of headers from taglist to the SIP message. */
int sip_add_tagis(msg_t *msg, sip_t *sip, tagi_t const **inout_list)
{
  tagi_t const *t;
  tag_type_t tag;
  tag_value_t value;
Pekka Pessi's avatar
Pekka Pessi committed
214

215 216
  if (!msg || !inout_list)
    return -1;
Pekka Pessi's avatar
Pekka Pessi committed
217

218 219 220
  if (sip == NULL)
    sip = sip_object(msg);

221
  for (t = *inout_list; t; t = t_next(t)) {
222
    tag = t->t_tag, value = t->t_value;
223

224
    if (tag == NULL || tag == siptag_end) {
225
      t = t_next(t);
226 227 228 229
      break;
    }

    if (!value)
Pekka Pessi's avatar
Pekka Pessi committed
230 231 232 233
      continue;

    if (SIPTAG_P(tag)) {
      msg_hclass_t *hc = (msg_hclass_t *)tag->tt_magic;
Pekka Pessi's avatar
Pekka Pessi committed
234
      msg_header_t *h = (msg_header_t *)value, **hh;
Pekka Pessi's avatar
Pekka Pessi committed
235 236

      if (h == SIP_NONE) {	/* Remove header */
Pekka Pessi's avatar
Pekka Pessi committed
237
	hh = msg_hclass_offset(msg_mclass(msg), (msg_pub_t *)sip, hc);
238 239 240 241 242 243
	if (hh != NULL &&
	    (char *)hh < ((char *)sip + sip->sip_size) &&
	    (char *)hh >= (char *)&sip->sip_request) {
	  while (*hh)
	    msg_header_remove(msg, (msg_pub_t *)sip, *hh);
	}
Pekka Pessi's avatar
Pekka Pessi committed
244
	continue;
245
      }
Pekka Pessi's avatar
Pekka Pessi committed
246 247 248 249

      if (tag == siptag_header)
	hc = h->sh_class;

Pekka Pessi's avatar
Pekka Pessi committed
250
      if (msg_header_add_dup_as(msg, (msg_pub_t *)sip, hc, h) < 0)
Pekka Pessi's avatar
Pekka Pessi committed
251 252 253 254 255
	break;
    }
    else if (SIPTAG_STR_P(tag)) {
      msg_hclass_t *hc = (msg_hclass_t *)tag->tt_magic;
      char const *s = (char const *)value;
Pekka Pessi's avatar
Pekka Pessi committed
256
      if (s && msg_header_add_make(msg, (msg_pub_t *)sip, hc, s) < 0)
257
	return -1;
Pekka Pessi's avatar
Pekka Pessi committed
258 259
    }
    else if (tag == siptag_header_str) {
Pekka Pessi's avatar
Pekka Pessi committed
260
      if (msg_header_add_str(msg, (msg_pub_t *)sip, (char const *)value) < 0)
261
	return -1;
Pekka Pessi's avatar
Pekka Pessi committed
262 263 264
    }
  }

265
  *inout_list = t;
Pekka Pessi's avatar
Pekka Pessi committed
266

267
  return 0;
Pekka Pessi's avatar
Pekka Pessi committed
268
}
269 270

static char const *append_escaped(su_strlst_t *l,
271
				  msg_hclass_t *hc,
272 273
				  char const *s);

274 275 276 277 278 279 280 281 282 283 284 285 286 287
/** Convert tagged SIP headers to a URL-encoded headers list.
 *
 * The SIP URI can contain a query part separated with the "?", which
 * specifies SIP headers that are included in the request constructed
 * from the URI. For example, using URI @code <sip:example.com?subject=test>
 * would include @Subject header with value "test" in the request.
 *
 * @param home memory home used to allocate query string (if NULL, use malloc)
 * @param tag, value, ... list of tagged arguments
 *
 * @bug This function returns NULL if SIPTAG_REQUEST(), SIPTAG_STATUS(),
 * SIPTAG_HEADER(), SIPTAG_UNKNOWN(), SIPTAG_ERROR(), SIPTAG_SEPARATOR(), or
 * any corresponding string tag is included in the tag list. It ignores
 * SIPTAG_SIP().
288 289 290
 *
 * @par Example
 * @code
291
 * url->url_headers =
292 293
 *   sip_headers_as_url_query(home, SIPTAG_REPLACES(replaces), TAG_END());
 * @endcode
294
 *
295 296
 * @since New in @VERSION_1_12_4.
 *
297
 * @sa
298 299 300
 * url_query_as_header_string(), sip_url_query_as_taglist(),
 * nta_msg_request_complete(),
 * @RFC3261 section 19.1.1 "Headers", #url_t, url_s#url_headers
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
 */
char *sip_headers_as_url_query(su_home_t *home,
			       tag_type_t tag, tag_value_t value,
			       ...)
{
  ta_list ta;
  tagi_t const *t;
  su_strlst_t *l = su_strlst_create(home);
  su_home_t *lhome = su_strlst_home(l);
  char const *retval = "";

  if (!l)
    return NULL;

  ta_start(ta, tag, value);

  for (t = ta_args(ta); t && retval; t = t_next(t)) {
318
    msg_hclass_t *hc;
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

    if (t->t_value == 0 || t->t_value == -1)
      continue;

    hc = (msg_hclass_t *)t->t_tag->tt_magic;

    if (SIPTAG_P(t->t_tag)) {
      sip_header_t const *h = (sip_header_t const *)t->t_value;
      char *s = sip_header_as_string(lhome, h);

      retval = append_escaped(l, hc, s);

      if (retval != s)
	su_free(lhome, s);
    }
    else if (SIPTAG_STR_P(t->t_tag)) {
      retval = append_escaped(l, hc, (char const *)t->t_value);
    }
  }

  ta_end(ta);

  if (retval)
    retval = su_strlst_join(l, home, "");

  su_strlst_destroy(l);

  return (char *)retval;
}

349
/* "[" / "]" / "/" / "?" / ":" / "+" / "$" */
350 351
#define HNV_UNRESERVED "[]/?+$"
#define HNV_RESERVED ":=,;"
352 353

/* Append a string to list and url-escape it if needed */
354 355
static
char const *append_escaped(su_strlst_t *l,
356
			   msg_hclass_t *hc,
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
			   char const *s)
{
  char const *name;

  if (hc == NULL)
    return NULL;

  if (hc->hc_hash == sip_payload_hash)
    name = "body";
  else				/* XXX - could we use short form? */
    name = hc->hc_name;

  if (name == NULL)
    return NULL;

  if (s) {
    su_home_t *lhome = su_strlst_home(l);
    size_t slen;
    isize_t elen;
    char *n, *escaped;
    char *sep = su_strlst_len(l) > 0 ? "&" : "";

    n = su_sprintf(lhome, "%s%s=", sep, name);
    if (!su_strlst_append(l, n))
      return NULL;

    for (;*n; n++)
      if (isupper(*n))
	*n = tolower(*n);
386

387
    slen = strlen(s); elen = url_esclen(s, HNV_RESERVED);
388 389 390

    if ((size_t)elen == slen)
      return su_strlst_append(l, s);
391

392 393
    escaped = su_alloc(lhome, elen + 1);
    if (escaped)
394
      return su_strlst_append(l, url_escape(escaped, s, HNV_RESERVED));
395 396 397 398
  }

  return NULL;
}
399 400 401 402 403 404 405

/** Convert URL query to a tag list.
 *
 * SIP headers encoded as URL @a query is parsed returned as a tag list.
 * Unknown headers are encoded as SIPTAG_HEADER_STR().
 *
 * @param home memory home used to alloate string (if NULL, malloc() it)
406
 * @param query query part from SIP URL
407
 * @param parser optional SIP parser used
408
 *
409 410 411 412
 * @sa sip_add_tl(), sip_add_tagis(), SIPTAG_HEADER_STR(),
 * sip_headers_as_url_query(), url_query_as_header_string(),
 * @RFC3261 section 19.1.1 "Headers", #url_t, url_s#url_headers
 *
413 414
 * @NEW_1_12_4.
 *
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
 * @bug Extension headers are ignored. The @a parser parameter is not used
 * at the moment.
 */
tagi_t *sip_url_query_as_taglist(su_home_t *home, char const *query,
				 msg_mclass_t const *parser)
{
  tagi_t *retval = NULL;
  char *s;
  su_strlst_t *l;
  isize_t N;
  size_t i, j, n;

  if (query == NULL || query[0] == '\0' || query[0] == '&')
    return NULL;

  s = su_strdup(home, query); if (!s) return NULL;
  l = su_strlst_split(home, s, "&");
  N = su_strlst_len(l);

  if (N == 0)
    goto error;

  retval = su_zalloc(home, (N + 1) * sizeof (*retval));
  if (retval == NULL)
    goto error;

  for (i = 0; i < N; i++) {
    char const *hnv;
    char *value;
    tag_type_t t;
    tag_value_t v;
446
    msg_hclass_t *hc = NULL;
447 448

    hnv = su_strlst_item(l, i);
449
    n = hnv ? strcspn(hnv, "=") : 0;
450 451 452 453 454
    if (n == 0)
      break;

    if (n == 4 && strncasecmp(hnv, "body", 4) == 0)
      t = siptag_payload, hc = sip_payload_class;
455 456 457 458 459 460 461 462 463
    else {
      for (j = 0; (t = sip_tag_list[j]); j++) {
	hc = (msg_hclass_t *)sip_tag_list[j]->tt_magic;
	if (n == 1 && strncasecmp(hnv, hc->hc_short, 1) == 0)
	  break;
	else if (n == (size_t)hc->hc_len &&
		 strncasecmp(hnv, hc->hc_name, n) == 0)
	  break;
      }
464 465
    }

466 467
    value = (char *)hnv + n;
    *value++ = ':';
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
    n = url_unescape_to(value, value, SIZE_MAX);
    value[n] = '\0';

    if (t) {
      msg_header_t *h = msg_header_make(home, hc, value);
      if (!h)
	break;
      v = (tag_value_t)h;
    }
    else {
      char *s;
      s = su_alloc(home, n + 1);
      if (!s)
	break;
      memcpy(s, value, n + 1);
      t = siptag_header_str;
      v = (tag_value_t)s;
    }
    retval[i].t_tag = t, retval[i].t_value = v;
  }

  retval[i].t_tag = NULL, retval[i].t_value = (tag_value_t)0;

  if (i < N) {
    for (j = 0; j < i; j++) {
      if (retval[i].t_tag == siptag_header_str)
	su_free(home, (void *)retval[i].t_value);
      else
	msg_header_free_all(home, (msg_header_t *)retval[i].t_value);
    }
    su_free(home, retval);
    retval = NULL;
  }

 error:
  su_free(home, s);
  su_strlst_destroy(l);

  return retval;
}