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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux