For string parameters that contain curly braces, those braces must be escaped by adding a "\" before them. This however means that a trailing backslash would falsely escape the closing bracket. To avoid this, single quotes must be added at start and end of the string. A function pa_strbuf_put_escaped_parameter_string() was added to the string buffer functions which must be used to correctly format a string which might fulfill one of the following conditions: - It contains curly braces - It contains a trailing "\" - It is enclosed in single quotes Other strings can be passed without modification. If pa_split_message_parameter_string() returns a string, it reverts the changes that pa_strbuf_put_escaped_parameter_string() has introduced when the last enclosing brackets are removed. --- doc/messaging_api.txt | 17 +++++++++++ src/pulse/util.c | 66 ++++++++++++++++++++++++++++++++++++----- src/pulsecore/core.c | 4 +-- src/pulsecore/message-handler.c | 36 +++++++++++----------- src/pulsecore/strbuf.c | 26 ++++++++++++++++ src/pulsecore/strbuf.h | 1 + 6 files changed, 123 insertions(+), 27 deletions(-) diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt index 07500724..7f7056a6 100644 --- a/doc/messaging_api.txt +++ b/doc/messaging_api.txt @@ -18,6 +18,23 @@ to specify message parameters. The following reference lists available messages, their parameters and return values. If a return value is enclosed in {}, this means that multiple elements of the same type may be returned. +For string parameters that contain curly braces, those braces must be escaped +by adding a "\" before them. This however means that a trailing backslash would +falsely escape the closing bracket. To avoid this, single quotes must be added +at start and end of the string. A function pa_strbuf_put_escaped_parameter_string() +was added to the string buffer functions which must be used to correctly format a +string which might fulfill one of the following conditions: + +- It contains curly braces +- It contains a trailing "\" +- It is enclosed in single quotes + +Other strings can be passed without modification. + +The function pa_split_message_parameter_string() can be used to parse the message +parameter string. This function also reverts the changes that +pa_strbuf_put_escaped_parameter_string() has introduced. + Object path: /core Message: list-handlers Parameters: None diff --git a/src/pulse/util.c b/src/pulse/util.c index c3a3320a..137aebd7 100644 --- a/src/pulse/util.c +++ b/src/pulse/util.c @@ -350,13 +350,18 @@ int pa_msleep(unsigned long t) { * length. If max_length is not 0, it is verified that the retrieved * element is within the bounds of the parent element. If the parameter * element is not NULL, a newly allocated string containing the retrieved - * element is returned. The caller is responsible to free the string. + * element is returned. In that case, "\" characters before curly braces + * and leading and trailing single quotes are removed if no enclosing + * braces remain. This does not modify the original string. The caller + * is responsible to free the string returned in element. * The variable state points to, should be initialized to NULL before * the first call. The function returns 1 on success, 0 if end of string * or end of element is encountered and -1 on parse error. */ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const char **start_pos, uint32_t *length, char **element, const char **state) { const char *current = *state ? *state : c; uint32_t open_braces; + bool unpacked = true; + bool found_backslash = false; pa_assert(start_pos); pa_assert(length); @@ -370,10 +375,17 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const /* Find opening brace */ while (*current != 0) { - if (*current == '{') + /* Skip escaped curly braces. */ + if (*current == '\\') { + found_backslash = true; + current++; + continue; + } + + if (*current == '{' && !found_backslash) break; - if (*current == '}') { + if (*current == '}' && !found_backslash) { /* reached end of element, same as end of string */ if (current == c + max_length) @@ -383,22 +395,35 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const return -1; } + found_backslash = false; current++; } /* No opening brace found, end of string */ if (*current == 0) - return 0; + return 0; *start_pos = current + 1; + found_backslash = false; open_braces = 1; while (open_braces != 0 && *current != 0) { current++; - if (*current == '{') + + /* Skip escaped curly braces. */ + if (*current == '\\') { + found_backslash = true; + continue; + } + + if (*current == '{' && !found_backslash) { open_braces++; - if (*current == '}') + unpacked = false; + } + if (*current == '}' && !found_backslash) open_braces--; + + found_backslash = false; } /* Parse error, closing brace missing */ @@ -416,8 +441,35 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const *state = current + 1; *length = current - *start_pos; - if (element) + if (element) { + char *result, *tmp; + *element = pa_xstrndup(*start_pos, *length); + /* Result is not completely unpacked, so do not remove escaping */ + if (!unpacked) + return 1; + + result = *element; + + /* Remove leading and trailing quotes if present */ + if (*result == '\'' && result[strlen(result) - 1] == '\'') { + memmove(result, result + 1, strlen(result)); + result[strlen(result) - 1] = 0; + } + + /* Remove escape character from curly braces if present. */ + while ((tmp = strstr(result, "\\{"))) + memmove(tmp, tmp + 1, strlen(result) - (size_t)(tmp - result)); + while ((tmp = strstr(result, "\\}"))) + memmove(tmp, tmp + 1, strlen(result) - (size_t)(tmp - result)); + + /* Remove trailing 0's. */ + tmp = pa_xstrdup(result); + pa_xfree(result); + + *element = tmp; + } + return 1; } diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 264c9999..1edeb9fd 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -72,11 +72,11 @@ static char *message_handler_list(pa_core *c) { buf = pa_strbuf_new(); PA_HASHMAP_FOREACH(handler, c->message_handlers, state) { - pa_strbuf_puts(buf, "{"); + pa_strbuf_putc(buf, '{'); pa_strbuf_printf(buf, "{%s} {", handler->object_path); if (handler->description) - pa_strbuf_puts(buf, handler->description); + pa_strbuf_put_escaped_parameter_string(buf, handler->description); pa_strbuf_puts(buf, "}}"); } diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c index db6b06ce..d51462d6 100644 --- a/src/pulsecore/message-handler.c +++ b/src/pulsecore/message-handler.c @@ -31,15 +31,25 @@ #include "message-handler.h" -/* Check if a string does not contain control characters. Currently these are - * only "{" and "}". */ -static bool string_is_valid(const char *test_string) { +/* Check if a path string starts with a / and only contains valid characters */ +static bool object_path_is_valid(const char *test_string) { uint32_t i; + if (test_string[0] != '/') + return false; + for (i = 0; test_string[i]; i++) { - if (test_string[i] == '{' || - test_string[i] == '}') - return false; + + if ((test_string[i] >= 'a' && test_string[i] <= 'z') || + (test_string[i] >= 'A' && test_string[i] <= 'Z') || + (test_string[i] >= '0' && test_string[i] <= '9') || + test_string[i] == '.' || + test_string[i] == '_' || + test_string[i] == '-' || + test_string[i] == '/') + continue; + + return false; } return true; @@ -56,13 +66,8 @@ void pa_message_handler_register(pa_core *c, const char *object_path, const char pa_assert(cb); pa_assert(userdata); - /* Ensure that the object path is not empty and starts with "/". */ - pa_assert(object_path[0] == '/'); - - /* Ensure that object path and description are valid strings */ - pa_assert(string_is_valid(object_path)); - if (description) - pa_assert(string_is_valid(description)); + /* Ensure that object path is valid */ + pa_assert(object_path_is_valid(object_path)); handler = pa_xnew0(struct pa_message_handler, 1); handler->userdata = userdata; @@ -116,11 +121,6 @@ int pa_message_handler_set_description(pa_core *c, const char *object_path, cons if (!(handler = pa_hashmap_get(c->message_handlers, object_path))) return -PA_ERR_NOENTITY; - if (description) { - if (!string_is_valid(description)) - return -PA_ERR_INVALID; - } - pa_xfree(handler->description); handler->description = pa_xstrdup(description); diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c index 11f131bf..8d5127fe 100644 --- a/src/pulsecore/strbuf.c +++ b/src/pulsecore/strbuf.c @@ -191,3 +191,29 @@ bool pa_strbuf_isempty(pa_strbuf *sb) { return sb->length <= 0; } + +/* Adds leading and trailing single quotes and also a "\" before curly + * braces to the input string while putting the string into the buffer. + * Used to prepare a string to be sent as part of a message parameter + * list. The function only needs to be used if the original string might + * fulfill any of the following conditions: + * - It contains curly braces + * - It is enclosed in single quotes + * - It ends with a "\" */ +void pa_strbuf_put_escaped_parameter_string(pa_strbuf *buf, const char *in_str) { + const char *s; + + pa_assert(in_str); + pa_assert(buf); + + pa_strbuf_putc(buf, '\''); + + for (s = in_str; *s; ++s) { + if (*s == '{' || *s == '}') + pa_strbuf_putc(buf, '\\'); + + pa_strbuf_putc(buf, *s); + } + + pa_strbuf_putc(buf, '\''); +} diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h index 469f6f78..b57125b4 100644 --- a/src/pulsecore/strbuf.h +++ b/src/pulsecore/strbuf.h @@ -34,6 +34,7 @@ size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_A void pa_strbuf_puts(pa_strbuf *sb, const char *t); void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); void pa_strbuf_putc(pa_strbuf *sb, char c); +void pa_strbuf_put_escaped_parameter_string(pa_strbuf *buf, const char *in_str); bool pa_strbuf_isempty(pa_strbuf *sb); -- 2.14.1