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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Date: Wed Jun 1 17:18:38 2016 +0530 json: Add some more negative test cases Signed-off-by: Arun Raghavan <[email protected]> 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 <[email protected]> Date: Wed Jun 1 17:18:37 2016 +0530 json: Add a positive test for nested objects Signed-off-by: Arun Raghavan <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Date: Wed Jun 1 17:18:35 2016 +0530 json: Handle error cases while parsing numbers Signed-off-by: Arun Raghavan <[email protected]> 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 <[email protected]> Date: Wed Jun 1 17:18:34 2016 +0530 json: Add overflow checks for integer and float parsing Signed-off-by: Arun Raghavan <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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_@PA_MAJORMINOR@_la_SOURCES += pulsecore/poll-posix.c pulsecore/poll.h endif -libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS) +libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version -libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) +libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) if HAVE_MEMFD libpulsecommon_@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-@[email protected] +libpulse_la_CFLAGS = $(AM_CFLAGS) +libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon-@[email protected] 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 <[email protected]> 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 <[email protected]> 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-@[email protected] libpulse.la libpulsecommon-@[email protected] 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-@[email protected] +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-@[email protected] @@ -652,6 +658,7 @@ libpulsecommon_@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 <[email protected]> + + 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 <[email protected]> + + 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 <[email protected]> + + 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; +} _______________________________________________ pulseaudio-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/pulseaudio-commits
