diff options
| author | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 04:51:16 +0100 |
| commit | adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81 (patch) | |
| tree | c57cb746653c3bbb9eff35d509e7b71c353b9301 /serve | |
| parent | 9542bf3faa27373076cb4b31dac01c34570962fb (diff) | |
| download | pure-adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81.tar.gz pure-adc4e5724c926cac4ecb8dc3a58a5f9b4d1b9b81.zip | |
Take a view's arguments from the request's query
GET /views/name?argument=value gives a view that takes arguments its
arguments: the query as it came is kept on the request's work slot (it
outlives the connection, which may go before the view is shown), and when
the view is shown, on the connection thread from a snapshot or by the
owner, its pairs (values %XX decoded) are laid out as a payload of the
arguments' shape and given before the view is evaluated. from and count
still page a table. An argument missing, unknown, given twice, not of its
type or badly escaped answers 400 invalid. A registered query takes its
view's arguments the same way (GET /queries/name?k=4). An arguments input
is not an input to post to.
server_arguments opens 200 accounts, then asks 83 times against a model:
balances by key (404 no_value for a key not there), counts above an
argument, texts with &, =, % and non-ASCII shown back, a table filtered by
two arguments whose column folds with one, paged, counts by an owner's
name, a registered query with an argument, and six refusals. The format's
document (008) describes arguments.
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/serve.c | 125 |
1 files changed, 115 insertions, 10 deletions
diff --git a/serve/serve.c b/serve/serve.c index 7fb3768..b9b526b 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -70,7 +70,9 @@ typedef struct Host Host; * an input. Kept per work slot, so an accepted event runs even if its * connection is gone before the burst. */ typedef enum { ROUTE_NONE, ROUTE_OBJECT, ROUTE_VIEW, ROUTE_INPUT, ROUTE_QUERY } RouteKind; -typedef struct { RouteKind kind; uint32_t target; size_t from, count; } Route; +/* A view's (or a query's) arguments come as the request's query: argued + * says so, and given is its text, kept on the request's work slot. */ +typedef struct { RouteKind kind; uint32_t target; size_t from, count; bool argued; const char *given; } Route; /* This node's key, for deliveries sealed to it (--key), and whether it * takes only those (--sealed-only), or only those from the senders named, @@ -223,6 +225,7 @@ struct Host { size_t views_capacity; uint64_t shown; /* views shown from snapshots */ Route *routes; /* by work slot */ + struct Given { char *text; size_t capacity; } *given; /* a view's arguments as asked, by work slot */ PureRuntime runtime; PureWork *work; Connection **table; /* open connections by index */ @@ -620,9 +623,10 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * uint64_t n; if (!value || (end && value > end)) return "query"; *value++ = 0; - bool from = !strcmp(pair, "from"); - if (!number(value, SIZE_MAX, &n) || (!from && strcmp(pair, "count"))) return "query"; - if (from) route->from = (size_t)n; + bool from = !strcmp(pair, "from"), paging = from || !strcmp(pair, "count"); + if (paging && !number(value, SIZE_MAX, &n)) return "query"; + if (!paging) route->argued = true; /* the query's view reads it, if it takes arguments */ + else if (from) route->from = (size_t)n; else route->count = (size_t)n; pair = end ? end + 1 : NULL; } @@ -631,7 +635,8 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * int found = route->kind == ROUTE_VIEW ? pure_module_view(m, c->key) : pure_module_input(m, c->key); /* An output is the app's to send and a timer ticks by itself: neither is posted. */ if (found >= 0 && route->kind == ROUTE_INPUT && - (m->input[found].kind == PURE_KIND_OUTPUT || m->input[found].kind == PURE_KIND_TIMER)) found = -1; + (m->input[found].kind == PURE_KIND_OUTPUT || m->input[found].kind == PURE_KIND_TIMER || + m->input[found].kind == PURE_KIND_ARGUMENTS)) found = -1; if (found < 0) return route->kind == ROUTE_VIEW ? "no_view" : "no_input"; *status = 400; route->target = (uint32_t)found; @@ -665,10 +670,10 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route * *value++ = 0; if (route->kind == ROUTE_VIEW) { uint64_t n; - bool from = !strcmp(pair, "from"); - if (!number(value, SIZE_MAX, &n) || m->view_type[found] != PURE_VIEW_TABLE || (!from && strcmp(pair, "count"))) - return "query"; - if (from) route->from = (size_t)n; + bool from = !strcmp(pair, "from"), paging = from || !strcmp(pair, "count"); + if (!paging && m->view_arguments[found]) route->argued = true; /* an argument, read when shown */ + else if (!number(value, SIZE_MAX, &n) || m->view_type[found] != PURE_VIEW_TABLE || !paging) return "query"; + else if (from) route->from = (size_t)n; else route->count = (size_t)n; } else { uint32_t f = 0; @@ -818,6 +823,10 @@ static void start_request(Connection *c, size_t head_size) { memmove(c->in, c->in + head_size, extra); c->in_used = extra; if (!parse_head(c, head, head_size, &query)) { refuse(c, 400, "Bad Request", "request"); return; } + /* The query as it came: resolving takes it apart, and a view's arguments are read from it when shown. */ + char raw[HEADER_LIMIT]; + size_t raw_size = query ? strlen(query) : 0; + if (raw_size) memcpy(raw, query, raw_size + 1); /* A node that keeps its deliveries sealed refuses the others, and one * that names its senders refuses any other sender; a seal needs this * node's key, and binds the delivery's source, number and count. */ @@ -852,6 +861,17 @@ static void start_request(Connection *c, size_t head_size) { return; } host->by_slot[c->ticket.slot] = c; + if (route.argued) { + struct Given *given = &host->given[c->ticket.slot]; + if (given->capacity <= raw_size) { + char *grown = realloc(given->text, raw_size + 1); + if (!grown) abort(); + given->text = grown; + given->capacity = raw_size + 1; + } + memcpy(given->text, raw, raw_size + 1); + route.given = given->text; + } host->routes[c->ticket.slot] = route; if (!write) { c->phase = WAITING; return; } unsigned char *ingress = c->ingress = pure_runtime_ingress(c->ticket, NULL); /* NULL for no bytes */ @@ -1068,7 +1088,89 @@ static void put_number(Output *o, double value) { /* A view's value: a number in Pure's shortest form or a text, as text; a * table as {"rows":N,"from":F,"items":[{column: value}...]} for the rows * asked, each cell computed only for them (a cell without a value is null). */ +static int hex_value(char c) { + return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; +} + +/* %XX decoded in place (never longer): the length after, or -1 for a bad escape. */ +static long percent_decoded(char *text, size_t length) { + size_t o = 0; + for (size_t i = 0; i < length; ++i) { + if (text[i] != '%') { text[o++] = text[i]; continue; } + int high = i + 2 < length ? hex_value(text[i + 1]) : -1, low = high >= 0 ? hex_value(text[i + 2]) : -1; + if (low < 0) return -1; + text[o++] = (char)(high << 4 | low); + i += 2; + } + return (long)o; +} + +/* A view's arguments from a query's name=value pairs (values %XX decoded; + * from and count page a table): a payload of their input's shape, the + * fixed fields in its prefix, the texts and bytes after it in order, each's + * length in the prefix but the last's. NULL for a pair naming no field or + * given twice, a fixed field missing or not of its type, or no memory. */ +static unsigned char *arguments_payload(const PureModule *m, int input, const char *given, size_t *size) { + const PureInputShape *shape = &m->input[input]; + size_t length = given ? strlen(given) : 0; + char *copy = malloc(length + 1); + unsigned char *payload = malloc(shape->prefix + length + 1); + const char *values[PURE_MODULE_FIELDS] = {0}; + long lengths[PURE_MODULE_FIELDS] = {0}; + bool ok = copy && payload; + if (ok) { + memcpy(copy, given ? given : "", length + 1); + memset(payload, 0, shape->prefix); + } + for (char *pair = copy; ok && pair && *pair;) { + char *end = strchr(pair, '&'), *value = strchr(pair, '='); + if (end) *end = 0; + if (!value || (end && value > end)) { ok = false; break; } + *value++ = 0; + uint32_t f = 0; + while (f < shape->field_count && strcmp(m->field[shape->field_start + f].name, pair)) ++f; + if (f == shape->field_count) ok = !strcmp(pair, "from") || !strcmp(pair, "count"); + else if (values[f]) ok = false; + else if ((lengths[f] = percent_decoded(value, strlen(value))) < 0) ok = false; + else values[f] = value; + pair = end ? end + 1 : NULL; + } + size_t at = shape->prefix; + for (uint32_t f = 0; ok && f < shape->field_count; ++f) { + const PureField *field = &m->field[shape->field_start + f]; + if (field->type == PURE_FIELD_U32 || field->type == PURE_FIELD_NUMBER) { + ok = values[f] && pure_json_fixed(field, (const unsigned char *)values[f], (size_t)lengths[f], payload); + continue; + } + if (field->offset != PURE_FIELD_REST) put_u32(payload + field->offset, (uint32_t)lengths[f]); + if (lengths[f]) memcpy(payload + at, values[f], (size_t)lengths[f]); + at += (size_t)lengths[f]; + } + free(copy); + if (!ok) { + free(payload); + return NULL; + } + *size = at; + return payload; +} + +static PureResult show_given(PureApp *app, const Route *route, size_t available, Applied *answer); + +/* A view shown with its arguments, when it takes some (they are the request's to give). */ static PureResult show_view(PureApp *app, const Route *route, size_t available, Applied *answer) { + int input = pure_module_arguments(app->module, route->target); + if (input < 0) return route->argued ? PURE_INVALID : show_given(app, route, available, answer); + size_t size; + unsigned char *payload = arguments_payload(app->module, input, route->given, &size); + if (!payload) return PURE_INVALID; + PureResult given = pure_app_arguments(app, route->target, (PureSpan){payload, size}, NULL); + PureResult status = given == PURE_OK ? show_given(app, route, available, answer) : PURE_INVALID; + free(payload); + return status; +} + +static PureResult show_given(PureApp *app, const Route *route, size_t available, Applied *answer) { const PureModule *m = app->module; uint32_t view = route->target; PureResource *out = &answer->resource; @@ -1952,7 +2054,8 @@ static bool start_shard(Host *host, const PureModule *module, bool documents, ui host->by_slot = calloc(host->capacity, sizeof(*host->by_slot)); host->jobs = calloc(host->capacity, sizeof(*host->jobs)); host->routes = calloc(host->capacity, sizeof(*host->routes)); - if (!host->work || !host->table || !host->by_slot || !host->jobs || !host->routes) { + host->given = calloc(host->capacity, sizeof(*host->given)); + if (!host->work || !host->table || !host->by_slot || !host->jobs || !host->routes || !host->given) { fputs("Cannot allocate connections\n", stderr); return false; } @@ -2058,6 +2161,8 @@ static void stop_shard(Host *host) { free(host->by_slot); free(host->jobs); free(host->routes); + for (size_t i = 0; host->given && i < host->capacity; ++i) free(host->given[i].text); + free(host->given); free(host->done); free(host->answering); free(host->views); |
