diff --git a/include/asterisk/config.h b/include/asterisk/config.h index 3aef5f14b9..44ba4e4877 100644 --- a/include/asterisk/config.h +++ b/include/asterisk/config.h @@ -1023,6 +1023,26 @@ struct ast_str *ast_variable_list_join(const struct ast_variable *head, const ch struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator, const char *name_value_separator); +/*! + * \brief Parse a string into an ast_variable list. The reverse of ast_variable_list_join + * + * \param input The name-value pair string to parse. + * \param item_separator The string used to separate the list items. + * Only the first character in the string will be used. + * If NULL, "," will be used. + * \param name_value_separator The string used to separate each item's name and value. + * Only the first character in the string will be used. + * If NULL, "=" will be used. + * \param quote_str The string used to quote values. + * Only the first character in the string will be used. + * If NULL, '"' will be used. + * + * \retval A pointer to a list of ast_variables. + * \retval NULL if there was an error or no variables could be parsed. + */ +struct ast_variable *ast_variable_list_from_quoted_string(const char *input, const char *item_separator, + const char *name_value_separator, const char *quote_str); + /*! * \brief Update variable value within a config * diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h index 95332eab17..9cc7f0b538 100644 --- a/include/asterisk/pbx.h +++ b/include/asterisk/pbx.h @@ -1472,6 +1472,28 @@ void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen, * \param used Number of bytes read from the template. (May be NULL) */ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used); + +/*! + * \brief Perform variable/function/expression substitution on an ast_str + * + * \param buf Result will be placed in this buffer. + * \param maxlen -1 if the buffer should not grow, 0 if the buffer + * may grow to any size, and >0 if the buffer should + * grow only to that number of bytes. + * \param c A channel from which to extract values, and to pass + * to any dialplan functions. + * \param headp A channel variables list to also search for variables. + * \param templ Variable template to expand. + * \param used Number of bytes read from the template. (May be NULL) + * \param use_both Normally, if a channel is specified, headp is ignored. + * If this parameter is set to 1 and both a channel and headp + * are specified, the channel will be searched for variables + * first and any not found will be searched for in headp. + */ +void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen, + struct ast_channel *c, struct varshead *headp, const char *templ, + size_t *used, int use_both); + /*! @} */ int ast_extension_patmatch(const char *pattern, const char *data); diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 93983c3e2b..d2c3c82ac8 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -308,6 +308,24 @@ enum ast_strsep_flags { */ char *ast_strsep(char **s, const char sep, uint32_t flags); +/*! + * \brief Like ast_strsep() except you can specify a specific quote character + * + \param s Pointer to address of the string to be processed. + Will be modified and can't be constant. + \param sep A single character delimiter. + \param quote The quote character + \param flags Controls post-processing of the result. + AST_STRSEP_TRIM trims all leading and trailing whitespace from the result. + AST_STRSEP_STRIP does a trim then strips the outermost quotes. You may want + to trim again after the strip. Just OR both the TRIM and STRIP flags. + AST_STRSEP_UNESCAPE unescapes '\' sequences. + AST_STRSEP_ALL does all of the above processing. + \return The next token or NULL if done or if there are more than 8 levels of + nested quotes. + */ +char *ast_strsep_quoted(char **s, const char sep, const char quote, uint32_t flags); + /*! \brief Strip backslash for "escaped" semicolons, the string to be stripped (will be modified). diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h index 9d43560a3e..c1b0797ca3 100644 --- a/include/asterisk/xml.h +++ b/include/asterisk/xml.h @@ -188,6 +188,18 @@ int ast_xml_set_attribute(struct ast_xml_node *node, const char *name, const cha struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue); struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name); +/*! + * \brief Find a direct child element by name. + * \param parent_node This is the parent node to search. + * \param name Node name to find. + * \param attrname attribute name to match (if NULL it won't be matched). + * \param attrvalue attribute value to match (if NULL it won't be matched). + * \retval NULL if not found. + * \return The node on success. + */ +#define ast_xml_find_child_element(_parent_node, _name, _attrname, _attrvalue) \ + ast_xml_find_element(ast_xml_node_get_children(_parent_node), _name, _attrname, _attrvalue) + /*! * \brief Get the prefix of a namespace. * \param ns The namespace @@ -248,6 +260,17 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node); * \brief Dump the specified document to a file. */ int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc); +/*! + * \brief Dump the specified document to a buffer + * + * \param doc The XML doc to dump + * \param buffer A pointer to a char * to receive the address of the results + * \param length A pointer to an int to receive the length of the results + * + * \note The result buffer must be freed with ast_xml_free_text(). + */ +void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length); + /*! * \brief Free the XPath results * \param results The XPath results object to dispose of diff --git a/main/config.c b/main/config.c index 122e7aad48..0e27d76640 100644 --- a/main/config.c +++ b/main/config.c @@ -722,11 +722,12 @@ struct ast_str *ast_variable_list_join(const struct ast_variable *head, const ch return local_str; } -struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator, - const char *name_value_separator) +struct ast_variable *ast_variable_list_from_quoted_string(const char *input, const char *item_separator, + const char *name_value_separator, const char *quote_str) { char item_sep; char nv_sep; + char quote; struct ast_variable *new_list = NULL; struct ast_variable *new_var = NULL; char *item_string; @@ -740,11 +741,22 @@ struct ast_variable *ast_variable_list_from_string(const char *input, const char item_sep = ast_strlen_zero(item_separator) ? ',' : item_separator[0]; nv_sep = ast_strlen_zero(name_value_separator) ? '=' : name_value_separator[0]; + quote = ast_strlen_zero(quote_str) ? '"' : quote_str[0]; item_string = ast_strip(ast_strdupa(input)); - while ((item = ast_strsep(&item_string, item_sep, AST_STRSEP_ALL))) { - item_name = ast_strsep(&item, nv_sep, AST_STRSEP_ALL); - item_value = ast_strsep(&item, nv_sep, AST_STRSEP_ALL); + while ((item = ast_strsep_quoted(&item_string, item_sep, quote, AST_STRSEP_ALL))) { + item_name = ast_strsep_quoted(&item, nv_sep, quote, AST_STRSEP_ALL); + if (!item_name) { + ast_variables_destroy(new_list); + return NULL; + } + + item_value = ast_strsep_quoted(&item, nv_sep, quote, AST_STRSEP_ALL); + if (!item_value) { + ast_variables_destroy(new_list); + return NULL; + } + new_var = ast_variable_new(item_name, item_value, ""); if (!new_var) { ast_variables_destroy(new_list); @@ -755,6 +767,12 @@ struct ast_variable *ast_variable_list_from_string(const char *input, const char return new_list; } +struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator, + const char *name_value_separator) +{ + return ast_variable_list_from_quoted_string(input, item_separator, name_value_separator, NULL); +} + const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var) { const char *tmp; diff --git a/main/datastore.c b/main/datastore.c index f37ee6c4d4..d5adfae9c7 100644 --- a/main/datastore.c +++ b/main/datastore.c @@ -69,6 +69,10 @@ int ast_datastore_free(struct ast_datastore *datastore) { int res = 0; + if (!datastore) { + return 0; + } + /* Using the destroy function (if present) destroy the data */ if (datastore->info->destroy != NULL && datastore->data != NULL) { datastore->info->destroy(datastore->data); diff --git a/main/pbx_variables.c b/main/pbx_variables.c index 1f78a59956..45b7ca0984 100644 --- a/main/pbx_variables.c +++ b/main/pbx_variables.c @@ -394,7 +394,9 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru return ret; } -void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used) +void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen, + struct ast_channel *c, struct varshead *headp, const char *templ, + size_t *used, int use_both) { /* Substitutes variables into buf, based on string templ */ const char *whereweare; @@ -501,7 +503,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str /* Store variable name expression to lookup. */ ast_str_set_substr(&substr1, 0, vars, len); - ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len); + ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", + ast_str_buffer(substr1), vars, len); /* Substitute if necessary */ if (needsub) { @@ -511,7 +514,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str continue; } } - ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL); + ast_str_substitute_variables_full2(&substr2, 0, c, headp, + ast_str_buffer(substr1), NULL, use_both); finalvars = ast_str_buffer(substr2); } else { finalvars = ast_str_buffer(substr1); @@ -520,31 +524,48 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str parse_variable_name(finalvars, &offset, &offset2, &isfunction); if (isfunction) { /* Evaluate function */ - if (c || !headp) { + res = -1; + if (c) { res = ast_func_read2(c, finalvars, &substr3, 0); - } else { + ast_debug(2, "Function %s result is '%s' from channel\n", + finalvars, res ? "" : ast_str_buffer(substr3)); + } + if (!c || (c && res < 0 && use_both)) { struct varshead old; struct ast_channel *bogus; bogus = ast_dummy_channel_alloc(); if (bogus) { old = *ast_channel_varshead(bogus); - *ast_channel_varshead(bogus) = *headp; + if (headp) { + *ast_channel_varshead(bogus) = *headp; + } res = ast_func_read2(bogus, finalvars, &substr3, 0); /* Don't deallocate the varshead that was passed in */ - *ast_channel_varshead(bogus) = old; + if (headp) { + *ast_channel_varshead(bogus) = old; + } ast_channel_unref(bogus); } else { ast_log(LOG_ERROR, "Unable to allocate bogus channel for function value substitution.\n"); res = -1; } + ast_debug(2, "Function %s result is '%s' from headp\n", + finalvars, res ? "" : ast_str_buffer(substr3)); } - ast_debug(2, "Function %s result is '%s'\n", - finalvars, res ? "" : ast_str_buffer(substr3)); } else { - /* Retrieve variable value */ - ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars); - res = 0; + const char *result; + if (c) { + result = ast_str_retrieve_variable(&substr3, 0, c, NULL, finalvars); + ast_debug(2, "Variable %s result is '%s' from channel\n", + finalvars, S_OR(result, "")); + } + if (!c || (c && !result && use_both)) { + result = ast_str_retrieve_variable(&substr3, 0, NULL, headp, finalvars); + ast_debug(2, "Variable %s result is '%s' from headp\n", + finalvars, S_OR(result, "")); + } + res = (result ? 0 : -1); } if (!res) { ast_str_substring(substr3, offset, offset2); @@ -596,7 +617,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str continue; } } - ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL); + ast_str_substitute_variables_full2(&substr2, 0, c, headp, + ast_str_buffer(substr1), NULL, use_both); finalvars = ast_str_buffer(substr2); } else { finalvars = ast_str_buffer(substr1); @@ -616,6 +638,12 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str ast_free(substr3); } +void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, + struct ast_channel *chan, struct varshead *headp, const char *templ, size_t *used) +{ + ast_str_substitute_variables_full2(buf, maxlen, chan, headp, templ, used, 0); +} + void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ) { ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, NULL); diff --git a/main/utils.c b/main/utils.c index 29676fafc1..7d1d6bd7b7 100644 --- a/main/utils.c +++ b/main/utils.c @@ -1859,6 +1859,67 @@ char *ast_strsep(char **iss, const char sep, uint32_t flags) return st; } +char *ast_strsep_quoted(char **iss, const char sep, const char quote, uint32_t flags) +{ + char *st = *iss; + char *is; + int inquote = 0; + int found = 0; + char stack[8]; + const char qstr[] = { quote }; + + if (ast_strlen_zero(st)) { + return NULL; + } + + memset(stack, 0, sizeof(stack)); + + for(is = st; *is; is++) { + if (*is == '\\') { + if (*++is != '\0') { + is++; + } else { + break; + } + } + + if (*is == quote) { + if (*is == stack[inquote]) { + stack[inquote--] = '\0'; + } else { + if (++inquote >= sizeof(stack)) { + return NULL; + } + stack[inquote] = *is; + } + } + + if (*is == sep && !inquote) { + *is = '\0'; + found = 1; + *iss = is + 1; + break; + } + } + if (!found) { + *iss = NULL; + } + + if (flags & AST_STRSEP_STRIP) { + st = ast_strip_quoted(st, qstr, qstr); + } + + if (flags & AST_STRSEP_TRIM) { + st = ast_strip(st); + } + + if (flags & AST_STRSEP_UNESCAPE) { + ast_unescape_quoted(st); + } + + return st; +} + char *ast_unescape_semicolon(char *s) { char *e; diff --git a/main/xml.c b/main/xml.c index 987f125399..d244e4eb5c 100644 --- a/main/xml.c +++ b/main/xml.c @@ -361,6 +361,11 @@ int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc) return xmlDocDump(output, (xmlDocPtr)doc); } +void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length) +{ + xmlDocDumpFormatMemory((xmlDocPtr)doc, (xmlChar **)buffer, length, 1); +} + const char *ast_xml_node_get_name(struct ast_xml_node *node) { return (const char *) ((xmlNode *) node)->name; diff --git a/tests/test_config.c b/tests/test_config.c index 1a0ddaf836..166879a820 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -1952,7 +1952,7 @@ AST_TEST_DEFINE(variable_list_from_string) switch (cmd) { case TEST_INIT: - info->name = "variable_list_from_string"; + info->name = "variable_list_from_quoted_string"; info->category = "/main/config/"; info->summary = "Test parsing a string into a variable list"; info->description = info->summary; @@ -1962,7 +1962,7 @@ AST_TEST_DEFINE(variable_list_from_string) } parse_string = "abc = 'def', ghi = 'j,kl', mno='pq=r', stu = 'vwx=\"yz\", ABC = \"DEF\"'"; - list = ast_variable_list_from_string(parse_string, ",", "="); + list = ast_variable_list_from_quoted_string(parse_string, ",", "=", "'"); ast_test_validate(test, list != NULL); str = ast_variable_list_join(list, "|", "^", "@", NULL); diff --git a/tests/test_strings.c b/tests/test_strings.c index f3c7e56408..a121056405 100644 --- a/tests/test_strings.c +++ b/tests/test_strings.c @@ -385,6 +385,143 @@ AST_TEST_DEFINE(strsep_test) return AST_TEST_PASS; } +AST_TEST_DEFINE(strsep_quoted_test) +{ + char *test1, *test2, *test3; + + switch (cmd) { + case TEST_INIT: + info->name = "strsep_quoted"; + info->category = "/main/strings/"; + info->summary = "Test ast_strsep_quoted"; + info->description = "Test ast_strsep_quoted"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + test1 = ast_strdupa("ghi=jkl,mno=\"pqr,stu\",abc=def, vwx = yz1 , vwx = yz1 , " + "\" vwx = yz1 \" , \" vwx , yz1 \",v'w'x, \"'x,v','x'\" , \" i\\'m a test\"" + ", \" i\\'m a, test\", \" i\\'m a, test\", e\\,nd, end\\"); + + test2 = ast_strsep_quoted(&test1, ',', '"', 0); + ast_test_validate(test, 0 == strcmp("ghi=jkl", test2)); + + test3 = ast_strsep_quoted(&test2, '=', '"', 0); + ast_test_validate(test, 0 == strcmp("ghi", test3)); + + test3 = ast_strsep_quoted(&test2, '=', '"', 0); + ast_test_validate(test, 0 == strcmp("jkl", test3)); + + test2 = ast_strsep_quoted(&test1, ',', '"', 0); + ast_test_validate(test, 0 == strcmp("mno=\"pqr,stu\"", test2)); + + test3 = ast_strsep_quoted(&test2, '=', '"', 0); + ast_test_validate(test, 0 == strcmp("mno", test3)); + + test3 = ast_strsep_quoted(&test2, '=', '"', 0); + ast_test_validate(test, 0 == strcmp("\"pqr,stu\"", test3)); + + test2 = ast_strsep_quoted(&test1, ',', '"', 0); + ast_test_validate(test, 0 == strcmp("abc=def", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', 0); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx = yz1", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx , yz1", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("v'w'x", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("\"'x,v','x'\"", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("\" i\\'m a test\"", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("\" i'm a, test\"", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_ALL); + ast_test_validate(test, 0 == strcmp("i'm a, test", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("e,nd", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("end", test2)); + + // Now use '|' as the quote character + test1 = ast_strdupa("ghi=jkl,mno=|pqr,stu|,abc=def, vwx = yz1 , vwx = yz1 , " + "| vwx = yz1 | , | vwx , yz1 |,v'w'x, |'x,v','x'| , | i\\'m a test|" + ", | i\\'m a, test|, | i\\'m a, test|, e\\,nd, end\\"); + + test2 = ast_strsep_quoted(&test1, ',', '|', 0); + ast_test_validate(test, 0 == strcmp("ghi=jkl", test2)); + + test3 = ast_strsep_quoted(&test2, '=', '|', 0); + ast_test_validate(test, 0 == strcmp("ghi", test3)); + + test3 = ast_strsep_quoted(&test2, '=', '|', 0); + ast_test_validate(test, 0 == strcmp("jkl", test3)); + + test2 = ast_strsep_quoted(&test1, ',', '|', 0); + ast_test_validate(test, 0 == strcmp("mno=|pqr,stu|", test2)); + + test3 = ast_strsep_quoted(&test2, '=', '|', 0); + ast_test_validate(test, 0 == strcmp("mno", test3)); + + test3 = ast_strsep_quoted(&test2, '=', '|', 0); + ast_test_validate(test, 0 == strcmp("|pqr,stu|", test3)); + + test2 = ast_strsep_quoted(&test1, ',', '|', 0); + ast_test_validate(test, 0 == strcmp("abc=def", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', 0); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx = yz1", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx , yz1", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("v'w'x", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("|'x,v','x'|", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("| i\\'m a test|", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("| i'm a, test|", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_ALL); + ast_test_validate(test, 0 == strcmp("i'm a, test", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("e,nd", test2)); + + test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("end", test2)); + + + // nothing failed; we're all good! + return AST_TEST_PASS; +} + static int test_semi(char *string1, char *string2, int test_len) { char *test2 = NULL; @@ -740,6 +877,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(begins_with_test); AST_TEST_UNREGISTER(ends_with_test); AST_TEST_UNREGISTER(strsep_test); + AST_TEST_UNREGISTER(strsep_quoted_test); AST_TEST_UNREGISTER(escape_semicolons_test); AST_TEST_UNREGISTER(escape_test); AST_TEST_UNREGISTER(strings_match); @@ -754,6 +892,7 @@ static int load_module(void) AST_TEST_REGISTER(begins_with_test); AST_TEST_REGISTER(ends_with_test); AST_TEST_REGISTER(strsep_test); + AST_TEST_REGISTER(strsep_quoted_test); AST_TEST_REGISTER(escape_semicolons_test); AST_TEST_REGISTER(escape_test); AST_TEST_REGISTER(strings_match);