diff options
-rw-r--r-- | doc/messaging_api.txt | 7 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/map-file | 7 | ||||
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | src/pulse/message-params.c | 158 | ||||
-rw-r--r-- | src/pulse/message-params.h | 39 | ||||
-rw-r--r-- | src/pulsecore/core.c | 22 | ||||
-rw-r--r-- | src/pulsecore/message-handler.c | 58 |
8 files changed, 249 insertions, 47 deletions
diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt index 431a5df27..e0a921da4 100644 --- a/doc/messaging_api.txt +++ b/doc/messaging_api.txt @@ -14,10 +14,15 @@ 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. +For string parameters that contain curly braces or backslashes, those characters +must be escaped by adding a "\" before them. + +Reference: + Object path: /core Message: list-handlers Parameters: None diff --git a/src/Makefile.am b/src/Makefile.am index 19f100f11..df912a8b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -711,6 +711,7 @@ libpulsecommon_@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 \ @@ -917,6 +918,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 \ @@ -929,7 +931,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 0acbf05b5..4d196c11d 100644 --- a/src/map-file +++ b/src/map-file @@ -229,8 +229,15 @@ pa_mainloop_quit; pa_mainloop_run; pa_mainloop_set_poll_func; pa_mainloop_wakeup; +pa_message_params_begin_list; +pa_message_params_end_list; +pa_message_params_free; +pa_message_params_new; pa_message_params_read_raw; pa_message_params_read_string; +pa_message_params_to_string_free; +pa_message_params_write_raw; +pa_message_params_write_string; pa_msleep; pa_thread_make_realtime; pa_operation_cancel; diff --git a/src/meson.build b/src/meson.build index 8d74a3164..b84112eee 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ libpulsecommon_sources = [ 'pulse/format.c', 'pulse/json.c', 'pulse/mainloop-api.c', + 'pulse/message-params.c', 'pulse/xmalloc.c', 'pulse/proplist.c', 'pulse/utf8.c', @@ -78,6 +79,7 @@ libpulsecommon_headers = [ 'pulse/format.h', 'pulse/json.h', 'pulse/mainloop-api.h', + 'pulse/message-params.h', 'pulse/xmalloc.h', 'pulse/proplist.h', 'pulse/utf8.h', diff --git a/src/pulse/message-params.c b/src/pulse/message-params.c index 0afda4f38..236800bca 100644 --- a/src/pulse/message-params.c +++ b/src/pulse/message-params.c @@ -28,23 +28,33 @@ #include <pulse/xmalloc.h> #include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> #include "message-params.h" +/* Message parameter structure, a wrapper for pa_strbuf */ +struct pa_message_params { + pa_strbuf *buffer; +}; + /* 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 it returns the * position of the current element in result and the state pointer is - * advanced to the next list element. + * advanced to 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. * * result is set to NULL on end of string or parse error. */ -static int split_list(char *c, char **result, void **state) { +static int 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); @@ -57,29 +67,52 @@ static int split_list(char *c, char **result, void **state) { /* Find opening brace */ while (*current != 0) { - if (*current == '{') + /* Skip escaped curly braces. */ + if (*current == '\\' && !found_backslash) { + 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) { + 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,23 +129,122 @@ static int split_list(char *c, char **result, void **state) { return 1; } +/* Read functions */ + /* Read a string from the parameter list. The state pointer is * advanced to the next element of the list. Returns a pointer - * to a sub-string within c. The result must not be freed. */ + * to a sub-string within c. Escape characters will be removed + * from the string. The result must not be freed. */ int pa_message_params_read_string(char *c, const char **result, void **state) { char *start_pos; + char *value = NULL; int r; + bool is_unpacked = true; pa_assert(result); - if ((r = split_list(c, &start_pos, state)) == 1) - *result = start_pos; + if ((r = split_list(c, &start_pos, &is_unpacked, state)) == 1) + value = start_pos; + + /* Check if we got a plain string not containing further lists */ + if (!is_unpacked) { + /* Parse error */ + r = -1; + value = NULL; + } + + if (value) + *result = pa_unescape(value); return r; } -/* Another wrapper for split_list() to distinguish between reading - * pure string data and raw data which may contain further lists. */ +/* A wrapper for split_list() to distinguish between reading pure + * string data and raw data which may contain further lists. */ int pa_message_params_read_raw(char *c, char **result, void **state) { - return split_list(c, result, state); + return split_list(c, result, NULL, state); +} + +/* 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_params *pa_message_params_new(void) { + pa_message_params *params; + + params = pa_xnew(pa_message_params, 1); + params->buffer = pa_strbuf_new(); + + return params; +} + +/* Frees a pa_message_params structure */ +void pa_message_params_free(pa_message_params *params) { + pa_assert(params); + + pa_strbuf_free(params->buffer); + pa_xfree(params); +} + +/* Converts a pa_message_param structure to string and frees the structure. + * The returned string needs to be freed with pa_xree(). */ +char *pa_message_params_to_string_free(pa_message_params *params) { + char *result; + + pa_assert(params); + + result = pa_strbuf_to_string_free(params->buffer); + + pa_xfree(params); + return result; +} + +/* Writes an opening curly brace */ +void pa_message_params_begin_list(pa_message_params *params) { + + pa_assert(params); + + pa_strbuf_putc(params->buffer, '{'); +} + +/* Writes a closing curly brace */ +void pa_message_params_end_list(pa_message_params *params) { + + pa_assert(params); + + pa_strbuf_putc(params->buffer, '}'); +} + +/* Writes a string to a message_params structure, adding curly braces + * around the string and escaping curly braces within the string. */ +void pa_message_params_write_string(pa_message_params *params, const char *value) { + char *output; + + pa_assert(params); + + /* Null value is written as empty element */ + if (!value) + value = ""; + + output = pa_escape(value, "{}"); + pa_strbuf_printf(params->buffer, "{%s}", output); + + pa_xfree(output); +} + +/* Writes a raw string to a message_params structure, adding curly braces + * around the string if add_braces is true. This function can be used to + * write parts of a string or whole parameter lists that have been prepared + * elsewhere (for example an array). */ +void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces) { + pa_assert(params); + + /* Null value is written as empty element */ + if (!value) + value = ""; + + if (add_braces) + pa_strbuf_printf(params->buffer, "{%s}", value); + else + pa_strbuf_puts(params->buffer, value); } diff --git a/src/pulse/message-params.h b/src/pulse/message-params.h index 5c9bc1a6e..f30164ff9 100644 --- a/src/pulse/message-params.h +++ b/src/pulse/message-params.h @@ -30,13 +30,48 @@ PA_C_DECL_BEGIN +/** Structure which holds a parameter list. Wrapper for pa_strbuf \since 15.0 */ +typedef struct pa_message_params pa_message_params; + +/** @{ \name Read functions */ + /** Read raw data from a parameter list. Used to split a message parameter - * string into list elements \since 15.0 */ + * string into list elements. The string returned in *result must not be freed. \since 15.0 */ int pa_message_params_read_raw(char *c, char **result, void **state); -/** Read a string from a parameter list. \since 15.0 */ +/** Read a string from a parameter list. Escaped curly braces and backslashes + * will be unescaped. \since 15.0 */ int pa_message_params_read_string(char *c, const char **result, void **state); +/** @} */ + +/** @{ \name Write functions */ + +/** Create a new pa_message_params structure \since 15.0 */ +pa_message_params *pa_message_params_new(void); + +/** Free a pa_message_params structure. \since 15.0 */ +void pa_message_params_free(pa_message_params *params); + +/** Convert pa_message_params to string, free pa_message_params structure. \since 15.0 */ +char *pa_message_params_to_string_free(pa_message_params *params); + +/** Start a list by writing an opening brace. \since 15.0 */ +void pa_message_params_begin_list(pa_message_params *params); + +/** End a list by writing a closing brace. \since 15.0 */ +void pa_message_params_end_list(pa_message_params *params); + +/** Append string to parameter list. Curly braces and backslashes will be escaped. \since 15.0 */ +void pa_message_params_write_string(pa_message_params *params, const char *value); + +/** Append raw string to parameter list. Used to write incomplete strings + * or complete parameter lists (for example arrays). Adds curly braces around + * the string if add_braces is true. \since 15.0 */ +void pa_message_params_write_raw(pa_message_params *params, const char *value, bool add_braces); + +/** @} */ + PA_C_DECL_END #endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index da8b3b3da..8b830199f 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,26 @@ 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_params *param; void *state = NULL; struct pa_message_handler *handler; - buf = pa_strbuf_new(); + param = pa_message_params_new(); - pa_strbuf_putc(buf, '{'); + pa_message_params_begin_list(param); PA_HASHMAP_FOREACH(handler, c->message_handlers, state) { - pa_strbuf_putc(buf, '{'); + pa_message_params_begin_list(param); - pa_strbuf_printf(buf, "{%s} {", handler->object_path); - if (handler->description) - pa_strbuf_puts(buf, handler->description); + /* object_path cannot contain characters that need escaping, therefore + * pa_message_params_write_raw() can safely be used here. */ + pa_message_params_write_raw(param, handler->object_path, true); + pa_message_params_write_string(param, handler->description); - pa_strbuf_puts(buf, "}}"); + pa_message_params_end_list(param); } - pa_strbuf_putc(buf, '}'); + pa_message_params_end_list(param); - return pa_strbuf_to_string_free(buf); + return pa_message_params_to_string_free(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 427186dbd..40644554a 100644 --- a/src/pulsecore/message-handler.c +++ b/src/pulsecore/message-handler.c @@ -31,17 +31,36 @@ #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. + * Also reject double slashes. */ +static bool object_path_is_valid(const char *test_string) { uint32_t i; + if (!test_string) + return false; + + /* Make sure the string starts with a / */ + 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] == '/' && test_string[i + 1] != '/')) + continue; + + return false; } + /* Make sure the string does not end with a / */ + if (test_string[i - 1] == '/') + return false; + return true; } @@ -56,13 +75,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 +105,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 +114,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 +132,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 +146,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); |