diff options
author | dvossel <dvossel@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-02-15 15:45:02 +0000 |
---|---|---|
committer | dvossel <dvossel@f38db490-d61c-443f-a65b-d21fe96a405b> | 2010-02-15 15:45:02 +0000 |
commit | d39a409a7e70847fa440f81efb5a926c2bb161f0 (patch) | |
tree | 72b0e2d48f3f92adf9294911b59271a48180b958 | |
parent | e8b816ba2e4bd13fc7731c0adfd62cf34367e374 (diff) |
chan_sip parse code refactoring plus two new unit tests
Code Refactoring Changes
- read_to_parts() moved to reqresp_parser.c and has been renamed as
get_name_and_number()
- get_in_brackets() moved to reqresp_parser.c
- find_closing_quotes() added to sip_utils.h
Logic Changes
- get_name_and_number() now uses parse_uri() and get_calleridname()
for parsing. Before this change only names within quotes were
found, when names not within quotes are possible.
New Unit Tests
-sip_get_name_and_number_test
-sip_get_in_brackets_test
(closes issue #16707)
Reported by: Nick_Lewis
Patches:
issue16706.diff uploaded by dvossel (license 671)
Review: https://reviewboard.asterisk.org/r/499/
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@246627 f38db490-d61c-443f-a65b-d21fe96a405b
-rw-r--r-- | channels/chan_sip.c | 103 | ||||
-rw-r--r-- | channels/sip/include/reqresp_parser.h | 25 | ||||
-rw-r--r-- | channels/sip/include/sip_utils.h | 7 | ||||
-rw-r--r-- | channels/sip/reqresp_parser.c | 259 |
4 files changed, 294 insertions, 100 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c index acad72536..20bc1378a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1234,7 +1234,6 @@ static const char *get_header(const struct sip_request *req, const char *name); static const char *referstatus2str(enum referstatus rstatus) attribute_pure; static int method_match(enum sipmethod id, const char *name); static void parse_copy(struct sip_request *dst, const struct sip_request *src); -static char *get_in_brackets(char *tmp); static const char *find_alias(const char *name, const char *_default); static const char *__get_header(const struct sip_request *req, const char *name, int *start); static int lws2sws(char *msgbuf, int len); @@ -3200,7 +3199,7 @@ static int sip_queryoption(struct ast_channel *chan, int option, void *data, int * optionally with a limit on the search. * start must be past the first quote. */ -static const char *find_closing_quote(const char *start, const char *lim) +const char *find_closing_quote(const char *start, const char *lim) { char last_char = '\0'; const char *s; @@ -3211,54 +3210,6 @@ static const char *find_closing_quote(const char *start, const char *lim) return s; } -/*! \brief Pick out text in brackets from character string - \return pointer to terminated stripped string - \param tmp input string that will be modified - Examples: -\verbatim - "foo" <bar> valid input, returns bar - foo returns the whole string - < "foo ... > returns the string between brackets - < "foo... bogus (missing closing bracket), returns the whole string - XXX maybe should still skip the opening bracket -\endverbatim - */ -static char *get_in_brackets(char *tmp) -{ - const char *parse = tmp; - char *first_bracket; - - /* - * Skip any quoted text until we find the part in brackets. - * On any error give up and return the full string. - */ - while ( (first_bracket = strchr(parse, '<')) ) { - char *first_quote = strchr(parse, '"'); - - if (!first_quote || first_quote > first_bracket) - break; /* no need to look at quoted part */ - /* the bracket is within quotes, so ignore it */ - parse = find_closing_quote(first_quote + 1, NULL); - if (!*parse) { /* not found, return full string ? */ - /* XXX or be robust and return in-bracket part ? */ - ast_log(LOG_WARNING, "No closing quote found in '%s'\n", tmp); - break; - } - parse++; - } - if (first_bracket) { - char *second_bracket = strchr(first_bracket + 1, '>'); - if (second_bracket) { - *second_bracket = '\0'; - tmp = first_bracket + 1; - } else { - ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp); - } - } - - return tmp; -} - /*! \brief Send message with Access-URL header, if this is an HTML URL only! */ static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen) { @@ -16425,54 +16376,6 @@ static struct ast_custom_function sipchaninfo_function = { .read = function_sipchaninfo_read, }; -static int read_to_parts(struct sip_pvt *p, struct sip_request *req, char **name, char **number) -{ - - char to_header[256]; - char *to_name = NULL; - char *to_number = NULL; - char *separator; - - ast_copy_string(to_header, get_header(req, "To"), sizeof(to_header)); - - /* Let's get that number first! */ - to_number = get_in_brackets(to_header); - - if (!strncasecmp(to_number, "sip:", 4)) { - to_number += 4; - } else if (!strncasecmp(to_number, "sips:", 5)) { - to_number += 5; - } else { - ast_log(LOG_WARNING, "Not a SIP URI? (%s)!\n", to_number); - return -1; - } - - /* Remove the host and such since we just want the number */ - if ((separator = strchr(to_number, '@'))) { - *separator = '\0'; - } - - /* We have the number. Let's get the name now. */ - - if (*to_header == '\"') { - to_name = to_header + 1; - if (!(separator = (char *)find_closing_quote(to_name, NULL))) { - ast_log(LOG_NOTICE, "No closing quote in name section of To: header (%s)\n", to_header); - return -1; - } - *separator = '\0'; - } - - if (number) { - *number = ast_strdup(to_number); - } - if (name && !ast_strlen_zero(to_name)) { - *name = ast_strdup(to_name); - } - - return 0; -} - /*! \brief update redirecting information for a channel based on headers * */ @@ -16489,7 +16392,7 @@ static void change_redirecting_information(struct sip_pvt *p, struct sip_request res = get_rdnis(p, req, &redirecting_from_name, &redirecting_from_number, &reason); if (res == -1) { if (is_response) { - read_to_parts(p, req, &redirecting_from_name, &redirecting_from_number); + get_name_and_number(get_header(req, "TO"), &redirecting_from_name, &redirecting_from_number); } else { return; } @@ -16502,7 +16405,7 @@ static void change_redirecting_information(struct sip_pvt *p, struct sip_request if (is_response) { parse_moved_contact(p, req, &redirecting_to_name, &redirecting_to_number, set_call_forward); } else { - read_to_parts(p, req, &redirecting_to_name, &redirecting_to_number); + get_name_and_number(get_header(req, "TO"), &redirecting_to_name, &redirecting_to_number); } if (!ast_strlen_zero(redirecting_from_number)) { diff --git a/channels/sip/include/reqresp_parser.h b/channels/sip/include/reqresp_parser.h index e724c98dd..d322e7e55 100644 --- a/channels/sip/include/reqresp_parser.h +++ b/channels/sip/include/reqresp_parser.h @@ -52,6 +52,31 @@ int parse_uri(char *uri, const char *scheme, char **ret_name, char **pass, char const char *get_calleridname(const char *input, char *output, size_t outputsize); /*! + * \brief Get name and number from sip header + * + * \note name and number point to malloced memory on return and must be + * freed. If name or number is not found, they will be returned as NULL. + * + * \retval 0 success + * \retval -1 failure + */ +int get_name_and_number(const char *hdr, char **name, char **number); + +/*! \brief Pick out text in brackets from character string + * \return pointer to terminated stripped string + * \param tmp input string that will be modified + * + * Examples: + * \verbatim + * "foo" <bar> valid input, returns bar + * foo returns the whole string + * < "foo ... > returns the string between brackets + * < "foo... bogus (missing closing bracket), returns the whole string + * \endverbatim + */ +char *get_in_brackets(char *tmp); + +/*! * \brief register request parsing tests */ void sip_request_parser_register_tests(void); diff --git a/channels/sip/include/sip_utils.h b/channels/sip/include/sip_utils.h index 86eb81933..549f9392f 100644 --- a/channels/sip/include/sip_utils.h +++ b/channels/sip/include/sip_utils.h @@ -33,4 +33,11 @@ */ unsigned int port_str2int(const char *pt, unsigned int standard); +/*! \brief Locate closing quote in a string, skipping escaped quotes. + * optionally with a limit on the search. + * start must be past the first quote. + */ +const char *find_closing_quote(const char *start, const char *lim); + + #endif diff --git a/channels/sip/reqresp_parser.c b/channels/sip/reqresp_parser.c index 803286c6b..df2138596 100644 --- a/channels/sip/reqresp_parser.c +++ b/channels/sip/reqresp_parser.c @@ -24,6 +24,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "include/sip.h" +#include "include/sip_utils.h" #include "include/reqresp_parser.h" /*! \brief * parses a URI in its components.*/ @@ -389,14 +390,272 @@ AST_TEST_DEFINE(get_calleridname_test) return res; } +int get_name_and_number(const char *hdr, char **name, char **number) +{ + char header[256]; + char tmp_name[50] = { 0, }; + char *tmp_number = NULL; + char *domain = 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, &domain, &dummy, 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); + + /* 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->category = "channels/chan_sip/"; + 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"); + res = AST_TEST_FAIL; + } + ast_free(name); + ast_free(number); + + /* Test 4. number only */ + number = name = NULL; + if ((get_name_and_number(in4, &name, &number)) || + !ast_strlen_zero(name) || + strcmp(number, "NUMBER")) { + + ast_test_status_update(test, "Test 4, get number with no name present failed.\n"); + res = AST_TEST_FAIL; + } + ast_free(name); + ast_free(number); + + /* Test 5. malformed string, since number can not be parsed, this should return an error. */ + number = name = NULL; + if (!(get_name_and_number(in5, &name, &number)) || + !ast_strlen_zero(name) || + !ast_strlen_zero(number)) { + + ast_test_status_update(test, "Test 5, processing malformed string failed.\n"); + res = AST_TEST_FAIL; + } + ast_free(name); + ast_free(number); + + /* Test 6. NULL output parameters */ + number = name = NULL; + if (!(get_name_and_number(in5, NULL, NULL))) { + + ast_test_status_update(test, "Test 6, NULL output parameters failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 7. NULL input parameter */ + number = name = NULL; + if (!(get_name_and_number(NULL, &name, &number)) || + !ast_strlen_zero(name) || + !ast_strlen_zero(number)) { + + ast_test_status_update(test, "Test 7, NULL input parameter failed.\n"); + res = AST_TEST_FAIL; + } + ast_free(name); + ast_free(number); + + return res; +} + +char *get_in_brackets(char *tmp) +{ + const char *parse = tmp; + char *first_bracket; + + + if (ast_strlen_zero(tmp)) { + return tmp; + } + + /* + * Skip any quoted text until we find the part in brackets. + * On any error give up and return the full string. + */ + while ( (first_bracket = strchr(parse, '<')) ) { + char *first_quote = strchr(parse, '"'); + + if (!first_quote || first_quote > first_bracket) + break; /* no need to look at quoted part */ + /* the bracket is within quotes, so ignore it */ + parse = find_closing_quote(first_quote + 1, NULL); + if (!*parse) { + ast_log(LOG_WARNING, "No closing quote found in '%s'\n", tmp); + break; + } + parse++; + } + if (first_bracket) { + char *second_bracket = strchr(first_bracket + 1, '>'); + if (second_bracket) { + *second_bracket = '\0'; + tmp = first_bracket + 1; + } else { + ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp); + } + } + + return tmp; +} + +AST_TEST_DEFINE(get_in_brackets_test) +{ + int res = AST_TEST_PASS; + char *in_brackets = "<sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah>"; + char no_name[] = "<sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah>"; + char quoted_string[] = "\"I'm a quote stri><ng\" <sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah>"; + char missing_end_quote[] = "\"I'm a quote string <sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah>"; + char name_no_quotes[] = "name not in quotes <sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah>"; + char no_end_bracket[] = "name not in quotes <sip:name:secret@host:port;transport=tcp?headers=testblah&headers2=blahblah"; + char *uri = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = "sip_get_in_brackets_test"; + info->category = "channels/chan_sip/"; + info->summary = "Tests getting a sip uri in <> brackets within a sip header."; + info->description = + "Runs through various test situations in which a sip uri " + "in angle brackets needs to be retrieved"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Test 1, simple get in brackets */ + if (!(uri = get_in_brackets(no_name)) || !(strcmp(uri, in_brackets))) { + + ast_test_status_update(test, "Test 1, simple get in brackets failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 2, starts with quoted string */ + if (!(uri = get_in_brackets(quoted_string)) || !(strcmp(uri, in_brackets))) { + + ast_test_status_update(test, "Test 2, get in brackets with quoted string in front failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 3, missing end quote */ + if (!(uri = get_in_brackets(missing_end_quote)) || !(strcmp(uri, in_brackets))) { + + ast_test_status_update(test, "Test 3, missing end quote failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 4, starts with a name not in quotes */ + if (!(uri = get_in_brackets(name_no_quotes)) || !(strcmp(uri, in_brackets))) { + + ast_test_status_update(test, "Test 4, passing name not in quotes failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 5, no end bracket, should just return everything after the first '<' */ + if (!(uri = get_in_brackets(no_end_bracket)) || !(strcmp(uri, in_brackets))) { + + ast_test_status_update(test, "Test 5, no end bracket failed.\n"); + res = AST_TEST_FAIL; + } + + /* Test 6, NULL input */ + if ((uri = get_in_brackets(NULL))) { + + ast_test_status_update(test, "Test 6, NULL input failed.\n"); + res = AST_TEST_FAIL; + } + + + return res; +} void sip_request_parser_register_tests(void) { AST_TEST_REGISTER(get_calleridname_test); AST_TEST_REGISTER(sip_parse_uri_test); + AST_TEST_REGISTER(get_in_brackets_test); + AST_TEST_REGISTER(get_name_and_number_test); } void sip_request_parser_unregister_tests(void) { AST_TEST_UNREGISTER(sip_parse_uri_test); AST_TEST_UNREGISTER(get_calleridname_test); + AST_TEST_UNREGISTER(get_in_brackets_test); + AST_TEST_UNREGISTER(get_name_and_number_test); } |