[pulseaudio-discuss] [PATCH v3 6/6] message-handler: Allow parameter strings to contain escaped curly braces

Georg Chini georg at chini.tk
Mon Jan 22 18:12:56 UTC 2018


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. A function pa_strbuf_put_escaped_parameter_string()
was added to the string buffer functions which must be used to correctly format a
string which 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.

If pa_split_message_parameter_string() returns a string, it reverts the changes
that pa_strbuf_put_escaped_parameter_string() has introduced when the last
enclosing brackets are removed.
---
 doc/messaging_api.txt           | 17 +++++++++++
 src/pulse/util.c                | 66 ++++++++++++++++++++++++++++++++++++-----
 src/pulsecore/core.c            |  4 +--
 src/pulsecore/message-handler.c | 36 +++++++++++-----------
 src/pulsecore/strbuf.c          | 26 ++++++++++++++++
 src/pulsecore/strbuf.h          |  1 +
 6 files changed, 123 insertions(+), 27 deletions(-)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index 07500724..7f7056a6 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -18,6 +18,23 @@ to specify message parameters. The following reference 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, 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. A function pa_strbuf_put_escaped_parameter_string()
+was added to the string buffer functions which must be used to correctly format a
+string which 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.
+
+The function pa_split_message_parameter_string() can be used to parse the message
+parameter string. This function also reverts the changes that
+pa_strbuf_put_escaped_parameter_string() has introduced.
+
 Object path: /core
 Message: list-handlers
 Parameters: None
diff --git a/src/pulse/util.c b/src/pulse/util.c
index c3a3320a..137aebd7 100644
--- a/src/pulse/util.c
+++ b/src/pulse/util.c
@@ -350,13 +350,18 @@ int pa_msleep(unsigned long t) {
  * length. If max_length is not 0, it is verified that the retrieved
  * element is within the bounds of the parent element. If the parameter
  * element is not NULL, a newly allocated string containing the retrieved
- * element is returned. The caller is responsible to free the string.
+ * element is returned. In that case, "\" characters before curly braces
+ * and leading and trailing single quotes are removed if no enclosing
+ * braces remain. This does not modify the original string. The caller
+ * is responsible to free the string returned in element.
  * 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
  * or end of element is encountered and -1 on parse error. */
 int pa_split_message_parameter_string(const char *c, uint32_t max_length, const char **start_pos, uint32_t *length, char **element, const char **state) {
     const char *current = *state ? *state : c;
     uint32_t open_braces;
+    bool unpacked = true;
+    bool found_backslash = false;
 
     pa_assert(start_pos);
     pa_assert(length);
@@ -370,10 +375,17 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const
     /* Find opening brace */
     while (*current != 0) {
 
-        if (*current == '{')
+        /* Skip escaped curly braces. */
+        if (*current == '\\') {
+            found_backslash = true;
+            current++;
+            continue;
+        }
+
+        if (*current == '{' && !found_backslash)
             break;
 
-        if (*current == '}') {
+        if (*current == '}' && !found_backslash) {
 
             /* reached end of element, same as end of string */
             if (current == c + max_length)
@@ -383,22 +395,35 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const
             return -1;
         }
 
+        found_backslash = false;
         current++;
     }
 
     /* No opening brace found, end of string */
     if (*current == 0)
-         return 0;
+        return 0;
 
     *start_pos = 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 == '}')
+            unpacked = false;
+        }
+        if (*current == '}' && !found_backslash)
             open_braces--;
+
+        found_backslash = false;
     }
 
     /* Parse error, closing brace missing */
@@ -416,8 +441,35 @@ int pa_split_message_parameter_string(const char *c, uint32_t max_length, const
     *state = current + 1;
     *length = current - *start_pos;
 
-    if (element)
+    if (element) {
+        char *result, *tmp;
+
         *element = pa_xstrndup(*start_pos, *length);
 
+        /* Result is not completely unpacked, so do not remove escaping */
+        if (!unpacked)
+            return 1;
+
+        result = *element;
+
+        /* Remove leading and trailing quotes if present */
+        if (*result == '\'' && result[strlen(result) - 1] == '\'') {
+            memmove(result, result + 1, strlen(result));
+            result[strlen(result) - 1] = 0;
+        }
+
+        /* Remove escape character from curly braces if present. */
+        while ((tmp = strstr(result, "\\{")))
+            memmove(tmp, tmp + 1, strlen(result) - (size_t)(tmp  - result));
+        while ((tmp = strstr(result, "\\}")))
+            memmove(tmp, tmp + 1, strlen(result) - (size_t)(tmp  - result));
+
+        /* Remove trailing 0's. */
+        tmp = pa_xstrdup(result);
+        pa_xfree(result);
+
+        *element = tmp;
+    }
+
     return 1;
 }
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index 264c9999..1edeb9fd 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -72,11 +72,11 @@ static char *message_handler_list(pa_core *c) {
     buf = pa_strbuf_new();
 
     PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
-        pa_strbuf_puts(buf, "{");
+        pa_strbuf_putc(buf, '{');
 
         pa_strbuf_printf(buf, "{%s} {", handler->object_path);
         if (handler->description)
-            pa_strbuf_puts(buf, handler->description);
+            pa_strbuf_put_escaped_parameter_string(buf, handler->description);
 
         pa_strbuf_puts(buf, "}}");
     }
diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
index db6b06ce..d51462d6 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;
@@ -116,11 +121,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/pulsecore/strbuf.c b/src/pulsecore/strbuf.c
index 11f131bf..8d5127fe 100644
--- a/src/pulsecore/strbuf.c
+++ b/src/pulsecore/strbuf.c
@@ -191,3 +191,29 @@ bool pa_strbuf_isempty(pa_strbuf *sb) {
 
     return sb->length <= 0;
 }
+
+/* Adds leading and trailing single quotes and also a "\" before curly
+ * braces to the input string while putting the string into the buffer.
+ * Used to prepare a string to be sent as part of a message parameter
+ * list. The function 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_strbuf_put_escaped_parameter_string(pa_strbuf *buf, const char *in_str) {
+    const char *s;
+
+    pa_assert(in_str);
+    pa_assert(buf);
+
+    pa_strbuf_putc(buf, '\'');
+
+    for (s = in_str; *s; ++s) {
+        if (*s == '{' || *s == '}')
+            pa_strbuf_putc(buf, '\\');
+
+        pa_strbuf_putc(buf, *s);
+    }
+
+    pa_strbuf_putc(buf, '\'');
+}
diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h
index 469f6f78..b57125b4 100644
--- a/src/pulsecore/strbuf.h
+++ b/src/pulsecore/strbuf.h
@@ -34,6 +34,7 @@ size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...)  PA_GCC_PRINTF_A
 void pa_strbuf_puts(pa_strbuf *sb, const char *t);
 void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m);
 void pa_strbuf_putc(pa_strbuf *sb, char c);
+void pa_strbuf_put_escaped_parameter_string(pa_strbuf *buf, const char *in_str);
 
 bool pa_strbuf_isempty(pa_strbuf *sb);
 
-- 
2.14.1



More information about the pulseaudio-discuss mailing list