libwebsockets/lib/misc/jrpc/jrpc.c

387 lines
8.9 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* We use the lejp parse stack to replace the callback context for JSON
* subtrees.
*
* It's optionally done when we see we're in a [] batch of reqs, we pass each
* unitary req to the internal req parser.
*
* Each req does it to hand off the parsing of the parameters section.
*/
#include <private-lib-core.h>
#include "private-lib-misc-jrpc.h"
static const char * const paths[] = {
"jsonrpc",
"method",
"version",
"params",
"id",
/* only for responses --> */
"result",
"error",
"code",
"message",
"data",
};
enum enum_paths {
LEJPN_JSONRPC,
LEJPN_METHOD,
LEJPN_VERSION,
LEJPN_PARAMS,
LEJPN_ID,
/* only for responses --> */
LEJPN_RESULT,
LEJPN_ERROR,
LEJPN_E_CODE,
LEJPN_E_MESSAGE,
LEJPN_E_DATA,
};
/*
* Get the registered handler for a method name... a registered handler for
* a NULL method name matches any other unmatched name.
*/
static const lws_jrpc_method_t *
lws_jrpc_method_lookup(lws_jrpc_t *jrpc, const char *method_name)
{
const lws_jrpc_method_t *m = jrpc->methods, *m_null = NULL;
while (1) {
if (!m->method_name)
return m;
if (!strcmp(method_name, m->method_name))
return m;
m++;
}
return m_null;
}
static signed char
req_cb(struct lejp_ctx *ctx, char reason)
{
lws_jrpc_obj_t *r = (lws_jrpc_obj_t *)ctx->user;
lws_jrpc_t *jrpc;
char *p;
lwsl_warn("%s: %d '%s' %s (sp %d, pst_sp %d)\n", __func__, reason, ctx->path, ctx->buf, ctx->sp, ctx->pst_sp);
if (reason == LEJPCB_PAIR_NAME && ctx->path_match - 1 == LEJPN_PARAMS) {
if (r->response)
goto fail_invalid_members;
/*
* Params are a wormhole to another LEJP parser context to deal
* with, chosen based on the method name and the callbacks
* associated with that at init time.
*
* Params may be provided in a toplevel array, called a "batch",
* these are treated as n independent subrequests to be handled
* sequentially, and if the request is parseable, the scope of
* errors is only the current batch entry.
*/
jrpc = lws_container_of(r->list.owner, lws_jrpc_t, req_owner);
r->pmethod = lws_jrpc_method_lookup(jrpc, r->method);
if (!r->pmethod || !r->pmethod->cb)
/*
* There's nothing we can do with no method binding, or
* one that lacks a callback...
*/
goto fail_method_not_found;
r->inside_params = 1;
lwsl_notice("%s: params: entering subparser\n", __func__);
lejp_parser_push(ctx, r, r->pmethod->paths,
(uint8_t)r->pmethod->count_paths, r->pmethod->cb);
}
if (reason == LEJPCB_COMPLETE && !r->response) {
if (!r->has_jrpc_member)
goto fail_invalid_request;
if (r->method[0] && !r->pmethod) {
jrpc = lws_container_of(r->list.owner, lws_jrpc_t,
req_owner);
r->pmethod = lws_jrpc_method_lookup(jrpc, r->method);
if (!r->pmethod || !r->pmethod->cb)
/*
* There's nothing we can do with no method
* binding, or one that lacks a callback...
*/
goto fail_method_not_found;
}
/*
* Indicate that the whole of the request has been parsed now
* and the id is known, so the method can complete and finalize
* its response
*/
r->pmethod->cb(ctx, LEJPCB_USER_START);
return 0;
}
/* we only match on the prepared path strings */
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
return 0;
if (ctx->path_match - 1 >= LEJPN_RESULT && !r->response)
goto fail_invalid_members;
switch (ctx->path_match - 1) {
case LEJPN_JSONRPC:
/*
* A String specifying the version of the JSON-RPC protocol.
* MUST be exactly "2.0".
*/
if (ctx->npos != 3 && strcmp(ctx->buf, "2.0")) {
r->parse_result = LWSJRPCWKE__INVALID_REQUEST;
return -1;
}
r->has_jrpc_member = 1;
break;
case LEJPN_METHOD:
if (r->response)
goto fail_invalid_members;
/*
* Method is defined to be a string... anything else is invalid
*/
if (reason != LEJPCB_VAL_STR_END)
goto fail_invalid_request;
/*
* Restrict the method length to something sane
*/
if (ctx->npos > sizeof(r->method) - 1)
goto fail_method_not_found;
lws_strnncpy(r->method, ctx->buf, ctx->npos, sizeof(r->method));
/* defer trying to use it so we catch parser errors */
break;
case LEJPN_ID:
/*
* "An identifier established by the Client that MUST contain a
* String, Number, or NULL value if included. If it is not
* included it is assumed to be a notification. The value SHOULD
* normally not be Null and Numbers SHOULD NOT contain
* fractional parts."
*
* We defaulted the id to null, let's continue to store the id
* exactly as it would be reissued, ie, if a string, then we'll
* add the quotes around it now.
*
* Restrict the method length and type to something sane
*/
if (ctx->npos > sizeof(r->id) - 3 ||
reason == LEJPCB_VAL_TRUE ||
reason == LEJPCB_VAL_FALSE ||
/* if float, has "fractional part" */
reason == LEJPCB_VAL_NUM_FLOAT)
goto fail_invalid_request;
r->seen_id = 1;
if (reason == LEJPCB_VAL_NULL)
/* it already defaults to null */
break;
p = r->id;
if (reason == LEJPCB_VAL_STR_END)
*p++ = '\"';
lws_strnncpy(p, ctx->buf, ctx->npos, sizeof(r->id) - 2);
if (reason == LEJPCB_VAL_STR_END) {
p += strlen(p);
*p++ = '\"';
*p = '\0';
}
break;
case LEJPN_VERSION:
/*
* Restrict the method length to something sane
*/
if (ctx->npos > sizeof(r->version) - 1)
goto fail_invalid_request;
lws_strnncpy(r->version, ctx->buf, ctx->npos, sizeof(r->version));
break;
/*
* Only for responses
*/
case LEJPN_RESULT:
break;
case LEJPN_ERROR:
break;
case LEJPN_E_CODE:
break;
case LEJPN_E_MESSAGE:
break;
case LEJPN_E_DATA:
break;
}
return 0;
fail_invalid_members:
r->parse_result = LWSJRPCE__INVALID_MEMBERS;
return -1;
fail_invalid_request:
r->parse_result = LWSJRPCWKE__INVALID_REQUEST;
return -1;
fail_method_not_found:
r->parse_result = LWSJRPCWKE__METHOD_NOT_FOUND;
return -1;
}
const char *
lws_jrpc_obj_id(const struct lws_jrpc_obj *r)
{
return r->id;
}
/*
* Return code is >= 0 if completed, representing the amount of unused data in
* the input buffer. -1 indicates more input data needed, <-1 indicates an
* error from the LWSJRPCWKE_ set above
*/
int
lws_jrpc_obj_parse(lws_jrpc_t *jrpc, int type, void *opaque,
const char *buf, size_t l, lws_jrpc_obj_t **_r)
{
lws_jrpc_obj_t *r = *_r;
int n;
if (!r) {
/*
* We need to init the request object
*/
r = *_r = malloc(sizeof(*r));
if (!r)
return LEJP_REJECT_UNKNOWN; /* OOM */
memset(r, 0, sizeof *r);
lws_dll2_add_tail(&r->list, &jrpc->req_owner);
r->opaque = opaque;
r->response = type == LWSJRPC_PARSE_RESPONSE;
lws_strncpy(r->id, "null", sizeof(r->id));
lejp_construct(&r->lejp_ctx, req_cb, r, paths,
LWS_ARRAY_SIZE(paths));
}
n = lejp_parse(&r->lejp_ctx, (uint8_t *)buf, (int)l);
lwsl_debug("%s: raw parse result %d\n", __func__, n);
if (n == LEJP_REJECT_CALLBACK)
return r->parse_result;
if (n < -1)
return LWSJRPCWKE__PARSE_ERROR;
return n;
}
void *
lws_jrpc_obj_get_opaque(const struct lws_jrpc_obj * r)
{
return (void *)r->opaque;
}
void
lws_jrpc_obj_destroy(lws_jrpc_obj_t **_r)
{
lws_jrpc_obj_t *r = *_r;
if (!r)
return;
lws_dll2_remove(&r->list);
free(r);
*_r = NULL;
}
struct lws_jrpc *
lws_jrpc_create(const lws_jrpc_method_t *methods, void *opaque)
{
lws_jrpc_t *j = malloc(sizeof(*j));
if (!j)
return NULL;
memset(j, 0, sizeof(*j));
j->opaque = opaque;
j->methods = methods;
return j;
}
void *
lws_jrpc_get_opaque(const struct lws_jrpc *jrpc)
{
return (void *)jrpc->opaque;
}
void
lws_jrpc_destroy(lws_jrpc_t **_jrpc)
{
struct lws_jrpc *jrpc = *_jrpc;
if (!jrpc)
return;
lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
jrpc->req_owner.head) {
lws_jrpc_obj_t *r = lws_container_of(p, lws_jrpc_obj_t, list);
lws_jrpc_obj_destroy(&r);
} lws_end_foreach_dll_safe(p, p1);
free(jrpc);
*_jrpc = NULL;
}