[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