msg_mclass.c 10.2 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
 * 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
 *
 */

25
/**@ingroup msg_parser
Pekka Pessi's avatar
Pekka Pessi committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
 * @CFILE msg_mclass.c
 *
 * Message factory object.
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Jun  5 14:34:24 2002 ppessi
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <limits.h>
Pekka Pessi's avatar
Pekka Pessi committed
43
#include <errno.h>
Pekka Pessi's avatar
Pekka Pessi committed
44 45

#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 54
#include "sofia-sip/msg_parser.h"
#include "sofia-sip/msg_mclass.h"
#include "sofia-sip/msg_mclass_hash.h"
Pekka Pessi's avatar
Pekka Pessi committed
55 56 57

/** Clone a message class.
 *
58
 * @relatesalso msg_mclass_s
Pekka Pessi's avatar
Pekka Pessi committed
59 60 61 62 63 64 65
 *
 * The function msg_mclass_clone() makes a copy of message class object @a
 * old. It is possible to resize the hash table by giving a non-zero @a
 * newsize. If @a newsize is 0, the size of hash table is not changed. If @a
 * empty is true, the copied message class object will not recognize any
 * headers. This is useful if more fine-grained control of parsing process
 * is required, for instance.
66 67 68 69 70 71
 *
 * @param[in] old      pointer to the message class object to be copied
 * @param[in] newsize  size of hash table in the copied object
 * @param[in] empty    if true, resulting copy does not contain any headers
 *
 * @return
Pekka Pessi's avatar
Pekka Pessi committed
72 73
 * The function msg_mclass_clone() returns a pointer to a newly
 * copied message class object, or NULL upon an error.
74
 * The returned message class object can be freed with free().
Pekka Pessi's avatar
Pekka Pessi committed
75 76 77 78 79 80
 *
 * @ERRORS
 * @ERROR ENOMEM
 * A memory allocation failed.
 * @ERROR EINVAL
 * The function was given invalid arguments.
81
 *
Pekka Pessi's avatar
Pekka Pessi committed
82 83 84 85 86 87 88 89 90 91
 * @note The empty parser can handle request/status line. All headers are
 * put into list of unknown headers (unless they are malformed, and they are
 * put into list of erronous headers). However, SIP, RTSP, and HTTP
 * protocols all require that the parser recognizes @b Content-Length header
 * before they can extract the message body from the data received from
 * network.
 *
 */
msg_mclass_t *msg_mclass_clone(msg_mclass_t const *old, int newsize, int empty)
{
92
  size_t size, shortsize;
93
  msg_mclass_t *mc;
Pekka Pessi's avatar
Pekka Pessi committed
94 95 96 97 98 99
  int identical;
  unsigned short i;

  if (newsize == 0)
    newsize = old->mc_hash_size;

100
  if (newsize < old->mc_hash_used ||
101
      (unsigned)newsize > USHRT_MAX / sizeof(msg_header_t *)) {
Pekka Pessi's avatar
Pekka Pessi committed
102 103 104 105 106
    errno = EINVAL;
    return NULL;
  }

  size = offsetof(msg_mclass_t, mc_hash[newsize]);
107 108 109 110 111
  if (old->mc_short)
    shortsize = MC_SHORT_SIZE * (sizeof old->mc_short[0]);
  else
    shortsize = 0;
  mc = malloc(size + shortsize);
Pekka Pessi's avatar
Pekka Pessi committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
  identical = newsize == old->mc_hash_size && !empty;

  if (mc) {
    if (!identical) {
      memcpy(mc, old, offsetof(msg_mclass_t, mc_hash));
      memset(mc->mc_hash, 0, size - offsetof(msg_mclass_t, mc_hash));
      mc->mc_short = NULL;
      mc->mc_hash_size = newsize;
      mc->mc_hash_used = 0;
      for (i = 0; !empty && i < old->mc_hash_size; i++) {
	msg_mclass_insert(mc, &old->mc_hash[i]);
      }
    }
    else {
      memcpy(mc, old, size);
      mc->mc_short = NULL;
    }
129

130 131 132
    if (shortsize) {
      if (empty)
	mc->mc_short = memset((char *)mc + size, 0, shortsize);
Pekka Pessi's avatar
Pekka Pessi committed
133
      else
134
	mc->mc_short = memcpy((char *)mc + size, old->mc_short, shortsize);
Pekka Pessi's avatar
Pekka Pessi committed
135 136 137 138 139 140 141 142
    }
  }

  return mc;
}

