From d36b85cfdf4714a0498aec2a1f548ce0467e4fe3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 17 May 2018 16:28:13 +0200 Subject: [PATCH] json: add some non-standard extensions Also clarify this and previously existing differences to standard JSON in ipc.rst. --- DOCS/man/ipc.rst | 32 +++++++++++++++++++++++++++++++- misc/json.c | 36 +++++++++++++++++++++++++++++++++--- test/json.c | 16 +++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst index 0c551e8706..2aa406b190 100644 --- a/DOCS/man/ipc.rst +++ b/DOCS/man/ipc.rst @@ -74,6 +74,12 @@ some wrapper like .NET's NamedPipeClientStream.) Protocol -------- +The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON, +"\u" escape sequences are not allowed to construct surrogate pairs. To avoid +getting conflicts, encode all text characters including and above codepoint +U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8" +section below). + Clients can execute commands on the player by sending JSON messages of the following form: @@ -266,4 +272,28 @@ sometimes sends invalid JSON. If that is a problem for the client application's parser, it should filter the raw data for invalid UTF-8 sequences and perform the desired replacement, before feeding the data to its JSON parser. -mpv will not attempt to construct invalid UTF-8 with broken escape sequences. +mpv will not attempt to construct invalid UTF-8 with broken "\u" escape +sequences. This includes surrogate pairs. + +JSON extensions +--------------- + +The following non-standard extensions are supported: + + - a list or object item can have a trailing "," + - object syntax accepts "=" in addition of ":" + - object keys can be unquoted, if they start with a character in "A-Za-z\_" + and contain only characters in "A-Za-z0-9\_" + - byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number) + +Example: + +:: + + { objkey = "value\x0A" } + +Is equivalent to: + +:: + + { "objkey": "value\n" } diff --git a/misc/json.c b/misc/json.c index 9cc0730e4d..d1b2afddb6 100644 --- a/misc/json.c +++ b/misc/json.c @@ -22,7 +22,12 @@ * doesn't verify what's passed to strtod(), and also prefers parsing numbers * as integers with stroll() if possible). * - * Does not support extensions like unquoted string literals. + * It has some non-standard extensions which shouldn't conflict with JSON: + * - a list or object item can have a trailing "," + * - object syntax accepts "=" in addition of ":" + * - object keys can be unquoted, if they start with a character in [A-Za-z_] + * and contain only characters in [A-Za-z0-9_] + * - byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number) * * Also see: http://tools.ietf.org/html/rfc8259 * @@ -45,6 +50,7 @@ #include "common/common.h" #include "misc/bstr.h" +#include "misc/ctype.h" #include "json.h" @@ -72,6 +78,24 @@ void json_skip_whitespace(char **src) eat_ws(src); } +static int read_id(void *ta_parent, struct mpv_node *dst, char **src) +{ + char *start = *src; + if (!mp_isalpha(**src) && **src != '_') + return -1; + while (mp_isalnum(**src) || **src == '_') + *src += 1; + if (**src == ' ') { + **src = '\0'; // we're allowed to mutate it => can avoid the strndup + *src += 1; + } else { + start = talloc_strndup(ta_parent, start, *src - start); + } + dst->format = MPV_FORMAT_STRING; + dst->u.string = start; + return 0; +} + static int read_str(void *ta_parent, struct mpv_node *dst, char **src) { if (!eat_c(src, '"')) @@ -122,12 +146,18 @@ static int read_sub(void *ta_parent, struct mpv_node *dst, char **src, if (list->num > 0 && !eat_c(src, ',')) return -1; // missing ',' eat_ws(src); + // non-standard extension: allow a trailing "," + if (eat_c(src, term)) + break; if (is_obj) { struct mpv_node keynode; - if (read_str(list, &keynode, src) < 0) + // non-standard extension: allow unquoted strings as keys + if (read_id(list, &keynode, src) < 0 && + read_str(list, &keynode, src) < 0) return -1; // key is not a string eat_ws(src); - if (!eat_c(src, ':')) + // non-standard extension: allow "=" instead of ":" + if (!eat_c(src, ':') && !eat_c(src, '=')) return -1; // ':' missing eat_ws(src); MP_TARRAY_GROW(list, list->keys, list->num); diff --git a/test/json.c b/test/json.c index d624f61cca..0a4462bc21 100644 --- a/test/json.c +++ b/test/json.c @@ -45,14 +45,24 @@ static const struct entry entries[] = { { "[1,2,3]", "[1,2,3]", NODE_ARRAY(NODE_INT64(1), NODE_INT64(2), NODE_INT64(3))}, { "[ ]", "[]", NODE_ARRAY()}, - { "[1,2,]", .expect_fail = true}, { "[1,,2]", .expect_fail = true}, + { "[,]", .expect_fail = true}, { TEXT({"a":1, "b":2}), TEXT({"a":1,"b":2}), NODE_MAP(L("a", "b"), L(NODE_INT64(1), NODE_INT64(2)))}, { "{ }", "{}", NODE_MAP(L(), L())}, { TEXT({"a":b}), .expect_fail = true}, - { TEXT({a:"b"}), .expect_fail = true}, - { TEXT({"a":1,}), .expect_fail = true}, + { TEXT({1a:"b"}), .expect_fail = true}, + + // non-standard extensions + { "[1,2,]", "[1,2]", NODE_ARRAY(NODE_INT64(1), NODE_INT64(2))}, + { TEXT({a:"b"}), TEXT({"a":"b"}), + NODE_MAP(L("a"), L(NODE_STR("b")))}, + { TEXT({a="b"}), TEXT({"a":"b"}), + NODE_MAP(L("a"), L(NODE_STR("b")))}, + { TEXT({a ="b"}), TEXT({"a":"b"}), + NODE_MAP(L("a"), L(NODE_STR("b")))}, + { TEXT({_a12="b"}), TEXT({"_a12":"b"}), + NODE_MAP(L("_a12"), L(NODE_STR("b")))}, }; #define MAX_DEPTH 10