#define UH_UBUS_MAX_POST_SIZE 4096
-static char *split_str(char *str)
+enum {
+ RPC_JSONRPC,
+ RPC_METHOD,
+ RPC_PARAMS,
+ RPC_ID,
+ __RPC_MAX,
+};
+
+static const struct blobmsg_policy rpc_policy[__RPC_MAX] = {
+ [RPC_JSONRPC] = { .name = "jsonrpc", .type = BLOBMSG_TYPE_STRING },
+ [RPC_METHOD] = { .name = "method", .type = BLOBMSG_TYPE_STRING },
+ [RPC_PARAMS] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY },
+ [RPC_ID] = { .name = "id", .type = BLOBMSG_TYPE_UNSPEC },
+};
+
+struct rpc_data {
+ struct blob_attr *id;
+ const char *method;
+ const char *object;
+ const char *function;
+ struct blob_attr *data;
+};
+
+enum rpc_error {
+ ERROR_PARSE,
+ ERROR_REQUEST,
+ ERROR_METHOD,
+ ERROR_PARAMS,
+ ERROR_INTERNAL,
+ ERROR_OBJECT,
+ ERROR_SESSION,
+ ERROR_ACCESS,
+ ERROR_TIMEOUT,
+ __ERROR_MAX
+};
+
+static const struct {
+ int code;
+ const char *msg;
+} json_errors[__ERROR_MAX] = {
+ [ERROR_PARSE] = { -32700, "Parse error" },
+ [ERROR_REQUEST] = { -32600, "Invalid request" },
+ [ERROR_METHOD] = { -32601, "Method not found" },
+ [ERROR_PARAMS] = { -32602, "Invalid parameters" },
+ [ERROR_INTERNAL] = { -32603, "Internal error" },
+ [ERROR_OBJECT] = { -32000, "Object not found" },
+ [ERROR_SESSION] = { -32001, "Session not found" },
+ [ERROR_ACCESS] = { -32002, "Access denied" },
+ [ERROR_TIMEOUT] = { -32003, "ubus request timed out" },
+};
+
+static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout);
+
+static void uh_ubus_next_batched_request(struct client *cl)
+{
+ struct dispatch_ubus *du = &cl->dispatch.ubus;
+
+ du->timeout.cb = __uh_ubus_next_batched_request;
+ uloop_timeout_set(&du->timeout, 1);
+}
+
+static void uh_ubus_send_header(struct client *cl)
+{
+ ops->http_header(cl, 200, "OK");
+ ustream_printf(cl->us, "Content-Type: application/json\r\n\r\n");
+}
+
+static void uh_ubus_send_response(struct client *cl)
{
- if (str)
- str = strchr(str, '/');
+ struct dispatch_ubus *du = &cl->dispatch.ubus;
+ const char *sep = "";
+ char *str;
- while (str && *str == '/') {
- *str = 0;
- str++;
+ if (du->array && du->array_idx > 1)
+ sep = ", ";
+
+ str = blobmsg_format_json_indent(buf.head, true, du->array);
+ ops->chunk_printf(cl, "%s%s", sep, str);
+ free(str);
+
+ du->jsobj_cur = NULL;
+ if (du->array)
+ uh_ubus_next_batched_request(cl);
+ else {
+ ops->chunk_printf(cl, "\n");
+ return ops->request_done(cl);
}
- return str;
}
-static bool
-uh_ubus_request_parse_url(struct client *cl, char *url, char **sid, char **obj, char **fun)
+static void uh_ubus_init_response(struct client *cl)
{
- url += strlen(conf.ubus_prefix);
- while (url && *url == '/')
- url++;
+ struct dispatch_ubus *du = &cl->dispatch.ubus;
+ struct json_object *obj = du->jsobj_cur;
- *sid = url;
+ blob_buf_init(&buf, 0);
+ blobmsg_add_string(&buf, "jsonrpc", "2.0");
- url = split_str(url);
- *obj = url;
+ if (obj)
+ obj = json_object_object_get(obj, "id");
- url = split_str(url);
- *fun = url;
+ if (obj)
+ blobmsg_add_json_element(&buf, "id", obj);
+ else
+ blobmsg_add_field(&buf, BLOBMSG_TYPE_UNSPEC, "id", NULL, 0);
+}
- return *sid && *obj && *fun;
+static void uh_ubus_json_error(struct client *cl, enum rpc_error type)
+{
+ void *c;
+
+ uh_ubus_init_response(cl);
+ c = blobmsg_open_table(&buf, "error");
+ blobmsg_add_u32(&buf, "code", json_errors[type].code);
+ blobmsg_add_string(&buf, "message", json_errors[type].msg);
+ blobmsg_close_table(&buf, c);
+ uh_ubus_send_response(cl);
}
static void
uh_ubus_request_data_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
- struct client *cl = container_of(du, struct client, dispatch.ubus);
- char *str;
- if (!du->header_sent) {
- ops->http_header(cl, 200, "OK");
- ustream_printf(cl->us, "Content-Type: application/json\r\n\r\n");
- du->header_sent = true;
- }
-
- str = blobmsg_format_json_indent(msg, true, 0);
- ops->chunk_write(cl, str, strlen(str));
- free(str);
+ blobmsg_add_field(&du->buf, BLOBMSG_TYPE_TABLE, "", blob_data(msg), blob_len(msg));
}
static void
{
struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
struct client *cl = container_of(du, struct client, dispatch.ubus);
+ struct blob_attr *cur;
+ void *r;
+ int rem;
+
+ uloop_timeout_cancel(&du->timeout);
+ uh_ubus_init_response(cl);
+ r = blobmsg_open_array(&buf, "result");
+ blobmsg_add_u32(&buf, "", ret);
+ blob_for_each_attr(cur, du->buf.head, rem)
+ blobmsg_add_blob(&buf, cur);
+ blobmsg_close_array(&buf, r);
+ uh_ubus_send_response(cl);
+}
- if (!du->header_sent)
- return ops->client_error(cl, 204, "No content", "Function did not return data");
+static void
+uh_ubus_timeout_cb(struct uloop_timeout *timeout)
+{
+ struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout);
+ struct client *cl = container_of(du, struct client, dispatch.ubus);
- ops->request_done(cl);
+ ubus_abort_request(ctx, &du->req);
+ uh_ubus_json_error(cl, ERROR_TIMEOUT);
}
static void uh_ubus_close_fds(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
+ blob_buf_free(&du->buf);
+ uloop_timeout_cancel(&cl->timeout);
+
if (du->jsobj)
json_object_put(du->jsobj);
ubus_abort_request(ctx, &du->req);
}
-static void uh_ubus_json_error(struct client *cl)
+static void uh_ubus_single_error(struct client *cl, enum rpc_error type)
{
- ops->client_error(cl, 400, "Bad Request", "Invalid JSON data");
+ uh_ubus_send_header(cl);
+ uh_ubus_json_error(cl, type);
+ ops->request_done(cl);
}
static void uh_ubus_send_request(struct client *cl, json_object *obj)
struct dispatch_ubus *du = &d->ubus;
int ret;
- blob_buf_init(&buf, 0);
-
- if (obj && !blobmsg_add_object(&buf, obj))
- return uh_ubus_json_error(cl);
-
+ blob_buf_init(&du->buf, 0);
+ memset(&du->req, 0, sizeof(du->req));
ret = ubus_invoke_async(ctx, du->obj, du->func, buf.head, &du->req);
if (ret)
- return ops->client_error(cl, 500, "Internal Error",
- "Error sending ubus request: %s", ubus_strerror(ret));
+ return uh_ubus_json_error(cl, ERROR_INTERNAL);
du->req.data_cb = uh_ubus_request_data_cb;
du->req.complete_cb = uh_ubus_request_cb;
ubus_complete_request_async(ctx, &du->req);
+ du->timeout.cb = uh_ubus_timeout_cb;
+ uloop_timeout_set(&du->timeout, conf.script_timeout);
+
du->req_pending = true;
}
-static void uh_ubus_data_done(struct client *cl)
+static bool parse_json_rpc(struct rpc_data *d, struct blob_attr *data)
+{
+ const struct blobmsg_policy data_policy[] = {
+ { .type = BLOBMSG_TYPE_STRING },
+ { .type = BLOBMSG_TYPE_STRING },
+ { .type = BLOBMSG_TYPE_TABLE },
+ };
+ struct blob_attr *tb[__RPC_MAX];
+ struct blob_attr *tb2[3];
+ struct blob_attr *cur;
+
+ blobmsg_parse(rpc_policy, __RPC_MAX, tb, blob_data(data), blob_len(data));
+
+ cur = tb[RPC_JSONRPC];
+ if (!cur || strcmp(blobmsg_data(cur), "2.0") != 0)
+ return false;
+
+ cur = tb[RPC_METHOD];
+ if (!cur)
+ return false;
+
+ d->id = tb[RPC_ID];
+ d->method = blobmsg_data(cur);
+
+ cur = tb[RPC_PARAMS];
+ if (!cur)
+ return false;
+
+ blobmsg_parse_array(data_policy, ARRAY_SIZE(data_policy), tb2,
+ blobmsg_data(cur), blobmsg_data_len(cur));
+
+ if (!tb2[0] || !tb2[1] || !tb2[2])
+ return false;
+
+ d->object = blobmsg_data(tb2[0]);
+ d->function = blobmsg_data(tb2[1]);
+ d->data = tb2[2];
+ return true;
+}
+
+static void uh_ubus_init_batch(struct client *cl)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
- struct json_object *obj = du->jsobj;
- if (!obj || json_object_get_type(obj) != json_type_object)
- return uh_ubus_json_error(cl);
+ du->array = true;
+ uh_ubus_send_header(cl);
+ ops->chunk_printf(cl, "[\n\t");
+}
- uh_ubus_send_request(cl, obj);
+static void uh_ubus_complete_batch(struct client *cl)
+{
+ ops->chunk_printf(cl, "\n]\n");
+ ops->request_done(cl);
}
-static int uh_ubus_data_send(struct client *cl, const char *data, int len)
+static void uh_ubus_handle_request_object(struct client *cl, struct json_object *obj)
{
struct dispatch_ubus *du = &cl->dispatch.ubus;
+ struct uh_ubus_session *ses;
+ struct rpc_data data = {};
+ enum rpc_error err = ERROR_PARSE;
- if (du->jsobj) {
- uh_ubus_json_error(cl);
- return 0;
+ if (json_object_get_type(obj) != json_type_object)
+ goto error;
+
+ du->jsobj_cur = obj;
+ blob_buf_init(&buf, 0);
+ if (!blobmsg_add_object(&buf, obj))
+ goto error;
+
+ if (!parse_json_rpc(&data, buf.head))
+ goto error;
+
+ if (strcmp(data.method, "call") != 0) {
+ err = ERROR_METHOD;
+ goto error;
}
- du->post_len += len;
- if (du->post_len > UH_UBUS_MAX_POST_SIZE) {
- ops->client_error(cl, 413, "Too Large", "Message too big");
- return 0;
+ ses = uh_ubus_session_get(du->sid);
+ if (!ses) {
+ err = ERROR_SESSION;
+ goto error;
}
- du->jsobj = json_tokener_parse_ex(du->jstok, data, len);
- return len;
+ if (!uh_ubus_session_acl_allowed(ses, data.object, data.function)) {
+ err = ERROR_ACCESS;
+ goto error;
+ }
+
+ du->func = data.function;
+ if (ubus_lookup_id(ctx, data.object, &du->obj)) {
+ err = ERROR_OBJECT;
+ goto error;
+ }
+
+ uh_ubus_send_request(cl, obj);
+ return;
+
+error:
+ uh_ubus_json_error(cl, err);
}
-static void uh_ubus_defer_post(struct client *cl)
+static void __uh_ubus_next_batched_request(struct uloop_timeout *timeout)
{
- struct dispatch *d = &cl->dispatch;
+ struct dispatch_ubus *du = container_of(timeout, struct dispatch_ubus, timeout);
+ struct client *cl = container_of(du, struct client, dispatch.ubus);
+ struct json_object *obj = du->jsobj;
+ int len;
- d->ubus.jstok = json_tokener_new();
- if (d->ubus.jstok)
- return ops->client_error(cl, 500, "Internal Error", "Internal Error");
+ len = json_object_array_length(obj);
+ if (du->array_idx >= len)
+ return uh_ubus_complete_batch(cl);
- d->data_send = uh_ubus_data_send;
- d->data_done = uh_ubus_data_done;
+ obj = json_object_array_get_idx(obj, du->array_idx++);
+ uh_ubus_handle_request_object(cl, obj);
+}
+
+static void uh_ubus_data_done(struct client *cl)
+{
+ struct dispatch_ubus *du = &cl->dispatch.ubus;
+ struct json_object *obj = du->jsobj;
+
+ switch (obj ? json_object_get_type(obj) : json_type_null) {
+ case json_type_object:
+ uh_ubus_send_header(cl);
+ return uh_ubus_handle_request_object(cl, obj);
+ case json_type_array:
+ uh_ubus_init_batch(cl);
+ if (json_object_array_length(obj) > 0)
+ return uh_ubus_next_batched_request(cl);
+ /* fall through */
+ default:
+ return uh_ubus_single_error(cl, ERROR_PARSE);
+ }
+}
+
+static int uh_ubus_data_send(struct client *cl, const char *data, int len)
+{
+ struct dispatch_ubus *du = &cl->dispatch.ubus;
+
+ if (du->jsobj || !du->jstok)
+ goto error;
+
+ du->post_len += len;
+ if (du->post_len > UH_UBUS_MAX_POST_SIZE)
+ goto error;
+
+ du->jsobj = json_tokener_parse_ex(du->jstok, data, len);
+ return len;
+
+error:
+ uh_ubus_single_error(cl, ERROR_PARSE);
+ return 0;
}
static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi)
{
- struct uh_ubus_session *ses;
struct dispatch *d = &cl->dispatch;
- char *sid, *obj, *fun;
+ char *sid, *sep;
blob_buf_init(&buf, 0);
- if (!uh_ubus_request_parse_url(cl, url, &sid, &obj, &fun))
- return ops->client_error(cl, 400, "Bad Request", "Invalid Request");
-
- ses = uh_ubus_session_get(sid);
- if (!ses)
- return ops->client_error(cl, 404, "Not Found", "No such session %s", sid);
+ url += strlen(conf.ubus_prefix);
+ while (*url == '/')
+ url++;
- if (!uh_ubus_session_acl_allowed(ses, obj, fun))
- return ops->client_error(cl, 403, "Denied", "Access to object denied");
+ sep = strchr(url, '/');
+ if (sep)
+ *sep = 0;
- if (ubus_lookup_id(ctx, obj, &d->ubus.obj))
- return ops->client_error(cl, 500, "Not Found", "No such object");
+ sid = url;
+ if (strlen(sid) != 32 ||
+ cl->request.method != UH_HTTP_MSG_POST)
+ return ops->client_error(cl, 400, "Bad Request", "Invalid Request");
d->close_fds = uh_ubus_close_fds;
d->free = uh_ubus_request_free;
- d->ubus.func = fun;
-
- if (cl->request.method == UH_HTTP_MSG_POST)
- uh_ubus_defer_post(cl);
- else
- uh_ubus_send_request(cl, NULL);
+ d->data_send = uh_ubus_data_send;
+ d->data_done = uh_ubus_data_done;
+ d->ubus.jstok = json_tokener_new();
+ d->ubus.sid = sid;
}
static bool