[pulseaudio-discuss] [PATCH v4 6/7] message-params: Allow parameter strings to contain escaped curly braces

Georg Chini georg at chini.tk
Sun Jan 28 17:40:28 UTC 2018


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



More information about the pulseaudio-discuss mailing list