diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 0b98a2bb07..79a2e49c8c 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -235,6 +235,66 @@ char *ast_strip(char *s), */ char *ast_strip_quoted(char *s, const char *beg_quotes, const char *end_quotes); +/*! + \brief Flags for ast_strsep + */ +enum ast_strsep_flags { + AST_STRSEP_STRIP = 0x01, /*!< Trim, then strip quotes. You may want to trim again */ + AST_STRSEP_TRIM = 0x02, /*!< Trim leading and trailing whitespace */ + AST_STRSEP_UNESCAPE = 0x04, /*!< Unescape '\' */ + AST_STRSEP_ALL = 0x07, /*!< Trim, strip, unescape */ +}; + +/*! + \brief Act like strsep but ignore separators inside quotes. + \param s Pointer to address of the the string to be processed. + Will be modified and can't be constant. + \param sep A single character delimiter. + \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. + + This function acts like strsep with three exceptions... + The separator is a single character instead of a string. + Separators inside quotes are treated literally instead of like separators. + You can elect to have leading and trailing whitespace and quotes + stripped from the result and have '\' sequences unescaped. + + Like strsep, ast_strsep maintains no internal state and you can call it + recursively using different separators on the same storage. + + Also like strsep, for consistent results, consecutive separators are not + collapsed so you may get an empty string as a valid result. + + Examples: + \code + char *mystr = ast_strdupa("abc=def,ghi='zzz=yyy,456',jkl"); + char *token, *token2, *token3; + + while((token = ast_strsep(&mystr, ',', AST_SEP_STRIP))) { + // 1st token will be aaa=def + // 2nd token will be ghi='zzz=yyy,456' + while((token2 = ast_strsep(&token, '=', AST_SEP_STRIP))) { + // 1st token2 will be ghi + // 2nd token2 will be zzz=yyy,456 + while((token3 = ast_strsep(&token2, ',', AST_SEP_STRIP))) { + // 1st token3 will be zzz=yyy + // 2nd token3 will be 456 + // and so on + } + } + // 3rd token will be jkl + } + + \endcode + */ +char *ast_strsep(char **s, const char sep, uint32_t flags); + /*! \brief Strip backslash for "escaped" semicolons, the string to be stripped (will be modified). diff --git a/main/utils.c b/main/utils.c index 229080b83d..40818c37ad 100644 --- a/main/utils.c +++ b/main/utils.c @@ -1473,6 +1473,66 @@ char *ast_strip_quoted(char *s, const char *beg_quotes, const char *end_quotes) return s; } +char *ast_strsep(char **iss, const char sep, uint32_t flags) +{ + char *st = *iss; + char *is; + int inquote = 0; + int found = 0; + char stack[8]; + + if (iss == NULL || *iss == '\0') { + return NULL; + } + + memset(stack, 0, sizeof(stack)); + + for(is = st; *is; is++) { + if (*is == '\\') { + if (*++is != '\0') { + is++; + } else { + break; + } + } + + if (*is == '\'' || *is == '"') { + 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, "'\"", "'\""); + } + + 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/tests/test_strings.c b/tests/test_strings.c index 5e5a17d27b..127ee789d8 100644 --- a/tests/test_strings.c +++ b/tests/test_strings.c @@ -310,11 +310,90 @@ AST_TEST_DEFINE(ends_with_test) return AST_TEST_PASS; } +AST_TEST_DEFINE(strsep_test) +{ + char *test1, *test2, *test3; + + switch (cmd) { + case TEST_INIT: + info->name = "strsep"; + info->category = "/main/strings/"; + info->summary = "Test ast_strsep"; + info->description = "Test ast_strsep"; + 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(&test1, ',', 0); + ast_test_validate(test, 0 == strcmp("ghi=jkl", test2)); + + test3 = ast_strsep(&test2, '=', 0); + ast_test_validate(test, 0 == strcmp("ghi", test3)); + + test3 = ast_strsep(&test2, '=', 0); + ast_test_validate(test, 0 == strcmp("jkl", test3)); + + test2 = ast_strsep(&test1, ',', 0); + ast_test_validate(test, 0 == strcmp("mno='pqr,stu'", test2)); + + test3 = ast_strsep(&test2, '=', 0); + ast_test_validate(test, 0 == strcmp("mno", test3)); + + test3 = ast_strsep(&test2, '=', 0); + ast_test_validate(test, 0 == strcmp("'pqr,stu'", test3)); + + test2 = ast_strsep(&test1, ',', 0); + ast_test_validate(test, 0 == strcmp("abc=def", test2)); + + test2 = ast_strsep(&test1, ',', 0); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx = yz1", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_STRIP); + ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("vwx , yz1", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_STRIP | AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("v\"w\"x", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("'\"x,v\",\"x\"'", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_TRIM); + ast_test_validate(test, 0 == strcmp("\" i\\'m a test\"", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("\" i'm a, test\"", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_ALL); + ast_test_validate(test, 0 == strcmp("i'm a, test", test2)); + + test2 = ast_strsep(&test1, ',', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE); + ast_test_validate(test, 0 == strcmp("e,nd", test2)); + + test2 = ast_strsep(&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 unload_module(void) { AST_TEST_UNREGISTER(str_test); AST_TEST_UNREGISTER(begins_with_test); AST_TEST_UNREGISTER(ends_with_test); + AST_TEST_UNREGISTER(strsep_test); return 0; } @@ -323,6 +402,7 @@ static int load_module(void) AST_TEST_REGISTER(str_test); AST_TEST_REGISTER(begins_with_test); AST_TEST_REGISTER(ends_with_test); + AST_TEST_REGISTER(strsep_test); return AST_MODULE_LOAD_SUCCESS; }