The patch adds the possibility to escape curly braces within parameter strings and introduces several new functions that can be used for writing parameters. For writing, the structure pa_message_param, which is a wrapper for pa_strbuf has been created. Following new write functions are available: pa_message_param_new() - creates a new pa_message_param structure pa_message_param_to_string() - converts a pa_message_param to string and frees the structure pa_message_param_begin_list() - starts a list pa_message_param_end_list() - ends a list pa_message_param_write_string() - writes a string to a pa_message_param structure 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. The function pa_message_param_write_string() has a parameter do_escape. If true, the necessary escaping is added. Escaping is only needed if a string 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. For reading, pa_message_param_read_string() reverts the changes that pa_message_param_write_string() might have introduced. The patch also adds more restrictions on the object path name. Now only alphanumeric characters and one of "_", ".", "-" and "/" are allowed. --- doc/messaging_api.txt | 34 ++++++++- src/map-file | 5 ++ src/pulse/message-params.c | 163 ++++++++++++++++++++++++++++++++++++++-- src/pulse/message-params.h | 22 ++++++ src/pulsecore/core.c | 20 ++--- src/pulsecore/message-handler.c | 36 ++++----- 6 files changed, 245 insertions(+), 35 deletions(-) diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt index 431a5df2..0e6be53f 100644 --- a/doc/messaging_api.txt +++ b/doc/messaging_api.txt @@ -14,10 +14,42 @@ look like that: {{Integer} {{1st float} {2nd float} ...}}{...} Any characters that are not enclosed in curly braces are ignored (all characters between { and {, between } and } and between } and {). The same syntax is used -to specify message parameters. The following reference lists available messages, +to specify message parameters. The reference further down 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. +There are several functions that simplify reading and writing message parameter +strings. For writing, the structure pa_message_param can be used. Following +functions are available: +pa_message_param_new() - creates a new pa_message_param structure +pa_message_param_to_string() - converts a pa_message_param to string and frees +the structure +pa_message_param_begin_list() - starts a list +pa_message_param_end_list() - ends a list +pa_message_param_write_string() - writes a string to a pa_message_param structure + +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. The function pa_message_param_write_string() +has a parameter do_escape. If true, the necessary escaping is added. Escaping +is only needed if a string 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. + +For reading, the following functions are available: +pa_message_param_split_list() - parse message parameter string +pa_message_param_read_string() - read a string from a parameter list + +pa_message_param_read_string() also reverts the changes that +pa_message_param_write_string() might have introduced. + +Reference: + Object path: /core Message: list-handlers Parameters: None diff --git a/src/map-file b/src/map-file index 385731dc..372d190d 100644 --- a/src/map-file +++ b/src/map-file @@ -225,8 +225,13 @@ pa_mainloop_quit; pa_mainloop_run; pa_mainloop_set_poll_func; pa_mainloop_wakeup; +pa_message_param_begin_list; +pa_message_param_end_list; +pa_message_param_new; pa_message_param_read_string; pa_message_param_split_list; +pa_message_param_to_string; +pa_message_param_write_string; pa_msleep; pa_operation_cancel; pa_operation_get_state; diff --git a/src/pulse/message-params.c b/src/pulse/message-params.c index 324fbb7b..c1abf62b 100644 --- a/src/pulse/message-params.c +++ b/src/pulse/message-params.c @@ -28,9 +28,17 @@ #include <pulse/xmalloc.h> #include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> #include "message-params.h" +/* Message parameter structure, a wrapper for pa_strbuf */ +struct pa_message_param { + pa_strbuf *buffer; +}; + +/* Read functions */ + /* Split the specified string into elements. An element is defined as * a sub-string between curly braces. The function is needed to parse * the parameters of messages. Each time it is called returns the position @@ -42,6 +50,7 @@ int pa_message_param_split_list(char *c, char **result, void **state) { char *current = *state ? *state : c; uint32_t open_braces; + bool found_backslash = false; pa_assert(result); @@ -54,29 +63,47 @@ int pa_message_param_split_list(char *c, char **result, void **state) { /* Find opening brace */ while (*current != 0) { - if (*current == '{') + /* Skip escaped curly braces. */ + if (*current == '\\') { + found_backslash = true; + current++; + continue; + } + + if (*current == '{' && !found_backslash) break; /* unexpected closing brace, parse error */ - if (*current == '}') + if (*current == '}' && !found_backslash) return -1; + found_backslash = false; current++; } /* No opening brace found, end of string */ if (*current == 0) - return 0; + return 0; *result = 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 == '}') + if (*current == '}' && !found_backslash) open_braces--; + + found_backslash = false; } /* Parse error, closing brace missing */ @@ -94,7 +121,8 @@ int pa_message_param_split_list(char *c, char **result, void **state) { } /* Read a string from the parameter list. The state pointer is - * advanced to the next element of the list */ + * advanced to the next element of the list Escaping is removed + * from the string. */ int pa_message_param_read_string(char *c, char **result, void **state) { char *start_pos; int err; @@ -106,5 +134,128 @@ int pa_message_param_read_string(char *c, char **result, void **state) { if ((err = pa_message_param_split_list(c, &start_pos, state)) > 0) *result = pa_xstrdup(start_pos); + if (*result) { + char *value, *tmp; + + value = *result; + + /* Remove leading and trailing quotes if present */ + if (*value == '\'' && value[strlen(value) - 1] == '\'') { + memmove(value, value + 1, strlen(value)); + value[strlen(value) - 1] = 0; + } + + /* Remove escape character from curly braces if present. */ + while ((tmp = strstr(value, "\\{"))) + memmove(tmp, tmp + 1, strlen(value) - (size_t)(tmp - value)); + while ((tmp = strstr(value, "\\}"))) + memmove(tmp, tmp + 1, strlen(value) - (size_t)(tmp - value)); + + /* Remove trailing 0's. */ + tmp = pa_xstrdup(value); + pa_xfree(value); + + *result = tmp; + } + return err; } + +/* Write functions. The functions are wrapper functions around pa_strbuf, + * so that the client does not need to use pa_strbuf directly. */ + +/* Creates a new pa_message_param structure */ +pa_message_param *pa_message_param_new(void) { + pa_message_param *param; + + param = pa_xnew(pa_message_param, 1); + param->buffer = pa_strbuf_new(); + + return param; +} + +/* Converts a pa_message_param structure to string and frees the structure */ +char *pa_message_param_to_string(pa_message_param *param) { + char *result; + + pa_assert(param); + + result = pa_strbuf_to_string_free(param->buffer); + + pa_xfree(param); + return result; +} + +/* Writes an opening curly brace */ +void pa_message_param_begin_list(pa_message_param *param) { + + pa_assert(param); + + pa_strbuf_putc(param->buffer, '{'); +} + +/* Writes a closing curly brace */ +void pa_message_param_end_list(pa_message_param *param) { + + pa_assert(param); + + pa_strbuf_putc(param->buffer, '}'); +} + +/* Writes a string to a message_param structure, adding curly braces + * around the string. If do_escape is true, leading and trailing single + * quotes and also a "\" before curly braces are added to the input string. + * Escaping 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_message_param_write_string(pa_message_param *param, const char *value, bool do_escape) { + const char *s; + char *output, *out_char; + size_t brace_count; + + pa_assert(param); + + /* Null value is written as empty element */ + if (!value) + value = ""; + + if (!do_escape) { + pa_strbuf_printf(param->buffer, "{%s}", value); + return; + } + + /* Using pa_strbuf_putc() to write to the strbuf while iterating over + * the input string would cause the allocation of a linked list element + * for each character of the input string. Therefore the output string + * is constructed locally before writing it to the buffer */ + + /* Count number of characters to escape */ + brace_count = 0; + for (s = value; *s; ++s) { + if (*s == '{' || *s == '}') + brace_count++; + } + + /* allocate output string */ + output = pa_xmalloc(strlen(value) + brace_count + 5); + out_char = output; + + *out_char++ = '{'; + *out_char++ = '\''; + + for (s = value; *s; ++s) { + if (*s == '{' || *s == '}') + *out_char++ = '\\'; + + *out_char++ = *s; + } + + *out_char++ = '\''; + *out_char++ = '}'; + *out_char = 0; + + pa_strbuf_puts(param->buffer, output); + pa_xfree(output); +} diff --git a/src/pulse/message-params.h b/src/pulse/message-params.h index 45c5850d..d24b4a54 100644 --- a/src/pulse/message-params.h +++ b/src/pulse/message-params.h @@ -19,6 +19,7 @@ ***/ #include <stddef.h> +#include <stdbool.h> #include <inttypes.h> #include <pulse/cdecl.h> @@ -29,12 +30,33 @@ PA_C_DECL_BEGIN +typedef struct pa_message_param pa_message_param; + +/** Read functions */ + /** Split message parameter string into list elements */ int pa_message_param_split_list(char *c, char **result, void **state); /** Read a string from the parameter list. */ int pa_message_param_read_string(char *c, char **result, void **state); +/** Write functions */ + +/** Create a new pa_message_param structure */ +pa_message_param *pa_message_param_new(void); + +/** Convert pa_message_param to string, free pa_message_param structure */ +char *pa_message_param_to_string(pa_message_param *param); + +/** Write an opening brace */ +void pa_message_param_begin_list(pa_message_param *param); + +/** Write a closing brace */ +void pa_message_param_end_list(pa_message_param *param); + +/** Append string to parameter list */ +void pa_message_param_write_string(pa_message_param *param, const char *value, bool do_escape); + PA_C_DECL_END #endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 94717a4f..e34306bb 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -29,6 +29,7 @@ #include <pulse/rtclock.h> #include <pulse/timeval.h> #include <pulse/xmalloc.h> +#include <pulse/message-params.h> #include <pulsecore/module.h> #include <pulsecore/core-rtclock.h> @@ -65,25 +66,24 @@ static void core_free(pa_object *o); /* Returns a list of handlers. */ static char *message_handler_list(pa_core *c) { - pa_strbuf *buf; + pa_message_param *param; void *state = NULL; struct pa_message_handler *handler; - buf = pa_strbuf_new(); + param = pa_message_param_new(); - pa_strbuf_putc(buf, '{'); + pa_message_param_begin_list(param); PA_HASHMAP_FOREACH(handler, c->message_handlers, state) { - pa_strbuf_putc(buf, '{'); + pa_message_param_begin_list(param); - pa_strbuf_printf(buf, "{%s} {", handler->object_path); - if (handler->description) - pa_strbuf_puts(buf, handler->description); + pa_message_param_write_string(param, handler->object_path, false); + pa_message_param_write_string(param, handler->description, true); - pa_strbuf_puts(buf, "}}"); + pa_message_param_end_list(param); } - pa_strbuf_putc(buf, '}'); + pa_message_param_end_list(param); - return pa_strbuf_to_string_free(buf); + return pa_message_param_to_string(param); } static int core_message_handler(const char *object_path, const char *message, char *message_parameters, char **response, void *userdata) { diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c index a9187b41..3a5282cc 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; @@ -123,11 +128,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); -- 2.14.1