From b7aa73406651f775ef8ccfc66a2697569927f4bc Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Sat, 15 Aug 2026 10:50:48 -0700 Subject: [PATCH] Added read-only helper functions to the json api which handle errors. --- json.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++-- layout-custom.c | 108 +++++++++++++++++--------------------- tmux.h | 14 ++++- 3 files changed, 191 insertions(+), 65 deletions(-) diff --git a/json.c b/json.c index 768b8beae..57f1b9068 100644 --- a/json.c +++ b/json.c @@ -26,7 +26,8 @@ #include "tmux.h" -#define TOKENS_MAX 4096 +#define INPUT_MAX 8192 +#define TOKENS_MAX (INPUT_MAX) #define ERROR_CTX_LEN 8 /* JSON Token types. */ @@ -115,6 +116,7 @@ static struct json_node *json_parse_boolean(struct json_token **, const char *, static int json_node_cmp(struct json_node *, struct json_node *); RB_GENERATE_STATIC(json_fields, json_node, oentry, json_node_cmp); +/* Comparator for the json_fields tree. */ static int json_node_cmp(struct json_node *a, struct json_node *b) { @@ -133,6 +135,7 @@ json_parse(const char *input, char **cause) return (json_parse_tokens(&tokens, cause)); } +/* Returns a field node from an object node. */ struct json_node * json_find(struct json_node *jn, const char *key) { @@ -145,8 +148,9 @@ json_find(struct json_node *jn, const char *key) return (RB_FIND(json_fields, &jn->fields, &tmp)); } +/* Returns the first member of an array node. */ struct json_node * -json_array_first(struct json_node *jn) +json_array_first(const struct json_node *jn) { if (jn->type != NODE_ARRAY) return (NULL); @@ -154,8 +158,9 @@ json_array_first(struct json_node *jn) return (TAILQ_FIRST(&jn->members)); } +/* Returns the next member of an array's member node. */ struct json_node * -json_array_next(struct json_node *jn) +json_array_next(const struct json_node *jn) { if (jn->parent->type != NODE_ARRAY) return (NULL); @@ -163,6 +168,7 @@ json_array_next(struct json_node *jn) return (TAILQ_NEXT(jn, aentry)); } +/* Returns the string value from a node. */ int json_get_string(struct json_node *jn, const char **s) { @@ -173,6 +179,7 @@ json_get_string(struct json_node *jn, const char **s) return (0); } +/* Returns the number value from a node. */ int json_get_number(struct json_node *jn, int64_t *i) { @@ -183,6 +190,7 @@ json_get_number(struct json_node *jn, int64_t *i) return (0); } +/* Returns the boolean value from a node. */ int json_get_boolean(struct json_node *jn, int *b) { @@ -193,6 +201,7 @@ json_get_boolean(struct json_node *jn, int *b) return (0); } +/* Returns the object value from a node. */ int json_get_object(struct json_node *jn, struct json_node **o) { @@ -202,6 +211,8 @@ json_get_object(struct json_node *jn, struct json_node **o) *o = jn; return (0); } + +/* Returns the array value from a node. */ int json_get_array(struct json_node *jn, struct json_node **a) { @@ -212,6 +223,123 @@ json_get_array(struct json_node *jn, struct json_node **a) return (0); } +/* Returns the string value from a given key in an object node. */ +const char * +json_find_string(const struct json_node *jn, const char *key, char **cause) +{ + struct json_node *field; + static char ret[INPUT_MAX]; + + if ((field = json_find((struct json_node *)jn, key)) == NULL) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" not found", key); + return (NULL); + } + + if (field->type != NODE_STRING) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" expected STRING value", + key); + return (NULL); + } + + if (xsnprintf(ret, sizeof ret, "%s", field->str) >= (int)sizeof ret) { + if (cause != NULL) + xasprintf(cause, "string overflow for key \"%s\"", key); + return (NULL); + } + + return (ret); +} + +/* Returns the number value from a given key in an object node. */ +const int64_t * +json_find_number(const struct json_node *jn, const char *key, char **cause) +{ + struct json_node *field; + static int64_t ret; + + if ((field = json_find((struct json_node *)jn, key)) == NULL) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" not found", key); + return (NULL); + } + if (field->type != NODE_NUMBER) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" expected NUMBER value", + key); + return (NULL); + } + ret = field->num; + + return (&ret); +} + +/* Returns the boolean value from a given key in an object node. */ +const int * +json_find_boolean(const struct json_node *jn, const char *key, char **cause) +{ + struct json_node *field; + static int ret; + + if ((field = json_find((struct json_node *)jn, key)) == NULL) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" not found", key); + return (NULL); + } + if (field->type != NODE_BOOLEAN) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" expected BOOLEAN value", + key); + return (NULL); + } + ret = field->boolean; + + return (&ret); +} + +/* Returns the object value from a given key in an object node. */ +const struct json_node * +json_find_object(const struct json_node *jn, const char *key, char **cause) +{ + struct json_node *field; + + if ((field = json_find((struct json_node *)jn, key)) == NULL) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" not found", key); + return (NULL); + } + if (field->type != NODE_OBJECT) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" expected OBJECT value", + key); + return (NULL); + } + + return (field); +} + +/* Returns the array value from a given key in an object node. */ +const struct json_node * +json_find_array(const struct json_node *jn, const char *key, char **cause) +{ + struct json_node *field; + + if ((field = json_find((struct json_node *)jn, key)) == NULL) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" not found", key); + return (NULL); + } + if (field->type != NODE_ARRAY) { + if (cause != NULL) + xasprintf(cause, "key \"%s\" expected ARRAY value", + key); + return (NULL); + } + + return (field); +} + /* Fill an error cause. */ static void json_error(char **cause, const char *reason, const char *input) diff --git a/layout-custom.c b/layout-custom.c index 84c7079b0..a021a8df0 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -89,7 +89,8 @@ static void layout_assign(struct window *, struct layout_parse_ctx *); static void layout_parse_apply_ctx(struct window *, struct layout_parse_ctx *); -static struct layout_cell *layout_parse_json_layout(struct json_node *, +static struct layout_cell *layout_parse_json_layout( + const struct json_node *, struct layout_cell *, struct layout_parse_ctx *); @@ -702,27 +703,22 @@ fail: static int layout_parse_json(struct json_node *jnroot, struct layout_parse_ctx *pctx) { - struct json_node *jn, *field, *object; + struct json_node *jn; + const struct json_node *object; + const int64_t *num; char **cause = pctx->cause; if (json_get_object(jnroot, &jn) != 0) { - *cause = xstrdup("invalid json"); + *cause = xstrdup("invalid layout json"); goto fail; } - if ((field = json_find(jn, "V")) == NULL) { - *cause = xstrdup("missing version"); + + if ((num = json_find_number(jn, "V", cause)) == NULL) { goto fail; } - if (json_get_number(field, &pctx->version) != 0) { - *cause = xstrdup("invalid version type"); - goto fail; - } - if ((field = json_find(jn, "L")) == NULL) { - *cause = xstrdup("missing layout"); - goto fail; - } - if (json_get_object(field, &object) != 0) { - *cause = xstrdup("invalid version type"); + pctx->version = *num; + + if ((object = json_find_object(jn, "L", cause)) == NULL) { goto fail; } pctx->root = layout_parse_json_layout(object, NULL, pctx); @@ -743,20 +739,19 @@ fail: /* Parse nodes into layout cells. */ static struct layout_cell * -layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent, - struct layout_parse_ctx *pctx) +layout_parse_json_layout(const struct json_node *node, + struct layout_cell *lcparent, struct layout_parse_ctx *pctx) { - struct json_node *field, *array, *member; + struct json_node *member; struct layout_cell *lc = layout_create_cell(lcparent), *lcchild; + const struct json_node *array; const char *str; - char *endptr; - int64_t num; + const int64_t *num; + const int *boolean; + char *endptr, **cause = pctx->cause; u_int id, index, zindex, active = -1, last = -1; - int boolean; - if ((field = json_find(node, "t")) == NULL) - goto fail; - if (json_get_string(field, &str) != 0) + if ((str = json_find_string(node, "t", cause)) == NULL) goto fail; if (strcmp(str, "p") == 0) lc->type = LAYOUT_WINDOWPANE; @@ -767,63 +762,56 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent, else goto fail; - if ((field = json_find(node, "w")) == NULL) + if ((num = json_find_number(node, "w", cause)) == NULL) goto fail; - if (json_get_number(field, &num) != 0) - goto fail; - lc->g.sx = num; + lc->g.sx = *num; - if ((field = json_find(node, "h")) == NULL) + if ((num = json_find_number(node, "h", cause)) == NULL) goto fail; - if (json_get_number(field, &num) != 0) - goto fail; - lc->g.sy = num; + lc->g.sy = *num; - if ((field = json_find(node, "x")) == NULL) + if ((num = json_find_number(node, "x", cause)) == NULL) goto fail; - if (json_get_number(field, &num) != 0) - goto fail; - lc->g.xoff = num; + lc->g.xoff = *num; - if ((field = json_find(node, "y")) == NULL) + if ((num = json_find_number(node, "y", cause)) == NULL) goto fail; - if (json_get_number(field, &num) != 0) - goto fail; - lc->g.yoff = num; + lc->g.yoff = *num; if (lc->type == LAYOUT_WINDOWPANE) { - if ((field = json_find(node, "I")) == NULL) - goto fail; - if (json_get_string(field, &str) != 0) + if ((str = json_find_string(node, "I", cause)) == NULL) goto fail; errno = 0; if (*str != '%') { + *cause = xstrdup("pane id must begin with '%'"); goto fail; } id = strtol(str + 1, &endptr, 10); if (errno != 0 || endptr != str + strlen(str)) { + *cause = xstrdup("invalid number string '%s'"); goto fail; } - if ((field = json_find(node, "i")) == NULL) + if ((num = json_find_number(node, "i", cause)) == NULL) goto fail; - if (json_get_number(field, &num) != 0) - goto fail; - index = num; + index = *num; - if ((field = json_find(node, "a")) != NULL) { - if (json_get_boolean(field, &boolean) != 0) + if (json_find((struct json_node *)node, "a") != NULL) { + boolean = json_find_boolean(node, "a", cause); + if (boolean == NULL) goto fail; - active = boolean; - } else if ((field = json_find(node, "l")) != NULL) { - if (json_get_number(field, &num) != 0) + active = *boolean; + } else if (json_find((struct json_node *)node, "l") != NULL) { + num = json_find_number(node, "l", cause); + if (num == NULL) goto fail; - last = num; + last = *num; } - if ((field = json_find(node, "z")) != NULL) { - if (json_get_number(field, &num) != 0) + if (json_find((struct json_node *)node, "z") != NULL) { + num = json_find_number(node, "z", cause); + if (num == NULL) goto fail; - zindex = num; + zindex = *num; lc->flags |= LAYOUT_CELL_FLOATING; } else zindex = INT_MAX; @@ -831,12 +819,12 @@ layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent, layout_parse_add_cctx(pctx, lc, active, last, id, index, zindex); } else { - if ((field = json_find(node, "c")) == NULL) + if ((array = json_find_array(node, "c", cause)) == NULL) goto fail; - if (json_get_array(field, &array) != 0) - goto fail; - if ((member = json_array_first(array)) == NULL) + if ((member = json_array_first(array)) == NULL) { + *cause = xstrdup("nodes must have children"); goto fail; + } while (member != NULL) { lcchild = layout_parse_json_layout(member, lc, pctx); diff --git a/tmux.h b/tmux.h index d9e93e5fe..448bef335 100644 --- a/tmux.h +++ b/tmux.h @@ -4278,8 +4278,8 @@ void hyperlinks_free(struct hyperlinks *); struct json_node *json_parse(const char *, char **); void json_destroy_node(struct json_node *); struct json_node *json_find(struct json_node *, const char *); -struct json_node *json_array_first(struct json_node *); -struct json_node *json_array_next(struct json_node *); +struct json_node *json_array_first(const struct json_node *); +struct json_node *json_array_next(const struct json_node *); int json_get_string(struct json_node *, const char **); int json_get_number(struct json_node *, int64_t *); int json_get_boolean(struct json_node *, int *); @@ -4287,5 +4287,15 @@ int json_get_object(struct json_node *, struct json_node **); int json_get_array(struct json_node *, struct json_node **); +const char *json_find_string(const struct json_node *, + const char *, char **); +const int64_t *json_find_number(const struct json_node *, + const char *, char **); +const int *json_find_boolean(const struct json_node *, + const char *, char **); +const struct json_node *json_find_object(const struct json_node *, + const char *, char **); +const struct json_node *json_find_array(const struct json_node *, + const char *, char **); #endif /* TMUX_H */