[pulseaudio-commits] Branch 'next' - 9 commits - configure.ac src/.gitignore src/Makefile.am src/pulse src/tests

Tanu Kaskinen tanuk at kemper.freedesktop.org
Thu Jun 2 12:01:11 UTC 2016


 configure.ac          |    4 
 src/.gitignore        |    1 
 src/Makefile.am       |   15 -
 src/pulse/format.c    |  226 ++++++++----------
 src/pulse/json.c      |  614 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pulse/json.h      |   53 ++++
 src/tests/json-test.c |  280 ++++++++++++++++++++++
 7 files changed, 1068 insertions(+), 125 deletions(-)

New commits:
commit 694644f2052b20c478266645cbd28f5d287fa454
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:39 2016 +0530

    json: Drop refcounting of json objects
    
    We don't actually use the refcounting bits.
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/format.c b/src/pulse/format.c
index ee8b7ac..8474978 100644
--- a/src/pulse/format.c
+++ b/src/pulse/format.c
@@ -300,7 +300,7 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
             break;
     }
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return type;
 }
 
@@ -324,12 +324,12 @@ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v
 
     if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) {
         pa_log_debug("Format info property '%s' type is not int.", key);
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
         return -PA_ERR_INVALID;
     }
 
     *v = pa_json_object_get_int(o);
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     return 0;
 }
@@ -376,7 +376,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int range.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -423,7 +423,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int array.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -447,12 +447,12 @@ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, cha
 
     if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) {
         pa_log_debug("Format info property '%s' type is not string.", key);
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
         return -PA_ERR_INVALID;
     }
 
     *v = pa_xstrdup(pa_json_object_get_string(o));
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     return 0;
 }
@@ -500,7 +500,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid string array.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -675,9 +675,9 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
 
 out:
     if (o1)
-        pa_json_object_unref(o1);
+        pa_json_object_free(o1);
     if (o2)
-        pa_json_object_unref(o2);
+        pa_json_object_free(o2);
 
     return ret;
 }
diff --git a/src/pulse/json.c b/src/pulse/json.c
index 04501b7..d126712 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -27,13 +27,11 @@
 #include <pulse/xmalloc.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/hashmap.h>
-#include <pulsecore/refcnt.h>
 #include <pulsecore/strbuf.h>
 
 #define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */
 
 struct pa_json_object {
-    PA_REFCNT_DECLARE;
     pa_json_type type;
 
     union {
@@ -309,7 +307,7 @@ static const char *parse_object(const char *str, pa_json_object *obj, unsigned i
     pa_json_object *name = NULL, *value = NULL;
 
     obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
-                                             pa_xfree, (pa_free_cb_t) pa_json_object_unref);
+                                             pa_xfree, (pa_free_cb_t) pa_json_object_free);
 
     while (*str != '}') {
         str++; /* Consume leading '{' or ',' */
@@ -330,7 +328,7 @@ static const char *parse_object(const char *str, pa_json_object *obj, unsigned i
         }
 
         pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value);
-        pa_json_object_unref(name);
+        pa_json_object_free(name);
 
         name = NULL;
         value = NULL;
@@ -349,9 +347,9 @@ error:
     obj->object_values = NULL;
 
     if (name)
-        pa_json_object_unref(name);
+        pa_json_object_free(name);
     if (value)
-        pa_json_object_unref(value);
+        pa_json_object_free(value);
 
     return NULL;
 }
@@ -390,7 +388,7 @@ static const char *parse_array(const char *str, pa_json_object *obj, unsigned in
     return str;
 
 error:
-    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
     obj->array_values = NULL;
     return NULL;
 }
@@ -467,7 +465,7 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
     return str;
 
 error:
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return NULL;
 }
 
@@ -484,7 +482,7 @@ pa_json_object* pa_json_parse(const char *str) {
 
     if (*str != '\0') {
         pa_log("Unable to parse complete JSON string, remainder is: %s", str);
-        pa_json_object_unref(obj);
+        pa_json_object_free(obj);
         return NULL;
     }
 
@@ -495,9 +493,7 @@ pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
     return obj->type;
 }
 
