kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

jsonrpc.c (11860B)



      1 /*
      2  * Copyright (c) 2016 Raphaƫl Monrouzeau <raphael.monrouzeau@gmail.com>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <limits.h>
     18 #include <stdbool.h>
     19 
     20 #include <yajl/yajl_tree.h>
     21 #include <yajl/yajl_gen.h>
     22 
     23 #include "kore.h"
     24 #include "http.h"
     25 #include "jsonrpc.h"
     26 
     27 static void
     28 init_log(struct jsonrpc_log *log)
     29 {
     30 	log->msg = NULL;
     31 	log->next = log;
     32 	log->prev = log;
     33 }
     34 
     35 static void
     36 append_log(struct jsonrpc_log *prev, int lvl, char *msg)
     37 {
     38 	struct jsonrpc_log *new = kore_malloc(sizeof(struct jsonrpc_log));
     39 
     40 	new->lvl = lvl;
     41 	new->msg = msg;
     42 
     43 	new->prev = prev;
     44 	new->next = prev->next;
     45 	prev->next->prev = new;
     46 	prev->next = new;
     47 }
     48 
     49 static void
     50 free_log(struct jsonrpc_log *root)
     51 {
     52 	for (struct jsonrpc_log *it = root->next; it != root; it = it->next) {
     53 		kore_free(it);
     54 	}
     55 }
     56 
     57 static void
     58 init_request(struct jsonrpc_request *req)
     59 {
     60 	init_log(&req->log);
     61 	kore_buf_init(&req->buf, 256);
     62 	req->gen = NULL;
     63 	req->http = NULL;
     64 	req->json = NULL;
     65 	req->id = NULL;
     66 	req->method = NULL;
     67 	req->params = NULL;
     68 	req->log_levels = (1 << LOG_EMERG) | (1 << LOG_ERR) | (1 << LOG_WARNING)
     69 			| (1 << LOG_NOTICE);
     70         req->flags = 0;
     71 }
     72 
     73 void
     74 jsonrpc_destroy_request(struct jsonrpc_request *req)
     75 {
     76 	if (req->gen != NULL) {
     77 		yajl_gen_free(req->gen);
     78 		req->gen = NULL;
     79 	}
     80 	if (req->json != NULL) {
     81 		yajl_tree_free(req->json);
     82 		req->json = NULL;
     83 	}
     84 	kore_buf_cleanup(&req->buf);
     85 	free_log(&req->log);
     86 }
     87 
     88 void
     89 jsonrpc_log(struct jsonrpc_request *req, int lvl, const char *fmt, ...)
     90 {
     91 	va_list	ap;
     92 	char	*msg;
     93 	size_t	start = req->buf.offset;
     94 
     95 	va_start(ap, fmt);
     96 	kore_buf_appendv(&req->buf, fmt, ap);
     97 	va_end(ap);
     98 
     99 	msg = kore_buf_stringify(&req->buf, NULL) + start;
    100 
    101 	append_log(&req->log, lvl, msg);
    102 }
    103 
    104 static int
    105 read_json_body(struct http_request *http_req, struct jsonrpc_request *req)
    106 {
    107 	char		*body_string;
    108 	ssize_t		body_len = 0, chunk_len;
    109 	u_int8_t	chunk_buffer[BUFSIZ];
    110 	char		error_buffer[1024];
    111 
    112 	for (;;) {
    113 		chunk_len = http_body_read(http_req, chunk_buffer,
    114 			sizeof(chunk_buffer));
    115 		if (chunk_len == -1) {
    116 			jsonrpc_log(req, LOG_CRIT,
    117 			    "Failed to read request body");
    118 			return (JSONRPC_SERVER_ERROR);
    119 		}
    120 
    121 		if (chunk_len == 0)
    122 			break;
    123 
    124 		if (body_len > SSIZE_MAX - chunk_len) {
    125 			jsonrpc_log(req, LOG_CRIT,
    126 			    "Request body bigger than the platform accepts");
    127 			return (JSONRPC_SERVER_ERROR);
    128 		}
    129 		body_len += chunk_len;
    130 
    131 		kore_buf_append(&req->buf, chunk_buffer, chunk_len);
    132 	}
    133 
    134 	/* Grab our body data as a NUL-terminated string. */
    135 	body_string = kore_buf_stringify(&req->buf, NULL);
    136 
    137 	/* Parse the body via yajl now. */
    138 	*error_buffer = 0;
    139 	req->json = yajl_tree_parse(body_string, error_buffer,
    140 	    sizeof(error_buffer));
    141 	if (req->json == NULL) {
    142 		if (strlen(error_buffer)) {
    143 			jsonrpc_log(req, LOG_ERR, "Invalid json: %s",
    144 			    error_buffer);
    145 		} else {
    146 			jsonrpc_log(req, LOG_ERR, "Invalid json");
    147 		}
    148 		return (JSONRPC_PARSE_ERROR);
    149 	}
    150 
    151 	return (0);
    152 }
    153 
    154 static int
    155 parse_json_body(struct jsonrpc_request *req)
    156 {
    157 	static const char	*proto_path[] = { "jsonrpc", NULL };
    158 	static const char	*id_path[] = { "id", NULL };
    159 	static const char	*method_path[] = { "method", NULL };
    160 	static const char	*params_path[] = { "params", NULL };
    161 
    162 	/* Check protocol first. */
    163 	yajl_val proto = yajl_tree_get(req->json, proto_path, yajl_t_string);
    164 	if (proto == NULL) {
    165 		jsonrpc_log(req, LOG_ERR,
    166 		    "JSON-RPC protocol MUST be indicated and \"2.0\"");
    167 		return (JSONRPC_PARSE_ERROR);
    168 	}
    169 
    170 	char *proto_string = YAJL_GET_STRING(proto);
    171 	if (proto_string == NULL) {
    172 		jsonrpc_log(req, LOG_ERR,
    173 		    "JSON-RPC protocol MUST be indicated and \"2.0\"");
    174 		return (JSONRPC_PARSE_ERROR);
    175 	}
    176 
    177 	if (strcmp("2.0", proto_string) != 0) {
    178 		jsonrpc_log(req, LOG_ERR,
    179 		    "JSON-RPC protocol MUST be indicated and \"2.0\"");
    180 		return (JSONRPC_PARSE_ERROR);
    181 	}
    182 
    183 	/* Check id. */ 
    184 	if ((req->id = yajl_tree_get(req->json, id_path, yajl_t_any)) != NULL) {
    185 		if (YAJL_IS_NUMBER(req->id)) {
    186 			if (!YAJL_IS_INTEGER(req->id)) {
    187 				jsonrpc_log(req, LOG_ERR,
    188 				    "JSON-RPC id SHOULD NOT contain fractional"
    189 				    " parts");
    190 				return (JSONRPC_PARSE_ERROR);
    191 			}
    192 		} else if (!YAJL_IS_STRING(req->id)) {
    193 			jsonrpc_log(req, LOG_ERR,
    194 			    "JSON-RPC id MUST contain a String or Number");
    195 			return (JSONRPC_PARSE_ERROR);
    196 		}
    197 	}
    198 
    199 	/* Check method. */
    200 	if ((req->method = YAJL_GET_STRING(yajl_tree_get(req->json, method_path,
    201 		yajl_t_string))) == NULL) {
    202 		jsonrpc_log(req, LOG_ERR,
    203 		    "JSON-RPC method MUST exist and be a String");
    204 		return (JSONRPC_PARSE_ERROR);
    205 	}
    206 
    207 	/* Check params. */
    208 	req->params = yajl_tree_get(req->json, params_path, yajl_t_any);
    209 	if (!(req->params == NULL || YAJL_IS_ARRAY(req->params)
    210 	    || YAJL_IS_OBJECT(req->params))) {
    211 		jsonrpc_log(req, LOG_ERR,
    212 		    "JSON-RPC params MUST be Object or Array");
    213 		return (JSONRPC_PARSE_ERROR);
    214 	}
    215 
    216 	return (0);
    217 }
    218 
    219 int
    220 jsonrpc_read_request(struct http_request *http_req, struct jsonrpc_request *req)
    221 {
    222 	int	ret;
    223 
    224 	init_request(req);
    225 	req->http = http_req;
    226 
    227 	if ((ret = read_json_body(http_req, req)) != 0)
    228 		return (ret);
    229 
    230 	return parse_json_body(req);
    231 }
    232 
    233 static int
    234 write_id(yajl_gen gen, yajl_val id)
    235 {
    236 	int	status;
    237 
    238 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(gen, "id")))
    239 		return (status);
    240 
    241 	if (YAJL_IS_NULL(id))
    242 		return yajl_gen_null(gen);
    243 
    244 	if (YAJL_IS_NUMBER(id)) {
    245 		if (YAJL_IS_INTEGER(id))
    246 			return yajl_gen_integer(gen, YAJL_GET_INTEGER(id));
    247 		return yajl_gen_null(gen);
    248 	}
    249 
    250 	if (YAJL_IS_STRING(id)) {
    251 		char	*id_str = YAJL_GET_STRING(id);
    252 
    253 		return yajl_gen_string(gen, (unsigned char *)id_str,
    254 			strlen(id_str));
    255 	}
    256 
    257 	return yajl_gen_null(gen);
    258 }
    259 
    260 static int
    261 open_response(yajl_gen genctx, yajl_val id)
    262 {
    263 	int		status;
    264 
    265 	if (YAJL_GEN_KO(status = yajl_gen_map_open(genctx)))
    266 		goto failed;
    267 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "jsonrpc")))
    268 		goto failed;
    269 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "2.0")))
    270 		goto failed;
    271 	status = write_id(genctx, id);
    272 failed:
    273 	return (status);
    274 }
    275 
    276 static int
    277 close_response(yajl_gen genctx)
    278 {
    279 	int	status;
    280 
    281 	if (YAJL_GEN_KO(status = yajl_gen_map_close(genctx)))
    282 		goto failed;
    283 	status = yajl_gen_map_close(genctx);
    284 failed:
    285 	return (status);
    286 }
    287 
    288 static int
    289 write_log(struct jsonrpc_request *req)
    290 {
    291 	bool	wrote_smth = false;
    292 	int	status = 0;
    293 	
    294 	for (struct jsonrpc_log *log = req->log.next; log != &req->log;
    295 		log = log->next) {
    296 
    297 		if (((1 << log->lvl) & req->log_levels) == 0)
    298 			continue;
    299 
    300 		if (!wrote_smth) {
    301 			if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen,
    302 			    "data")))
    303 				goto failed;
    304 			if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen)))
    305 				goto failed;
    306 			yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1);
    307 			wrote_smth = true;
    308 		}
    309 		
    310 		if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen)))
    311 			goto failed;
    312 		if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, log->lvl)))
    313 			goto failed;
    314 		if (YAJL_GEN_KO(status = yajl_gen_string(req->gen,
    315 		    (unsigned char *)log->msg, strlen(log->msg))))
    316 			goto failed;
    317 		if (YAJL_GEN_KO(status = yajl_gen_array_close(req->gen)))
    318 			goto failed;
    319 	}
    320 
    321 	if (wrote_smth) {
    322 		yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
    323 		status = yajl_gen_array_close(req->gen);
    324 	}
    325 failed:
    326 	return (status);
    327 }
    328 
    329 static int
    330 write_error(struct jsonrpc_request *req, int code, const char *message)
    331 {
    332 	int	status;
    333 
    334 	yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
    335 
    336 	if (YAJL_GEN_KO(status = open_response(req->gen, req->id)))
    337 		goto failed;
    338 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "error")))
    339 		goto failed;
    340 	if (YAJL_GEN_KO(status = yajl_gen_map_open(req->gen)))
    341 		goto failed;
    342 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "code")))
    343 		goto failed;
    344 	if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, code)))
    345 		goto failed;
    346 	if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "message")))
    347 		goto failed;
    348 
    349 	yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1);
    350 
    351 	if (YAJL_GEN_KO(status = yajl_gen_string(req->gen,
    352 			(const unsigned char *)message, strlen(message))))
    353 		goto failed;
    354 
    355 	yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
    356 
    357 	if (YAJL_GEN_KO(status = write_log(req)))
    358 		goto failed;
    359 
    360 	status = close_response(req->gen);
    361 failed:
    362 	return (status);
    363 }
    364 
    365 static const char *
    366 known_msg(int code)
    367 {
    368 	switch (code) {
    369 	case JSONRPC_PARSE_ERROR:
    370 		return (JSONRPC_PARSE_ERROR_MSG);
    371 	case JSONRPC_INVALID_REQUEST:
    372 		return (JSONRPC_INVALID_REQUEST_MSG);
    373 	case JSONRPC_METHOD_NOT_FOUND:
    374 		return (JSONRPC_METHOD_NOT_FOUND_MSG);
    375 	case JSONRPC_INVALID_PARAMS:
    376 		return (JSONRPC_INVALID_PARAMS_MSG);
    377 	case JSONRPC_INTERNAL_ERROR:
    378 		return (JSONRPC_INTERNAL_ERROR_MSG);
    379 	case JSONRPC_SERVER_ERROR:
    380 		return (JSONRPC_SERVER_ERROR_MSG);
    381 	case JSONRPC_LIMIT_REACHED:
    382 		return (JSONRPC_LIMIT_REACHED_MSG);
    383 	default:
    384 		return (NULL);
    385 	}
    386 }
    387 
    388 int
    389 jsonrpc_error(struct jsonrpc_request *req, int code, const char *msg)
    390 {
    391 	char			*msg_fallback;
    392 	const unsigned char	*body = NULL;
    393 	size_t			body_len = 0;
    394 	int			status;
    395 
    396 	if (req->id == NULL)
    397 		goto succeeded;
    398 
    399 	if ((req->gen = yajl_gen_alloc(NULL)) == NULL) {
    400 		kore_log(LOG_ERR, "jsonrpc_error: Failed to allocate yajl gen");
    401 		goto failed;
    402 	}
    403 
    404 	yajl_gen_config(req->gen, yajl_gen_beautify,
    405 	    req->flags & yajl_gen_beautify);
    406 
    407 	if (msg == NULL)
    408 		msg = known_msg(code);
    409 
    410 	if (msg == NULL) {
    411 		size_t	start = req->buf.offset;
    412 		kore_buf_appendf(&req->buf, "%d", code);
    413 		msg_fallback = kore_buf_stringify(&req->buf, NULL) + start;
    414 	}
    415 
    416 	if (YAJL_GEN_KO(status = write_error(req, code,
    417 	    msg ? msg : msg_fallback))) {
    418 		kore_log(LOG_ERR, "jsonrpc_error: Failed to yajl gen text [%d]",
    419 			status);
    420 		goto failed;
    421 	}
    422 
    423 	http_response_header(req->http, "content-type", "application/json");
    424 	yajl_gen_get_buf(req->gen, &body, &body_len);
    425 succeeded:
    426 	http_response(req->http, HTTP_STATUS_OK, body, body_len);
    427 	if (req->gen != NULL)
    428 		yajl_gen_clear(req->gen);
    429 	jsonrpc_destroy_request(req);
    430 	return (KORE_RESULT_OK);
    431 failed:
    432 	http_response(req->http, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
    433 	jsonrpc_destroy_request(req);
    434 	return (KORE_RESULT_OK);
    435 }
    436 
    437 int
    438 jsonrpc_result(struct jsonrpc_request *req,
    439     int (*write_result)(struct jsonrpc_request *, void *), void *ctx)
    440 {
    441 	const unsigned char	*body = NULL;
    442 	size_t			body_len = 0;
    443 
    444 	if (req->id == NULL)
    445 		goto succeeded;
    446 
    447 	if ((req->gen = yajl_gen_alloc(NULL)) == NULL) {
    448 		kore_log(LOG_ERR, "jsonrpc_result: Failed to allocate yajl gen");
    449 		goto failed;
    450         }
    451 
    452 	yajl_gen_config(req->gen, yajl_gen_beautify,
    453 	    req->flags & yajl_gen_beautify);
    454 
    455 	yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
    456 
    457 	if (YAJL_GEN_KO(open_response(req->gen, req->id)))
    458 		goto failed;
    459 	if (YAJL_GEN_KO(YAJL_GEN_CONST_STRING(req->gen, "result")))
    460 		goto failed;
    461 	if (YAJL_GEN_KO(write_result(req, ctx)))
    462 		goto failed;
    463 	if (YAJL_GEN_KO(yajl_gen_map_close(req->gen)))
    464 		goto failed;
    465 	
    466 	http_response_header(req->http, "content-type", "application/json");
    467 	yajl_gen_get_buf(req->gen, &body, &body_len);
    468 succeeded:
    469 	http_response(req->http, HTTP_STATUS_OK, body, body_len);
    470 	if (req->gen != NULL)
    471 		yajl_gen_clear(req->gen);
    472 	jsonrpc_destroy_request(req);
    473 	return (KORE_RESULT_OK);
    474 failed:
    475 	http_response(req->http, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
    476 	jsonrpc_destroy_request(req);
    477 	return (KORE_RESULT_OK);
    478 }