/**Add a new header to the message class.
 *
143
 * @relatesalso msg_mclass_s
Pekka Pessi's avatar
Pekka Pessi committed
144 145 146 147 148 149
 *
 * Insert a header class @a hc to the message class object @a mc. If the
 * given @a offset of the header in @ref msg_pub_t "public message
 * structure" is zero, the function extends the public message structure in
 * order to store newly inserted header there.
 *
150 151
 * @param[in,out] mc       pointer to a message class object
 * @param[in]     hc       pointer to a header class object
152 153
 * @param[in]     offset   offset of the header in
 *                         @ref msg_pub_t "public message structure"
Pekka Pessi's avatar
Pekka Pessi committed
154 155 156 157 158
 *
 * If the @a offset is 0, the msg_mclass_insert_header() increases size of
 * the public message structure and places the header at the end of message.
 *
 * @return Number of collisions in hash table, or -1 upon an error.
159
 *
160
 * @deprecated Use msg_mclass_insert_with_mask() instead.
Pekka Pessi's avatar
Pekka Pessi committed
161
 */
162
int msg_mclass_insert_header(msg_mclass_t *mc,
Pekka Pessi's avatar
Pekka Pessi committed
163 164 165 166 167
			     msg_hclass_t *hc,
			     unsigned short offset)
{
  msg_href_t hr[1];

168
  if (mc == NULL || hc == NULL) {
Pekka Pessi's avatar
Pekka Pessi committed
169 170 171 172
    errno = EINVAL;
    return -1;
  }

173
  if (msg_hclass_offset(mc, NULL, hc))
174
    return (void)(errno = EEXIST), -1;
175

Pekka Pessi's avatar
Pekka Pessi committed
176 177 178 179 180 181 182 183 184 185 186 187 188
  if (offset == 0)
    offset = mc->mc_msize, mc->mc_msize += sizeof(msg_header_t *);

  assert(offset < mc->mc_msize);

  hr->hr_class = hc;
  hr->hr_offset = offset;

  return msg_mclass_insert(mc, hr);
}

/**Add a new header to the message class.
 *
189
 * @relatesalso msg_mclass_s
Pekka Pessi's avatar
Pekka Pessi committed
190
 *
191 192 193 194
 * Insert a header class @a hc to the message class @a mc. If the given @a
 * offset of the header in @ref msg_pub_t "public message structure" is
 * zero, extend the size of the public message structure in order to store
 * headers at the end of structure.
Pekka Pessi's avatar
Pekka Pessi committed
195
 *
196 197
 * @param[in,out] mc       pointer to a message class
 * @param[in]     hc       pointer to a header class
198 199 200
 * @param[in]     offset   offset of the header in
 *                         @ref msg_pub_t "public message structure"
 * @param[in]     flags    classification flags for the header
Pekka Pessi's avatar
Pekka Pessi committed
201 202 203
 *
 * @return Number of collisions in hash table, or -1 upon an error.
 */
204
int msg_mclass_insert_with_mask(msg_mclass_t *mc,
Pekka Pessi's avatar
Pekka Pessi committed
205 206 207 208 209 210
				msg_hclass_t *hc,
				unsigned short offset,
				unsigned short flags)
{
  msg_href_t hr[1];

211
  if (mc == NULL || hc == NULL) {
Pekka Pessi's avatar
Pekka Pessi committed
212 213 214 215
    errno = EINVAL;
    return -1;
  }

216
  if (msg_hclass_offset(mc, NULL, hc))
217
    return (void)(errno = EEXIST), -1;
218

Pekka Pessi's avatar
Pekka Pessi committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232
  if (offset == 0)
    offset = mc->mc_msize, mc->mc_msize += sizeof(msg_header_t *);

  assert(offset < mc->mc_msize);

  hr->hr_class = hc;
  hr->hr_offset = offset;
  hr->hr_flags = flags;

  return msg_mclass_insert(mc, hr);
}

/** Add a header reference to the message class.
 *
233
 * @relatesalso msg_mclass_s
Pekka Pessi's avatar
Pekka Pessi committed
234
 *
235 236
 * @param[in,out] mc       pointer to a message class object
 * @param[in]     hr       header reference object
Pekka Pessi's avatar
Pekka Pessi committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
 *
 * @return Number of collisions in hash table, or -1 upon an error.
 */
int msg_mclass_insert(msg_mclass_t *mc, msg_href_t const *hr)
{
  int j, j0;
  int N;
  int collisions = 0;
  msg_hclass_t *hc;

  if (mc == NULL) {
    errno = EINVAL;
    return -1;
  }

252
  if (hr == NULL || (hc = hr->hr_class) == NULL)
Pekka Pessi's avatar
Pekka Pessi committed
253 254 255 256 257 258 259 260 261 262
    return 0;

  /* Add short form */
  if (mc->mc_short && hc->hc_short[0]) {
    char compact = hc->hc_short[0];
    msg_href_t *shorts = (msg_href_t *)mc->mc_short;

    if (compact < 'a' || compact > 'z')
      return -1;

263
    if (shorts[compact - 'a'].hr_class &&
Pekka Pessi's avatar
Pekka Pessi committed
264 265
	shorts[compact - 'a'].hr_class != hc)
      return -1;
266

Pekka Pessi's avatar
Pekka Pessi committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
    shorts[compact - 'a'] = *hr;
  }

  N = mc->mc_hash_size;
  j0 = msg_header_name_hash(hc->hc_name, NULL) % N;

  for (j = j0; mc->mc_hash[j].hr_class; ) {
    collisions++;
    if (mc->mc_hash[j].hr_class == hc)
      return -1;
    j = (j + 1) % N;
    if (j == j0)
      return -1;
  }

  mc->mc_hash[j] = hr[0];
  mc->mc_hash_used++;
284

Pekka Pessi's avatar
Pekka Pessi committed
285 286 287
  return collisions;
}

