#include "config.h" #include #include #include #include #include #include /* If they set a filter, we keep it in a tree. */ struct json_filter { /* We accumulate errors: if they treat an array as an object */ bool misused; /* Pointer to parent, or NULL at top. */ struct json_filter *parent; /* Tracks how far we are into filter, e.g. * if they specify "peers.foo" and we're * in "peer.foo.bar" depth will be 1. */ size_t depth; /* If we're in "peer.bar", we're negative */ bool positive; /* If this is an array */ struct json_filter *filter_array; /* Otherwise, object: one per keyword */ STRMAP(struct json_filter *) filter_map; }; /* Returns true if we should print this member: this is a shortcut for: * * json_filter_down(filter, member); * ret = json_filter_ok(filter, NULL); * json_filter_up(filter); * */ bool json_filter_ok(const struct json_filter *filter, const char *member) { if (!filter) return true; if (filter->depth > 0 || !member) return filter->positive; return strmap_get(&filter->filter_map, member) != NULL; } /* Returns true if we should print this new obj/array */ bool json_filter_down(struct json_filter **filter, const char *member) { struct json_filter *child; if (!*filter) return true; if ((*filter)->depth > 0) { (*filter)->depth++; return (*filter)->positive; } /* If we're a leaf node: all true. */ if (!(*filter)->filter_array && strmap_empty(&(*filter)->filter_map)) { assert((*filter)->positive); (*filter)->depth = 1; return true; } /* Array? */ if (!member) { if (!(*filter)->filter_array) { (*filter)->misused = true; goto fail; } child = (*filter)->filter_array; } else { if ((*filter)->filter_array) { (*filter)->misused = true; goto fail; } child = strmap_get(&(*filter)->filter_map, member); } if (child) { /* Should have been cleaned up last time. */ assert(child->depth == 0); /* We only have positive filters natively. */ assert(child->positive == true); *filter = child; return true; } /* OK, this path wasn't specified. */ fail: (*filter)->positive = false; (*filter)->depth = 1; return false; } /* Returns true if we were printing (i.e. close object/arr) */ bool json_filter_up(struct json_filter **filter) { if (!*filter) return true; if ((*filter)->depth == 0) { assert((*filter)->parent); assert((*filter)->parent->depth == 0); /* Reset for next time */ (*filter)->positive = true; *filter = (*filter)->parent; return true; } (*filter)->depth--; return (*filter)->positive; } static void destroy_json_filter(struct json_filter *filter) { strmap_clear(&filter->filter_map); } struct json_filter *json_filter_new(const tal_t *ctx) { struct json_filter *filter = tal(ctx, struct json_filter); filter->misused = false; filter->parent = NULL; filter->depth = 0; filter->positive = true; filter->filter_array = NULL; strmap_init(&filter->filter_map); tal_add_destructor(filter, destroy_json_filter); return filter; } struct json_filter *json_filter_subobj(struct json_filter *filter, const char *fieldname, size_t fieldnamelen) { struct json_filter *subfilter = json_filter_new(filter); subfilter->parent = filter; strmap_add(&filter->filter_map, tal_strndup(filter, fieldname, fieldnamelen), subfilter); return subfilter; } struct json_filter *json_filter_subarr(struct json_filter *filter) { struct json_filter *subfilter = json_filter_new(filter); subfilter->parent = filter; filter->filter_array = subfilter; return subfilter; } bool json_filter_finished(const struct json_filter *filter) { return !filter->parent && filter->depth == 0; } static bool strmap_filter_misused(const char *member, struct json_filter *filter, const char **ret) { *ret = json_filter_misused(tmpctx, filter); if (*ret == NULL) return true; /* If there was a problem, prepend member and stop iterating */ *ret = tal_fmt(tmpctx, ".%s%s", member, *ret); return false; } const char *json_filter_misused(const tal_t *ctx, const struct json_filter *f) { const char *ret; if (f->misused) { if (f->filter_array) return tal_fmt(ctx, " is an object"); else return tal_fmt(ctx, " is an array"); } if (f->filter_array) { ret = json_filter_misused(tmpctx, f->filter_array); if (ret) return tal_fmt(ctx, "[]%s", ret); return NULL; } else { ret = NULL; strmap_iterate(&f->filter_map, strmap_filter_misused, &ret); return tal_steal(ctx, ret); } } /* Recursively populate filter. NULL on success. * * Example for listtransactions to include output type, amount_msat, * {"transactions": [{"outputs": [{"amount_msat": true, "type": true}]}]} */ static struct command_result * build_filter(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, struct json_filter *filter) { struct command_result *ret; size_t i; const jsmntok_t *t; struct json_filter *subf; if (tok->type == JSMN_ARRAY) { if (tok->size != 1) return command_fail_badparam(cmd, name, buffer, tok, "Arrays can only have one element"); subf = json_filter_subarr(filter); return build_filter(cmd, name, buffer, tok + 1, subf); } json_for_each_obj(i, t, tok) { bool is_true; const jsmntok_t *val = t + 1; if (t->type != JSMN_STRING) return command_fail_badparam(cmd, name, buffer, t, "expected string key"); subf = json_filter_subobj(filter, buffer + t->start, t->end - t->start); if (val->type == JSMN_OBJECT || val->type == JSMN_ARRAY) { ret = build_filter(cmd, name, buffer, val, subf); if (ret) return ret; } else if (!json_to_bool(buffer, val, &is_true) || !is_true) return command_fail_badparam(cmd, name, buffer, val, "value must be true"); } return NULL; } struct command_result *parse_filter(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok) { struct json_filter **filter = command_filter_ptr(cmd); if (tok->type != JSMN_OBJECT) return command_fail_badparam(cmd, name, buffer, tok, "Expected object"); *filter = json_filter_new(cmd); return build_filter(cmd, name, buffer, tok, *filter); }