mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
spa: json: check object key-value pair parity during parsing
Check each object key is associated with value. Disallow object or array valued keys. Add flag tracking whether the parser is at global top-level or not, as there we may either be in object context or in a single-value context. Save depth=0 array flag bit in state, so that spa_json_next preserves its complete state across calls. The higher-depth flag bits can be in temporary stack as they are not needed across calls.
This commit is contained in:
parent
921c8b99db
commit
3775e4e624
2 changed files with 138 additions and 44 deletions
|
|
@ -44,7 +44,7 @@ static inline void spa_json_init(struct spa_json * iter, const char *data, size_
|
||||||
{
|
{
|
||||||
*iter = SPA_JSON_INIT(data, size);
|
*iter = SPA_JSON_INIT(data, size);
|
||||||
}
|
}
|
||||||
#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xf0, 0 })
|
#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 })
|
||||||
|
|
||||||
static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub)
|
static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub)
|
||||||
{
|
{
|
||||||
|
|
@ -60,13 +60,14 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
int utf8_remain = 0;
|
int utf8_remain = 0;
|
||||||
enum {
|
enum {
|
||||||
__NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT,
|
__NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT,
|
||||||
__ARRAY_FLAG = 0x10,
|
__ARRAY_FLAG = 0x10, /* in array context */
|
||||||
__OBJECT_FLAG = 0x20,
|
__PREV_ARRAY_FLAG = 0x20, /* depth=0 array context flag */
|
||||||
__ERROR_FLAG = 0x40,
|
__ERROR_FLAG = 0x40,
|
||||||
__FLAGS = 0xf0,
|
__KEY_FLAG = 0x80, /* inside object key */
|
||||||
|
__SUB_FLAG = 0x100, /* not at top-level */
|
||||||
|
__FLAGS = 0xff0,
|
||||||
};
|
};
|
||||||
uint8_t object_stack[16] = {0};
|
uint64_t array_stack[8] = {0}; /* array context flags of depths 1...512 */
|
||||||
uint8_t array_stack[SPA_N_ELEMENTS(object_stack)] = {0};
|
|
||||||
|
|
||||||
*value = iter->cur;
|
*value = iter->cur;
|
||||||
|
|
||||||
|
|
@ -81,6 +82,7 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
flag = iter->state & __FLAGS;
|
flag = iter->state & __FLAGS;
|
||||||
switch (iter->state & ~__FLAGS) {
|
switch (iter->state & ~__FLAGS) {
|
||||||
case __NONE:
|
case __NONE:
|
||||||
|
flag &= ~(__KEY_FLAG | __PREV_ARRAY_FLAG);
|
||||||
iter->state = __STRUCT | flag;
|
iter->state = __STRUCT | flag;
|
||||||
iter->depth = 0;
|
iter->depth = 0;
|
||||||
goto again;
|
goto again;
|
||||||
|
|
@ -91,21 +93,48 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
case ':': case '=':
|
case ':': case '=':
|
||||||
if (flag & __ARRAY_FLAG)
|
if (flag & __ARRAY_FLAG)
|
||||||
goto error;
|
goto error;
|
||||||
|
if (!(flag & __KEY_FLAG))
|
||||||
|
goto error;
|
||||||
|
iter->state |= __SUB_FLAG;
|
||||||
continue;
|
continue;
|
||||||
case '#':
|
case '#':
|
||||||
iter->state = __COMMENT | flag;
|
iter->state = __COMMENT | flag;
|
||||||
continue;
|
continue;
|
||||||
case '"':
|
case '"':
|
||||||
|
if (flag & __KEY_FLAG)
|
||||||
|
flag |= __SUB_FLAG;
|
||||||
|
if (!(flag & __ARRAY_FLAG))
|
||||||
|
SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG));
|
||||||
*value = iter->cur;
|
*value = iter->cur;
|
||||||
iter->state = __STRING | flag;
|
iter->state = __STRING | flag;
|
||||||
continue;
|
continue;
|
||||||
case '[': case '{':
|
case '[': case '{':
|
||||||
iter->state = __STRUCT | (cur == '[' ? __ARRAY_FLAG : __OBJECT_FLAG);
|
if (!(flag & __ARRAY_FLAG)) {
|
||||||
if ((iter->depth >> 3) < SPA_N_ELEMENTS(object_stack)) {
|
/* At top-level we may be either in object context
|
||||||
uint8_t mask = 1 << (iter->depth & 0x7);
|
* or in single-item context, and then we need ot
|
||||||
SPA_FLAG_UPDATE(object_stack[iter->depth >> 3], mask, flag & __OBJECT_FLAG);
|
* accept array/object here.
|
||||||
SPA_FLAG_UPDATE(array_stack[iter->depth >> 3], mask, flag & __ARRAY_FLAG);
|
*/
|
||||||
|
if ((iter->state & __SUB_FLAG) && !(flag & __KEY_FLAG))
|
||||||
|
goto error;
|
||||||
|
SPA_FLAG_CLEAR(flag, __KEY_FLAG);
|
||||||
}
|
}
|
||||||
|
iter->state = __STRUCT | __SUB_FLAG | flag;
|
||||||
|
SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, cur == '[');
|
||||||
|
|
||||||
|
/* We need to remember previous array state across calls
|
||||||
|
* for depth=0, so store that in state. Others bits go to
|
||||||
|
* temporary stack.
|
||||||
|
*/
|
||||||
|
if (iter->depth == 0) {
|
||||||
|
SPA_FLAG_UPDATE(iter->state, __PREV_ARRAY_FLAG, flag & __ARRAY_FLAG);
|
||||||
|
} else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) {
|
||||||
|
uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f);
|
||||||
|
SPA_FLAG_UPDATE(array_stack[(iter->depth-1) >> 6], mask, flag & __ARRAY_FLAG);
|
||||||
|
} else {
|
||||||
|
/* too deep */
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
*value = iter->cur;
|
*value = iter->cur;
|
||||||
if (++iter->depth > 1)
|
if (++iter->depth > 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -114,9 +143,13 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
case '}': case ']':
|
case '}': case ']':
|
||||||
if ((flag & __ARRAY_FLAG) && cur != ']')
|
if ((flag & __ARRAY_FLAG) && cur != ']')
|
||||||
goto error;
|
goto error;
|
||||||
if ((flag & __OBJECT_FLAG) && cur != '}')
|
if (!(flag & __ARRAY_FLAG) && cur != '}')
|
||||||
goto error;
|
goto error;
|
||||||
iter->state = __STRUCT;
|
if (flag & __KEY_FLAG) {
|
||||||
|
/* incomplete key-value pair */
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
iter->state = __STRUCT | __SUB_FLAG | flag;
|
||||||
if (iter->depth == 0) {
|
if (iter->depth == 0) {
|
||||||
if (iter->parent)
|
if (iter->parent)
|
||||||
iter->parent->cur = iter->cur;
|
iter->parent->cur = iter->cur;
|
||||||
|
|
@ -125,12 +158,15 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
--iter->depth;
|
--iter->depth;
|
||||||
if ((iter->depth >> 3) < SPA_N_ELEMENTS(object_stack)) {
|
if (iter->depth == 0) {
|
||||||
uint8_t mask = 1 << (iter->depth & 0x7);
|
SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, flag & __PREV_ARRAY_FLAG);
|
||||||
if (SPA_FLAG_IS_SET(object_stack[iter->depth >> 3], mask))
|
} else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) {
|
||||||
iter->state |= __OBJECT_FLAG;
|
uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f);
|
||||||
if (SPA_FLAG_IS_SET(array_stack[iter->depth >> 3], mask))
|
SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG,
|
||||||
iter->state |= __ARRAY_FLAG;
|
SPA_FLAG_IS_SET(array_stack[(iter->depth-1) >> 6], mask));
|
||||||
|
} else {
|
||||||
|
/* too deep */
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
case '\\':
|
case '\\':
|
||||||
|
|
@ -140,6 +176,10 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
/* allow bare ascii */
|
/* allow bare ascii */
|
||||||
if (!(cur >= 32 && cur <= 126))
|
if (!(cur >= 32 && cur <= 126))
|
||||||
goto error;
|
goto error;
|
||||||
|
if (flag & __KEY_FLAG)
|
||||||
|
flag |= __SUB_FLAG;
|
||||||
|
if (!(flag & __ARRAY_FLAG))
|
||||||
|
SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG));
|
||||||
*value = iter->cur;
|
*value = iter->cur;
|
||||||
iter->state = __BARE | flag;
|
iter->state = __BARE | flag;
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +267,11 @@ static inline int spa_json_next(struct spa_json * iter, const char **value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) {
|
||||||
|
/* incomplete key-value pair */
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
if ((iter->state & ~__FLAGS) != __STRUCT) {
|
if ((iter->state & ~__FLAGS) != __STRUCT) {
|
||||||
iter->state = __STRUCT | (iter->state & __FLAGS);
|
iter->state = __STRUCT | (iter->state & __FLAGS);
|
||||||
return iter->cur - *value;
|
return iter->cur - *value;
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,6 @@ static void expect_null(struct spa_json *it)
|
||||||
|
|
||||||
PWTEST(json_parse)
|
PWTEST(json_parse)
|
||||||
{
|
{
|
||||||
char buf[1024];
|
|
||||||
int i;
|
|
||||||
struct spa_json it[5];
|
struct spa_json it[5];
|
||||||
const char *json = " { "
|
const char *json = " { "
|
||||||
"\"foo\": \"bar\", # comment\n"
|
"\"foo\": \"bar\", # comment\n"
|
||||||
|
|
@ -306,17 +304,6 @@ PWTEST(json_parse)
|
||||||
expect_end(&it[0]);
|
expect_end(&it[0]);
|
||||||
expect_end(&it[0]);
|
expect_end(&it[0]);
|
||||||
|
|
||||||
/* overflowing parser nesting stack is not an error */
|
|
||||||
for (i = 0; i < 256; ++i)
|
|
||||||
buf[i] = '[';
|
|
||||||
for (; i < 512; ++i)
|
|
||||||
buf[i] = ']';
|
|
||||||
buf[i++] = '\0';
|
|
||||||
|
|
||||||
spa_json_init(&it[0], buf, strlen(buf));
|
|
||||||
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
|
||||||
expect_end(&it[0]);
|
|
||||||
|
|
||||||
/* non-null terminated strings OK */
|
/* non-null terminated strings OK */
|
||||||
json = "1.234";
|
json = "1.234";
|
||||||
spa_json_init(&it[0], json, 4);
|
spa_json_init(&it[0], json, 4);
|
||||||
|
|
@ -363,12 +350,29 @@ PWTEST(json_parse)
|
||||||
expect_string(&it[0], "hello");
|
expect_string(&it[0], "hello");
|
||||||
expect_end(&it[0]);
|
expect_end(&it[0]);
|
||||||
|
|
||||||
|
/* top-level context */
|
||||||
|
json = "x y x y";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_string_or_bare(&it[0], "x");
|
||||||
|
expect_string_or_bare(&it[0], "y");
|
||||||
|
expect_string_or_bare(&it[0], "x");
|
||||||
|
expect_string_or_bare(&it[0], "y");
|
||||||
|
expect_end(&it[0]);
|
||||||
|
|
||||||
|
json = "x = y x = y";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_string_or_bare(&it[0], "x");
|
||||||
|
expect_string_or_bare(&it[0], "y");
|
||||||
|
expect_string_or_bare(&it[0], "x");
|
||||||
|
expect_string_or_bare(&it[0], "y");
|
||||||
|
expect_end(&it[0]);
|
||||||
|
|
||||||
return PWTEST_PASS;
|
return PWTEST_PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
PWTEST(json_parse_fail)
|
PWTEST(json_parse_fail)
|
||||||
{
|
{
|
||||||
char buf[1024];
|
char buf[2048];
|
||||||
struct spa_json it[5];
|
struct spa_json it[5];
|
||||||
const char *json, *value;
|
const char *json, *value;
|
||||||
int i;
|
int i;
|
||||||
|
|
@ -410,17 +414,64 @@ PWTEST(json_parse_fail)
|
||||||
expect_parse_error(&it[0], json, 1, 14);
|
expect_parse_error(&it[0], json, 1, 14);
|
||||||
|
|
||||||
/* bad nesting */
|
/* bad nesting */
|
||||||
json = "{ {[{[{[{[{[{[{[{[{[{[{[{[ ]}]}]}]}]}]}]}]}]}]}]}]} ]";
|
json = "{a: {a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[ ]}]}]}]}]}]}]}]}]}]}]}]} ]";
|
||||||
spa_json_init(&it[0], json, strlen(json));
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
||||||
expect_parse_error(&it[0], json, 1, strlen(json));
|
expect_parse_error(&it[0], json, 1, strlen(json));
|
||||||
|
|
||||||
/* bad nesting */
|
/* bad nesting */
|
||||||
json = "[ {[{[{[{[{[{[{[{[{[{[{[{[ ]}]}]}]}]}]}]}]}]}]}]}]} }";
|
json = "[ {a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[{a:[ ]}]}]}]}]}]}]}]}]}]}]}]} }";
|
||||||
spa_json_init(&it[0], json, strlen(json));
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
||||||
expect_parse_error(&it[0], json, 1, strlen(json));
|
expect_parse_error(&it[0], json, 1, strlen(json));
|
||||||
|
|
||||||
|
/* bad object key-values */
|
||||||
|
json = "{ = }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_parse_error(&it[1], json, 1, 3);
|
||||||
|
|
||||||
|
json = "{ x }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_parse_error(&it[1], json, 1, 5);
|
||||||
|
|
||||||
|
json = "{ x : }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_parse_error(&it[1], json, 1, 7);
|
||||||
|
|
||||||
|
json = "{ x = y, : }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_string_or_bare(&it[1], "y");
|
||||||
|
expect_parse_error(&it[1], json, 1, 10);
|
||||||
|
|
||||||
|
json = "{ x = {1:3}, z : }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_object(&it[1], &it[2]);
|
||||||
|
expect_string_or_bare(&it[1], "z");
|
||||||
|
expect_parse_error(&it[1], json, 1, 18);
|
||||||
|
|
||||||
|
json = "{ x y x }";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_string_or_bare(&it[1], "y");
|
||||||
|
expect_string_or_bare(&it[1], "x");
|
||||||
|
expect_parse_error(&it[1], json, 1, 9);
|
||||||
|
|
||||||
|
json = "x y x";
|
||||||
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
expect_string_or_bare(&it[0], "x");
|
||||||
|
expect_string_or_bare(&it[0], "y");
|
||||||
|
expect_parse_error(&it[0], json, 1, 6);
|
||||||
|
|
||||||
/* unclosed string */
|
/* unclosed string */
|
||||||
json = "\"foo";
|
json = "\"foo";
|
||||||
spa_json_init(&it[0], json, strlen(json));
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
|
|
@ -454,11 +505,12 @@ PWTEST(json_parse_fail)
|
||||||
expect_parse_error(&it[0], json, 1, 1);
|
expect_parse_error(&it[0], json, 1, 1);
|
||||||
|
|
||||||
/* bad nesting in subparser */
|
/* bad nesting in subparser */
|
||||||
json = "{[]";
|
json = "{a:[]";
|
||||||
spa_json_init(&it[0], json, strlen(json));
|
spa_json_init(&it[0], json, strlen(json));
|
||||||
expect_object(&it[0], &it[1]);
|
expect_object(&it[0], &it[1]);
|
||||||
|
expect_string_or_bare(&it[1], "a");
|
||||||
expect_array(&it[1], &it[2]);
|
expect_array(&it[1], &it[2]);
|
||||||
expect_parse_error(&it[1], json, 1, 4);
|
expect_parse_error(&it[1], json, 1, 6);
|
||||||
|
|
||||||
/* entered parser assumes nesting */
|
/* entered parser assumes nesting */
|
||||||
json = "[]";
|
json = "[]";
|
||||||
|
|
@ -467,17 +519,16 @@ PWTEST(json_parse_fail)
|
||||||
expect_array(&it[1], &it[2]);
|
expect_array(&it[1], &it[2]);
|
||||||
expect_parse_error(&it[1], json, 1, 3);
|
expect_parse_error(&it[1], json, 1, 3);
|
||||||
|
|
||||||
/* overflowing parser nesting stack */
|
/* overflowing parser nesting stack is an error*/
|
||||||
for (i = 0; i < 256; ++i)
|
for (i = 0; i < 514; ++i)
|
||||||
buf[i] = '[';
|
buf[i] = '[';
|
||||||
for (; i < 511; ++i)
|
for (; i < 2*514; ++i)
|
||||||
buf[i] = ']';
|
buf[i] = ']';
|
||||||
buf[i++] = '}';
|
|
||||||
buf[i++] = '\0';
|
buf[i++] = '\0';
|
||||||
|
|
||||||
spa_json_init(&it[0], buf, strlen(buf));
|
spa_json_init(&it[0], buf, strlen(buf));
|
||||||
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
pwtest_int_eq(spa_json_next(&it[0], &value), 1);
|
||||||
expect_parse_error(&it[0], buf, 1, strlen(buf));
|
expect_parse_error(&it[0], buf, 1, 514);
|
||||||
|
|
||||||
/* bad utf8 */
|
/* bad utf8 */
|
||||||
json = "\"\xc0\"";
|
json = "\"\xc0\"";
|
||||||
|
|
@ -898,10 +949,8 @@ PWTEST(json_data)
|
||||||
"n_object_unquoted_key.json",
|
"n_object_unquoted_key.json",
|
||||||
"n_object_with_trailing_garbage.json",
|
"n_object_with_trailing_garbage.json",
|
||||||
"n_single_space.json",
|
"n_single_space.json",
|
||||||
"n_structure_double_array.json",
|
|
||||||
"n_structure_no_data.json",
|
"n_structure_no_data.json",
|
||||||
"n_structure_null-byte-outside-string.json",
|
"n_structure_null-byte-outside-string.json",
|
||||||
"n_structure_object_with_trailing_garbage.json",
|
|
||||||
"n_structure_trailing_#.json",
|
"n_structure_trailing_#.json",
|
||||||
"n_multidigit_number_then_00.json",
|
"n_multidigit_number_then_00.json",
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue