Index: channels/sip/reqresp_parser.c =================================================================== --- channels/sip/reqresp_parser.c (revision 249060) +++ channels/sip/reqresp_parser.c (working copy) @@ -27,11 +27,13 @@ #include "include/sip_utils.h" #include "include/reqresp_parser.h" + /*! \brief * parses a URI in its components.*/ -int parse_uri(char *uri, const char *scheme, char **ret_name, char **pass, char **domain, char **port, char **transport) +int parse_uri_full(char *uri, const char *scheme, char **user, char **pass, char **host, char **port, struct uriparams *params, char **headers, char **residue) { - char *name = NULL; - char *tmp; /* used as temporary place holder */ + char *userinfo = NULL; + char *parameters = NULL; + char *c = NULL; int error = 0; /* check for valid input */ @@ -39,16 +41,6 @@ return -1; } - /* strip [?headers] from end of uri */ - if ((tmp = strrchr(uri, '?'))) { - *tmp = '\0'; - } - - /* init field as required */ - if (pass) - *pass = ""; - if (port) - *port = ""; if (scheme) { int l; char *scheme2 = ast_strdupa(scheme); @@ -65,57 +57,385 @@ error = -1; } } - if (transport) { - char *t, *type = ""; - *transport = ""; - if ((t = strstr(uri, "transport="))) { - strsep(&t, "="); - if ((type = strsep(&t, ";"))) { - *transport = type; - } - } - } - if (!domain) { - /* if we don't want to split around domain, keep everything as a name, - * so we need to do nothing here, except remember why. - */ + if (!host) { + /* if we don't want to split around host, keep everything as a userinfo - cos thats how old parse_uri operated*/ + userinfo = uri; } else { - /* store the result in a temp. variable to avoid it being - * overwritten if arguments point to the same place. - */ - char *c, *dom = ""; - - if ((c = strchr(uri, '@')) == NULL) { + char *hostport; + if ((c = strchr(uri, '@'))) { + *c++ = '\0'; + hostport = c; + userinfo = uri; + uri = hostport; /* userinfo can contain ? and ; chars so step forward before looking for params and headers */ + } else { /* domain-only URI, according to the SIP RFC. */ - dom = uri; - name = ""; - } else { - *c++ = '\0'; - dom = c; - name = uri; + hostport = uri; + userinfo = ""; } - /* Remove parameters in domain and name */ - dom = strsep(&dom, ";"); - name = strsep(&name, ";"); - - if (port && (c = strchr(dom, ':'))) { /* Remove :port */ + if (port && (c = strchr(hostport, ':'))) { /* Remove :port */ *c++ = '\0'; *port = c; + uri = c; + } else if (port) { + *port = ""; } - if (pass && (c = strchr(name, ':'))) { /* user:password */ + + *host = hostport; + } + + if (pass && (c = strchr(userinfo, ':'))) { /* user:password */ *c++ = '\0'; *pass = c; + } else if (pass) { + *pass = ""; } - *domain = dom; + + if (user) + *user = userinfo; + + + parameters = uri; + /* strip [?headers] from end of uri */ + if (headers && (c = strrchr(uri, '?'))) { + *c++ = '\0'; + *headers = c; + uri = c; + + if ((c = strrchr(uri, ';'))) { + *c++ = '\0'; + uri = c; /* residue */ } - if (ret_name) /* same as for domain, store the result only at the end */ - *ret_name = name; + } else if (headers) { + *headers = ""; + } + if (params) { + char *rem=parameters; /* unparsed or unrecognised remainder */ + char *label; + char *value; + char *end=strchr(parameters,'\0'); + + params->transport = ""; + params->user = ""; + params->method = ""; + params->ttl = ""; + params->maddr = ""; + params->lr = ""; + + if ((c = strchr(parameters, ';'))) { + *c++='\0'; + parameters=c; + } else { + parameters=end; + } + + while ((value = strchr(parameters, '='))) { + *value++ = '\0'; + label = parameters; + if ((c = strchr(value, ';'))) { + *c++='\0'; + parameters=c; + } else { + parameters=end; + } + + 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")) { + params->ttl=value; + rem = parameters; + } else if (!strcmp(label,"maddr")) { + params->maddr=value; + rem = parameters; + } else if (!strcmp(label,"lr")) { + params->lr=value; + rem = parameters; + } else { + value--; + *value = '='; + if(c) { + c--; + *c = ';'; + } + } + } + if (rem > uri) { /* no headers */ + uri = rem; + } + } + + if (residue) { + *residue = uri; + } + return error; } + +AST_TEST_DEFINE(sip_parse_uri_fully_test) +{ + int res = AST_TEST_PASS; + char uri[1024]; + char *user, *pass, *host, *port, *headers, *residue; + struct uriparams params; + + struct testdata { + char *desc; + char *uri; + char **userptr; + char **passptr; + char **hostptr; + char **portptr; + char **headersptr; + char **residueptr; + struct uriparams *paramsptr; + char *user; + char *pass; + char *host; + char *port; + char *headers; + char *residue; + struct uriparams params; + struct testdata *next; + }; + + struct testdata *testdataptr; + + /* Tests */ + struct testdata testdata1; + struct testdata testdata2; + struct testdata testdata3; + struct testdata testdata4; + struct testdata testdata5; + struct testdata testdata6; + struct testdata testdata7; + + /* Link the tests with testdataN.next=testdataN+1 */ + testdata1.next = &testdata2; + testdata2.next = &testdata3; + testdata3.next = &testdata4; + testdata4.next = &testdata5; + testdata5.next = &testdata6; + testdata6.next = &testdata7; + testdata7.next = NULL; + + + + /* Test Descriptions */ + + testdata1.desc = "no headers"; + testdata2.desc = "with headers"; + testdata3.desc = "difficult user"; + testdata4.desc = "difficult pass"; + testdata5.desc = "difficult host"; + testdata6.desc = "difficult params near transport"; + testdata7.desc = "difficult params near headers"; + + /* URIs to be parsed */ + + testdata1.uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=residue"; + testdata2.uri = "sip:user:secret@host:5060;param=discard;transport=tcp;param2=discard2?header=blah&header2=blah2;param3=residue"; + testdata3.uri = "sip:-_.!~*'()&=+$,;?/:secret@host:5060;transport=tcp"; + testdata4.uri = "sip:user:-_.!~*'()&=+$,@host:5060;transport=tcp"; + testdata5.uri = "sip:user:secret@1-1.a-1.:5060;transport=tcp"; + testdata6.uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$;transport=tcp"; + testdata7.uri = "sip:user:secret@host:5060;-_.!~*'()[]/:&+$=-_.!~*'()[]/:&+$?header=blah&header2=blah2;-_.!~*'()[]/:&+$=residue"; + + /* Pointer Inputs */ + + /* Either stringptr = &string for a response or stringptr = NULL for no response */ + /* TODO add tests with no response */ + + testdata1.userptr = &user; + testdata2.userptr = &user; + testdata3.userptr = &user; + testdata4.userptr = &user; + testdata5.userptr = &user; + testdata6.userptr = &user; + testdata7.userptr = &user; + + testdata1.passptr = &pass; + testdata2.passptr = &pass; + testdata3.passptr = &pass; + testdata4.passptr = &pass; + testdata5.passptr = &pass; + testdata6.passptr = &pass; + testdata7.passptr = &pass; + + testdata1.hostptr = &host; + testdata2.hostptr = &host; + testdata3.hostptr = &host; + testdata4.hostptr = &host; + testdata5.hostptr = &host; + testdata6.hostptr = &host; + testdata7.hostptr = &host; + + testdata1.portptr = &port; + testdata2.portptr = &port; + testdata3.portptr = &port; + testdata4.portptr = &port; + testdata5.portptr = &port; + testdata6.portptr = &port; + testdata7.portptr = &port; + + testdata1.headersptr = &headers; + testdata2.headersptr = &headers; + testdata3.headersptr = &headers; + testdata4.headersptr = &headers; + testdata5.headersptr = &headers; + testdata6.headersptr = &headers; + testdata7.headersptr = &headers; + + testdata1.residueptr = &residue; + testdata2.residueptr = &residue; + testdata3.residueptr = &residue; + testdata4.residueptr = &residue; + testdata5.residueptr = &residue; + testdata6.residueptr = &residue; + testdata7.residueptr = &residue; + + testdata1.paramsptr = ¶ms; + testdata2.paramsptr = ¶ms; + testdata3.paramsptr = ¶ms; + testdata4.paramsptr = ¶ms; + testdata5.paramsptr = ¶ms; + testdata6.paramsptr = ¶ms; + testdata7.paramsptr = ¶ms; + + + /* Expected Results */ + + testdata1.user = "user"; + testdata2.user = "user"; + testdata3.user = "-_.!~*'()&=+$,;?/"; + testdata4.user = "user"; + testdata5.user = "user"; + testdata6.user = "user"; + testdata7.user = "user"; + + testdata1.pass = "secret"; + testdata2.pass = "secret"; + testdata3.pass = "secret"; + testdata4.pass = "-_.!~*'()&=+$,"; + testdata5.pass = "secret"; + testdata6.pass = "secret"; + testdata7.pass = "secret"; + + testdata1.host = "host"; + testdata2.host = "host"; + testdata3.host = "host"; + testdata4.host = "host"; + testdata5.host = "1-1.a-1."; + testdata6.host = "host"; + testdata7.host = "host"; + + testdata1.port = "5060"; + testdata2.port = "5060"; + testdata3.port = "5060"; + testdata4.port = "5060"; + testdata5.port = "5060"; + testdata6.port = "5060"; + testdata7.port = "5060"; + + testdata1.headers = ""; + testdata2.headers = "header=blah&header2=blah2"; + testdata3.headers = ""; + testdata4.headers = ""; + testdata5.headers = ""; + testdata6.headers = ""; + testdata7.headers = "header=blah&header2=blah2"; + + testdata1.residue = "param2=residue"; + testdata2.residue = "param3=residue"; + testdata3.residue = ""; + testdata4.residue = ""; + testdata5.residue = ""; + testdata6.residue = ""; + testdata7.residue = "-_.!~*'()[]/:&+$=residue"; + + testdata1.params.transport = "tcp"; + testdata2.params.transport = "tcp"; + testdata3.params.transport = "tcp"; + testdata4.params.transport = "tcp"; + testdata5.params.transport = "tcp"; + testdata6.params.transport = "tcp"; + testdata7.params.transport = ""; + + /* An example of another parameter - not all parameters checked */ + testdata1.params.user = ""; + testdata2.params.user = ""; + testdata3.params.user = ""; + testdata4.params.user = ""; + testdata5.params.user = ""; + testdata6.params.user = ""; + testdata7.params.user = ""; + + + + switch (cmd) { + case TEST_INIT: + info->name = "sip_uri_full_parse_test"; + info->category = "channels/chan_sip/"; + 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; + } + + testdataptr = &testdata1; + while(1) { + user = pass = host = port = headers = residue = NULL; + params.transport = params.user = params.method = params.ttl = params.maddr = params.lr = NULL; + ast_copy_string(uri,testdataptr->uri,sizeof(uri)); + if (parse_uri_full(uri, "sip:,sips:", testdataptr->userptr, testdataptr->passptr, testdataptr->hostptr, testdataptr->portptr, testdataptr->paramsptr, testdataptr->headersptr, testdataptr->residueptr) || + ((testdataptr->userptr) && strcmp(testdataptr->user, user)) || + ((testdataptr->passptr) && strcmp(testdataptr->pass, pass)) || + ((testdataptr->hostptr) && strcmp(testdataptr->host, host)) || + ((testdataptr->portptr) && strcmp(testdataptr->port, port)) || + ((testdataptr->headersptr) && strcmp(testdataptr->headers, headers)) || + ((testdataptr->residueptr) && strcmp(testdataptr->residue, residue)) || + ((testdataptr->paramsptr) && strcmp(testdataptr->params.transport,params.transport)) || + ((testdataptr->paramsptr) && strcmp(testdataptr->params.user,params.user)) + ) { + ast_test_status_update(test, "Sub-Test: %s, failed.\n", testdataptr->desc); + res = AST_TEST_FAIL; + } + if (!testdataptr->next) { + break; + } + testdataptr = testdataptr->next; + } + + + return res; +} + + +int parse_uri(char *uri, const char *scheme, char **user, char **pass, char **host, char **port, char **transport) { + int ret; + char *headers; + struct uriparams params; + + headers = NULL; + ret = parse_uri_full(uri, scheme, user, pass, host, port, ¶ms, &headers, NULL); + if (transport) { + *transport=params.transport; + } + return ret; +} + AST_TEST_DEFINE(sip_parse_uri_test) { int res = AST_TEST_PASS; @@ -161,7 +481,7 @@ strcmp(domain, "host") || !ast_strlen_zero(port) || strcmp(transport, "tcp")) { - ast_test_status_update(test, "Test 2: uri with addtion of tcp transport failed. \n"); + ast_test_status_update(test, "Test 2: uri with addtion of tcp transport failed. name=%s,pass=%s,domain=%s,port=%s,transport=%s\n", name,pass,domain,port,transport); res = AST_TEST_FAIL; } @@ -185,7 +505,7 @@ strcmp(domain, "host") || strcmp(port, "port") || strcmp(transport, "tcp")) { - ast_test_status_update(test, "Test 4: add port and unparsed header field failed.\n"); + ast_test_status_update(test, "Test 4: add port and unparsed header field failed. %s/%s/%s/%s/%s\n",name,pass,domain,port,transport); res = AST_TEST_FAIL; } @@ -222,7 +542,7 @@ const char *get_calleridname(const char *input, char *output, size_t outputsize) { /* From RFC3261: - * + * * From = ( "From" / "f" ) HCOLON from-spec * from-spec = ( name-addr / addr-spec ) *( SEMI from-param ) * name-addr = [ display-name ] LAQUOT addr-spec RAQUOT @@ -535,44 +855,82 @@ return res; } -char *get_in_brackets(char *tmp) +int get_in_brackets_full(char *tmp,char **out,char **residue) { const char *parse = tmp; char *first_bracket; + char *second_bracket; + if (out) { + *out = ""; + } + if (residue) { + *residue = ""; + } + if (ast_strlen_zero(tmp)) { - return tmp; + return 1; } /* * Skip any quoted text until we find the part in brackets. - * On any error give up and return the full string. + * On any error give up and return -1 */ while ( (first_bracket = strchr(parse, '<')) ) { char *first_quote = strchr(parse, '"'); - - if (!first_quote || first_quote > first_bracket) + first_bracket++; + 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; + return -1; } parse++; } + + /* If no first bracket then still look for a second bracket as some other parsing functions + may overwrite first bracket with NULL when terminating a token based display-name. As this + only affects token based display-names there is no danger of brackets being in quotes */ if (first_bracket) { - char *second_bracket = strchr(first_bracket + 1, '>'); - if (second_bracket) { - *second_bracket = '\0'; - tmp = first_bracket + 1; + parse = first_bracket; } else { + parse = tmp; + } + + if ((second_bracket = strchr(parse, '>'))) { + *second_bracket++ = '\0'; + if (out) { + *out = first_bracket; + } + if (residue) { + *residue = second_bracket; + } + return 0; + } + + if ((first_bracket)) { ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp); + return -1; } + + if (out) { + *out = tmp; } + return 1; +} +char *get_in_brackets(char *tmp) +{ + char *out; + if((get_in_brackets_full(tmp,&out,NULL))) { return tmp; + } else { + return out; + } } AST_TEST_DEFINE(get_in_brackets_test) @@ -645,12 +1003,209 @@ return res; } + +int parse_name_andor_addr(char *uri, const char *scheme, char **name, char **user, char **pass, char **host, char **port, struct uriparams *params, char **headers, char **residue) +{ + char buf[1024]; + char **residue2=residue; + int ret; + if (name) { + get_calleridname(uri,buf,sizeof(buf)); + *name = buf; + } + ret = get_in_brackets_full(uri,&uri,residue); + if (ret == 0) { /* uri is in brackets so do not treat unknown trailing uri parameters as potential messageheader parameters */ + *residue = *residue + 1; /* step over the first semicolon so as per parse uri residue */ + residue2=NULL; + } + return parse_uri_full(uri, scheme, user, pass, host, port, params, headers, residue2); +} + +AST_TEST_DEFINE(parse_name_andor_addr_test) +{ + int res = AST_TEST_PASS; + char uri[1024]; + char *name, *user, *pass, *host, *port, *headers, *residue; + struct uriparams params; + + struct testdata { + char *desc; + char *uri; + char **nameptr; + char **userptr; + char **passptr; + char **hostptr; + char **portptr; + char **headersptr; + char **residueptr; + struct uriparams *paramsptr; + char *name; + char *user; + char *pass; + char *host; + char *port; + char *headers; + char *residue; + struct uriparams params; + struct testdata *next; + }; + + struct testdata *testdataptr; + + /* Tests */ + struct testdata testdata1; + struct testdata testdata2; + struct testdata testdata3; + + /* Link the tests with testdataN.next=&testdataN+1 */ + testdata1.next = &testdata2; + testdata2.next = &testdata3; + testdata3.next = NULL; + + + + /* Test Descriptions */ + + testdata1.desc = "quotes and brackets"; + testdata2.desc = "noquotes"; + testdata3.desc = "nobrackets"; + + /* URIs to be parsed */ + + testdata1.uri = "\"name :@;?&\" ;tag=tag"; + testdata2.uri = "givenname familyname ;expires=3600"; + testdata3.uri = "sip:user:secret@host:5060;param=discard;transport=tcp;q=1"; + + /* Pointer Inputs */ + + /* Either stringptr = &string for a response or stringptr = NULL for no response */ + /* TODO add tests with no response */ + + testdata1.nameptr = &name; + testdata2.nameptr = &name; + testdata3.nameptr = &name; + + testdata1.userptr = &user; + testdata2.userptr = &user; + testdata3.userptr = &user; + + testdata1.passptr = &pass; + testdata2.passptr = &pass; + testdata3.passptr = &pass; + + testdata1.hostptr = &host; + testdata2.hostptr = &host; + testdata3.hostptr = &host; + + testdata1.portptr = &port; + testdata2.portptr = &port; + testdata3.portptr = &port; + + + testdata1.headersptr = &headers; + testdata2.headersptr = &headers; + testdata3.headersptr = &headers; + + testdata1.residueptr = &residue; + testdata2.residueptr = &residue; + testdata3.residueptr = &residue; + + testdata1.paramsptr = ¶ms; + testdata2.paramsptr = ¶ms; + testdata3.paramsptr = ¶ms; + + + /* Expected Results */ + + testdata1.name = "name :@;?&"; + testdata2.name = "givenname familyname"; + testdata3.name = ""; + + testdata1.user = "user"; + testdata2.user = "user"; + testdata3.user = "user"; + + testdata1.pass = "secret"; + testdata2.pass = "secret"; + testdata3.pass = "secret"; + + testdata1.host = "host"; + testdata2.host = "host"; + testdata3.host = "host"; + + testdata1.port = "5060"; + testdata2.port = "5060"; + testdata3.port = "5060"; + + testdata1.headers = ""; + testdata2.headers = ""; + testdata3.headers = ""; + + testdata1.residue = "tag=tag"; + testdata2.residue = "expires=3600"; + testdata3.residue = "q=1"; + + testdata1.params.transport = "tcp"; + testdata2.params.transport = "tcp"; + testdata3.params.transport = "tcp"; + + /* An example of another parameter - not all parameters checked */ + testdata1.params.user = ""; + testdata2.params.user = ""; + testdata3.params.user = ""; + + + + switch (cmd) { + case TEST_INIT: + info->name = "parse_name_andor_addr_test"; + info->category = "channels/chan_sip/"; + info->summary = "tests parsing of name_andor_addr abnf structure"; + info->description = + "Tests parsing of abnf name-andor-addr = name-addr / addr-spec " + "Verifies output matches expected behavior."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + testdataptr = &testdata1; + while(1) { + user = pass = host = port = headers = residue = NULL; + params.transport = params.user = params.method = params.ttl = params.maddr = params.lr = NULL; + ast_copy_string(uri,testdataptr->uri,sizeof(uri)); + if (parse_name_andor_addr(uri, "sip:,sips:", testdataptr->nameptr, testdataptr->userptr, testdataptr->passptr, testdataptr->hostptr, testdataptr->portptr, testdataptr->paramsptr, testdataptr->headersptr, testdataptr->residueptr) || + ((testdataptr->nameptr) && strcmp(testdataptr->name, name)) || + ((testdataptr->userptr) && strcmp(testdataptr->user, user)) || + ((testdataptr->passptr) && strcmp(testdataptr->pass, pass)) || + ((testdataptr->hostptr) && strcmp(testdataptr->host, host)) || + ((testdataptr->portptr) && strcmp(testdataptr->port, port)) || + ((testdataptr->headersptr) && strcmp(testdataptr->headers, headers)) || + ((testdataptr->residueptr) && strcmp(testdataptr->residue, residue)) || + ((testdataptr->paramsptr) && strcmp(testdataptr->params.transport,params.transport)) || + ((testdataptr->paramsptr) && strcmp(testdataptr->params.user,params.user)) + ) { + ast_test_status_update(test, "Sub-Test: %s,failed.\n", testdataptr->desc); + res = AST_TEST_FAIL; + } + if (!testdataptr->next) { + break; + } + testdataptr = testdataptr->next; + } + + + 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); + AST_TEST_REGISTER(sip_parse_uri_fully_test); + AST_TEST_REGISTER(parse_name_andor_addr_test); } void sip_request_parser_unregister_tests(void) { @@ -658,4 +1213,6 @@ AST_TEST_UNREGISTER(get_calleridname_test); AST_TEST_UNREGISTER(get_in_brackets_test); AST_TEST_UNREGISTER(get_name_and_number_test); + AST_TEST_UNREGISTER(sip_parse_uri_fully_test); + AST_TEST_UNREGISTER(parse_name_andor_addr_test); }