diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index 79a2e49c8c..cf3036a57c 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -309,6 +309,59 @@ char *ast_unescape_semicolon(char *s); */ char *ast_unescape_c(char *s); +/*! + * \brief Escape the 'to_escape' characters in the given string. + * + * \note The given output buffer has to have enough memory allocated to store the + * original string plus any escaped values. + * + * \param dest the escaped string + * \param s the source string to escape + * \param num number of characters to be copied from the source + * \param to_escape an array of characters to escape + * + * \return Pointer to the destination. + */ +char* ast_escape(char *dest, const char *s, size_t num, const char *to_escape); + +/*! + * \brief Escape standard 'C' sequences in the given string. + * + * \note The given output buffer has to have enough memory allocated to store the + * original string plus any escaped values. + * + * \param dest the escaped string + * \param s the source string to escape + * \param num number of characters to be copied from the source + * \param to_escape an array of characters to escape + * + * \return Pointer to the escaped string. + */ +char* ast_escape_c(char *dest, const char *s, size_t num); + +/*! + * \brief Escape the 'to_escape' characters in the given string. + * + * \note Caller is responsible for freeing the returned string + * + * \param s the source string to escape + * \param to_escape an array of characters to escape + * + * \return Pointer to the escaped string or NULL. + */ +char *ast_escape_alloc(const char *s, const char *to_escape); + +/*! + * \brief Escape standard 'C' sequences in the given string. + * + * \note Caller is responsible for freeing the returned string + * + * \param s the source string to escape + * + * \return Pointer to the escaped string or NULL. + */ +char *ast_escape_c_alloc(const char *s); + /*! \brief Size-limited null-terminating string copy. \param dst The destination buffer. diff --git a/main/manager_channels.c b/main/manager_channels.c index c12d94ee9c..2b9f975fad 100644 --- a/main/manager_channels.c +++ b/main/manager_channels.c @@ -408,6 +408,7 @@ struct ast_str *ast_manager_build_channel_state_string_prefix( { struct ast_str *out = ast_str_create(1024); int res = 0; + char *caller_name, *connected_name; if (!out) { return NULL; @@ -418,6 +419,9 @@ struct ast_str *ast_manager_build_channel_state_string_prefix( return NULL; } + caller_name = ast_escape_c_alloc(snapshot->caller_name); + connected_name = ast_escape_c_alloc(snapshot->connected_name); + res = ast_str_set(&out, 0, "%sChannel: %s\r\n" "%sChannelState: %u\r\n" @@ -436,9 +440,9 @@ struct ast_str *ast_manager_build_channel_state_string_prefix( prefix, snapshot->state, prefix, ast_state2str(snapshot->state), prefix, S_OR(snapshot->caller_number, ""), - prefix, S_OR(snapshot->caller_name, ""), + prefix, S_OR(caller_name, ""), prefix, S_OR(snapshot->connected_number, ""), - prefix, S_OR(snapshot->connected_name, ""), + prefix, S_OR(connected_name, ""), prefix, snapshot->language, prefix, snapshot->accountcode, prefix, snapshot->context, @@ -448,18 +452,26 @@ struct ast_str *ast_manager_build_channel_state_string_prefix( if (!res) { ast_free(out); + ast_free(caller_name); + ast_free(connected_name); return NULL; } if (snapshot->manager_vars) { struct ast_var_t *var; + char *val; AST_LIST_TRAVERSE(snapshot->manager_vars, var, entries) { + val = ast_escape_c_alloc(var->value); ast_str_append(&out, 0, "%sChanVariable: %s=%s\r\n", prefix, - var->name, var->value); + var->name, S_OR(val, "")); + ast_free(val); } } + ast_free(caller_name); + ast_free(connected_name); + return out; } @@ -556,6 +568,9 @@ static struct ast_manager_event_blob *channel_new_callerid( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { + struct ast_manager_event_blob *res; + char *callerid; + /* No NewCallerid event on cache clear or first event */ if (!old_snapshot || !new_snapshot) { return NULL; @@ -565,11 +580,19 @@ static struct ast_manager_event_blob *channel_new_callerid( return NULL; } - return ast_manager_event_blob_create( + if (!(callerid = ast_escape_c_alloc( + ast_describe_caller_presentation(new_snapshot->caller_pres)))) { + return NULL; + } + + res = ast_manager_event_blob_create( EVENT_FLAG_CALL, "NewCallerid", "CID-CallingPres: %d (%s)\r\n", new_snapshot->caller_pres, - ast_describe_caller_presentation(new_snapshot->caller_pres)); + callerid); + + ast_free(callerid); + return res; } static struct ast_manager_event_blob *channel_new_connected_line( diff --git a/main/presencestate.c b/main/presencestate.c index 07df7429d9..4b7163c1e4 100644 --- a/main/presencestate.c +++ b/main/presencestate.c @@ -393,14 +393,23 @@ int ast_presence_state_engine_init(void) static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg) { struct ast_presence_state_message *presence_state = stasis_message_data(msg); + struct ast_manager_event_blob *res; - return ast_manager_event_blob_create(EVENT_FLAG_CALL, "PresenceStateChange", + char *subtype = ast_escape_c_alloc(presence_state->subtype); + char *message = ast_escape_c_alloc(presence_state->message); + + res = ast_manager_event_blob_create(EVENT_FLAG_CALL, "PresenceStateChange", "Presentity: %s\r\n" "Status: %s\r\n" "Subtype: %s\r\n" "Message: %s\r\n", presence_state->provider, ast_presence_state2str(presence_state->state), - presence_state->subtype, - presence_state->message); + subtype ?: "", + message ?: ""); + + ast_free(subtype); + ast_free(message); + + return res; } diff --git a/main/stasis_channels.c b/main/stasis_channels.c index e9c23e6680..a34f724411 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -791,8 +791,12 @@ static struct ast_manager_event_blob *varset_to_ami(struct stasis_message *msg) struct ast_channel_blob *obj = stasis_message_data(msg); const char *variable = ast_json_string_get(ast_json_object_get(obj->blob, "variable")); - const char *value = - ast_json_string_get(ast_json_object_get(obj->blob, "value")); + RAII_VAR(char *, value, ast_escape_c_alloc( + ast_json_string_get(ast_json_object_get(obj->blob, "value"))), ast_free); + + if (!value) { + return NULL; + } if (obj->snapshot) { channel_event_string = diff --git a/main/utils.c b/main/utils.c index 44e993c56f..71d56e7381 100644 --- a/main/utils.c +++ b/main/utils.c @@ -1618,6 +1618,114 @@ char *ast_unescape_c(char *src) return ret; } +/* + * Standard escape sequences - Note, '\0' is not included as a valid character + * to escape, but instead is used here as a NULL terminator for the string. + */ +char escape_sequences[] = { + '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\', '\'', '\"', '\?', '\0' +}; + +/* + * Standard escape sequences output map (has to maintain matching order with + * escape_sequences). '\0' is included here as a NULL terminator for the string. + */ +static char escape_sequences_map[] = { + 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '\'', '"', '?', '\0' +}; + +char* ast_escape(char *dest, const char *s, size_t num, const char *to_escape) +{ + char *p; + + if (!dest || ast_strlen_zero(s)) { + return dest; + } + + if (ast_strlen_zero(to_escape)) { + ast_copy_string(dest, s, num); + return dest; + } + + for (p = dest; *s && num--; ++s, ++p) { + /* If in the list of characters to escape then escape it */ + if (strchr(to_escape, *s)) { + /* + * See if the character to escape is part of the standard escape + * sequences. If so we'll have to use its mapped counterpart + * otherwise just use the current character. + */ + char *c = strchr(escape_sequences, *s); + *p++ = '\\'; + *p = c ? escape_sequences_map[c - escape_sequences] : *s; + } else { + *p = *s; + } + } + + *p = '\0'; + return dest; +} + +char* ast_escape_c(char *dest, const char *s, size_t num) +{ + /* + * Note - This is an optimized version of ast_escape. When looking only + * for escape_sequences a couple of checks used in the generic case can + * be left out thus making it slightly more efficient. + */ + char *p; + + if (!dest || ast_strlen_zero(s)) { + return dest; + } + + for (p = dest; *s && num--; ++s, ++p) { + /* + * See if the character to escape is part of the standard escape + * sequences. If so use its mapped counterpart. + */ + char *c = strchr(escape_sequences, *s); + if (c) { + *p++ = '\\'; + *p = escape_sequences_map[c - escape_sequences]; + } else { + *p = *s; + } + } + + *p = '\0'; + return dest; +} + +static char *escape_alloc(const char *s, size_t *size) +{ + if (!s || !(*size = strlen(s))) { + return NULL; + } + + /* + * The result string needs to be twice the size of the given + * string just in case every character in it needs to be escaped. + */ + *size = *size * 2 + 1; + return ast_calloc(sizeof(char), *size); +} + +char *ast_escape_alloc(const char *s, const char *to_escape) +{ + size_t size = 0; + char *dest = escape_alloc(s, &size); + return ast_escape(dest, s, size, to_escape); +} + +char *ast_escape_c_alloc(const char *s) +{ + size_t size = 0; + char *dest = escape_alloc(s, &size); + return ast_escape_c(dest, s, size); +} + int ast_build_string_va(char **buffer, size_t *space, const char *fmt, va_list ap) { int result; diff --git a/tests/test_strings.c b/tests/test_strings.c index cf089a8653..fc8f38c899 100644 --- a/tests/test_strings.c +++ b/tests/test_strings.c @@ -452,6 +452,38 @@ AST_TEST_DEFINE(escape_semicolons_test) return AST_TEST_PASS; } +AST_TEST_DEFINE(escape_test) +{ + char buf[128]; + +#define TEST_ESCAPE(s, to_escape, expected) \ + !strcmp(ast_escape(buf, s, sizeof(buf) / sizeof(char), to_escape), expected) + + switch (cmd) { + case TEST_INIT: + info->name = "escape"; + info->category = "/main/strings/"; + info->summary = "Test ast_escape"; + info->description = "Test escaping values in a string"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_validate(test, TEST_ESCAPE("null escape", NULL, "null escape")); + ast_test_validate(test, TEST_ESCAPE("no matching escape", "Z", "no matching escape")); + ast_test_validate(test, TEST_ESCAPE("escape Z", "Z", "escape \\Z")); + ast_test_validate(test, TEST_ESCAPE("Z", "Z", "\\Z")); + ast_test_validate(test, TEST_ESCAPE(";;", ";;", "\\;\\;")); + ast_test_validate(test, TEST_ESCAPE("escape \n", "\n", "escape \\n")); + ast_test_validate(test, TEST_ESCAPE("escape \n again \n", "\n", "escape \\n again \\n")); + + ast_test_validate(test, !strcmp(ast_escape_c(buf, "escape \a\b\f\n\r\t\v\\\'\"\?", + sizeof(buf) / sizeof(char)), + "escape \\a\\b\\f\\n\\r\\t\\v\\\\\\\'\\\"\\?")); + return AST_TEST_PASS; +} + static int unload_module(void) { AST_TEST_UNREGISTER(str_test); @@ -459,6 +491,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(ends_with_test); AST_TEST_UNREGISTER(strsep_test); AST_TEST_UNREGISTER(escape_semicolons_test); + AST_TEST_UNREGISTER(escape_test); return 0; } @@ -469,6 +502,7 @@ static int load_module(void) AST_TEST_REGISTER(ends_with_test); AST_TEST_REGISTER(strsep_test); AST_TEST_REGISTER(escape_semicolons_test); + AST_TEST_REGISTER(escape_test); return AST_MODULE_LOAD_SUCCESS; }