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. The path name may not end with a /. If the user specifies a trailing / when sending a message, it will be removed. --- doc/messaging_api.txt | 34 +++++++- src/Makefile.am | 3 +- src/map-file | 5 ++ src/pulse/message-params.c | 181 ++++++++++++++++++++++++++++++++++++++-- src/pulse/message-params.h | 23 ++++- src/pulsecore/core.c | 20 ++--- src/pulsecore/message-handler.c | 53 +++++++----- src/utils/pactl.c | 4 +- 8 files changed, 279 insertions(+), 44 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/Makefile.am b/src/Makefile.am index d4e34482..7e4933ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -673,6 +673,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \ pulse/timeval.c pulse/timeval.h \ pulse/rtclock.c pulse/rtclock.h \ pulse/volume.c pulse/volume.h \ + pulse/message-params.c pulse/message-params.h \ pulsecore/atomic.h \ pulsecore/authkey.c pulsecore/authkey.h \ pulsecore/conf-parser.c pulsecore/conf-parser.h \ @@ -881,6 +882,7 @@ libpulse_la_SOURCES = \ pulse/mainloop-api.c pulse/mainloop-api.h \ pulse/mainloop-signal.c pulse/mainloop-signal.h \ pulse/mainloop.c pulse/mainloop.h \ + pulse/message-params.c pulse/message-params.h \ pulse/operation.c pulse/operation.h \ pulse/proplist.c pulse/proplist.h \ pulse/pulseaudio.h \ @@ -893,7 +895,6 @@ libpulse_la_SOURCES = \ pulse/timeval.c pulse/timeval.h \ pulse/utf8.c pulse/utf8.h \ pulse/util.c pulse/util.h \ - pulse/message-params.c pulse/message-params.h \ pulse/volume.c pulse/volume.h \ pulse/xmalloc.c pulse/xmalloc.h 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 cb080c33..a644c173 100644 --- a/src/pulse/message-params.c +++ b/src/pulse/message-params.c @@ -28,20 +28,55 @@ #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; +}; + +/* Remove escaping from a string */ +static char *unescape(char *value) { + char *tmp; + + if (!value) + return NULL; + + /* If the string is enclosed in single quotes, remove them */ + 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)); + + /* Return the pointer that was passed in. */ + return value; +} + +/* 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 * of the current element in result and the state pointer is advanced to - * the next list element. + * the next list element. On return, the parameter *is_unpacked indicates + * if the string is plain text or contains a sub-list. is_unpacked may + * be NULL. * 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 * is encountered and -1 on parse error. */ -int pa_message_param_split_list(char *c, char **result, void **state) { +int pa_message_param_split_list(char *c, char **result, bool *is_unpacked, void **state) { char *current = *state ? *state : c; uint32_t open_braces; + bool found_backslash = false; pa_assert(result); @@ -54,29 +89,52 @@ 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; + if (is_unpacked) + *is_unpacked = true; *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 (is_unpacked) + *is_unpacked = false; + } + if (*current == '}' && !found_backslash) open_braces--; + + found_backslash = false; } /* Parse error, closing brace missing */ @@ -96,21 +154,126 @@ 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. If allocate is * true, a newly allocated string will be returned, else a - * pointer to a sub-string within c. */ + * pointer to a sub-string within c. Escaping will be removed + * if the string does not contain another list. */ int pa_message_param_read_string(char *c, char **result, bool allocate, void **state) { char *start_pos; int err; + bool is_unpacked; pa_assert(result); *result = NULL; - if ((err = pa_message_param_split_list(c, &start_pos, state)) != 1) + if ((err = pa_message_param_split_list(c, &start_pos, &is_unpacked, state)) != 1) return err; *result = start_pos; + + if (is_unpacked) + *result = unescape(*result); + if (allocate) *result = pa_xstrdup(*result); 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 02e08363..c0675c6e 100644 --- a/src/pulse/message-params.h +++ b/src/pulse/message-params.h @@ -30,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); +int pa_message_param_split_list(char *c, char **result, bool * is_unpacked, void **state); /** Read a string from the parameter list. */ int pa_message_param_read_string(char *c, char **result, bool allocate, 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..747f343c 100644 --- a/src/pulsecore/message-handler.c +++ b/src/pulsecore/message-handler.c @@ -31,15 +31,29 @@ #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) + return false; + + /* Make sure the string starts with / and does not end with a / */ + if (test_string[0] != '/' || test_string[strlen(test_string) - 1] == '/') + 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 +70,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; @@ -91,7 +100,7 @@ void pa_message_handler_unregister(pa_core *c, const char *object_path) { int pa_message_handler_send_message(pa_core *c, const char *object_path, const char *message, const char *message_parameters, char **response) { struct pa_message_handler *handler; int ret; - char *parameter_copy; + char *parameter_copy, *path_copy; pa_assert(c); pa_assert(object_path); @@ -100,8 +109,16 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c *response = NULL; - if (!(handler = pa_hashmap_get(c->message_handlers, object_path))) + path_copy = pa_xstrdup(object_path); + + /* Remove trailing / from path name if present */ + if (path_copy[strlen(path_copy) - 1] == '/') + path_copy[strlen(path_copy) - 1] = 0; + + if (!(handler = pa_hashmap_get(c->message_handlers, path_copy))) { + pa_xfree(path_copy); return -PA_ERR_NOENTITY; + } parameter_copy = pa_xstrdup(message_parameters); @@ -110,6 +127,7 @@ int pa_message_handler_send_message(pa_core *c, const char *object_path, const c ret = handler->callback(handler->object_path, message, parameter_copy, response, handler->userdata); pa_xfree(parameter_copy); + pa_xfree(path_copy); return ret; } @@ -123,11 +141,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/utils/pactl.c b/src/utils/pactl.c index fdafe57b..1f13e60c 100644 --- a/src/utils/pactl.c +++ b/src/utils/pactl.c @@ -864,14 +864,14 @@ static void list_handlers_callback(pa_context *c, int success, char *response, v return; } - if (pa_message_param_split_list(response, ¶m_list, &state) <= 0) { + if (pa_message_param_split_list(response, ¶m_list, NULL, &state) <= 0) { pa_log(_("list-handlers message response could not be parsed correctly")); quit(1); return; } state = NULL; - while ((err = pa_message_param_split_list(param_list, &start_pos, &state)) > 0) { + while ((err = pa_message_param_split_list(param_list, &start_pos, NULL, &state)) > 0) { void *state2 = NULL; char *path; char *description; -- 2.14.1