-void pa_json_object_unref(pa_json_object *obj) {
-    if (PA_REFCNT_DEC(obj) > 0)
-        return;
+void pa_json_object_free(pa_json_object *obj) {
 
     switch (pa_json_object_get_type(obj)) {
         case PA_JSON_TYPE_INIT:
@@ -516,7 +512,7 @@ void pa_json_object_unref(pa_json_object *obj) {
             break;
 
         case PA_JSON_TYPE_ARRAY:
-            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
             break;
 
         default:
diff --git a/src/pulse/json.h b/src/pulse/json.h
index d8a946f..7759bf2 100644
--- a/src/pulse/json.h
+++ b/src/pulse/json.h
@@ -36,7 +36,7 @@ typedef struct pa_json_object pa_json_object;
 
 pa_json_object* pa_json_parse(const char *str);
 pa_json_type pa_json_object_get_type(const pa_json_object *obj);
-void pa_json_object_unref(pa_json_object *obj);
+void pa_json_object_free(pa_json_object *obj);
 
 /* All pointer members that are returned are valid while the corresponding object is valid */
 
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 08b2ff6..3e956db 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -45,7 +45,7 @@ START_TEST (string_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
         fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -63,7 +63,7 @@ START_TEST(int_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
         fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -85,7 +85,7 @@ START_TEST(double_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
         fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -98,7 +98,7 @@ START_TEST(null_test) {
     fail_unless(o != NULL);
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -111,7 +111,7 @@ START_TEST(bool_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(o) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("false");
 
@@ -119,7 +119,7 @@ START_TEST(bool_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(o) == false);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -137,7 +137,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
     fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
 
@@ -149,7 +149,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
     fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("{\"person\":true}");
 
@@ -161,7 +161,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("{ \"parent\": { \"child\": false } }");
     fail_unless(o != NULL);
@@ -174,7 +174,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v) == false);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -188,7 +188,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     fail_unless(pa_json_object_get_array_length(o) == 0);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("[\"a member\"]");
 
@@ -201,7 +201,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
     fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
 
@@ -225,7 +225,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v2) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 

commit e0f30a34312ddc6a13c0d73cd8c502230b986a09
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:38 2016 +0530

    json: Add some more negative test cases
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 4edfa09..08b2ff6 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -242,6 +242,9 @@ START_TEST(bad_test) {
         "-" /* Bad number string */,
         "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": {  \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
         "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
+        "asdf" /* Unquoted string */,
+        "{ a: true }" /* Unquoted key in object */,
+        "\"    \a\"" /* Alarm is not a valid character */
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit f3c05d30d3e67c6b4822eb845621948ce4ad5702
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:37 2016 +0530

    json: Add a positive test for nested objects
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 3f8ed92..4edfa09 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -162,6 +162,19 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_bool(v) == true);
 
     pa_json_object_unref(o);
+
+    o = pa_json_parse("{ \"parent\": { \"child\": false } }");
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "parent");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+    v = pa_json_object_get_object_member(v, "child");
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v) == false);
+
+    pa_json_object_unref(o);
 }
 END_TEST
 

commit 81b59e9a5a7fb361bca38985ecee9795fdbe5022
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:36 2016 +0530

    json: Error out for objects and arrays that are nested too deep
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 3c89a85..04501b7 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -30,6 +30,8 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/strbuf.h>
 
+#define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */
+
 struct pa_json_object {
     PA_REFCNT_DECLARE;
     pa_json_type type;
@@ -44,7 +46,7 @@ struct pa_json_object {
     };
 };
 
-static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth);
 
 static pa_json_object* json_object_new(void) {
     pa_json_object *obj;
@@ -303,7 +305,7 @@ error:
     return NULL;
 }
 
-static const char *parse_object(const char *str, pa_json_object *obj) {
+static const char *parse_object(const char *str, pa_json_object *obj, unsigned int depth) {
     pa_json_object *name = NULL, *value = NULL;
 
     obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
@@ -312,7 +314,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
     while (*str != '}') {
         str++; /* Consume leading '{' or ',' */
 
-        str = parse_value(str, ":", &name);
+        str = parse_value(str, ":", &name, depth + 1);
         if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) {
             pa_log("Could not parse key for object");
             goto error;
@@ -321,7 +323,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
         /* Consume the ':' */
         str++;
 
-        str = parse_value(str, ",}", &value);
+        str = parse_value(str, ",}", &value, depth + 1);
         if (!str) {
             pa_log("Could not parse value for object");
             goto error;
@@ -354,7 +356,7 @@ error:
     return NULL;
 }
 
-static const char *parse_array(const char *str, pa_json_object *obj) {
+static const char *parse_array(const char *str, pa_json_object *obj, unsigned int depth) {
     pa_json_object *value;
 
     obj->array_values = pa_idxset_new(NULL, NULL);
@@ -370,7 +372,7 @@ static const char *parse_array(const char *str, pa_json_object *obj) {
         if (*str == ']')
             break;
 
-        str = parse_value(str, ",]", &value);
+        str = parse_value(str, ",]", &value, depth + 1);
         if (!str) {
             pa_log("Could not parse value for array");
             goto error;
@@ -398,7 +400,7 @@ typedef enum {
     JSON_PARSER_STATE_FINISH,
 } json_parser_state;
 
-static const char* parse_value(const char *str, const char *end, pa_json_object **obj) {
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth) {
     json_parser_state state = JSON_PARSER_STATE_INIT;
     pa_json_object *o;
 
@@ -406,6 +408,11 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
 
     o = json_object_new();
 
+    if (depth > MAX_NESTING_DEPTH) {
+        pa_log("Exceeded maximum permitted nesting depth of objects (%u)", MAX_NESTING_DEPTH);
+        goto error;
+    }
+
     while (!is_end(*str, end)) {
         switch (state) {
             case JSON_PARSER_STATE_INIT:
@@ -424,10 +431,10 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
                     str = parse_number(str, o);
                     state = JSON_PARSER_STATE_FINISH;
                 } else if (*str == '{') {
-                    str = parse_object(str, o);
+                    str = parse_object(str, o, depth);
                     state = JSON_PARSER_STATE_FINISH;
                 } else if (*str == '[') {
-                    str = parse_array(str, o);
+                    str = parse_array(str, o, depth);
                     state = JSON_PARSER_STATE_FINISH;
                 } else {
                     pa_log("Invalid JSON string: %s", str);
@@ -468,7 +475,7 @@ error:
 pa_json_object* pa_json_parse(const char *str) {
     pa_json_object *obj;
 
-    str = parse_value(str, NULL, &obj);
+    str = parse_value(str, NULL, &obj, 0);
 
     if (!str) {
         pa_log("JSON parsing failed");
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index ca92877..3f8ed92 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -227,6 +227,8 @@ START_TEST(bad_test) {
         "1." /* Bad number string */,
         "1.e3" /* Bad number string */,
         "-" /* Bad number string */,
+        "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": {  \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
+        "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit e4dc10e54c7866795b872cea9e1a920c89d32ba8
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:35 2016 +0530

    json: Handle error cases while parsing numbers
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index d77c7ad..3c89a85 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -194,7 +194,7 @@ error:
 }
 
 static const char* parse_number(const char *str, pa_json_object *obj) {
-    bool negative = false, has_fraction = false, has_exponent = false;
+    bool negative = false, has_fraction = false, has_exponent = false, valid = false;
     unsigned int integer = 0;
     unsigned int fraction = 0;
     unsigned int fraction_digits = 0;
@@ -206,11 +206,14 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
     if (*str == '0') {
+        valid = true;
         str++;
         goto fraction;
     }
 
     while (is_digit(*str)) {
+        valid = true;
+
         if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
             pa_log("Integer overflow while parsing number");
             goto error;
@@ -221,11 +224,20 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
 fraction:
+
+    if (!valid) {
+        pa_log("Missing digits while parsing number");
+        goto error;
+    }
+
     if (*str == '.') {
         has_fraction = true;
         str++;
+        valid = false;
 
         while (is_digit(*str)) {
+            valid = true;
+
             if (fraction > (UINT_MAX / 10)) {
                 pa_log("Integer overflow while parsing fractional part of number");
                 goto error;
@@ -235,6 +247,11 @@ fraction:
             fraction_digits++;
             str++;
         }
+
+        if (!valid) {
+            pa_log("No digit after '.' while parsing fraction");
+            goto error;
+        }
     }
 
     if (*str == 'e' || *str == 'E') {
@@ -242,6 +259,7 @@ fraction:
 
         has_exponent = true;
         str++;
+        valid = false;
 
         if (*str == '-') {
             exponent_negative = true;
@@ -250,6 +268,8 @@ fraction:
             str++;
 
         while (is_digit(*str)) {
+            valid = true;
+
             if (exponent > (INT_MAX / 10)) {
                 pa_log("Integer overflow while parsing exponent part of number");
                 goto error;
@@ -259,6 +279,11 @@ fraction:
             str++;
         }
 
+        if (!valid) {
+            pa_log("No digit in exponent while parsing fraction");
+            goto error;
+        }
+
         if (exponent_negative)
             exponent *= -1;
     }
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index a5f1f74..ca92877 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -223,6 +223,10 @@ START_TEST(bad_test) {
         "123456789012345678901234567890" /* Overflow */,
         "0.123456789012345678901234567890" /* Overflow */,
         "1e123456789012345678901234567890" /* Overflow */,
+        "1e" /* Bad number string */,
+        "1." /* Bad number string */,
+        "1.e3" /* Bad number string */,
+        "-" /* Bad number string */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit a8e2aad845d502131f8f332bd0b06e2a7a3969d7
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:34 2016 +0530

    json: Add overflow checks for integer and float parsing
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 6297902..d77c7ad 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -211,6 +211,11 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
     while (is_digit(*str)) {
+        if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
+            pa_log("Integer overflow while parsing number");
+            goto error;
+        }
+
         integer = (integer * 10) + (*str - '0');
         str++;
     }
@@ -221,6 +226,11 @@ fraction:
         str++;
 
         while (is_digit(*str)) {
+            if (fraction > (UINT_MAX / 10)) {
+                pa_log("Integer overflow while parsing fractional part of number");
+                goto error;
+            }
+
             fraction = (fraction * 10) + (*str - '0');
             fraction_digits++;
             str++;
@@ -240,6 +250,11 @@ fraction:
             str++;
 
         while (is_digit(*str)) {
+            if (exponent > (INT_MAX / 10)) {
+                pa_log("Integer overflow while parsing exponent part of number");
+                goto error;
+            }
+
             exponent = (exponent * 10) + (*str - '0');
             str++;
         }
@@ -258,6 +273,9 @@ fraction:
     }
 
     return str;
+
+error:
+    return NULL;
 }
 
 static const char *parse_object(const char *str, pa_json_object *obj) {
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 7d273d7..a5f1f74 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -220,6 +220,9 @@ START_TEST(bad_test) {
     unsigned int i;
     const char *bad_parse[] = {
         "\"" /* Quote not closed */,
+        "123456789012345678901234567890" /* Overflow */,
+        "0.123456789012345678901234567890" /* Overflow */,
+        "1e123456789012345678901234567890" /* Overflow */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit 4f47766f99bfd283be948355a6c6b50cb45df44d
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:33 2016 +0530

    json: Correctly handle bad strings with missing closing quotes
    
    Also add a test for this case.
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 8484bba..6297902 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -122,7 +122,7 @@ static const char* parse_string(const char *str, pa_json_object *obj) {
 
     str++; /* Consume leading '"' */
 
-    while (*str != '"') {
+    while (*str && *str != '"') {
         if (*str != '\\') {
             /* We only accept ASCII printable characters. */
             if (*str < 0x20 || *str > 0x7E) {
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 2e1ca6b..7d273d7 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -216,6 +216,18 @@ START_TEST(array_test) {
 }
 END_TEST
 
+START_TEST(bad_test) {
+    unsigned int i;
+    const char *bad_parse[] = {
+        "\"" /* Quote not closed */,
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {
+        fail_unless(pa_json_parse(bad_parse[i]) == NULL);
+    }
+}
+END_TEST
+
 int main(int argc, char *argv[]) {
     int failed = 0;
     Suite *s;
@@ -231,6 +243,7 @@ int main(int argc, char *argv[]) {
     tcase_add_test(tc, bool_test);
     tcase_add_test(tc, object_test);
     tcase_add_test(tc, array_test);
+    tcase_add_test(tc, bad_test);
     suite_add_tcase(s, tc);
 
     sr = srunner_create(s);

commit 37948b50cef7fd564a5a625a49f9572ec2f566bd
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Wed Jun 1 17:18:32 2016 +0530

    format: Drop dependency on json-c
    
    json-c has a symbol clash (json_object_get_type) with json-glib (which
    at least a number of our GNOME clients use). This patch moves to our own
    JSON parser so that we can avoid this kind of situation altogether.
    
    Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=95135
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/configure.ac b/configure.ac
index cac5eff..3e8b82f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -680,10 +680,6 @@ AS_IF([test "x$enable_tests" = "xyes" && test "x$HAVE_LIBCHECK" = "x0"],
 
 AM_CONDITIONAL([HAVE_TESTS], [test "x$HAVE_LIBCHECK" = x1])
 
-#### json parsing ####
-
-PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ])
-
 #### Sound file ####
 
 PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ])
diff --git a/src/Makefile.am b/src/Makefile.am
index c6b998c..7b19497 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -739,9 +739,9 @@ else
 libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += pulsecore/poll-posix.c pulsecore/poll.h
 endif
 
-libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS)
+libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
 libpulsecommon_ at PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
-libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS)  $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
+libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
 
 if HAVE_MEMFD
 libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += \
@@ -893,8 +893,8 @@ libpulse_la_SOURCES = \
 		pulse/volume.c pulse/volume.h \
 		pulse/xmalloc.c pulse/xmalloc.h
 
-libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS)
-libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon- at PA_MAJORMINOR@.la
+libpulse_la_CFLAGS = $(AM_CFLAGS)
+libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon- at PA_MAJORMINOR@.la
 libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO)
 
 if HAVE_DBUS
diff --git a/src/pulse/format.c b/src/pulse/format.c
index c2a1552..ee8b7ac 100644
--- a/src/pulse/format.c
+++ b/src/pulse/format.c
@@ -23,8 +23,7 @@
 #include <config.h>
 #endif
 
-#include <json.h>
-
+#include <pulse/json.h>
 #include <pulse/internal.h>
 #include <pulse/xmalloc.h>
 
@@ -32,6 +31,7 @@
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
 
 #include "format.h"
 
@@ -236,7 +236,8 @@ int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, p
 
 pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     pa_prop_type_t type;
 
     pa_assert(f);
@@ -246,47 +247,47 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
     if (!str)
         return PA_PROP_TYPE_INVALID;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o)
         return PA_PROP_TYPE_INVALID;
 
-    switch (json_object_get_type(o)) {
-        case json_type_int:
+    switch (pa_json_object_get_type(o)) {
+        case PA_JSON_TYPE_INT:
             type = PA_PROP_TYPE_INT;
             break;
 
-        case json_type_string:
+        case PA_JSON_TYPE_STRING:
             type = PA_PROP_TYPE_STRING;
             break;
 
-        case json_type_array:
-            if (json_object_array_length(o) == 0) {
+        case PA_JSON_TYPE_ARRAY:
+            if (pa_json_object_get_array_length(o) == 0) {
                 /* Unlikely, but let's account for this anyway. We need at
                  * least one element to figure out the array type. */
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
 
-            o1 = json_object_array_get_idx(o, 1);
+            o1 = pa_json_object_get_array_member(o, 0);
 
-            if (json_object_get_type(o1) == json_type_int)
+            if (pa_json_object_get_type(o1) == PA_JSON_TYPE_INT)
                 type = PA_PROP_TYPE_INT_ARRAY;
-            else if (json_object_get_type(o1) == json_type_string)
+            else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_STRING)
                 type = PA_PROP_TYPE_STRING_ARRAY;
             else
                 type = PA_PROP_TYPE_INVALID;
 
             break;
 
-        case json_type_object:
+        case PA_JSON_TYPE_OBJECT:
             /* We actually know at this point that it's a int range, but let's
              * confirm. */
-            if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, NULL)) {
+            if (!pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) {
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
 
-            if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, NULL)) {
+            if (!pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) {
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
@@ -299,13 +300,13 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
             break;
     }
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return type;
 }
 
 int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) {
     const char *str;
-    json_object *o;
+    pa_json_object *o;
 
     pa_assert(f);
     pa_assert(key);
@@ -315,27 +316,28 @@ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_int) {
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) {
         pa_log_debug("Format info property '%s' type is not int.", key);
-        json_object_put(o);
+        pa_json_object_unref(o);
         return -PA_ERR_INVALID;
     }
 
-    *v = json_object_get_int(o);
-    json_object_put(o);
+    *v = pa_json_object_get_int(o);
+    pa_json_object_unref(o);
 
     return 0;
 }
 
 int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -347,24 +349,26 @@ int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key,
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_object)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_OBJECT)
         goto out;
 
-    if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, &o1))
+    if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) ||
+            (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
         goto out;
 
-    *min = json_object_get_int(o1);
+    *min = pa_json_object_get_int(o1);
 
-    if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, &o1))
+    if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) ||
+            (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
         goto out;
 
-    *max = json_object_get_int(o1);
+    *max = pa_json_object_get_int(o1);
 
     ret = 0;
 
@@ -372,13 +376,14 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int range.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
 int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int i, ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -390,26 +395,26 @@ int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key,
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_array)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
         goto out;
 
-    *n_values = json_object_array_length(o);
+    *n_values = pa_json_object_get_array_length(o);
     *values = pa_xnew(int, *n_values);
 
     for (i = 0; i < *n_values; i++) {
-        o1 = json_object_array_get_idx(o, i);
+        o1 = pa_json_object_get_array_member(o, i);
 
-        if (json_object_get_type(o1) != json_type_int) {
+        if (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT) {
             goto out;
         }
 
-        (*values)[i] = json_object_get_int(o1);
+        (*values)[i] = pa_json_object_get_int(o1);
     }
 
     ret = 0;
@@ -418,13 +423,13 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int array.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
 int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) {
     const char *str = NULL;
-    json_object *o;
+    pa_json_object *o;
 
     pa_assert(f);
     pa_assert(key);
@@ -434,27 +439,28 @@ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, cha
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_string) {
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) {
         pa_log_debug("Format info property '%s' type is not string.", key);
-        json_object_put(o);
+        pa_json_object_unref(o);
         return -PA_ERR_INVALID;
     }
 
-    *v = pa_xstrdup(json_object_get_string(o));
-    json_object_put(o);
+    *v = pa_xstrdup(pa_json_object_get_string(o));
+    pa_json_object_unref(o);
 
     return 0;
 }
 
 int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int i, ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -466,26 +472,26 @@ int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *ke
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_array)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
         goto out;
 
-    *n_values = json_object_array_length(o);
+    *n_values = pa_json_object_get_array_length(o);
     *values = pa_xnew(char *, *n_values);
 
     for (i = 0; i < *n_values; i++) {
-        o1 = json_object_array_get_idx(o, i);
+        o1 = pa_json_object_get_array_member(o, i);
 
-        if (json_object_get_type(o1) != json_type_string) {
+        if (pa_json_object_get_type(o1) != PA_JSON_TYPE_STRING) {
             goto out;
         }
 
-        (*values)[i] = pa_xstrdup(json_object_get_string(o1));
+        (*values)[i] = pa_xstrdup(pa_json_object_get_string(o1));
     }
 
     ret = 0;
@@ -494,7 +500,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid string array.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
@@ -528,85 +534,76 @@ void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map
 }
 
 void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_int(value);
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "%d", value);
 }
 
 void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) {
-    json_object *o;
+    pa_strbuf *buf;
+    char *str;
     int i;
 
     pa_assert(f);
     pa_assert(key);
+    pa_assert(n_values > 0);
 
-    o = json_object_new_array();
+    buf = pa_strbuf_new();
 
-    for (i = 0; i < n_values; i++)
-        json_object_array_add(o, json_object_new_int(values[i]));
+    pa_strbuf_printf(buf, "[ %d", values[0]);
 
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
+    for (i = 1; i < n_values; i++)
+        pa_strbuf_printf(buf, ", %d", values[i]);
 
-    json_object_put(o);
+    pa_strbuf_printf(buf, " ]");
+    str = pa_strbuf_to_string_free(buf);
+
+    pa_proplist_sets(f->plist, key, str);
+    pa_xfree (str);
 }
 
 void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_object();
-
-    json_object_object_add(o, PA_JSON_MIN_KEY, json_object_new_int(min));
-    json_object_object_add(o, PA_JSON_MAX_KEY, json_object_new_int(max));
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "{ \"" PA_JSON_MIN_KEY "\": %d, \"" PA_JSON_MAX_KEY "\": %d }",
+            min, max);
 }
 
 void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_string(value);
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "\"%s\"", value);
 }
 
 void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) {
-    json_object *o;
+    pa_strbuf *buf;
+    char *str;
     int i;
 
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_array();
+    buf = pa_strbuf_new();
 
-    for (i = 0; i < n_values; i++)
-        json_object_array_add(o, json_object_new_string(values[i]));
+    pa_strbuf_printf(buf, "[ \"%s\"", values[0]);
+
+    for (i = 1; i < n_values; i++)
+        pa_strbuf_printf(buf, ", \"%s\"", values[i]);
 
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
+    pa_strbuf_printf(buf, " ]");
+    str = pa_strbuf_to_string_free(buf);
 
-    json_object_put(o);
+    pa_proplist_sets(f->plist, key, str);
+    pa_xfree (str);
 }
 
-static bool pa_json_is_fixed_type(json_object *o) {
-    switch(json_object_get_type(o)) {
-        case json_type_object:
-        case json_type_array:
+static bool pa_json_is_fixed_type(pa_json_object *o) {
+    switch(pa_json_object_get_type(o)) {
+        case PA_JSON_TYPE_OBJECT:
+        case PA_JSON_TYPE_ARRAY:
             return false;
 
         default:
@@ -614,20 +611,15 @@ static bool pa_json_is_fixed_type(json_object *o) {
     }
 }
 
-static int pa_json_value_equal(json_object *o1, json_object *o2) {
-    return (json_object_get_type(o1) == json_object_get_type(o2)) &&
-        pa_streq(json_object_to_json_string(o1), json_object_to_json_string(o2));
-}
-
 static int pa_format_info_prop_compatible(const char *one, const char *two) {
-    json_object *o1 = NULL, *o2 = NULL;
+    pa_json_object *o1 = NULL, *o2 = NULL;
     int i, ret = 0;
 
-    o1 = json_tokener_parse(one);
+    o1 = pa_json_parse(one);
     if (!o1)
         goto out;
 
-    o2 = json_tokener_parse(two);
+    o2 = pa_json_parse(two);
     if (!o2)
         goto out;
 
@@ -635,46 +627,46 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
     pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), false);
 
     if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) {
-        ret = pa_json_value_equal(o1, o2);
+        ret = pa_json_object_equal(o1, o2);
         goto out;
     }
 
     if (pa_json_is_fixed_type(o1)) {
-        json_object *tmp = o2;
+        pa_json_object *tmp = o2;
         o2 = o1;
         o1 = tmp;
     }
 
     /* o2 is now a fixed type, and o1 is not */
 
-    if (json_object_get_type(o1) == json_type_array) {
-        for (i = 0; i < json_object_array_length(o1); i++) {
-            if (pa_json_value_equal(json_object_array_get_idx(o1, i), o2)) {
+    if (pa_json_object_get_type(o1) == PA_JSON_TYPE_ARRAY) {
+        for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
+            if (pa_json_object_equal(pa_json_object_get_array_member(o1, i), o2)) {
                 ret = 1;
                 break;
             }
         }
-    } else if (json_object_get_type(o1) == json_type_object) {
+    } else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_OBJECT) {
         /* o1 should be a range type */
         int min, max, v;
-        json_object *o_min = NULL, *o_max = NULL;
+        const pa_json_object *o_min = NULL, *o_max = NULL;
 
-        if (json_object_get_type(o2) != json_type_int) {
+        if (pa_json_object_get_type(o2) != PA_JSON_TYPE_INT) {
             /* We don't support non-integer ranges */
             goto out;
         }
 
-        if (!json_object_object_get_ex(o1, PA_JSON_MIN_KEY, &o_min) ||
-            json_object_get_type(o_min) != json_type_int)
+        if (!(o_min = pa_json_object_get_object_member(o1, PA_JSON_MIN_KEY)) ||
+            pa_json_object_get_type(o_min) != PA_JSON_TYPE_INT)
             goto out;
 
-        if (!json_object_object_get_ex(o1, PA_JSON_MAX_KEY, &o_max) ||
-            json_object_get_type(o_max) != json_type_int)
+        if (!(o_max = pa_json_object_get_object_member(o1, PA_JSON_MAX_KEY)) ||
+            pa_json_object_get_type(o_max) != PA_JSON_TYPE_INT)
             goto out;
 
-        v = json_object_get_int(o2);
-        min = json_object_get_int(o_min);
-        max = json_object_get_int(o_max);
+        v = pa_json_object_get_int(o2);
+        min = pa_json_object_get_int(o_min);
+        max = pa_json_object_get_int(o_max);
 
         ret = v >= min && v <= max;
     } else {
@@ -683,9 +675,9 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
 
 out:
     if (o1)
-        json_object_put(o1);
+        pa_json_object_unref(o1);
     if (o2)
-        json_object_put(o2);
+        pa_json_object_unref(o2);
 
     return ret;
 }
diff --git a/src/pulse/json.c b/src/pulse/json.c
index 7cb33ef..8484bba 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -44,8 +44,6 @@ struct pa_json_object {
     };
 };
 
-#define JSON_OBJECT_TYPE(o) ((o)->type)
-
 static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
 
 static pa_json_object* json_object_new(void) {
@@ -272,7 +270,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
         str++; /* Consume leading '{' or ',' */
 
         str = parse_value(str, ":", &name);
-        if (!str || JSON_OBJECT_TYPE(name) != PA_JSON_TYPE_STRING) {
+        if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) {
             pa_log("Could not parse key for object");
             goto error;
         }
@@ -408,7 +406,7 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
         }
     }
 
-    if (JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INIT) {
+    if (pa_json_object_get_type(o) == PA_JSON_TYPE_INIT) {
         /* We didn't actually get any data */
         pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end));
         goto error;
@@ -444,14 +442,14 @@ pa_json_object* pa_json_parse(const char *str) {
 }
 
 pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
-    return JSON_OBJECT_TYPE(obj);
+    return obj->type;
 }
 
 void pa_json_object_unref(pa_json_object *obj) {
     if (PA_REFCNT_DEC(obj) > 0)
         return;
 
-    switch (JSON_OBJECT_TYPE(obj)) {
+    switch (pa_json_object_get_type(obj)) {
         case PA_JSON_TYPE_INIT:
         case PA_JSON_TYPE_INT:
         case PA_JSON_TYPE_DOUBLE:
@@ -479,36 +477,92 @@ void pa_json_object_unref(pa_json_object *obj) {
 }
 
 int pa_json_object_get_int(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INT, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
     return o->int_value;
 }
 
 double pa_json_object_get_double(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_DOUBLE, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
     return o->double_value;
 }
 
 bool pa_json_object_get_bool(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_BOOL, false);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     return o->bool_value;
 }
 
 const char* pa_json_object_get_string(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_STRING, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
     return o->string_value;
 }
 
 const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_OBJECT, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
     return pa_hashmap_get(o->object_values, name);
 }
 
 int pa_json_object_get_array_length(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     return pa_idxset_size(o->array_values);
 }
 
 const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     return pa_idxset_get_by_index(o->array_values, index);
 }
+
+bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) {
+    int i;
+
+    if (pa_json_object_get_type(o1) != pa_json_object_get_type(o2))
+        return false;
+
+    switch (pa_json_object_get_type(o1)) {
+        case PA_JSON_TYPE_NULL:
+            return true;
+
+        case PA_JSON_TYPE_BOOL:
+            return o1->bool_value == o2->bool_value;
+
+        case PA_JSON_TYPE_INT:
+            return o1->int_value == o2->int_value;
+
+        case PA_JSON_TYPE_DOUBLE:
+            return PA_DOUBLE_IS_EQUAL(o1->double_value, o2->double_value);
+
+        case PA_JSON_TYPE_STRING:
+            return pa_streq(o1->string_value, o2->string_value);
+
+        case PA_JSON_TYPE_ARRAY:
+            if (pa_json_object_get_array_length(o1) != pa_json_object_get_array_length(o2))
+                return false;
+
+            for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
+                if (!pa_json_object_equal(pa_json_object_get_array_member(o1, i),
+                            pa_json_object_get_array_member(o2, i)))
+                    return false;
+            }
+
+            return true;
+
+        case PA_JSON_TYPE_OBJECT: {
+            void *state;
+            const char *key;
+            const pa_json_object *v1, *v2;
+
+            if (pa_hashmap_size(o1->object_values) != pa_hashmap_size(o2->object_values))
+                return false;
+
+            PA_HASHMAP_FOREACH_KV(key, v1, o1->object_values, state) {
+                v2 = pa_json_object_get_object_member(o2, key);
+                if (!v2 || !pa_json_object_equal(v1, v2))
+                    return false;
+            }
+
+            return true;
+        }
+
+        default:
+            pa_assert_not_reached();
+    }
+}
diff --git a/src/pulse/json.h b/src/pulse/json.h
index 99c22ec..d8a946f 100644
--- a/src/pulse/json.h
+++ b/src/pulse/json.h
@@ -19,6 +19,8 @@
 
 #include <stdbool.h>
 
+#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
+
 typedef enum {
     PA_JSON_TYPE_INIT = 0,
     PA_JSON_TYPE_NULL,
@@ -47,3 +49,5 @@ const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o,
 
 int pa_json_object_get_array_length(const pa_json_object *o);
 const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index);
+
+bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2);
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index e028e68..2e1ca6b 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -26,8 +26,6 @@
 #include <pulse/json.h>
 #include <pulsecore/core-util.h>
 
-#define IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
-
 START_TEST (string_test) {
     pa_json_object *o;
     unsigned int i;
@@ -85,7 +83,7 @@ START_TEST(double_test) {
 
         fail_unless(o != NULL);
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
-        fail_unless(IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
+        fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
 
         pa_json_object_unref(o);
     }
@@ -149,7 +147,7 @@ START_TEST(object_test) {
     v = pa_json_object_get_object_member(o, "age");
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
-    fail_unless(IS_EQUAL(pa_json_object_get_double(v), -45.3));
+    fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
 
     pa_json_object_unref(o);
 
@@ -205,7 +203,7 @@ START_TEST(array_test) {
     v = pa_json_object_get_array_member(o, 1);
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
-    fail_unless(IS_EQUAL(pa_json_object_get_double(v), 1234.5));
+    fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), 1234.5));
     v = pa_json_object_get_array_member(o, 2);
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);

commit c82aeb7fc2e2a4d30561917154b07119a7e08e18
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Wed Jun 1 17:18:31 2016 +0530

    pulse: Add a JSON-parsing library
    
    Adding this to be able to drop dependency on json-c.
    
    Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=95135
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/.gitignore b/src/.gitignore
index bfe74bd..f7ec1bc 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -50,6 +50,7 @@ gtk-test
 hook-list-test
 interpol-test
 ipacl-test
+json-test
 lfe-filter-test
 lock-autospawn-test
 lo-latency-test
diff --git a/src/Makefile.am b/src/Makefile.am
index a311575..c6b998c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -249,6 +249,7 @@ TESTS_default = \
 		thread-mainloop-test \
 		utf8-test \
 		format-test \
+		json-test \
 		get-binary-name-test \
 		hook-list-test \
 		memblock-test \
@@ -381,6 +382,11 @@ format_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 format_test_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
 format_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
 
+json_test_SOURCES = tests/json-test.c
+json_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
+json_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
+json_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
+
 srbchannel_test_SOURCES = tests/srbchannel-test.c
 srbchannel_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 srbchannel_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
@@ -652,6 +658,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \
 		pulse/client-conf.c pulse/client-conf.h \
 		pulse/fork-detect.c pulse/fork-detect.h \
 		pulse/format.c pulse/format.h \
+		pulse/json.c pulse/json.h \
 		pulse/xmalloc.c pulse/xmalloc.h \
 		pulse/proplist.c pulse/proplist.h \
 		pulse/utf8.c pulse/utf8.h \
diff --git a/src/pulse/json.c b/src/pulse/json.c
new file mode 100644
index 0000000..7cb33ef
--- /dev/null
+++ b/src/pulse/json.c
@@ -0,0 +1,514 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <pulse/json.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/strbuf.h>
+
+struct pa_json_object {
+    PA_REFCNT_DECLARE;
+    pa_json_type type;
+
+    union {
+        int int_value;
+        double double_value;
+        bool bool_value;
+        char *string_value;
+        pa_hashmap *object_values; /* name -> object */
+        pa_idxset *array_values; /* objects */
+    };
+};
+
+#define JSON_OBJECT_TYPE(o) ((o)->type)
+
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
+
+static pa_json_object* json_object_new(void) {
+    pa_json_object *obj;
+
+    obj = pa_xnew0(pa_json_object, 1);
+
+    return obj;
+}
+
+static bool is_whitespace(char c) {
+    return c == '\t' || c == '\n' || c == '\r' || c == ' ';
+}
+
+static bool is_digit(char c) {
+    return c >= '0' && c <= '9';
+}
+
+static bool is_end(const char c, const char *end) {
+    if (!end)
+        return c == '\0';
+    else  {
+        while (*end) {
+            if (c == *end)
+                return true;
+            end++;
+        }
+    }
+
+    return false;
+}
+
+static const char* consume_string(const char *str, const char *expect) {
+    while (*expect) {
+        if (*str != *expect)
+            return NULL;
+
+        str++;
+        expect++;
+    }
+
+    return str;
+}
+
+static const char* parse_null(const char *str, pa_json_object *obj) {
+    str = consume_string(str, "null");
+
+    if (str)
+        obj->type = PA_JSON_TYPE_NULL;
+
+    return str;
+}
+
+static const char* parse_boolean(const char *str, pa_json_object *obj) {
+    const char *tmp;
+
+    tmp = consume_string(str, "true");
+
+    if (tmp) {
+        obj->type = PA_JSON_TYPE_BOOL;
+        obj->bool_value = true;
+    } else {
+        tmp = consume_string(str, "false");
+
+        if (str) {
+            obj->type = PA_JSON_TYPE_BOOL;
+            obj->bool_value = false;
+        }
+    }
+
+    return tmp;
+}
+
+static const char* parse_string(const char *str, pa_json_object *obj) {
+    pa_strbuf *buf = pa_strbuf_new();
+
+    str++; /* Consume leading '"' */
+
+    while (*str != '"') {
+        if (*str != '\\') {
+            /* We only accept ASCII printable characters. */
+            if (*str < 0x20 || *str > 0x7E) {
+                pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *str);
+                goto error;
+            }
+
+            /* Normal character, juts consume */
+            pa_strbuf_putc(buf, *str);
+        } else {
+            /* Need to unescape */
+            str++;
+
+            switch (*str) {
+                case '"':
+                case '\\':
+                case '/':
+                    pa_strbuf_putc(buf, *str);
+                    break;
+
+                case 'b':
+                    pa_strbuf_putc(buf, '\b' /* backspace */);
+                    break;
+
+                case 'f':
+                    pa_strbuf_putc(buf, '\f' /* form feed */);
+                    break;
+
+                case 'n':
+                    pa_strbuf_putc(buf, '\n' /* new line */);
+                    break;
+
+                case 'r':
+                    pa_strbuf_putc(buf, '\r' /* carriage return */);
+                    break;
+
+                case 't':
+                    pa_strbuf_putc(buf, '\t' /* horizontal tab */);
+                    break;
+
+                case 'u':
+                    pa_log("Unicode code points are currently unsupported");
+                    goto error;
+
+                default:
+                    pa_log("Unexepcted escape value: %c", *str);
+                    goto error;
+            }
+        }
+
+        str++;
+    }
+
+    if (*str != '"') {
+        pa_log("Failed to parse remainder of string: %s", str);
+        goto error;
+    }
+
+    str++;
+
+    obj->type = PA_JSON_TYPE_STRING;
+    obj->string_value = pa_strbuf_to_string_free(buf);
+
+    return str;
+
+error:
+    pa_strbuf_free(buf);
+    return NULL;
+}
+
+static const char* parse_number(const char *str, pa_json_object *obj) {
+    bool negative = false, has_fraction = false, has_exponent = false;
+    unsigned int integer = 0;
+    unsigned int fraction = 0;
+    unsigned int fraction_digits = 0;
+    int exponent = 0;
+
+    if (*str == '-') {
+        negative = true;
+        str++;
+    }
+
+    if (*str == '0') {
+        str++;
+        goto fraction;
+    }
+
+    while (is_digit(*str)) {
+        integer = (integer * 10) + (*str - '0');
+        str++;
+    }
+
+fraction:
+    if (*str == '.') {
+        has_fraction = true;
+        str++;
+
+        while (is_digit(*str)) {
+            fraction = (fraction * 10) + (*str - '0');
+            fraction_digits++;
+            str++;
+        }
+    }
+
+    if (*str == 'e' || *str == 'E') {
+        bool exponent_negative = false;
+
+        has_exponent = true;
+        str++;
+
+        if (*str == '-') {
+            exponent_negative = true;
+            str++;
+        } else if (*str == '+')
+            str++;
+
+        while (is_digit(*str)) {
+            exponent = (exponent * 10) + (*str - '0');
+            str++;
+        }
+
+        if (exponent_negative)
+            exponent *= -1;
+    }
+
+    if (has_fraction || has_exponent) {
+        obj->type = PA_JSON_TYPE_DOUBLE;
+        obj->double_value =
+            (negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent);
+    } else {
+        obj->type = PA_JSON_TYPE_INT;
+        obj->int_value = (negative ? -1 : 1) * integer;
+    }
+
+    return str;
+}
+
+static const char *parse_object(const char *str, pa_json_object *obj) {
+    pa_json_object *name = NULL, *value = NULL;
+
+    obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
+                                             pa_xfree, (pa_free_cb_t) pa_json_object_unref);
+
+    while (*str != '}') {
+        str++; /* Consume leading '{' or ',' */
+
+        str = parse_value(str, ":", &name);
+        if (!str || JSON_OBJECT_TYPE(name) != PA_JSON_TYPE_STRING) {
+            pa_log("Could not parse key for object");
+            goto error;
+        }
+
+        /* Consume the ':' */
+        str++;
+
+        str = parse_value(str, ",}", &value);
+        if (!str) {
+            pa_log("Could not parse value for object");
+            goto error;
+        }
+
+        pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value);
+        pa_json_object_unref(name);
+
+        name = NULL;
+        value = NULL;
+    }
+
+    /* Drop trailing '}' */
+    str++;
+
+    /* We now know the value was correctly parsed */
+    obj->type = PA_JSON_TYPE_OBJECT;
+
+    return str;
+
+error:
+    pa_hashmap_free(obj->object_values);
+    obj->object_values = NULL;
+
+    if (name)
+        pa_json_object_unref(name);
+    if (value)
+        pa_json_object_unref(value);
+
+    return NULL;
+}
+
+static const char *parse_array(const char *str, pa_json_object *obj) {
+    pa_json_object *value;
+
+    obj->array_values = pa_idxset_new(NULL, NULL);
+
+    while (*str != ']') {
+        str++; /* Consume leading '[' or ',' */
+
+        /* Need to chew up whitespaces as a special case to deal with the
+         * possibility of an empty array */
+        while (is_whitespace(*str))
+            str++;
+
+        if (*str == ']')
+            break;
+
+        str = parse_value(str, ",]", &value);
+        if (!str) {
+            pa_log("Could not parse value for array");
+            goto error;
+        }
+
+        pa_idxset_put(obj->array_values, value, NULL);
+    }
+
+    /* Drop trailing ']' */
+    str++;
+
+    /* We now know the value was correctly parsed */
+    obj->type = PA_JSON_TYPE_ARRAY;
+
+    return str;
+
+error:
+    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+    obj->array_values = NULL;
+    return NULL;
+}
+
+typedef enum {
+    JSON_PARSER_STATE_INIT,
+    JSON_PARSER_STATE_FINISH,
+} json_parser_state;
+
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj) {
+    json_parser_state state = JSON_PARSER_STATE_INIT;
+    pa_json_object *o;
+
+    pa_assert(str != NULL);
+
+    o = json_object_new();
+
+    while (!is_end(*str, end)) {
+        switch (state) {
+            case JSON_PARSER_STATE_INIT:
+                if (is_whitespace(*str)) {
+                    str++;
+                } else if (*str == 'n') {
+                    str = parse_null(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == 't' || *str == 'f') {
+                    str = parse_boolean(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '"') {
+                    str = parse_string(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (is_digit(*str) || *str == '-') {
+                    str = parse_number(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '{') {
+                    str = parse_object(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '[') {
+                    str = parse_array(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else {
+                    pa_log("Invalid JSON string: %s", str);
+                    goto error;
+                }
+
+                if (!str)
+                    goto error;
+
+                break;
+
+            case JSON_PARSER_STATE_FINISH:
+                /* Consume trailing whitespaces */
+                if (is_whitespace(*str)) {
+                    str++;
+                } else {
+                    goto error;
+                }
+        }
+    }
+
+    if (JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INIT) {
+        /* We didn't actually get any data */
+        pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end));
+        goto error;
+    }
+
+    *obj = o;
+
+    return str;
+
+error:
+    pa_json_object_unref(o);
+    return NULL;
+}
+
+
+pa_json_object* pa_json_parse(const char *str) {
+    pa_json_object *obj;
+
+    str = parse_value(str, NULL, &obj);
+
+    if (!str) {
+        pa_log("JSON parsing failed");
+        return NULL;
+    }
+
+    if (*str != '\0') {
+        pa_log("Unable to parse complete JSON string, remainder is: %s", str);
+        pa_json_object_unref(obj);
+        return NULL;
+    }
+
+    return obj;
+}
+
+pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
+    return JSON_OBJECT_TYPE(obj);
+}
+
+void pa_json_object_unref(pa_json_object *obj) {
+    if (PA_REFCNT_DEC(obj) > 0)
+        return;
+
+    switch (JSON_OBJECT_TYPE(obj)) {
+        case PA_JSON_TYPE_INIT:
+        case PA_JSON_TYPE_INT:
+        case PA_JSON_TYPE_DOUBLE:
+        case PA_JSON_TYPE_BOOL:
+        case PA_JSON_TYPE_NULL:
+            break;
+
+        case PA_JSON_TYPE_STRING:
+            pa_xfree(obj->string_value);
+            break;
+
+        case PA_JSON_TYPE_OBJECT:
+            pa_hashmap_free(obj->object_values);
+            break;
+
+        case PA_JSON_TYPE_ARRAY:
+            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+            break;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+    pa_xfree(obj);
+}
+
+int pa_json_object_get_int(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INT, 0);
+    return o->int_value;
+}
+
+double pa_json_object_get_double(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_DOUBLE, 0);
+    return o->double_value;
+}
+
+bool pa_json_object_get_bool(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_BOOL, false);
+    return o->bool_value;
+}
+
+const char* pa_json_object_get_string(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_STRING, NULL);
+    return o->string_value;
+}
+
+const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_OBJECT, NULL);
+    return pa_hashmap_get(o->object_values, name);
+}
+
+int pa_json_object_get_array_length(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, 0);
+    return pa_idxset_size(o->array_values);
+}
+
+const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, NULL);
+    return pa_idxset_get_by_index(o->array_values, index);
+}
diff --git a/src/pulse/json.h b/src/pulse/json.h
new file mode 100644
index 0000000..99c22ec
--- /dev/null
+++ b/src/pulse/json.h
@@ -0,0 +1,49 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+typedef enum {
+    PA_JSON_TYPE_INIT = 0,
+    PA_JSON_TYPE_NULL,
+    PA_JSON_TYPE_INT,
+    PA_JSON_TYPE_DOUBLE,
+    PA_JSON_TYPE_BOOL,
+    PA_JSON_TYPE_STRING,
+    PA_JSON_TYPE_ARRAY,
+    PA_JSON_TYPE_OBJECT,
+} pa_json_type;
+
+typedef struct pa_json_object pa_json_object;
+
+pa_json_object* pa_json_parse(const char *str);
+pa_json_type pa_json_object_get_type(const pa_json_object *obj);
+void pa_json_object_unref(pa_json_object *obj);
+
+/* All pointer members that are returned are valid while the corresponding object is valid */
+
+int pa_json_object_get_int(const pa_json_object *o);
+double pa_json_object_get_double(const pa_json_object *o);
+bool pa_json_object_get_bool(const pa_json_object *o);
+const char* pa_json_object_get_string(const pa_json_object *o);
+
+const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name);
+
+int pa_json_object_get_array_length(const pa_json_object *o);
+const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index);
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
new file mode 100644
index 0000000..e028e68
--- /dev/null
+++ b/src/tests/json-test.c
@@ -0,0 +1,244 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulse/json.h>
+#include <pulsecore/core-util.h>
+
+#define IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
+
+START_TEST (string_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *strings_parse[] = {
+        "\"\"", "\"test\"", "\"test123\"", "\"123\"", "\"newline\\n\"", "\"  spaces \"",
+        "   \"lots of spaces\"     ", "\"esc\\nape\"", "\"escape a \\\" quote\"",
+    };
+    const char *strings_compare[] = {
+        "", "test", "test123", "123", "newline\n", "  spaces ",
+        "lots of spaces", "esc\nape", "escape a \" quote",
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(strings_parse); i++) {
+        o = pa_json_parse(strings_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
+        fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(int_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *ints_parse[] = { "1", "-1", "1234", "0" };
+    const int ints_compare[] = { 1, -1, 1234, 0 };
+
+    for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) {
+        o = pa_json_parse(ints_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
+        fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(double_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *doubles_parse[] = {
+        "1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2",
+    };
+    const double doubles_compare[] = {
+        1.0, -1.1, 123400.0, 1234.0, 0.1234, -0.1234, 123.4, 123.45, 123450.0,
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(doubles_parse); i++) {
+        o = pa_json_parse(doubles_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
+        fail_unless(IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(null_test) {
+    pa_json_object *o;
+
+    o = pa_json_parse("null");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(bool_test) {
+    pa_json_object *o;
+
+    o = pa_json_parse("true");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(o) == true);
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("false");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(o) == false);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(object_test) {
+    pa_json_object *o;
+    const pa_json_object *v;
+
+    o = pa_json_parse(" { \"name\" : \"A Person\" } ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "name");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "age");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+    fail_unless(IS_EQUAL(pa_json_object_get_double(v), -45.3));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("{\"person\":true}");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "person");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v) == true);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(array_test) {
+    pa_json_object *o;
+    const pa_json_object *v, *v2;
+
+    o = pa_json_parse(" [  ] ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 0);
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("[\"a member\"]");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 1);
+
+    v = pa_json_object_get_array_member(o, 0);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 3);
+
+    v = pa_json_object_get_array_member(o, 0);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+    v = pa_json_object_get_array_member(o, 1);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+    fail_unless(IS_EQUAL(pa_json_object_get_double(v), 1234.5));
+    v = pa_json_object_get_array_member(o, 2);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+    v2 =pa_json_object_get_object_member(v, "another");
+    fail_unless(v2 != NULL);
+    fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v2) == true);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+    int failed = 0;
+    Suite *s;
+    TCase *tc;
+    SRunner *sr;
+
+    s = suite_create("JSON");
+    tc = tcase_create("json");
+    tcase_add_test(tc, string_test);
+    tcase_add_test(tc, int_test);
+    tcase_add_test(tc, double_test);
+    tcase_add_test(tc, null_test);
+    tcase_add_test(tc, bool_test);
+    tcase_add_test(tc, object_test);
+    tcase_add_test(tc, array_test);
+    suite_add_tcase(s, tc);
+
+    sr = srunner_create(s);
+    srunner_run_all(sr, CK_NORMAL);
+    failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+
+    return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}



More information about the pulseaudio-commits mailing list