diff --git a/src/mjson.c b/src/mjson.c index 55ba507..36c151b 100644 --- a/src/mjson.c +++ b/src/mjson.c @@ -998,10 +998,20 @@ void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len, mjson_print_fn_t fn, void *fn_data, void *ud) { const char *result = NULL, *error = NULL; - int result_sz = 0, error_sz = 0; + int result_sz = 0, error_sz = 0, id_tok = MJSON_TOK_INVALID, + params_tok = MJSON_TOK_INVALID; struct jsonrpc_method *m = NULL; struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fn_data, ud}; + // Request must be a valid object + if (mjson_find(buf, len, "$", &r.method, &r.method_len) != + MJSON_TOK_OBJECT) { + mjson_printf(fn, fn_data, + "{\"error\":{\"code\":%d,\"message\":%Q},\"id\":null}\n", + JSONRPC_ERROR_PARSE, JSONRPC_ERROR_MSG_PARSE); + return; + } + // Is is a response frame? mjson_find(buf, len, "$.result", &result, &result_sz); if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz); @@ -1010,17 +1020,48 @@ void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len, return; } + // id is optional + id_tok = mjson_find(buf, len, "$.id", &r.id, &r.id_len); + // id (if exists) must be a number, string or null + if (id_tok != MJSON_TOK_INVALID && id_tok != MJSON_TOK_NUMBER && + id_tok != MJSON_TOK_STRING && id_tok != MJSON_TOK_NULL) { + mjson_printf(fn, fn_data, + "{\"error\":{\"code\":%d,\"message\":%Q},\"id\":null}\n", + JSONRPC_ERROR_BAD_REQUEST, JSONRPC_ERROR_MSG_BAD_REQUEST); + return; + } + // Method must exist and must be a string if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) != MJSON_TOK_STRING) { - mjson_printf(fn, fn_data, - "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n", len, buf); + // use null as id if it is not present + if(id_tok == MJSON_TOK_INVALID){ + mjson_printf(fn, fn_data, + "{\"error\":{\"code\":%d,\"message\":%Q},\"id\":null}\n", + JSONRPC_ERROR_BAD_REQUEST, JSONRPC_ERROR_MSG_BAD_REQUEST); + return; + } + jsonrpc_return_error(&r, JSONRPC_ERROR_BAD_REQUEST, + JSONRPC_ERROR_MSG_BAD_REQUEST, NULL); return; } - // id and params are optional - mjson_find(buf, len, "$.id", &r.id, &r.id_len); - mjson_find(buf, len, "$.params", &r.params, &r.params_len); + // params are optional + params_tok = mjson_find(buf, len, "$.params", &r.params, &r.params_len); + // params (if exists) must be an object or an array + if (params_tok != MJSON_TOK_INVALID && params_tok != MJSON_TOK_OBJECT && + params_tok != MJSON_TOK_ARRAY) { + // use null as id if it is not present + if(id_tok == MJSON_TOK_INVALID){ + mjson_printf(fn, fn_data, + "{\"error\":{\"code\":%d,\"message\":%Q},\"id\":null}\n", + JSONRPC_ERROR_BAD_REQUEST, JSONRPC_ERROR_MSG_BAD_REQUEST); + return; + } + jsonrpc_return_error(&r, JSONRPC_ERROR_BAD_REQUEST, + JSONRPC_ERROR_MSG_BAD_REQUEST, NULL); + return; + } for (m = ctx->methods; m != NULL; m = m->next) { if (mjson_globmatch(m->method, m->method_sz, r.method + 1, @@ -1031,7 +1072,8 @@ void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len, } } if (m == NULL) { - jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL); + jsonrpc_return_error(&r, JSONRPC_ERROR_METHOD_NOT_FOUND, + JSONRPC_ERROR_MSG_METHOD_NOT_FOUND, NULL); } } diff --git a/src/mjson.h b/src/mjson.h index d8156e0..cb2c798 100644 --- a/src/mjson.h +++ b/src/mjson.h @@ -208,10 +208,17 @@ extern void jsonrpc_list(struct jsonrpc_request *r); #define jsonrpc_process(buf, len, fn, fnd, ud) \ jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud)) -#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */ -#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */ -#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */ -#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */ +#define JSONRPC_ERROR_PARSE -32700 /* Invalid JSON was received */ +#define JSONRPC_ERROR_BAD_REQUEST -32600 /* The JSON sent is not a valid Request object */ +#define JSONRPC_ERROR_METHOD_NOT_FOUND -32601 /* The method does not exist or is not available*/ +#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid method parameter(s) */ +#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */ + +#define JSONRPC_ERROR_MSG_PARSE "Parse error" +#define JSONRPC_ERROR_MSG_BAD_REQUEST "Invalid Request" +#define JSONRPC_ERROR_MSG_METHOD_NOT_FOUND "Method not found" +#define JSONRPC_ERROR_MSG_BAD_PARAMS "Invalid params" +#define JSONRPC_ERROR_MSG_INTERNAL "Internal error" #endif // MJSON_ENABLE_RPC #ifdef __cplusplus diff --git a/test/unit_test.c b/test/unit_test.c index 6d5f5bd..10019fe 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -616,15 +616,26 @@ static void test_rpc(void) { res = "{\"id\":1,\"result\":[\"rpc.list\"]}\n"; fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Call non-existent method req = "{\"id\": 1, \"method\": \"foo\"}\n"; res = - "{\"id\":1,\"error\":{\"code\":-32601,\"message\":\"method not " + "{\"id\":1,\"error\":{\"code\":-32601,\"message\":\"Method not " "found\"}}\n"; fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad method + req = "{\"id\": 1, \"method\": 123}\n"; + res = "{\"id\":1,\"error\":{\"code\":-32600,\"message\":\"Invalid " + "Request\"}}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Register our own function @@ -634,13 +645,97 @@ static void test_rpc(void) { jsonrpc_export("foo", foo); jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, (void *) "hi"); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test for bad frame req = "boo\n"; - res = "{\"error\":{\"code\":-32700,\"message\":\"boo\\n\"}}\n"; + res = "{\"error\":{\"code\":-32700,\"message\":\"Parse error\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: id is array + req = "{\"id\": [2], \"method\": \"baz\"}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: id is object + req = "{\"id\": {\"a\":2}, \"method\": \"baz\"}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: id is boolean + req = "{\"id\":true, \"method\": \"baz\"}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: params is number + req = "{\"id\": 1, \"method\": \"foo\", \"params\": 1}\n"; + res = "{\"id\":1,\"error\":{\"code\":-32600,\"message\":\"Invalid " + "Request\"}}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: params is string + req = "{\"id\": 1, \"method\": \"foo\", \"params\": \"bar\"}\n"; + res = "{\"id\":1,\"error\":{\"code\":-32600,\"message\":\"Invalid " + "Request\"}}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad request: params is boolean + req = "{\"id\": 1, \"method\": \"foo\", \"params\": true}\n"; + res = "{\"id\":1,\"error\":{\"code\":-32600,\"message\":\"Invalid " + "Request\"}}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad notification: params is number + req = "{\"method\": \"foo\", \"params\": 1}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad notification: params is string + req = "{\"method\": \"foo\", \"params\": \"bar\"}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; + fb.len = 0; + jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); + ASSERT(strcmp(buf, res) == 0); + + // Test for bad notification: params is boolean + req = "{\"method\": \"foo\", \"params\": true}\n"; + res = "{\"error\":{\"code\":-32600,\"message\":\"Invalid Request\"}," + "\"id\":null}\n"; fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test simple error response, without data @@ -649,6 +744,7 @@ static void test_rpc(void) { jsonrpc_export("foo1", foo1); fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test more complex error response, with data @@ -659,6 +755,7 @@ static void test_rpc(void) { jsonrpc_export("foo2", foo2); fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test notify - must not generate a response @@ -672,6 +769,7 @@ static void test_rpc(void) { res = ">>{\"id\":123,\"result\":[1,2,3]}<<"; fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test error response @@ -679,6 +777,7 @@ static void test_rpc(void) { res = ">>{\"id\":566,\"error\":{}}<<"; fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); // Test glob pattern in the RPC function name @@ -687,6 +786,7 @@ static void test_rpc(void) { jsonrpc_export("Bar.*", foo3); fb.len = 0; jsonrpc_process(req, (int) strlen(req), mjson_print_fixed_buf, &fb, NULL); + ASSERT(fb.len > 0); ASSERT(strcmp(buf, res) == 0); }