#include #include #include #include #include #include "jsonrpc.h" int dummy = 0; json_t *jsonrpc_async_dummy = (json_t *)&dummy; json_t *jsonrpc_error_object(int code, json_t *data) { // reference to data is stolen char *message = ""; switch (code) { case JSONRPC_PARSE_ERROR: message = "Parse Error"; break; case JSONRPC_INVALID_REQUEST: message = "Invalid Request"; break; case JSONRPC_METHOD_NOT_FOUND: message = "Method not found"; break; case JSONRPC_INVALID_PARAMS: message = "Invalid params"; break; case JSONRPC_INTERNAL_ERROR: message = "Internal error"; break; } json_t *json = json_pack("{s:i,s:s}", "code", code, "message", message); if (data) { json_object_set_new(json, "data", data); } return json; } json_t *jsonrpc_error_response(json_t *json_id, json_t *json_error) { // json_error reference is stolen // json_id could be NULL if (json_id) { json_incref(json_id); } else { json_id = json_null(); } json_error = json_error ? json_error : json_null(); json_t *response = json_pack("{s:s,s:o,s:o}", "jsonrpc", "2.0", "id", json_id, "error", json_error); return response; } json_t *jsonrpc_result_response(json_t *json_id, json_t *json_result) { // json_result reference is stolen // json_id shouldn't be NULL if (json_id) { json_incref(json_id); } else { json_id = json_null(); } json_result = json_result ? json_result : json_null(); json_t *response = json_pack("{s:s,s:o,s:o}", "jsonrpc", "2.0", "id", json_id, "result", json_result); return response; } json_t *jsonrpc_validate_request(json_t *json_request, const char **str_method, json_t **json_params, json_t **json_id) { size_t flags = 0; json_error_t error; const char *str_version = NULL; int rc; json_t *data = NULL; int valid_id = 0; *str_method = NULL; *json_params = NULL; *json_id = NULL; rc = json_unpack_ex(json_request, &error, flags, "{s:s,s:s,s?o,s?o}", "jsonrpc", &str_version, "method", str_method, "params", json_params, "id", json_id ); if (rc==-1) { data = json_string(error.text); goto invalid; } if (0!=strcmp(str_version, "2.0")) { data = json_string("\"jsonrpc\" MUST be exactly \"2.0\""); goto invalid; } if (*json_id) { if (!json_is_string(*json_id) && !json_is_number(*json_id) && !json_is_null(*json_id)) { data = json_string("\"id\" MUST contain a String, Number, or NULL value if included"); goto invalid; } } // Note that we only return json_id in the error response after we have established that it is jsonrpc/2.0 compliant // otherwise we would be returning a non-compliant response ourselves! valid_id = 1; if (*json_params) { if (!json_is_array(*json_params) && !json_is_object(*json_params)) { data = json_string("\"params\" MUST be Array or Object if included"); goto invalid; } } return NULL; invalid: if (!valid_id) *json_id = NULL; return jsonrpc_error_response(*json_id, jsonrpc_error_object(JSONRPC_INVALID_REQUEST, data)); } json_t *jsonrpc_validate_params(json_t *json_params, const char *params_spec) { json_t *data = NULL; if (strlen(params_spec)==0) { // empty string means no arguments if (!json_params) { // no params field: OK } else if (json_is_array(json_params) && json_array_size(json_params)==0) { // an empty Array: OK } else { data = json_string("method takes no arguments"); } } else if (!json_params) { // non-empty string but no params field data = json_string("method takes arguments but params field missing"); } else { // non-empty string and have params field size_t flags = JSON_VALIDATE_ONLY; json_error_t error; int rc = json_unpack_ex(json_params, &error, flags, params_spec); if (rc==-1) { data = json_string(error.text); } } return data ? jsonrpc_error_object(JSONRPC_INVALID_PARAMS, data) : NULL; } json_t *jsonrpc_handle_request_single(void *apparg, json_t *json_request, struct jsonrpc_method_entry_t method_table[]) { int rc; json_t *json_response; const char *str_method; json_t *json_params, *json_id; json_t *json_result; json_response = jsonrpc_validate_request(json_request, &str_method, &json_params, &json_id); if (json_response) return json_response; int is_notification = json_id==NULL; struct jsonrpc_method_entry_t *entry; for (entry=method_table; entry->name!=NULL; entry++) { if (0==strcmp(entry->name, str_method)) break; } if (entry->name==NULL) { json_response = jsonrpc_error_response(json_id, jsonrpc_error_object(JSONRPC_METHOD_NOT_FOUND, NULL)); goto done; } if (entry->params_spec) { json_t *error_obj = jsonrpc_validate_params(json_params, entry->params_spec); if (error_obj) { json_response = jsonrpc_error_response(json_id, error_obj); goto done; } } json_response = NULL; json_result = NULL; rc = entry->funcptr(apparg, json_params, &json_result); if (is_notification) { json_decref(json_result); json_result = NULL; } else { if (rc==0) { if (json_result == jsonrpc_async_dummy) json_response = NULL; else json_response = jsonrpc_result_response( json_id, json_result); } else if (rc==JSONRPC_INVALID_PARAMS) { json_response = jsonrpc_error_response(json_id, jsonrpc_error_object(JSONRPC_INVALID_PARAMS, json_result)); } else { json_response = jsonrpc_error_response(json_id, jsonrpc_error_object(JSONRPC_INTERNAL_ERROR, json_result)); } } done: if (is_notification && json_response) { json_decref(json_response); json_response = NULL; } return json_response; } char *jsonrpc_handler(void *apparg, const char *input, size_t input_len, struct jsonrpc_method_entry_t method_table[]) { json_t *json_request, *json_response; json_error_t error; char *output = NULL; json_request = json_loadb(input, input_len, 0, &error); if (!json_request) { json_response = jsonrpc_error_response(NULL, jsonrpc_error_object(JSONRPC_PARSE_ERROR, NULL)); } else if json_is_array(json_request) { size_t len = json_array_size(json_request); if (len==0) { json_response = jsonrpc_error_response(NULL, jsonrpc_error_object(JSONRPC_INVALID_REQUEST, NULL)); } else { size_t k; json_response = NULL; for (k=0; k < len; k++) { json_t *req = json_array_get(json_request, k); json_t *rep = jsonrpc_handle_request_single(apparg, req, method_table); if (rep) { if (!json_response) json_response = json_array(); json_array_append_new(json_response, rep); } } } json_decref(json_request); } else { json_response = jsonrpc_handle_request_single(apparg, json_request, method_table); json_decref(json_request); } if (json_response) { output = json_dumps(json_response, JSON_INDENT(2)); json_decref(json_response); } return output; }