diff options
-rw-r--r-- | include/asterisk/app.h | 3 | ||||
-rw-r--r-- | main/app.c | 88 | ||||
-rw-r--r-- | tests/test_app.c | 86 |
3 files changed, 137 insertions, 40 deletions
diff --git a/include/asterisk/app.h b/include/asterisk/app.h index 6a0620c1a..acbab9199 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -512,8 +512,7 @@ struct ast_app_option { ... do any argument parsing here ... - if (ast_parseoptions(my_app_options, &opts, opt_args, options)) { - ast_module_user_remove(u); + if (ast_app_parse_options(my_app_options, &opts, opt_args, options)) { return -1; } } diff --git a/main/app.c b/main/app.c index 0619151d8..bfdec4fdd 100644 --- a/main/app.c +++ b/main/app.c @@ -1809,13 +1809,19 @@ char *ast_read_textfile(const char *filename) return output; } -int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr) +static int parse_options(const struct ast_app_option *options, void *_flags, char **args, char *optstr, int flaglen) { char *s, *arg; int curarg, res = 0; unsigned int argloc; + struct ast_flags *flags = _flags; + struct ast_flags64 *flags64 = _flags; - ast_clear_flag(flags, AST_FLAGS_ALL); + if (flaglen == 32) { + ast_clear_flag(flags, AST_FLAGS_ALL); + } else { + flags64->flags = 0; + } if (!optstr) { return 0; @@ -1826,8 +1832,40 @@ int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags curarg = *s++ & 0x7f; /* the array (in app.h) has 128 entries */ argloc = options[curarg].arg_index; if (*s == '(') { + int paren = 1, quote = 0; + int parsequotes = (s[1] == '"') ? 1 : 0; + /* Has argument */ arg = ++s; + for (; *s; s++) { + if (*s == '(' && !quote) { + paren++; + } else if (*s == ')' && !quote) { + /* Count parentheses, unless they're within quotes (or backslashed, below) */ + paren--; + } else if (*s == '"' && parsequotes) { + /* Leave embedded quotes alone, unless they are the first character */ + quote = quote ? 0 : 1; + ast_copy_string(s, s + 1, INT_MAX); + s--; + } else if (*s == '\\') { + if (!quote) { + /* If a backslash is found outside of quotes, remove it */ + ast_copy_string(s, s + 1, INT_MAX); + } else if (quote && s[1] == '"') { + /* Backslash for a quote character within quotes, remove the backslash */ + ast_copy_string(s, s + 1, INT_MAX); + } else { + /* Backslash within quotes, keep both characters */ + s++; + } + } + + if (paren == 0) { + break; + } + } + /* This will find the closing paren we found above, or none, if the string ended before we found one. */ if ((s = strchr(s, ')'))) { if (argloc) { args[argloc - 1] = arg; @@ -1841,48 +1879,24 @@ int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags } else if (argloc) { args[argloc - 1] = ""; } - ast_set_flag(flags, options[curarg].flag); + if (flaglen == 32) { + ast_set_flag(flags, options[curarg].flag); + } else { + ast_set_flag64(flags64, options[curarg].flag); + } } return res; } -int ast_app_parse_options64(const struct ast_app_option *options, struct ast_flags64 *flags, char **args, char *optstr) +int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr) { - char *s, *arg; - int curarg, res = 0; - unsigned int argloc; - - flags->flags = 0; - - if (!optstr) { - return 0; - } - - s = optstr; - while (*s) { - curarg = *s++ & 0x7f; /* the array (in app.h) has 128 entries */ - ast_set_flag64(flags, options[curarg].flag); - argloc = options[curarg].arg_index; - if (*s == '(') { - /* Has argument */ - arg = ++s; - if ((s = strchr(s, ')'))) { - if (argloc) { - args[argloc - 1] = arg; - } - *s++ = '\0'; - } else { - ast_log(LOG_WARNING, "Missing closing parenthesis for argument '%c' in string '%s'\n", curarg, arg); - res = -1; - break; - } - } else if (argloc) { - args[argloc - 1] = NULL; - } - } + return parse_options(options, flags, args, optstr, 32); +} - return res; +int ast_app_parse_options64(const struct ast_app_option *options, struct ast_flags64 *flags, char **args, char *optstr) +{ + return parse_options(options, flags, args, optstr, 64); } void ast_app_options2str64(const struct ast_app_option *options, struct ast_flags64 *flags, char *buf, size_t len) diff --git a/tests/test_app.c b/tests/test_app.c index fa19b6e9c..9962a1c59 100644 --- a/tests/test_app.c +++ b/tests/test_app.c @@ -40,6 +40,88 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define BASE_GROUP "a group" +AST_TEST_DEFINE(options_parsing) +{ + enum test_option_flags { + OPT_SIMPLE, + OPT_WITHQUOTES, + OPT_WITHBACKSLASH, + }; + enum test_option_args { + OPT_ARG_SIMPLE, + OPT_ARG_WITHQUOTES, + OPT_ARG_WITHBACKSLASH, + OPT_ARG_ARRAY_SIZE, + }; + AST_APP_OPTIONS(test_options, { + AST_APP_OPTION_ARG('a', OPT_SIMPLE, OPT_ARG_SIMPLE), + AST_APP_OPTION_ARG('b', OPT_WITHQUOTES, OPT_ARG_WITHQUOTES), + AST_APP_OPTION_ARG('c', OPT_WITHBACKSLASH, OPT_ARG_WITHBACKSLASH), + }); + struct ast_flags opts = { 0, }; + struct ast_flags64 opts64 = { 0, }; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + struct { + const char *string; + const char *parse[3]; + } options[] = { + { "a(simple)b(\"quoted\")c(back\\slash)", { "simple", "quoted", "backslash", }, }, + { "b(\"((())))\")a(simple)c(back\\)slash)", { "simple", "((())))", "back)slash", }, }, + { "b(\"((\\\"\\)\\(\")a(simple)c(back\\\"\\)\\(\\\"slash)", { "simple", "((\"\\)\\(", "back\")(\"slash", }, }, + }; + int i, j, res = AST_TEST_PASS; + char buffer[256]; + + switch (cmd) { + case TEST_INIT: + info->name = "options_parsing"; + info->category = "main/app/"; + info->summary = "App options unit test"; + info->description = + "This tests the options parsing code to ensure that it behaves as expected"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + for (i = 0; i < ARRAY_LEN(options); i++) { + ast_copy_string(buffer, options[i].string, sizeof(buffer)); + + if (ast_app_parse_options(test_options, &opts, opt_args, buffer)) { + ast_test_status_update(test, "ast_app_parse_options() of '%s' failed\n", options[i].string); + res = AST_TEST_FAIL; + } else { + /* Check arguments for success */ + for (j = 0; j < 3; j++) { + if (strcmp(opt_args[j], options[i].parse[j])) { + ast_test_status_update(test, "Parse of option %c from '%s' produced '%s', " + "but it should have produced '%s'\n", + 'a' + j, options[i].string, opt_args[j], options[i].parse[j]); + res = AST_TEST_FAIL; + } + } + } + + ast_copy_string(buffer, options[i].string, sizeof(buffer)); + if (ast_app_parse_options64(test_options, &opts64, opt_args, buffer)) { + ast_test_status_update(test, "ast_app_parse_options64() of '%s' failed\n", options[i].string); + res = AST_TEST_FAIL; + } else { + /* Check arguments for success */ + for (j = 0; j < 3; j++) { + if (strcmp(opt_args[j], options[i].parse[j])) { + ast_test_status_update(test, "Parse of option %c from '%s' produced '%s', " + "but it should have produced '%s'\n", + 'a' + j, options[i].string, opt_args[j], options[i].parse[j]); + res = AST_TEST_FAIL; + } + } + } + } + + return res; +} + AST_TEST_DEFINE(app_group) { struct ast_channel *test_channel1 = NULL; @@ -147,13 +229,15 @@ exit_group_test: static int unload_module(void) { AST_TEST_UNREGISTER(app_group); + AST_TEST_UNREGISTER(options_parsing); return 0; } static int load_module(void) { AST_TEST_REGISTER(app_group); + AST_TEST_REGISTER(options_parsing); return AST_MODULE_LOAD_SUCCESS; } -AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "App unit test"); +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "App unit tests"); |