]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
config: Make text_file_save and 'dialplan save' escape semicolons in values.
authorGeorge Joseph <george.joseph@fairview5.com>
Wed, 5 Nov 2014 15:02:42 +0000 (15:02 +0000)
committerGeorge Joseph <george.joseph@fairview5.com>
Wed, 5 Nov 2014 15:02:42 +0000 (15:02 +0000)
When a config file is read, an unescaped semicolon signals comments which are
stripped from the value before it's stored.  Escaped semicolons are then
unescaped and become part of the value.  Both of these behaviors are normal
and expected.  When the config is serialized either by 'dialplan save' or
AMI/UpdateConfig however, the now unescaped semicolons are written as-is.
If you actually reload the file just saved, the unescaped semicolons are
now treated as start of comments.

Since true comments are stripped on read, any semicolons in
ast_variable.value must have been escaped originally.  This patch
re-escapes semicolons in ast_variable.values before they're written to
file either by 'dialplan save' or config/ast_config_text_file_save which
is called by AMI/UpdateConfig. I also fixed a few pre-existing formatting
issues nearby in pbx_config.c

Tested-by: George Joseph
ASTERISK-20127 #close

Review: https://reviewboard.asterisk.org/r/4132/

git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/11@427328 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/test.h
include/asterisk/utils.h
main/config.c
main/utils.c
pbx/pbx_config.c
tests/test_strings.c

index 606c332763495a655b1533ca751e42665f07e700..964f5ad6bfd98e0a2c4f501b74f7c82c87c3f811 100644 (file)
@@ -272,5 +272,30 @@ int __ast_test_status_update(const char *file, const char *func, int line,
  */
 #define ast_test_status_update(t, f, ...) __ast_test_status_update(__FILE__, __PRETTY_FUNCTION__, __LINE__, (t), (f), ## __VA_ARGS__)
 
+/*!
+ * \brief Check a test condition, failing the test if it's not true.
+ *
+ * \since 11.14.0
+ *
+ * This macro evaluates \a condition. If the condition evaluates to true (non-zero),
+ * nothing happens. If it evaluates to false (zero), then the failure is printed
+ * using \ref ast_test_status_update, and the current test is ended with AST_TEST_FAIL.
+ *
+ * Sadly, the name 'ast_test_assert' was already taken.
+ *
+ * Note that since this macro returns from the current test, there must not be any
+ * cleanup work to be done before returning. Use \ref RAII_VAR for test cleanup.
+ *
+ * \param test Currently executing test
+ * \param condition Boolean condition to check.
+ */
+#define ast_test_validate(test, condition)                             \
+       do {                                                            \
+               if (!(condition)) {                                     \
+                       __ast_test_status_update(__FILE__, __PRETTY_FUNCTION__, __LINE__, (test), "Condition failed: %s\n", #condition); \
+                       return AST_TEST_FAIL;                           \
+               }                                                       \
+       } while(0)
+
 #endif /* TEST_FRAMEWORK */
 #endif /* _AST_TEST_H */
index 8ca16329b3cf44156ed2c05fc34df2e46f5028ed..efe283f6a57417cf25ac1227111dde364b42670c 100644 (file)
@@ -317,6 +317,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
  *
index 451d17b8153d26be2e74260befe02fbf5cb90cb4..da974d9fb5f448bc93f91e1b0561c451d4566b3b 100644 (file)
@@ -2268,6 +2268,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) {
@@ -2313,10 +2314,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);
index 45c17a6c2bbb5d7cae12b6779586490c78b34944..2dd9c11fdf5861c9f813545c2e86050bcbdf31da 100644 (file)
@@ -494,6 +494,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;
index 478d3ce54ab49336ad487af28eed46adf4aa753c..1fb562e3c9dd0839c6d3178b5a1301dc290197f2 100644 (file)
@@ -789,7 +789,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");
@@ -850,20 +854,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);
                                }
                        }
                }
index 2a18c1f3b06aa427b9ded6c6bc9824e135a50d32..4e0555ebb14ee418e512290f61fa3a454be4a2d5 100644 (file)
@@ -251,15 +251,86 @@ cleanup:
        return res;
 }
 
+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)
 {
        AST_TEST_UNREGISTER(str_test);
+       AST_TEST_UNREGISTER(escape_semicolons_test);
        return 0;
 }
 
 static int load_module(void)
 {
        AST_TEST_REGISTER(str_test);
+       AST_TEST_REGISTER(escape_semicolons_test);
        return AST_MODULE_LOAD_SUCCESS;
 }