Tristan Matthews | 0a329cc | 2013-07-17 13:20:14 -0400 | [diff] [blame] | 1 | /* $Id$ */ |
| 2 | /* |
| 3 | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| 4 | * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 | */ |
| 20 | #include <pjsip/sip_tel_uri.h> |
| 21 | #include <pjsip/sip_msg.h> |
| 22 | #include <pjsip/sip_parser.h> |
| 23 | #include <pjsip/print_util.h> |
| 24 | #include <pj/pool.h> |
| 25 | #include <pj/assert.h> |
| 26 | #include <pj/string.h> |
| 27 | #include <pj/ctype.h> |
| 28 | #include <pj/except.h> |
| 29 | #include <pjlib-util/string.h> |
| 30 | #include <pjlib-util/scanner.h> |
| 31 | |
| 32 | #define ALPHA |
| 33 | #define DIGITS "0123456789" |
| 34 | #define HEX "aAbBcCdDeEfF" |
| 35 | #define HEX_DIGITS DIGITS HEX |
| 36 | #define VISUAL_SEP "-.()" |
| 37 | #define PHONE_DIGITS DIGITS VISUAL_SEP |
| 38 | #define GLOBAL_DIGITS "+" PHONE_DIGITS |
| 39 | #define LOCAL_DIGITS HEX_DIGITS "*#" VISUAL_SEP |
| 40 | #define NUMBER_SPEC LOCAL_DIGITS GLOBAL_DIGITS |
| 41 | #define PHONE_CONTEXT ALPHA GLOBAL_DIGITS |
| 42 | //#define RESERVED ";/?:@&=+$," |
| 43 | #define RESERVED "/:@&$,+" |
| 44 | #define MARK "-_.!~*'()" |
| 45 | #define UNRESERVED ALPHA DIGITS MARK |
| 46 | #define ESCAPED "%" |
| 47 | #define URIC RESERVED UNRESERVED ESCAPED "[]+" |
| 48 | #define PARAM_UNRESERVED "[]/:&+$" |
| 49 | #define PARAM_CHAR PARAM_UNRESERVED UNRESERVED ESCAPED |
| 50 | |
| 51 | static pj_cis_buf_t cis_buf; |
| 52 | static pj_cis_t pjsip_TEL_NUMBER_SPEC; |
| 53 | static pj_cis_t pjsip_TEL_EXT_VALUE_SPEC; |
| 54 | static pj_cis_t pjsip_TEL_PHONE_CONTEXT_SPEC; |
| 55 | static pj_cis_t pjsip_TEL_URIC_SPEC; |
| 56 | static pj_cis_t pjsip_TEL_VISUAL_SEP_SPEC; |
| 57 | static pj_cis_t pjsip_TEL_PNAME_SPEC; |
| 58 | static pj_cis_t pjsip_TEL_PVALUE_SPEC; |
| 59 | static pj_cis_t pjsip_TEL_PVALUE_SPEC_ESC; |
| 60 | static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC; |
| 61 | static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC_ESC; |
| 62 | |
| 63 | static pj_str_t pjsip_ISUB_STR = { "isub", 4 }; |
| 64 | static pj_str_t pjsip_EXT_STR = { "ext", 3 }; |
| 65 | static pj_str_t pjsip_PH_CTX_STR = { "phone-context", 13 }; |
| 66 | |
| 67 | |
| 68 | static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri* ); |
| 69 | static void *tel_uri_get_uri( pjsip_tel_uri* ); |
| 70 | static pj_ssize_t tel_uri_print( pjsip_uri_context_e context, |
| 71 | const pjsip_tel_uri *url, |
| 72 | char *buf, pj_size_t size); |
| 73 | static int tel_uri_cmp( pjsip_uri_context_e context, |
| 74 | const pjsip_tel_uri *url1, const pjsip_tel_uri *url2); |
| 75 | static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs); |
| 76 | static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool, |
| 77 | pj_bool_t parse_params); |
| 78 | |
| 79 | typedef const pj_str_t* (*P_GET_SCHEME)(const void*); |
| 80 | typedef void* (*P_GET_URI)(void*); |
| 81 | typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *, |
| 82 | char*,pj_size_t); |
| 83 | typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*, |
| 84 | const void*); |
| 85 | typedef void* (*P_CLONE)(pj_pool_t*, const void*); |
| 86 | |
| 87 | static pjsip_uri_vptr tel_uri_vptr = |
| 88 | { |
| 89 | (P_GET_SCHEME) &tel_uri_get_scheme, |
| 90 | (P_GET_URI) &tel_uri_get_uri, |
| 91 | (P_PRINT_URI) &tel_uri_print, |
| 92 | (P_CMP_URI) &tel_uri_cmp, |
| 93 | (P_CLONE) &tel_uri_clone |
| 94 | }; |
| 95 | |
| 96 | |
| 97 | PJ_DEF(pjsip_tel_uri*) pjsip_tel_uri_create(pj_pool_t *pool) |
| 98 | { |
| 99 | pjsip_tel_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_tel_uri); |
| 100 | uri->vptr = &tel_uri_vptr; |
| 101 | pj_list_init(&uri->other_param); |
| 102 | return uri; |
| 103 | } |
| 104 | |
| 105 | |
| 106 | static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri *uri ) |
| 107 | { |
| 108 | PJ_UNUSED_ARG(uri); |
| 109 | return &pjsip_parser_const()->pjsip_TEL_STR; |
| 110 | } |
| 111 | |
| 112 | static void *tel_uri_get_uri( pjsip_tel_uri *uri ) |
| 113 | { |
| 114 | return uri; |
| 115 | } |
| 116 | |
| 117 | |
| 118 | pj_status_t pjsip_tel_uri_subsys_init(void) |
| 119 | { |
| 120 | pj_status_t status; |
| 121 | |
| 122 | pj_cis_buf_init(&cis_buf); |
| 123 | |
| 124 | status = pj_cis_init(&cis_buf, &pjsip_TEL_EXT_VALUE_SPEC); |
| 125 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 126 | pj_cis_add_str(&pjsip_TEL_EXT_VALUE_SPEC, PHONE_DIGITS); |
| 127 | |
| 128 | status = pj_cis_init(&cis_buf, &pjsip_TEL_NUMBER_SPEC); |
| 129 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 130 | pj_cis_add_str(&pjsip_TEL_NUMBER_SPEC, NUMBER_SPEC); |
| 131 | |
| 132 | status = pj_cis_init(&cis_buf, &pjsip_TEL_VISUAL_SEP_SPEC); |
| 133 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 134 | pj_cis_add_str(&pjsip_TEL_VISUAL_SEP_SPEC, VISUAL_SEP); |
| 135 | |
| 136 | status = pj_cis_init(&cis_buf, &pjsip_TEL_PHONE_CONTEXT_SPEC); |
| 137 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 138 | pj_cis_add_alpha(&pjsip_TEL_PHONE_CONTEXT_SPEC); |
| 139 | pj_cis_add_num(&pjsip_TEL_PHONE_CONTEXT_SPEC); |
| 140 | pj_cis_add_str(&pjsip_TEL_PHONE_CONTEXT_SPEC, PHONE_CONTEXT); |
| 141 | |
| 142 | status = pj_cis_init(&cis_buf, &pjsip_TEL_URIC_SPEC); |
| 143 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 144 | pj_cis_add_alpha(&pjsip_TEL_URIC_SPEC); |
| 145 | pj_cis_add_num(&pjsip_TEL_URIC_SPEC); |
| 146 | pj_cis_add_str(&pjsip_TEL_URIC_SPEC, URIC); |
| 147 | |
| 148 | status = pj_cis_init(&cis_buf, &pjsip_TEL_PNAME_SPEC); |
| 149 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 150 | pj_cis_add_alpha(&pjsip_TEL_PNAME_SPEC); |
| 151 | pj_cis_add_num(&pjsip_TEL_PNAME_SPEC); |
| 152 | pj_cis_add_str(&pjsip_TEL_PNAME_SPEC, "-"); |
| 153 | |
| 154 | status = pj_cis_init(&cis_buf, &pjsip_TEL_PVALUE_SPEC); |
| 155 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 156 | pj_cis_add_alpha(&pjsip_TEL_PVALUE_SPEC); |
| 157 | pj_cis_add_num(&pjsip_TEL_PVALUE_SPEC); |
| 158 | pj_cis_add_str(&pjsip_TEL_PVALUE_SPEC, PARAM_CHAR); |
| 159 | |
| 160 | status = pj_cis_dup(&pjsip_TEL_PVALUE_SPEC_ESC, &pjsip_TEL_PVALUE_SPEC); |
| 161 | pj_cis_del_str(&pjsip_TEL_PVALUE_SPEC_ESC, "%"); |
| 162 | |
| 163 | status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_URIC_SPEC); |
| 164 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 165 | pj_cis_add_cis(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_PVALUE_SPEC); |
| 166 | pj_cis_add_str(&pjsip_TEL_PARSING_PVALUE_SPEC, "="); |
| 167 | |
| 168 | status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, |
| 169 | &pjsip_TEL_PARSING_PVALUE_SPEC); |
| 170 | pj_cis_del_str(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, "%"); |
| 171 | |
| 172 | status = pjsip_register_uri_parser("tel", &tel_uri_parse); |
| 173 | PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| 174 | |
| 175 | return PJ_SUCCESS; |
| 176 | } |
| 177 | |
| 178 | /* Print tel: URI */ |
| 179 | static pj_ssize_t tel_uri_print( pjsip_uri_context_e context, |
| 180 | const pjsip_tel_uri *uri, |
| 181 | char *buf, pj_size_t size) |
| 182 | { |
| 183 | int printed; |
| 184 | char *startbuf = buf; |
| 185 | char *endbuf = buf+size-1; |
| 186 | const pjsip_parser_const_t *pc = pjsip_parser_const(); |
| 187 | |
| 188 | PJ_UNUSED_ARG(context); |
| 189 | |
| 190 | /* Print scheme. */ |
| 191 | copy_advance(buf, pc->pjsip_TEL_STR); |
| 192 | *buf++ = ':'; |
| 193 | |
| 194 | /* Print number. */ |
| 195 | copy_advance_escape(buf, uri->number, pjsip_TEL_NUMBER_SPEC); |
| 196 | |
| 197 | /* ISDN sub-address or extension must appear first. */ |
| 198 | |
| 199 | /* Extension param. */ |
| 200 | copy_advance_pair_escape(buf, ";ext=", 5, uri->ext_param, |
| 201 | pjsip_TEL_EXT_VALUE_SPEC); |
| 202 | |
| 203 | /* ISDN sub-address. */ |
| 204 | copy_advance_pair_escape(buf, ";isub=", 6, uri->isub_param, |
| 205 | pjsip_TEL_URIC_SPEC); |
| 206 | |
| 207 | /* Followed by phone context, if present. */ |
| 208 | copy_advance_pair_escape(buf, ";phone-context=", 15, uri->context, |
| 209 | pjsip_TEL_PHONE_CONTEXT_SPEC); |
| 210 | |
| 211 | |
| 212 | /* Print other parameters. */ |
| 213 | printed = (int)pjsip_param_print_on(&uri->other_param, buf, (endbuf-buf), |
| 214 | &pjsip_TEL_PNAME_SPEC, |
| 215 | &pjsip_TEL_PVALUE_SPEC, ';'); |
| 216 | if (printed < 0) |
| 217 | return -1; |
| 218 | buf += printed; |
| 219 | |
| 220 | *buf = '\0'; |
| 221 | |
| 222 | return (buf-startbuf); |
| 223 | } |
| 224 | |
| 225 | /* Compare two numbers, according to RFC 3966: |
| 226 | * - both must be either local or global numbers. |
| 227 | * - The 'global-number-digits' and the 'local-number-digits' must be |
| 228 | * equal, after removing all visual separators. |
| 229 | */ |
| 230 | PJ_DEF(int) pjsip_tel_nb_cmp(const pj_str_t *number1, const pj_str_t *number2) |
| 231 | { |
| 232 | const char *s1 = number1->ptr, |
| 233 | *e1 = number1->ptr + number1->slen, |
| 234 | *s2 = number2->ptr, |
| 235 | *e2 = number2->ptr + number2->slen; |
| 236 | |
| 237 | /* Compare each number, ignoreing visual separators. */ |
| 238 | while (s1!=e1 && s2!=e2) { |
| 239 | int diff; |
| 240 | |
| 241 | if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) { |
| 242 | ++s1; |
| 243 | continue; |
| 244 | } |
| 245 | if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) { |
| 246 | ++s2; |
| 247 | continue; |
| 248 | } |
| 249 | |
| 250 | diff = pj_tolower(*s1) - pj_tolower(*s2); |
| 251 | if (!diff) { |
| 252 | ++s1, ++s2; |
| 253 | continue; |
| 254 | } else |
| 255 | return diff; |
| 256 | } |
| 257 | |
| 258 | /* Exhaust remaining visual separators. */ |
| 259 | while (s1!=e1 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) |
| 260 | ++s1; |
| 261 | while (s2!=e2 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) |
| 262 | ++s2; |
| 263 | |
| 264 | if (s1==e1 && s2==e2) |
| 265 | return 0; |
| 266 | else if (s1==e1) |
| 267 | return -1; |
| 268 | else |
| 269 | return 1; |
| 270 | } |
| 271 | |
| 272 | /* Compare two tel: URI */ |
| 273 | static int tel_uri_cmp( pjsip_uri_context_e context, |
| 274 | const pjsip_tel_uri *url1, const pjsip_tel_uri *url2) |
| 275 | { |
| 276 | int result; |
| 277 | |
| 278 | PJ_UNUSED_ARG(context); |
| 279 | |
| 280 | /* Scheme must match. */ |
| 281 | if (url1->vptr != url2->vptr) |
| 282 | return -1; |
| 283 | |
| 284 | /* Compare number. */ |
| 285 | result = pjsip_tel_nb_cmp(&url1->number, &url2->number); |
| 286 | if (result != 0) |
| 287 | return result; |
| 288 | |
| 289 | /* Compare phone-context as hostname or as as global nb. */ |
| 290 | if (url1->context.slen) { |
| 291 | if (*url1->context.ptr != '+') |
| 292 | result = pj_stricmp(&url1->context, &url2->context); |
| 293 | else |
| 294 | result = pjsip_tel_nb_cmp(&url1->context, &url2->context); |
| 295 | |
| 296 | if (result != 0) |
| 297 | return result; |
| 298 | |
| 299 | } else if (url2->context.slen) |
| 300 | return -1; |
| 301 | |
| 302 | /* Compare extension. */ |
| 303 | if (url1->ext_param.slen) { |
| 304 | result = pjsip_tel_nb_cmp(&url1->ext_param, &url2->ext_param); |
| 305 | if (result != 0) |
| 306 | return result; |
| 307 | } |
| 308 | |
| 309 | /* Compare isub bytes by bytes. */ |
| 310 | if (url1->isub_param.slen) { |
| 311 | result = pj_stricmp(&url1->isub_param, &url2->isub_param); |
| 312 | if (result != 0) |
| 313 | return result; |
| 314 | } |
| 315 | |
| 316 | /* Other parameters are compared regardless of the order. |
| 317 | * If one URI has parameter not found in the other URI, the URIs are |
| 318 | * not equal. |
| 319 | */ |
| 320 | if (url1->other_param.next != &url1->other_param) { |
| 321 | const pjsip_param *p1, *p2; |
| 322 | int cnt1 = 0, cnt2 = 0; |
| 323 | |
| 324 | p1 = url1->other_param.next; |
| 325 | while (p1 != &url1->other_param) { |
| 326 | p2 = pjsip_param_cfind(&url2->other_param, &p1->name); |
| 327 | if (!p2 ) |
| 328 | return 1; |
| 329 | |
| 330 | result = pj_stricmp(&p1->value, &p2->value); |
| 331 | if (result != 0) |
| 332 | return result; |
| 333 | |
| 334 | p1 = p1->next; |
| 335 | ++cnt1; |
| 336 | } |
| 337 | |
| 338 | p2 = url2->other_param.next; |
| 339 | while (p2 != &url2->other_param) |
| 340 | ++cnt2, p2 = p2->next; |
| 341 | |
| 342 | if (cnt1 < cnt2) |
| 343 | return -1; |
| 344 | else if (cnt1 > cnt2) |
| 345 | return 1; |
| 346 | |
| 347 | } else if (url2->other_param.next != &url2->other_param) |
| 348 | return -1; |
| 349 | |
| 350 | /* Equal. */ |
| 351 | return 0; |
| 352 | } |
| 353 | |
| 354 | /* Clone tel: URI */ |
| 355 | static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs) |
| 356 | { |
| 357 | pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); |
| 358 | |
| 359 | pj_strdup(pool, &uri->number, &rhs->number); |
| 360 | pj_strdup(pool, &uri->context, &rhs->context); |
| 361 | pj_strdup(pool, &uri->ext_param, &rhs->ext_param); |
| 362 | pj_strdup(pool, &uri->isub_param, &rhs->isub_param); |
| 363 | pjsip_param_clone(pool, &uri->other_param, &rhs->other_param); |
| 364 | |
| 365 | return uri; |
| 366 | } |
| 367 | |
| 368 | /* Parse tel: URI |
| 369 | * THis actually returns (pjsip_tel_uri *) type. |
| 370 | */ |
| 371 | static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool, |
| 372 | pj_bool_t parse_params) |
| 373 | { |
| 374 | pjsip_tel_uri *uri; |
| 375 | pj_str_t token; |
| 376 | int skip_ws = scanner->skip_ws; |
| 377 | const pjsip_parser_const_t *pc = pjsip_parser_const(); |
| 378 | |
| 379 | scanner->skip_ws = 0; |
| 380 | |
| 381 | /* Parse scheme. */ |
| 382 | pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &token); |
| 383 | if (pj_scan_get_char(scanner) != ':') |
| 384 | PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); |
| 385 | if (pj_stricmp_alnum(&token, &pc->pjsip_TEL_STR) != 0) |
| 386 | PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); |
| 387 | |
| 388 | /* Create URI */ |
| 389 | uri = pjsip_tel_uri_create(pool); |
| 390 | |
| 391 | /* Get the phone number. */ |
| 392 | #if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 |
| 393 | pj_scan_get_unescape(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number); |
| 394 | #else |
| 395 | pj_scan_get(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number); |
| 396 | uri->number = pj_str_unescape(pool, &uri->number); |
| 397 | #endif |
| 398 | |
| 399 | /* Get all parameters. */ |
| 400 | if (parse_params && *scanner->curptr==';') { |
| 401 | pj_str_t pname, pvalue; |
| 402 | const pjsip_parser_const_t *pc = pjsip_parser_const(); |
| 403 | |
| 404 | do { |
| 405 | /* Eat the ';' separator. */ |
| 406 | pj_scan_get_char(scanner); |
| 407 | |
| 408 | /* Get pname. */ |
| 409 | pj_scan_get(scanner, &pc->pjsip_PARAM_CHAR_SPEC, &pname); |
| 410 | |
| 411 | if (*scanner->curptr == '=') { |
| 412 | pj_scan_get_char(scanner); |
| 413 | |
| 414 | # if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 |
| 415 | pj_scan_get_unescape(scanner, |
| 416 | &pjsip_TEL_PARSING_PVALUE_SPEC_ESC, |
| 417 | &pvalue); |
| 418 | # else |
| 419 | pj_scan_get(scanner, &pjsip_TEL_PARSING_PVALUE_SPEC, |
| 420 | &pvalue); |
| 421 | pvalue = pj_str_unescape(pool, &pvalue); |
| 422 | # endif |
| 423 | |
| 424 | } else { |
| 425 | pvalue.slen = 0; |
| 426 | pvalue.ptr = NULL; |
| 427 | } |
| 428 | |
| 429 | /* Save the parameters. */ |
| 430 | if (pj_stricmp_alnum(&pname, &pjsip_ISUB_STR)==0) { |
| 431 | uri->isub_param = pvalue; |
| 432 | } else if (pj_stricmp_alnum(&pname, &pjsip_EXT_STR)==0) { |
| 433 | uri->ext_param = pvalue; |
| 434 | } else if (pj_stricmp_alnum(&pname, &pjsip_PH_CTX_STR)==0) { |
| 435 | uri->context = pvalue; |
| 436 | } else { |
| 437 | pjsip_param *param = PJ_POOL_ALLOC_T(pool, pjsip_param); |
| 438 | param->name = pname; |
| 439 | param->value = pvalue; |
| 440 | pj_list_insert_before(&uri->other_param, param); |
| 441 | } |
| 442 | |
| 443 | } while (*scanner->curptr==';'); |
| 444 | } |
| 445 | |
| 446 | scanner->skip_ws = skip_ws; |
| 447 | pj_scan_skip_whitespace(scanner); |
| 448 | return uri; |
| 449 | } |
| 450 | |