summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Chini <georg@chini.tk>2020-01-14 13:24:16 +0100
committerTanu Kaskinen <tanuk@iki.fi>2020-12-03 14:41:39 +0000
commit590fd1ca698031b77ddcf6fc44d3729211760dac (patch)
tree199fdb895399b34e7fd7f5e9c67c1bd2f8a6b79f
parent4a28b164d162bd99659653f78b057f5d0019f977 (diff)
downloadpulseaudio-590fd1ca698031b77ddcf6fc44d3729211760dac.tar.gz
message-params: Allow parameter strings to contain escaped curly braces
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_params, which is a wrapper for pa_strbuf has been created. Following new write functions are available: pa_message_params_new() - creates a new pa_message_params structure pa_message_params_free() - frees a pa_message_params structure pa_message_param_to_string_free() - converts a pa_message_param to string and frees the structure pa_message_params_begin_list() - starts a list pa_message_params_end_list() - ends a list pa_message_params_write_string() - writes a string to a pa_message_params structure pa_message_params_write_raw() - writes a raw string to a pa_message_params structure For string parameters that contain curly braces or backslashes, those characters will be escaped when using pa_message_params_write_string(), while write_raw() will put the string into the buffer without any changes. For reading, pa_message_params_read_string() reverts the changes that pa_message_params_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 / or contain a double slash. If the user specifies a trailing / when sending a message, it will be silently removed. Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/51>
-rw-r--r--doc/messaging_api.txt7
-rw-r--r--src/Makefile.am3
-rw-r--r--src/map-file7
-rw-r--r--src/meson.build2
-rw-r--r--src/pulse/message-params.c158
-rw-r--r--src/pulse/message-params.h39
-rw-r--r--src/pulsecore/core.c22
-rw-r--r--src/pulsecore/message-handler.c58
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);