]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Geolocation: Base Asterisk Prereqs
authorGeorge Joseph <gjoseph@digium.com>
Mon, 27 Jun 2022 17:31:04 +0000 (11:31 -0600)
committerFriendly Automation <jenkins2@gerrit.asterisk.org>
Thu, 7 Jul 2022 13:22:43 +0000 (08:22 -0500)
* Added ast_variable_list_from_quoted_string()
  Parse a quoted string into an ast_variable list.

* Added ast_str_substitute_variables_full2()
  Perform variable/function/expression substitution on an ast_str.

* Added ast_strsep_quoted()
  Like ast_strsep except you can specify a specific quote character.
  Also added unit test.

* Added ast_xml_find_child_element()
  Find a direct child element by name.

* Added ast_xml_doc_dump_memory()
  Dump the specified document to a buffer

* ast_datastore_free() now checks for a NULL datastore
  before attempting to destroy it.

Change-Id: I5dcefed2f5f93a109e8b489e18d80d42e45244ec

include/asterisk/config.h
include/asterisk/pbx.h
include/asterisk/strings.h
include/asterisk/xml.h
main/config.c
main/datastore.c
main/pbx_variables.c
main/utils.c
main/xml.c
tests/test_config.c
tests/test_strings.c

index 3aef5f14b96e653efb4791a0290bc035af5355c2..44ba4e487780a1652100bb57e9f38f75e9f6a0ac 100644 (file)
@@ -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
  *
index 95332eab172fcae2d9053ffcc7f02308afe5e358..9cc7f0b538db9a4a0a016873cb1200a34cb67b99 100644 (file)
@@ -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);
index 93983c3e2bba73d22170feedbb0f0c50d661facd..d2c3c82ac8bc5b5ebeebe4f9b70aaa7877e15db1 100644 (file)
@@ -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).
index 9d43560a3e40fc0155188f35352d6a229c23a6da..c1b0797ca3125dc7ec3feab59f7ca3dac55238bf 100644 (file)
@@ -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
index 122e7aad480e3005ae755c9c8a98383d8cc94a15..0e27d766407e21ca0962dd219e3f1646dfd2bc19 100644 (file)
@@ -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;
index f37ee6c4d4de0cbbc3d506fcbb68701ba1fc0263..d5adfae9c77738494a79d279595d2ecd3b108a6c 100644 (file)
@@ -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);
index 1f78a599563ff28ddc433aac65f1289dd1029c51..45b7ca09842a2a61ae17a5fd66665a436f73adeb 100644 (file)
@@ -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);
index 29676fafc14d11069954868ff815b0260018e510..7d1d6bd7b7ef9408232fd3312eedda35d32ef97c 100644 (file)
@@ -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;
index 987f1253995934a2603c3dbf08fc007f542de171..d244e4eb5c785d47284e80531ada940b1972ea77 100644 (file)
@@ -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;
index 1a0ddaf836cd82e22d583c515adb46a8d4817bcf..166879a820d2b5a4c814ea03605b3d876f46775f 100644 (file)
@@ -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);
 
index f3c7e56408fc19ef85584468c4efc33c637884ee..a12105640505d907632515cdfec1b13e5c7c4dfa 100644 (file)
@@ -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);