288
/** Calculate length of line ending (0, 1 or 2). @internal */
Pekka Pessi's avatar
Pekka Pessi committed
289 290 291 292
#define CRLF_TEST(cr, lf) ((cr) == '\r' ? ((lf) == '\n') + 1 : (cr)=='\n')

/**Search for a header class.
 *
293
 * @relatesalso msg_mclass_s
Pekka Pessi's avatar
Pekka Pessi committed
294 295 296 297 298
 *
 * The function msg_find_hclass() searches for a header class from a message
 * class based on the contents of the header to be parsed. The buffer @a s
 * should point to the first character in the header name.
 *
299 300
 * @param[in]  mc   message class object
 * @param[in]  s    header contents
301
 * @param[out] return_start_of_content start of header content (may be NULL)
Pekka Pessi's avatar
Pekka Pessi committed
302 303 304 305 306 307 308 309 310 311
 *
 * @return The function msg_find_hclass() returns a pointer to a header
 * reference structure. A pointer to a header reference for unknown headers
 * is returned, if the header is not included in the message class.
 *
 * @par
 * The return-value parameter @a return_start_of_content will contain the
 * start of the header contents within @a s, or 0 upon an error parsing the
 * header name and following colon.
 *
312
 * @par
Pekka Pessi's avatar
Pekka Pessi committed
313 314
 * Upon a fatal error, a NULL pointer is returned.
 */
315 316
msg_href_t const *msg_find_hclass(msg_mclass_t const *mc,
				  char const *s,
317
				  isize_t *return_start_of_content)
Pekka Pessi's avatar
Pekka Pessi committed
318 319
{
  msg_href_t const *hr;
320 321
  short i, N, m;
  isize_t len;
Pekka Pessi's avatar
Pekka Pessi committed
322 323 324 325 326

  assert(mc);

  N = mc->mc_hash_size;

327
  i = msg_header_name_hash(s, &len) % N;
Pekka Pessi's avatar
Pekka Pessi committed
328

329
  if (len == 0 || len > HC_LEN_MAX) {
Pekka Pessi's avatar
Pekka Pessi committed
330 331 332 333 334
    if (return_start_of_content)
      *return_start_of_content = 0;
    return mc->mc_error;
  }

335 336
  m = (short)len;

Pekka Pessi's avatar
Pekka Pessi committed
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  if (m == 1 && mc->mc_short) {
    short c = s[0];
    if (c >= 'a' && c <= 'z')
      hr = &mc->mc_short[c - 'a'];
    else if (c >= 'A' && c <= 'Z')
      hr = &mc->mc_short[c - 'A'];
    else
      hr = mc->mc_unknown;

    if (hr->hr_class == NULL)
      hr = mc->mc_unknown;
  }
  else {
    msg_hclass_t *hc;

352
    /* long form */
Pekka Pessi's avatar
Pekka Pessi committed
353
    for (hr = NULL; (hc = mc->mc_hash[i].hr_class); i = (i + 1) % N) {
354
      if (m == hc->hc_len && strncasecmp(s, hc->hc_name, m) == 0) {
Pekka Pessi's avatar
Pekka Pessi committed
355 356 357 358 359 360 361 362 363 364 365 366
	hr = &mc->mc_hash[i];
	break;
      }
    }

    if (hr == NULL)
      hr = mc->mc_unknown;
  }

  if (!return_start_of_content)	/* Just header name */
    return hr;

367 368
  if (s[len] == ':') {		/* Fast path */
    *return_start_of_content = ++len;
Pekka Pessi's avatar
Pekka Pessi committed
369 370 371
    return hr;
  }

372
  if (IS_LWS(s[len])) {
Pekka Pessi's avatar
Pekka Pessi committed
373 374
    int crlf = 0;
    do {
375 376
      len += span_ws(s + len + crlf) + crlf; /* Skip lws before colon */
      crlf = CRLF_TEST(s[len], s[len + 1]);
Pekka Pessi's avatar
Pekka Pessi committed
377
    }
378
    while (IS_WS(s[len + crlf]));
Pekka Pessi's avatar
Pekka Pessi committed
379 380
  }

381 382
  if (s[len++] != ':')		/* Colon is required in header */
    len = 0;
Pekka Pessi's avatar
Pekka Pessi committed
383

384
  *return_start_of_content = len;
Pekka Pessi's avatar
Pekka Pessi committed
385 386 387

  return hr;
}