diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h index 464ff1cdc34e5aeb0ac6d4b72dfa83603049f703..a0e7cb6bf59a5655a842ab9d541dab217cfe7b92 100644 --- a/include/asterisk/utils.h +++ b/include/asterisk/utils.h @@ -318,6 +318,16 @@ int ast_xml_escape(const char *string, char *outbuf, size_t buflen); */ char *ast_escape_quoted(const char *string, char *outbuf, int buflen); +/*! + * \brief Escape semicolons found in a string. + * + * \param string string to be escaped + * \param outbuf resulting escaped string + * \param buflen size of output buffer + * \return a pointer to the escaped string + */ +char *ast_escape_semicolons(const char *string, char *outbuf, int buflen); + /*! * \brief Unescape quotes in a string * diff --git a/main/config.c b/main/config.c index 23de2c8babf72fb33ec834f9b565bca312a8f843..95f0b696e73698501765add28d3a717061bfa991 100644 --- a/main/config.c +++ b/main/config.c @@ -2513,6 +2513,7 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c while (var) { struct ast_category_template_instance *x; int found = 0; + AST_LIST_TRAVERSE(&cat->template_instances, x, next) { struct ast_variable *v; for (v = x->inst->root; v; v = v->next) { @@ -2558,10 +2559,22 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!') fprintf(f,"%s", cmt->cmt); } - if (var->sameline) - fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt); - else - fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value); + + { /* Block for 'escaped' scope */ + int escaped_len = 2 * strlen(var->value) + 1; + char escaped[escaped_len]; + + ast_escape_semicolons(var->value, escaped, escaped_len); + + if (var->sameline) { + fprintf(f, "%s %s %s %s", var->name, (var->object ? "=>" : "="), + escaped, var->sameline->cmt); + } else { + fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), + escaped); + } + } + for (cmt = var->trailing; cmt; cmt=cmt->next) { if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!') fprintf(f,"%s", cmt->cmt); diff --git a/main/utils.c b/main/utils.c index 40818c37addf1bcc5e805130bd777e688f5f89eb..3a095ca7b85838d0b4e4da3d6675f06ba16a9361 100644 --- a/main/utils.c +++ b/main/utils.c @@ -484,6 +484,37 @@ char *ast_escape_quoted(const char *string, char *outbuf, int buflen) return outbuf; } +char *ast_escape_semicolons(const char *string, char *outbuf, int buflen) +{ + const char *ptr = string; + char *out = outbuf; + + if (string == NULL || outbuf == NULL) { + ast_assert(string != NULL && outbuf != NULL); + return NULL; + } + + while (*ptr && out - outbuf < buflen - 1) { + if (*ptr == ';') { + if (out - outbuf >= buflen - 2) { + break; + } + strcpy(out, "\\;"); + out += 2; + } else { + *out = *ptr; + out++; + } + ptr++; + } + + if (buflen) { + *out = '\0'; + } + + return outbuf; +} + void ast_unescape_quoted(char *quote_str) { int esc_pos; diff --git a/pbx/pbx_config.c b/pbx/pbx_config.c index e76f6e892bff43b3a2dd014fa39aef6a65a9435f..a42ea47fd039c18e0bba5e2298271305c7e195d7 100644 --- a/pbx/pbx_config.c +++ b/pbx/pbx_config.c @@ -890,7 +890,11 @@ static char *handle_cli_dialplan_save(struct ast_cli_entry *e, int cmd, struct a if ((v = ast_variable_browse(cfg, "globals"))) { fprintf(output, "[globals]\n"); while(v) { - fprintf(output, "%s => %s\n", v->name, v->value); + int escaped_len = 2 * strlen(v->value) + 1; + char escaped[escaped_len]; + + ast_escape_semicolons(v->value, escaped, escaped_len); + fprintf(output, "%s => %s\n", v->name, escaped); v = v->next; } fprintf(output, "\n"); @@ -951,20 +955,33 @@ static char *handle_cli_dialplan_save(struct ast_cli_entry *e, int cmd, struct a const char *sep, *cid; const char *el = ast_get_extension_label(p); char label[128] = ""; - + char *appdata = ast_get_extension_app_data(p); + char *escaped; + if (ast_get_extension_matchcid(p)) { sep = "/"; cid = ast_get_extension_cidmatch(p); - } else + } else { sep = cid = ""; - - if (el && (snprintf(label, sizeof(label), "(%s)", el) != (strlen(el) + 2))) + } + + if (el && (snprintf(label, sizeof(label), "(%s)", el) != (strlen(el) + 2))) { incomplete = 1; /* error encountered or label > 125 chars */ - + } + + if (!ast_strlen_zero(appdata)) { + int escaped_len = 2 * strlen(appdata) + 1; + char escaped[escaped_len]; + + ast_escape_semicolons(appdata, escaped, escaped_len); + } else { + escaped = ""; + } + fprintf(output, "exten => %s%s%s,%d%s,%s(%s)\n", ast_get_extension_name(p), (ast_strlen_zero(sep) ? "" : sep), (ast_strlen_zero(cid) ? "" : cid), ast_get_extension_priority(p), label, - ast_get_extension_app(p), (ast_strlen_zero(ast_get_extension_app_data(p)) ? "" : (const char *)ast_get_extension_app_data(p))); + ast_get_extension_app(p), escaped); } } } diff --git a/tests/test_strings.c b/tests/test_strings.c index 127ee789d8e397147ec4ff8e29c7486f2cfbd2f6..05cc8df845ad9b3d78d4b90f263b232753a04e69 100644 --- a/tests/test_strings.c +++ b/tests/test_strings.c @@ -387,6 +387,74 @@ AST_TEST_DEFINE(strsep_test) return AST_TEST_PASS; } +static int test_semi(char *string1, char *string2, int test_len) +{ + char *test2 = NULL; + if (test_len >= 0) { + test2 = ast_alloca(test_len); + *test2 = '\0'; + } + ast_escape_semicolons(string1, test2, test_len); + if (test2 != NULL && strcmp(string2, test2) == 0) { + return 1; + } else { + return 0; + } +} + +AST_TEST_DEFINE(escape_semicolons_test) +{ + switch (cmd) { + case TEST_INIT: + info->name = "escape_semicolons"; + info->category = "/main/strings/"; + info->summary = "Test ast_escape_semicolons"; + info->description = "Test ast_escape_semicolons"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + + ast_test_validate(test, test_semi("this is a ;test", "this is a \\;test", 18)); + ast_test_validate(test, test_semi(";", "\\;", 3)); + + /* The following tests should return empty because there's not enough room to output + * an escaped ; or even a single character. + */ + ast_test_validate(test, test_semi(";", "", 0)); + ast_test_validate(test, test_semi(";", "", 1)); + ast_test_validate(test, test_semi(";", "", 2)); + ast_test_validate(test, test_semi("x", "", 0)); + ast_test_validate(test, test_semi("x", "", 1)); + + /* At least some output should be produced now. */ + ast_test_validate(test, test_semi("xx;xx", "x", 2)); + ast_test_validate(test, test_semi("xx;xx", "xx", 3)); + + /* There's still not enough room to output \; so + * don't even print the \ + */ + ast_test_validate(test, test_semi("xx;xx", "xx", 4)); + + ast_test_validate(test, test_semi("xx;xx", "xx\\;", 5)); + ast_test_validate(test, test_semi("xx;xx", "xx\\;x", 6)); + ast_test_validate(test, test_semi("xx;xx", "xx\\;xx", 7)); + ast_test_validate(test, test_semi("xx;xx", "xx\\;xx", 8)); + + /* Random stuff */ + ast_test_validate(test, test_semi("xx;xx;this is a test", "xx\\;xx\\;this is a test", 32)); + ast_test_validate(test, test_semi(";;;;;", "\\;\\;\\;\\;\\;", 32)); + ast_test_validate(test, test_semi(";;;;;", "\\;\\;\\;\\;", 10)); + ast_test_validate(test, test_semi(";;;;;", "\\;\\;\\;\\;\\;", 11)); + ast_test_validate(test, test_semi(";;\\;;;", "\\;\\;\\\\;\\;\\;", 32)); + + ast_test_status_update(test, "This test should produce 2 'ast_escape_semicolons: FRACK!, Failed assertion' messages.\n"); + ast_test_validate(test, !test_semi(NULL, "xx\\;xx", 8)); + ast_test_validate(test, !test_semi("xx;xx", "xx\\;xx", -1)); + + return AST_TEST_PASS; +} static int unload_module(void) { @@ -394,6 +462,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(escape_semicolons_test); return 0; } @@ -403,6 +472,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(escape_semicolons_test); return AST_MODULE_LOAD_SUCCESS; }