Skip to content
Snippets Groups Projects
reqresp_parser.c 76.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • David Vossel's avatar
    David Vossel committed
    /*
     * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 2010, Digium, Inc.
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    /*!
     * \file
     * \brief sip request parsing functions and unit tests
     */
    
    
    /*** MODULEINFO
    
    	<support_level>extended</support_level>
    
    David Vossel's avatar
    David Vossel committed
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    #include "include/sip.h"
    
    #include "include/sip_utils.h"
    
    David Vossel's avatar
    David Vossel committed
    #include "include/reqresp_parser.h"
    
    
    #ifdef HAVE_XLOCALE_H
    
    locale_t c_locale;
    
    David Vossel's avatar
    David Vossel committed
    /*! \brief * parses a URI in its components.*/
    
    Mark Michelson's avatar
    Mark Michelson committed
    int parse_uri_full(char *uri, const char *scheme, char **user, char **pass,
    
    		   char **hostport, struct uriparams *params, char **headers,
    
    Mark Michelson's avatar
    Mark Michelson committed
    		   char **residue)
    
    David Vossel's avatar
    David Vossel committed
    {
    
    	char *userinfo = NULL;
    	char *parameters = NULL;
    	char *endparams = NULL;
    	char *c = NULL;
    
    David Vossel's avatar
    David Vossel committed
    	int error = 0;
    
    David Vossel's avatar
    David Vossel committed
    
    
    	/*
    	 * Initialize requested strings - some functions don't care if parse_uri fails
    	 * and will attempt to use string pointers passed into parse_uri even after a
    	 * parse_uri failure
    	 */
    	if (user) {
    		*user = "";
    	}
    	if (pass) {
    		*pass = "";
    	}
    
    	if (hostport) {
    		*hostport = "";
    
    	}
    	if (headers) {
    		*headers = "";
    	}
    	if (residue) {
    		*residue = "";
    	}
    
    
    David Vossel's avatar
    David Vossel committed
    	/* check for valid input */
    	if (ast_strlen_zero(uri)) {
    		return -1;
    	}
    
    	if (scheme) {
    		int l;
    		char *scheme2 = ast_strdupa(scheme);
    		char *cur = strsep(&scheme2, ",");
    		for (; !ast_strlen_zero(cur); cur = strsep(&scheme2, ",")) {
    			l = strlen(cur);
    			if (!strncasecmp(uri, cur, l)) {
    
    				teluri_scheme = !strncasecmp(uri, "tel:", 4);	/* TEL URI */
    
    David Vossel's avatar
    David Vossel committed
    				uri += l;
    				break;
    			}
    		}
    		if (ast_strlen_zero(cur)) {
    			ast_debug(1, "No supported scheme found in '%s' using the scheme[s] %s\n", uri, scheme);
    			error = -1;
    		}
    	}
    
    	if (!hostport) {
    		/* if we don't want to split around hostport, keep everything as a
    
    Mark Michelson's avatar
    Mark Michelson committed
    		 * userinfo - cos thats how old parse_uri operated*/
    
    	} else if (teluri_scheme) {
    		/*
    		 * tel: TEL URI INVITE RFC 3966 patch
    		 * See http://www.ietf.org/rfc/rfc3966.txt
    		 *
    		 * Once the full RFC 3966 parsing is implemented,
    		 * the ext= or isub= parameters would be extracted from userinfo.
    		 * When this kind of subaddressing would be implemented, the userinfo must be further parsed.
    		 * Those parameters would be used for ISDN or PSTN local extensions.
    		 *
    		 * Current restrictions:
    		 * We currently consider the ";isub=" or the ";ext=" as part of the userinfo (unparsed).
    		 */
    
    		if ((c = strstr(uri, ";phone-context="))) {
    			/*
    			 * Local number with context or domain.
    			 * ext= or isub= TEL URI parameters should be upfront.
    			 * All other parameters should come after the ";phone-context=" parameter.
    			 * If other parameters would occur before ";phone-context=" they will be ignored.
    			 */
    
                            *c = '\0';
                            userinfo = uri;
                            uri = c + 15;
    			*hostport = uri;
                    } else if ('+' == uri[0]) {
    			/* Global number without context or domain; possibly followed by RFC 3966 and optional other parameters. */
    
                            userinfo = uri;
    			*hostport = uri;
    		} else {
    			ast_debug(1, "No RFC 3966 global number or context found in '%s'; returning local number anyway\n", uri);
                            userinfo = uri;		/* Return local number anyway */
    			error = -1;
    		}
    
    Mark Michelson's avatar
    Mark Michelson committed
    		char *dom = "";
    
    		if ((c = strchr(uri, '@'))) {
    			*c++ = '\0';
    
    Mark Michelson's avatar
    Mark Michelson committed
    			dom = c;
    
    Mark Michelson's avatar
    Mark Michelson committed
    			uri = c; /* userinfo can contain ? and ; chars so step forward before looking for params and headers */
    
    		} else {
    			/* domain-only URI, according to the SIP RFC. */
    
    Mark Michelson's avatar
    Mark Michelson committed
    			dom = uri;
    
    		*hostport = dom;
    
    	if (pass && (c = strchr(userinfo, ':'))) {	  /* user:password */
    		*c++ = '\0';
    		*pass = c;
    	} else if (pass) {
    		*pass = "";
    	}
    
    David Vossel's avatar
    David Vossel committed
    
    
    	if (user) {
    		*user = userinfo;
    	}
    
    	parameters = uri;
    	/* strip [?headers] from end of uri  - even if no header pointer exists*/
    	if ((c = strrchr(uri, '?'))) {
    
    		uri = c;
    		if (headers) {
    			*headers = c;
    
    David Vossel's avatar
    David Vossel committed
    		}
    
    		if ((c = strrchr(uri, ';'))) {
    			*c++ = '\0';
    		} else {
    			c = strrchr(uri, '\0');
    		}
    		uri = c; /* residue */
    
    
    David Vossel's avatar
    David Vossel committed
    
    
    	} else if (headers) {
    		*headers = "";
    	}
    
    David Vossel's avatar
    David Vossel committed
    
    
    	/* parse parameters */
    	endparams = strchr(parameters,'\0');
    	if ((c = strchr(parameters, ';'))) {
    
    		parameters = c;
    	} else {
    		parameters = endparams;
    
    
    	if (params) {
    		char *rem = parameters; /* unparsed or unrecognised remainder */
    		char *label;
    		char *value;
    		int lr = 0;
    
    		params->transport = "";
    		params->user = "";
    		params->method = "";
    		params->ttl = "";
    		params->maddr = "";
    		params->lr = 0;
    
    		rem = parameters;
    
    		while ((value = strchr(parameters, '=')) || (lr = !strncmp(parameters, "lr", 2))) {
    			/* The while condition will not continue evaluation to set lr if it matches "lr=" */
    			if (lr) {
    				value = parameters;
    			} else {
    				*value++ = '\0';
    			}
    			label = parameters;
    			if ((c = strchr(value, ';'))) {
    
    				parameters = c;
    			} else {
    				parameters = endparams;
    
    
    			if (!strcmp(label, "transport")) {
    
    				params->transport = value;
    
    				rem = parameters;
    			} else if (!strcmp(label, "user")) {
    
    				params->user = value;
    
    				rem = parameters;
    			} else if (!strcmp(label, "method")) {
    
    				params->method = value;
    
    				rem = parameters;
    			} else if (!strcmp(label, "ttl")) {
    
    				rem = parameters;
    			} else if (!strcmp(label, "maddr")) {
    
    				params->maddr = value;
    
    				rem = parameters;
    			/* Treat "lr", "lr=yes", "lr=on", "lr=1", "lr=almostanything" as lr enabled and "", "lr=no", "lr=off", "lr=0", "lr=" and "lranything" as lr disabled */
    			} else if ((!strcmp(label, "lr") && strcmp(value, "no") && strcmp(value, "off") && strcmp(value, "0") && strcmp(value, "")) || ((lr) && strcmp(value, "lr"))) {
    
    				rem = parameters;
    			} else {
    				value--;
    				*value = '=';
    
    			}
    		}
    		if (rem > uri) { /* no headers */
    			uri = rem;
    		}
    
    	}
    
    	if (residue) {
    		*residue = uri;
    
    David Vossel's avatar
    David Vossel committed
    	}
    
    	return error;
    }
    
    
    AST_TEST_DEFINE(sip_parse_uri_full_test)
    
    {
    	int res = AST_TEST_PASS;
    	char uri[1024];
    
    	char *user, *pass, *hostport, *headers, *residue;
    
    	struct uriparams params;
    
    	struct testdata {
    		char *desc;
    		char *uri;
    		char *user;
    		char *pass;
    
    		char *hostport;
    
    		char *headers;
    		char *residue;
    		struct uriparams params;
    		AST_LIST_ENTRY(testdata) list;
    	};
    
    
    	struct testdata *testdataptr;
    
    	static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist;
    
    	struct testdata td1 = {
    		.desc = "no headers",
    		.uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=residue",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    Mark Michelson's avatar
    Mark Michelson committed
    		.residue = "param2=residue",
    
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td2 = {
    		.desc = "with headers",
    		.uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=discard2?header=blah&header2=blah2;param3=residue",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "header=blah&header2=blah2",
    		.residue = "param3=residue",
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td3 = {
    		.desc = "difficult user",
    		.uri = "sip:-_.!~*'()&=+$,;?/:secret@host:5060;transport=tcp",
    		.user = "-_.!~*'()&=+$,;?/",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "",
    		.residue = "",
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td4 = {
    		.desc = "difficult pass",
    		.uri = "sip:user:-_.!~*'()&=+$,@host:5060;transport=tcp",
    		.user = "user",
    		.pass = "-_.!~*'()&=+$,",
    
    		.hostport = "host:5060",
    
    		.headers = "",
    		.residue = "",
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td5 = {
    		.desc = "difficult host",
    		.uri = "sip:user:secret@1-1.a-1.:5060;transport=tcp",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "1-1.a-1.:5060",
    
    		.headers = "",
    		.residue = "",
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td6 = {
    		.desc = "difficult params near transport",
    		.uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$;transport=tcp",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "",
    		.residue = "",
    		.params.transport = "tcp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td7 = {
    		.desc = "difficult params near headers",
    		.uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$?header=blah&header2=blah2;-_.!~*'()[]/:&+$=residue",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "header=blah&header2=blah2",
    		.residue = "-_.!~*'()[]/:&+$=residue",
    		.params.transport = "",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td8 = {
    		.desc = "lr parameter",
    		.uri = "sip:user:secret@host:5060;param=discard;lr?header=blah",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "header=blah",
    		.residue = "",
    		.params.transport = "",
    		.params.lr = 1,
    		.params.user = ""
    	};
    
    	struct testdata td9 = {
    		.desc = "alternative lr parameter",
    		.uri = "sip:user:secret@host:5060;param=discard;lr=yes?header=blah",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "header=blah",
    		.residue = "",
    		.params.transport = "",
    		.params.lr = 1,
    		.params.user = ""
    	};
    
    	struct testdata td10 = {
    		.desc = "no lr parameter",
    		.uri = "sip:user:secret@host:5060;paramlr=lr;lr=no;lr=off;lr=0;lr=;=lr;lrextra;lrparam2=lr?header=blah",
    		.user = "user",
    		.pass = "secret",
    
    		.hostport = "host:5060",
    
    		.headers = "header=blah",
    		.residue = "",
    		.params.transport = "",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    
    	/* RFC 3966 TEL URI INVITE */
    	struct testdata td11 = {
    		.desc = "tel local number",
    		.uri = "tel:0987654321;phone-context=+32987654321",
    		.user = "0987654321",
    		.pass = "",
    		.hostport = "+32987654321",
    		.headers = "",
    		.residue = "",
    		.params.transport = "",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	struct testdata td12 = {
    		.desc = "tel global number",
    		.uri = "tel:+32987654321",
    		.user = "+32987654321",
    		.pass = "",
    		.hostport = "+32987654321",
    		.headers = "",
    		.residue = "",
    		.params.transport = "",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    	/*
    	 * Once the full RFC 3966 parsing is implemented,
    	 * only the ext= or isub= parameters would be extracted from .user
    	 * Then the ;param=discard would be ignored,
    	 * and the .user would only contain "0987654321"
    	 */
    	struct testdata td13 = {
    		.desc = "tel local number",
    		.uri = "tel:0987654321;ext=1234;param=discard;phone-context=+32987654321;transport=udp;param2=discard2?header=blah&header2=blah2;param3=residue",
    		.user = "0987654321;ext=1234;param=discard",
    		.pass = "",
    		.hostport = "+32987654321",
    		.headers = "header=blah&header2=blah2",
    		.residue = "param3=residue",
    		.params.transport = "udp",
    		.params.lr = 0,
    		.params.user = ""
    	};
    
    
    	AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &td1);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td2, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td3, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td4, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td5, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td6, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td7, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td8, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td9, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td10, list);
    
    	AST_LIST_INSERT_TAIL(&testdatalist, &td11, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td12, list);
    	AST_LIST_INSERT_TAIL(&testdatalist, &td13, list);
    
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "sip_uri_full_parse_test";
    
    		info->summary = "tests sip full uri parsing";
    		info->description =
    			"Tests full parsing of various URIs "
    			"Verifies output matches expected behavior.";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) {
    
    		user = pass = hostport = headers = residue = NULL;
    
    		params.transport = params.user = params.method = params.ttl = params.maddr = NULL;
    		params.lr = 0;
    
    		ast_copy_string(uri,testdataptr->uri,sizeof(uri));
    
    		if (parse_uri_full(uri, "sip:,sips:,tel:", &user,
    
    				   &pass, &hostport,
    				   &params,
    				   &headers,
    				   &residue) ||
    			(user && strcmp(testdataptr->user, user)) ||
    			(pass && strcmp(testdataptr->pass, pass)) ||
    			(hostport && strcmp(testdataptr->hostport, hostport)) ||
    			(headers && strcmp(testdataptr->headers, headers)) ||
    			(residue && strcmp(testdataptr->residue, residue)) ||
    			(strcmp(testdataptr->params.transport,params.transport)) ||
    			(testdataptr->params.lr != params.lr) ||
    			(strcmp(testdataptr->params.user,params.user))
    
    		) {
    				ast_test_status_update(test, "Sub-Test: %s, failed.\n", testdataptr->desc);
    				res = AST_TEST_FAIL;
    		}
    	}
    
    
    	return res;
    }
    
    Mark Michelson's avatar
    Mark Michelson committed
    int parse_uri(char *uri, const char *scheme, char **user, char **pass,
    
    	      char **hostport, char **transport) {
    
    	int ret;
    	char *headers;
    	struct uriparams params;
    
    	headers = NULL;
    
    	ret = parse_uri_full(uri, scheme, user, pass, hostport, &params, &headers, NULL);
    
    	if (transport) {
    		*transport=params.transport;
    	}
    	return ret;
    }
    
    
    David Vossel's avatar
    David Vossel committed
    AST_TEST_DEFINE(sip_parse_uri_test)
    {
    	int res = AST_TEST_PASS;
    
    	char *name, *pass, *hostport, *transport;
    
    David Vossel's avatar
    David Vossel committed
    	char uri1[] = "sip:name@host";
    	char uri2[] = "sip:name@host;transport=tcp";
    	char uri3[] = "sip:name:secret@host;transport=tcp";
    	char uri4[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah";
    
    	/* test 5 is for NULL input */
    	char uri6[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah";
    	char uri7[] = "sip:name:secret@host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah";
    
    	char uri8[] = "sip:host";
    	char uri9[] = "sip:host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah";
    	char uri10[] = "host:port;transport=tcp?headers=%40%40testblah&headers2=blah%20blah";
    	char uri11[] = "host";
    
    	char uri12[] = "tel:911";	/* TEL URI Local number without context or global number */
    
    David Vossel's avatar
    David Vossel committed
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "sip_uri_parse_test";
    
    David Vossel's avatar
    David Vossel committed
    		info->summary = "tests sip uri parsing";
    		info->description =
    
    							"Tests parsing of various URIs "
    							"Verifies output matches expected behavior.";
    
    David Vossel's avatar
    David Vossel committed
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	/* Test 1, simple URI */
    
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri1, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			strcmp(name, "name")        ||
    			!ast_strlen_zero(pass)      ||
    
    			strcmp(hostport, "host")      ||
    
    			!ast_strlen_zero(transport)) {
    		ast_test_status_update(test, "Test 1: simple uri failed. \n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 2, add tcp transport */
    
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri2, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			strcmp(name, "name")        ||
    			!ast_strlen_zero(pass)      ||
    
    			strcmp(hostport, "host")    ||
    
    			strcmp(transport, "tcp")) {
    		ast_test_status_update(test, "Test 2: uri with addtion of tcp transport failed. \n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 3, add secret */
    
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri3, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			strcmp(name, "name")        ||
    			strcmp(pass, "secret")      ||
    
    			strcmp(hostport, "host")    ||
    
    			strcmp(transport, "tcp")) {
    		ast_test_status_update(test, "Test 3: uri with addition of secret failed.\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 4, add port and unparsed header field*/
    
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri4, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			strcmp(name, "name")        ||
    			strcmp(pass, "secret")      ||
    
    			strcmp(hostport, "host:port") ||
    
    			strcmp(transport, "tcp")) {
    		ast_test_status_update(test, "Test 4: add port and unparsed header field failed.\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 5, verify parse_uri does not crash when given a NULL uri */
    
    	name = pass = hostport = transport = NULL;
    	if (!parse_uri(NULL, "sip:,sips:", &name, &pass, &hostport, &transport)) {
    
    		ast_test_status_update(test, "Test 5: passing a NULL uri failed.\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 6, verify parse_uri does not crash when given a NULL output parameters */
    
    	name = pass = hostport = transport = NULL;
    
    Mark Michelson's avatar
    Mark Michelson committed
    	if (parse_uri(uri6, "sip:,sips:", NULL, NULL, NULL, NULL)) {
    
    		ast_test_status_update(test, "Test 6: passing NULL output parameters failed.\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* Test 7, verify parse_uri returns user:secret and hostport when no port or secret output parameters are supplied. */
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri7, "sip:,sips:", &name, NULL, &hostport, NULL) ||
    
    			strcmp(name, "name:secret")        ||
    
    			strcmp(hostport, "host:port")) {
    
    
    		ast_test_status_update(test, "Test 7: providing no port and secret output parameters failed.\n");
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 8, verify parse_uri can handle a hostport only uri */
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri8, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    			strcmp(hostport, "host") ||
    
    			!ast_strlen_zero(name)) {
    		ast_test_status_update(test, "Test 8: add port and unparsed header field failed.\n");
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* Test 9, add port and unparsed header field with hostport only uri*/
    	name = pass = hostport = transport = NULL;
    	if (parse_uri(uri9, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			!ast_strlen_zero(name)        ||
    			!ast_strlen_zero(pass)      ||
    
    			strcmp(hostport, "host:port")    ||
    
    			strcmp(transport, "tcp")) {
    
    		ast_test_status_update(test, "Test 9: hostport only uri failed \n");
    
    		res = AST_TEST_FAIL;
    	}
    
    	/* Test 10, handle invalid/missing "sip:,sips:" scheme
    	 * we expect parse_uri to return an error, but still parse
    	 * the results correctly here */
    
    	name = pass = hostport = transport = NULL;
    	if (!parse_uri(uri10, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			!ast_strlen_zero(name)        ||
    			!ast_strlen_zero(pass)      ||
    
    			strcmp(hostport, "host:port")    ||
    
    			strcmp(transport, "tcp")) {
    		ast_test_status_update(test, "Test 10: missing \"sip:sips:\" scheme failed\n");
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* Test 11, simple hostport only URI with missing scheme
    
    	 * we expect parse_uri to return an error, but still parse
    	 * the results correctly here */
    
    	name = pass = hostport = transport = NULL;
    	if (!parse_uri(uri11, "sip:,sips:", &name, &pass, &hostport, &transport) ||
    
    			!ast_strlen_zero(name)      ||
    			!ast_strlen_zero(pass)      ||
    
    			strcmp(hostport, "host")      ||
    
    			!ast_strlen_zero(transport)) {
    		ast_test_status_update(test, "Test 11: simple uri with missing scheme failed. \n");
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* Test 12, simple URI */
    	name = pass = hostport = transport = NULL;
    	if (!parse_uri(uri12, "sip:,sips:,tel:", &name, &pass, &hostport, &transport) ||
    			strcmp(name, "911")      ||	/* We return local number anyway */
    			!ast_strlen_zero(pass)      ||
    			!ast_strlen_zero(hostport)      ||	/* No global number nor context */
    			!ast_strlen_zero(transport)) {
    		ast_test_status_update(test, "Test 12: TEL URI INVITE failed.\n");
    		res = AST_TEST_FAIL;
    	}
    
    
    David Vossel's avatar
    David Vossel committed
    	return res;
    }
    
    David Vossel's avatar
    David Vossel committed
    
    /*! \brief  Get caller id name from SIP headers, copy into output buffer
     *
     *  \retval input string pointer placed after display-name field if possible
     */
    const char *get_calleridname(const char *input, char *output, size_t outputsize)
    {
    	/* From RFC3261:
    
    David Vossel's avatar
    David Vossel committed
    	 * From           =  ( "From" / "f" ) HCOLON from-spec
    	 * from-spec      =  ( name-addr / addr-spec ) *( SEMI from-param )
    	 * name-addr      =  [ display-name ] LAQUOT addr-spec RAQUOT
    	 * display-name   =  *(token LWS)/ quoted-string
    	 * token          =  1*(alphanum / "-" / "." / "!" / "%" / "*"
    	 *                     / "_" / "+" / "`" / "'" / "~" )
    	 * quoted-string  =  SWS DQUOTE *(qdtext / quoted-pair ) DQUOTE
    	 * qdtext         =  LWS / %x21 / %x23-5B / %x5D-7E
    	 *                     / UTF8-NONASCII
    	 * quoted-pair    =  "\" (%x00-09 / %x0B-0C / %x0E-7F)
    	 *
    	 * HCOLON         = *WSP ":" SWS
    	 * SWS            = [LWS]
    	 * LWS            = *[*WSP CRLF] 1*WSP
    	 * WSP            = (SP / HTAB)
    	 *
    	 * Deviations from it:
    	 * - following CRLF's in LWS is not done (here at least)
    	 * - ascii NUL is never legal as it terminates the C-string
    	 * - utf8-nonascii is not checked for validity
    	 */
    	char *orig_output = output;
    	const char *orig_input = input;
    
    
    	if (!output || !outputsize) {
    		/* Bad output parameters.  Should never happen. */
    		return input;
    	}
    
    
    David Vossel's avatar
    David Vossel committed
    	/* clear any empty characters in the beginning */
    	input = ast_skip_blanks(input);
    
    	/* make sure the output buffer is initilized */
    	*orig_output = '\0';
    
    	/* make room for '\0' at the end of the output buffer */
    
    	--outputsize;
    
    	/* no data at all or no display name? */
    	if (!input || *input == '<') {
    		return input;
    	}
    
    David Vossel's avatar
    David Vossel committed
    
    	/* quoted-string rules */
    	if (input[0] == '"') {
    		input++; /* skip the first " */
    
    
    		for (; *input; ++input) {
    
    David Vossel's avatar
    David Vossel committed
    			if (*input == '"') {  /* end of quoted-string */
    				break;
    			} else if (*input == 0x5c) { /* quoted-pair = "\" (%x00-09 / %x0B-0C / %x0E-7F) */
    
    				++input;
    				if (!*input) {
    					break;
    				}
    				if ((unsigned char) *input > 0x7f || *input == 0xa || *input == 0xd) {
    
    David Vossel's avatar
    David Vossel committed
    					continue;  /* not a valid quoted-pair, so skip it */
    				}
    
    			} else if ((*input != 0x9 && (unsigned char) *input < 0x20)
    				|| *input == 0x7f) {
    
    David Vossel's avatar
    David Vossel committed
    				continue; /* skip this invalid character. */
    			}
    
    
    			if (0 < outputsize) {
    				/* We still have room for the output display-name. */
    				*output++ = *input;
    				--outputsize;
    			}
    
    David Vossel's avatar
    David Vossel committed
    		}
    
    		/* if this is successful, input should be at the ending quote */
    
    David Vossel's avatar
    David Vossel committed
    			ast_log(LOG_WARNING, "No ending quote for display-name was found\n");
    			*orig_output = '\0';
    			return orig_input;
    		}
    
    		/* make sure input is past the last quote */
    
    David Vossel's avatar
    David Vossel committed
    
    
    David Vossel's avatar
    David Vossel committed
    		*output = '\0';
    	} else {  /* either an addr-spec or tokenLWS-combo */
    
    		for (; *input; ++input) {
    
    David Vossel's avatar
    David Vossel committed
    			/* token or WSP (without LWS) */
    			if ((*input >= '0' && *input <= '9') || (*input >= 'A' && *input <= 'Z')
    				|| (*input >= 'a' && *input <= 'z') || *input == '-' || *input == '.'
    				|| *input == '!' || *input == '%' || *input == '*' || *input == '_'
    				|| *input == '+' || *input == '`' || *input == '\'' || *input == '~'
    				|| *input == 0x9 || *input == ' ') {
    
    				if (0 < outputsize) {
    					/* We still have room for the output display-name. */
    					*output++ = *input;
    					--outputsize;
    				}
    
    David Vossel's avatar
    David Vossel committed
    			} else if (*input == '<') {   /* end of tokenLWS-combo */
    				/* we could assert that the previous char is LWS, but we don't care */
    				break;
    			} else if (*input == ':') {
    				/* This invalid character which indicates this is addr-spec rather than display-name. */
    				*orig_output = '\0';
    				return orig_input;
    			} else {         /* else, invalid character we can skip. */
    				continue;    /* skip this character */
    			}
    		}
    
    
    		if (*input != '<') {   /* if we never found the start of addr-spec then this is invalid */
    			*orig_output = '\0';
    			return orig_input;
    		}
    
    
    		/* terminate output while trimming any trailing whitespace */
    
    David Vossel's avatar
    David Vossel committed
    		do {
    			*output-- = '\0';
    
    		} while (orig_output <= output && (*output == 0x9 || *output == ' '));
    
    David Vossel's avatar
    David Vossel committed
    	}
    
    	return input;
    }
    
    
    David Vossel's avatar
    David Vossel committed
    AST_TEST_DEFINE(get_calleridname_test)
    {
    	int res = AST_TEST_PASS;
    
    	const char *in1 = " \" quoted-text internal \\\" quote \"<stuff>";
    
    David Vossel's avatar
    David Vossel committed
    	const char *in2 = " token text with no quotes <stuff>";
    	const char *overflow1 = " \"quoted-text overflow 1234567890123456789012345678901234567890\" <stuff>";
    
    	const char *overflow2 = " non-quoted text overflow 1234567890123456789012345678901234567890 <stuff>";
    
    David Vossel's avatar
    David Vossel committed
    	const char *noendquote = " \"quoted-text no end <stuff>";
    
    	const char *addrspec = " sip:blah@blah";
    
    	const char *no_quotes_no_brackets = "blah@blah";
    
    David Vossel's avatar
    David Vossel committed
    	const char *after_dname;
    	char dname[40];
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "sip_get_calleridname_test";
    
    David Vossel's avatar
    David Vossel committed
    		info->summary = "decodes callerid name from sip header";
    		info->description = "Decodes display-name field of sip header.  Checks for valid output and expected failure cases.";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	/* quoted-text with backslash escaped quote */
    	after_dname = get_calleridname(in1, dname, sizeof(dname));
    
    	ast_test_status_update(test, "display-name1: %s\nafter: %s\n", dname, after_dname);
    
    David Vossel's avatar
    David Vossel committed
    	if (strcmp(dname, " quoted-text internal \" quote ")) {
    
    		ast_test_status_update(test, "display-name1 test failed\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* token text */
    	after_dname = get_calleridname(in2, dname, sizeof(dname));
    
    	ast_test_status_update(test, "display-name2: %s\nafter: %s\n", dname, after_dname);
    
    David Vossel's avatar
    David Vossel committed
    	if (strcmp(dname, "token text with no quotes")) {
    
    		ast_test_status_update(test, "display-name2 test failed\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* quoted-text buffer overflow */
    	after_dname = get_calleridname(overflow1, dname, sizeof(dname));
    
    	ast_test_status_update(test, "overflow display-name1: %s\nafter: %s\n", dname, after_dname);
    
    	if (strcmp(dname, "quoted-text overflow 123456789012345678")) {
    
    		ast_test_status_update(test, "overflow display-name1 test failed\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* non-quoted-text buffer overflow */
    	after_dname = get_calleridname(overflow2, dname, sizeof(dname));
    	ast_test_status_update(test, "overflow display-name2: %s\nafter: %s\n", dname, after_dname);
    	if (strcmp(dname, "non-quoted text overflow 12345678901234")) {
    		ast_test_status_update(test, "overflow display-name2 test failed\n");
    		res = AST_TEST_FAIL;
    	}
    
    
    David Vossel's avatar
    David Vossel committed
    	/* quoted-text buffer with no terminating end quote */
    	after_dname = get_calleridname(noendquote, dname, sizeof(dname));
    
    	ast_test_status_update(test, "noendquote display-name1: %s\nafter: %s\n", dname, after_dname);
    
    David Vossel's avatar
    David Vossel committed
    	if (*dname != '\0' && after_dname != noendquote) {
    
    		ast_test_status_update(test, "no end quote for quoted-text display-name failed\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    	/* addr-spec rather than display-name. */
    	after_dname = get_calleridname(addrspec, dname, sizeof(dname));
    
    	ast_test_status_update(test, "addr-spec display-name1: %s\nafter: %s\n", dname, after_dname);
    
    David Vossel's avatar
    David Vossel committed
    	if (*dname != '\0' && after_dname != addrspec) {
    
    		ast_test_status_update(test, "detection of addr-spec failed\n");
    
    David Vossel's avatar
    David Vossel committed
    		res = AST_TEST_FAIL;
    	}
    
    
    	/* no quotes, no brackets */
    	after_dname = get_calleridname(no_quotes_no_brackets, dname, sizeof(dname));
    	ast_test_status_update(test, "no_quotes_no_brackets display-name1: %s\nafter: %s\n", dname, after_dname);
    	if (*dname != '\0' && after_dname != no_quotes_no_brackets) {
    		ast_test_status_update(test, "detection of addr-spec failed\n");
    		res = AST_TEST_FAIL;
    	}
    
    
    David Vossel's avatar
    David Vossel committed
    	return res;
    }
    
    David Vossel's avatar
    David Vossel committed
    
    
    int get_name_and_number(const char *hdr, char **name, char **number)
    {
    	char header[256];
    
    	char *tmp_number = NULL;
    
    	char *hostport = NULL;
    
    	char *dummy = NULL;
    
    	if (!name || !number || ast_strlen_zero(hdr)) {
    		return -1;
    	}
    
    	*number = NULL;
    	*name = NULL;
    	ast_copy_string(header, hdr, sizeof(header));
    
    	/* strip the display-name portion off the beginning of the header. */
    	get_calleridname(header, tmp_name, sizeof(tmp_name));
    
    	/* get uri within < > brackets */
    	tmp_number = get_in_brackets(header);
    
    	/* parse out the number here */
    
    	if (parse_uri(tmp_number, "sip:,sips:", &tmp_number, &dummy, &hostport, NULL) || ast_strlen_zero(tmp_number)) {
    
    		ast_log(LOG_ERROR, "can not parse name and number from sip header.\n");
    		return -1;
    	}
    
    	/* number is not option, and must be present at this point */
    	*number = ast_strdup(tmp_number);
    
    	ast_uri_decode(*number, ast_uri_sip_user);
    
    
    	/* name is optional and may not be present at this point */
    	if (!ast_strlen_zero(tmp_name)) {
    		*name = ast_strdup(tmp_name);
    	}
    
    	return 0;
    }
    
    
    AST_TEST_DEFINE(get_name_and_number_test)
    {
    	int res = AST_TEST_PASS;
    	char *name = NULL;
    	char *number = NULL;
    	const char *in1 = "NAME <sip:NUMBER@place>";
    	const char *in2 = "\"NA><ME\" <sip:NUMBER@place>";
    	const char *in3 = "NAME";
    	const char *in4 = "<sip:NUMBER@place>";
    	const char *in5 = "This is a screwed up string <sip:LOLCLOWNS<sip:>@place>";
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "sip_get_name_and_number_test";
    
    		info->summary = "Tests getting name and number from sip header";
    		info->description =
    				"Runs through various test situations in which a name and "
    				"and number can be retrieved from a sip header.";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	/* Test 1. get name and number */
    	number = name = NULL;
    	if ((get_name_and_number(in1, &name, &number)) ||
    		strcmp(name, "NAME") ||
    		strcmp(number, "NUMBER")) {
    
    		ast_test_status_update(test, "Test 1, simple get name and number failed.\n");
    		res = AST_TEST_FAIL;
    	}
    	ast_free(name);
    	ast_free(number);
    
    	/* Test 2. get quoted name and number */
    	number = name = NULL;
    	if ((get_name_and_number(in2, &name, &number)) ||
    		strcmp(name, "NA><ME") ||
    		strcmp(number, "NUMBER")) {
    
    		ast_test_status_update(test, "Test 2, get quoted name and number failed.\n");
    		res = AST_TEST_FAIL;
    	}
    	ast_free(name);
    	ast_free(number);
    
    	/* Test 3. name only */
    	number = name = NULL;
    	if (!(get_name_and_number(in3, &name, &number))) {
    
    		ast_test_status_update(test, "Test 3, get name only was expected to fail but did not.\n");