Commit 69c7ff06 authored by Daniel Verkamp's avatar Daniel Verkamp Committed by Ben Walker
Browse files

json: allow decoding of non-standard comments



Comments are not allowed in the JSON RFC, but some JSON libraries accept
JavaScript-style comments.

Add a flag that enables non-spec-compliant comment parsing.

Change-Id: I9dfb66bb46ecff1a22d8af5a9c50620686a4707c
Signed-off-by: default avatarDaniel Verkamp <daniel.verkamp@intel.com>
parent 0b8198ce
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -107,6 +107,13 @@ struct spdk_json_val {
 */
#define SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE	0x000000001

/**
 * Allow parsing of comments.
 *
 * Comments are not allowed by the JSON RFC, so this is not enabled by default.
 */
#define SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS	0x000000002

/*
 * Parse JSON data.
 *
+54 −0
Original line number Diff line number Diff line
@@ -373,6 +373,50 @@ done:
	}
}

static int
json_valid_comment(const uint8_t *start, const uint8_t *buf_end)
{
	const uint8_t *p = start;
	bool multiline;

	assert(buf_end > p);
	if (buf_end - p < 2) {
		return SPDK_JSON_PARSE_INCOMPLETE;
	}

	if (p[0] != '/') {
		return SPDK_JSON_PARSE_INVALID;
	}
	if (p[1] == '*') {
		multiline = true;
	} else if (p[1] == '/') {
		multiline = false;
	} else {
		return SPDK_JSON_PARSE_INVALID;
	}
	p += 2;

	if (multiline) {
		while (p != buf_end - 1) {
			if (p[0] == '*' && p[1] == '/') {
				/* Include the terminating star and slash in the comment */
				return p - start + 2;
			}
			p++;
		}
	} else {
		while (p != buf_end) {
			if (*p == '\r' || *p == '\n') {
				/* Do not include the line terminator in the comment */
				return p - start;
			}
			p++;
		}
	}

	return SPDK_JSON_PARSE_INCOMPLETE;
}

struct json_literal {
	enum spdk_json_val_type type;
	uint32_t len;
@@ -574,6 +618,16 @@ spdk_json_parse(void *json, size_t size, struct spdk_json_val *values, size_t nu
			state = STATE_VALUE;
			break;

		case '/':
			if (!(flags & SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS)) {
				return SPDK_JSON_PARSE_INVALID;
			}
			rc = json_valid_comment(data, json_end);
			if (rc < 0) return rc;
			/* Skip over comment */
			data += rc;
			break;

		default:
			return SPDK_JSON_PARSE_INVALID;
		}
+68 −6
Original line number Diff line number Diff line
@@ -55,18 +55,24 @@ static int g_cur_val;
 * Do two checks - first pass NULL for values to ensure the count is correct,
 *  then pass g_vals to get the actual values.
 */
#define PARSE_PASS(in, num_vals, trailing) \
#define PARSE_PASS_FLAGS(in, num_vals, trailing, flags) \
	BUF_SETUP(in); \
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, 0) == num_vals); \
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == num_vals); \
	memset(g_vals, 0, sizeof(g_vals)); \
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, g_vals, sizeof(g_vals), &g_end, SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == num_vals); \
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, g_vals, sizeof(g_vals), &g_end, flags | SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == num_vals); \
	CU_ASSERT(g_end == g_buf + sizeof(in) - sizeof(trailing)); \
	CU_ASSERT(memcmp(g_end, trailing, sizeof(trailing) - 1) == 0); \
	g_cur_val = 0

#define PARSE_FAIL(in, retval) \
#define PARSE_PASS(in, num_vals, trailing) \
	PARSE_PASS_FLAGS(in, num_vals, trailing, 0)

#define PARSE_FAIL_FLAGS(in, retval, flags) \
	BUF_SETUP(in); \
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, 0) == retval)
	CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, flags) == retval)

#define PARSE_FAIL(in, retval) \
	PARSE_FAIL_FLAGS(in, retval, 0)

#define VAL_STRING_MATCH(str, var_type) \
	CU_ASSERT(g_vals[g_cur_val].type == var_type); \
@@ -797,6 +803,61 @@ test_parse_nesting(void)
	PARSE_FAIL("{\"a\": [0, 1, 2]", SPDK_JSON_PARSE_INCOMPLETE);
}


static void
test_parse_comment(void)
{
	/* Comments are not allowed by the JSON RFC */
	PARSE_PASS("[0]", 3, "");
	PARSE_FAIL("/* test */[0]", SPDK_JSON_PARSE_INVALID);
	PARSE_FAIL("[/* test */0]", SPDK_JSON_PARSE_INVALID);
	PARSE_FAIL("[0/* test */]", SPDK_JSON_PARSE_INVALID);

	/*
	 * This is allowed since the parser stops once it reads a complete JSON object.
	 * The next parse call would fail (see tests above) when parsing the comment.
	 */
	PARSE_PASS("[0]/* test */", 3, "/* test */");

	/*
	 * Test with non-standard comments enabled.
	 */
	PARSE_PASS_FLAGS("/* test */[0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_ARRAY_BEGIN(1);
	VAL_NUMBER("0");
	VAL_ARRAY_END();

	PARSE_PASS_FLAGS("[/* test */0]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_ARRAY_BEGIN(1);
	VAL_NUMBER("0");
	VAL_ARRAY_END();

	PARSE_PASS_FLAGS("[0/* test */]", 3, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_ARRAY_BEGIN(1);
	VAL_NUMBER("0");
	VAL_ARRAY_END();

	PARSE_FAIL_FLAGS("/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	PARSE_FAIL_FLAGS("[/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	PARSE_FAIL_FLAGS("[0/* test */", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);

	/*
	 * Single-line comments
	 */
	PARSE_PASS_FLAGS("// test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_NUMBER("0");

	PARSE_PASS_FLAGS("// test\r\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_NUMBER("0");

	PARSE_PASS_FLAGS("// [0] test\n0", 1, "", SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	VAL_NUMBER("0");

	PARSE_FAIL_FLAGS("//", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	PARSE_FAIL_FLAGS("// test", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
	PARSE_FAIL_FLAGS("//\n", SPDK_JSON_PARSE_INCOMPLETE, SPDK_JSON_PARSE_FLAG_ALLOW_COMMENTS);
}

int main(int argc, char **argv)
{
	CU_pSuite	suite = NULL;
@@ -822,7 +883,8 @@ int main(int argc, char **argv)
		CU_add_test(suite, "parse_number", test_parse_number) == NULL ||
		CU_add_test(suite, "parse_array", test_parse_array) == NULL ||
		CU_add_test(suite, "parse_object", test_parse_object) == NULL ||
		CU_add_test(suite, "parse_nesting", test_parse_nesting) == NULL) {
		CU_add_test(suite, "parse_nesting", test_parse_nesting) == NULL ||
		CU_add_test(suite, "parse_comment", test_parse_comment) == NULL) {
		CU_cleanup_registry();
		return CU_get_error();
	}