sip_tag_class.c 11.6 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
 * 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
 * 
 * @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 45 46 47 48
#include <sofia-sip/su_strlst.h>

#include <ctype.h>
#include <assert.h>
#include <stddef.h>
#include <string.h>
Pekka Pessi's avatar
Pekka Pessi committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

/** Tag class for SIP header tags. @HIDE */
tag_class_t siphdrtag_class[1] = 
  {{
    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 */
tag_class_t sipstrtag_class[1] = 
  {{
    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,
81
    /* tc_scan */     t_str_scan
Pekka Pessi's avatar
Pekka Pessi committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  }};

/** Tag class for SIP message tags. @HIDE */
tag_class_t sipmsgtag_class[1] = 
  {{
    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,
  }};

100 101 102 103 104 105 106 107 108 109 110

/** Filter a for SIP header tag.
 *
 * @param[in] dst tag list for filtering result. May be NULL.
 * @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 
 *                   the filtering result
 *
 * This function is also used to calculate size for filtering result.
 */
Pekka Pessi's avatar
Pekka Pessi committed
111 112 113 114 115 116
tagi_t *siptag_filter(tagi_t *dst,
		      tagi_t const f[],
		      tagi_t const *src, 
		      void **bb)
{
  tagi_t stub[2] = {{ NULL }};
117
  tag_type_t srctt, tt = f->t_tag;
Pekka Pessi's avatar
Pekka Pessi committed
118 119 120 121
  msg_hclass_t *hc = (msg_hclass_t *)tt->tt_magic;

  assert(src);

122
  srctt = src->t_tag;
Pekka Pessi's avatar
Pekka Pessi committed
123

124 125
  /* Match filtered header with a header from a SIP message */
  if (srctt && srctt->tt_class == sipmsgtag_class) {
Pekka Pessi's avatar
Pekka Pessi committed
126
    sip_t const *sip = (sip_t const *)src->t_value;
Pekka Pessi's avatar
Pekka Pessi committed
127 128 129 130 131 132
    sip_header_t const **hh, *h;

    if (sip == NULL)
      return dst;

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

136
    /* Is header present in the SIP message? */
137 138
    if ((char *)hh >= ((char *)sip + sip->sip_size) ||
	(char *)hh < (char *)&sip->sip_request)
Pekka Pessi's avatar
Pekka Pessi committed
139 140 141 142 143 144 145 146
      return dst;

    h = *hh;

    if (h == NULL)
      return dst;

    stub[0].t_tag = tt;
147
    stub[0].t_value = (tag_value_t)h;
148
    src = stub; srctt = tt;
Pekka Pessi's avatar
Pekka Pessi committed
149 150
  }

151
  if (tt != srctt)
Pekka Pessi's avatar
Pekka Pessi committed
152 153 154 155 156 157 158 159 160 161 162 163 164
    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;
  }
}

165
/** Duplicate headers from taglist and add them to the SIP message. */
166
int sip_add_tl(msg_t *msg, sip_t *sip,
167
	       tag_type_t tag, tag_value_t value, ...)
168 169 170 171
{
  tagi_t const *t;
  ta_list ta;
  int retval;
172

173
  ta_start(ta, tag, value);
174

175
  t = ta_args(ta);
176

177
  retval = sip_add_tagis(msg, sip, &t);
178

179 180 181
  ta_end(ta);
  return retval;
}
182

183 184 185 186 187 188
/** 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
189

190 191
  if (!msg || !inout_list)
    return -1;
Pekka Pessi's avatar
Pekka Pessi committed
192

193
  for (t = *inout_list; t; t = t_next(t)) {
194
    tag = t->t_tag, value = t->t_value;
195

196
    if (tag == NULL || tag == siptag_end) {
197
      t = t_next(t);
198 199 200 201
      break;
    }

    if (!value)
Pekka Pessi's avatar
Pekka Pessi committed
202 203 204 205
      continue;

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

      if (h == SIP_NONE) {	/* Remove header */
Pekka Pessi's avatar
Pekka Pessi committed
209
	hh = msg_hclass_offset(msg_mclass(msg), (msg_pub_t *)sip, hc);
Pekka Pessi's avatar
Pekka Pessi committed
210
	while (*hh)
Pekka Pessi's avatar
Pekka Pessi committed
211
	  msg_header_remove(msg, (msg_pub_t *)sip, *hh);
Pekka Pessi's avatar
Pekka Pessi committed
212 213 214 215 216 217
	continue;
      } 

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

Pekka Pessi's avatar
Pekka Pessi committed
218
      if (msg_header_add_dup_as(msg, (msg_pub_t *)sip, hc, h) < 0)
Pekka Pessi's avatar
Pekka Pessi committed
219 220 221 222 223
	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
224
      if (s && msg_header_add_make(msg, (msg_pub_t *)sip, hc, s) < 0)
225
	return -1;
Pekka Pessi's avatar
Pekka Pessi committed
226 227
    }
    else if (tag == siptag_header_str) {
Pekka Pessi's avatar
Pekka Pessi committed
228
      if (msg_header_add_str(msg, (msg_pub_t *)sip, (char const *)value) < 0)
229
	return -1;
Pekka Pessi's avatar
Pekka Pessi committed
230 231 232
    }
  }

233
  *inout_list = t;
Pekka Pessi's avatar
Pekka Pessi committed
234

235
  return 0;
Pekka Pessi's avatar
Pekka Pessi committed
236
}
237 238

static char const *append_escaped(su_strlst_t *l,
239
				  msg_hclass_t *hc,
240 241
				  char const *s);

242 243 244 245 246 247 248 249 250 251 252 253 254 255
/** 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().
256 257 258 259 260 261 262
 *
 * @par Example
 * @code
 * url->url_headers = 
 *   sip_headers_as_url_query(home, SIPTAG_REPLACES(replaces), TAG_END());
 * @endcode
 * 
263 264 265 266 267 268
 * @since New in @VERSION_1_12_4.
 *
 * @sa 
 * 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
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
 */
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)) {
286
    msg_hclass_t *hc;
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316

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

317 318 319 320 321
/* "[" / "]" / "/" / "?" / ":" / "+" / "$" */
#define HNV_UNRESERVED "[]/?;+$"
#define HNV_RESERVED ":=,"

/* Append a string to list and url-escape it if needed */
322 323
static
char const *append_escaped(su_strlst_t *l,
324
			   msg_hclass_t *hc,
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
			   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);
    
355
    slen = strlen(s); elen = url_esclen(s, HNV_RESERVED);
356 357 358 359 360 361

    if ((size_t)elen == slen)
      return su_strlst_append(l, s);
    
    escaped = su_alloc(lhome, elen + 1);
    if (escaped)
362
      return su_strlst_append(l, url_escape(escaped, s, HNV_RESERVED));
363 364 365 366
  }

  return NULL;
}
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

/** 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)
 * @param query query part from SIP URL 
 * @param parser optional SIP parser used
 * 
 * @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
 *
 * @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;
412
    msg_hclass_t *hc = NULL;
413 414 415 416 417 418 419 420 421 422 423 424

    hnv = su_strlst_item(l, i);
    n = strcspn(hnv, "=");
    if (n == 0)
      break;

    if (n == 4 && strncasecmp(hnv, "body", 4) == 0)
      t = siptag_payload, hc = sip_payload_class;
    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;
Michael Jerris's avatar
Michael Jerris committed
425
      else if (n == (size_t)hc->hc_len && strncasecmp(hnv, hc->hc_name, n) == 0)
426 427 428
	break;
    }

429 430
    value = (char *)hnv + n;
    *value++ = ':';
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    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;
}