commit 45c909599635c6365456c2b57beae193a2d66b68
parent dbb36807a4481ac1e804b81b88e737e032e9d590
Author: Robin Bron <robin.bron@yourhosting.nl>
Date: Fri, 6 Mar 2026 21:39:39 +0100
Fix handling incomplete input
Diffstat:
4 files changed, 71 insertions(+), 34 deletions(-)
diff --git a/Makefile b/Makefile
@@ -7,7 +7,7 @@ SRC:=
# UNAME_SYSTEM=$(call lc,$(shell uname -s))
BIN?=udphole
-VERSION?=1.3.8
+VERSION?=1.3.9
CC:=gcc
CPP:=g++
diff --git a/src/common/resp.c b/src/common/resp.c
@@ -39,22 +39,27 @@ static int resp_read_line_from_buf(const char **buf, size_t *len, char *out, siz
return -1;
}
-resp_object *resp_read_buf(const char *buf, size_t len) {
- const char *p = buf;
- size_t remaining = len;
+int resp_read_buf(const char *buf, size_t len, resp_object **out_obj) {
+ if (!out_obj) return -1;
+
+ const char *start = buf;
+ const char *p = buf;
+ size_t remaining = len;
int type_c = resp_read_byte_from_buf(&p, &remaining);
- if (type_c < 0) return NULL;
- if (type_c == -2) return NULL;
+ if (type_c < 0) return 0; // no data yet
+ if (type_c == -2) return 0; // no data yet
+
resp_object *o = calloc(1, sizeof(resp_object));
- if (!o) return NULL;
+ if (!o) return -1;
+
char line[LINE_BUF];
switch ((char)type_c) {
case '+':
o->type = RESPT_SIMPLE;
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o);
- return NULL;
+ return -1;
}
o->u.s = strdup(line);
break;
@@ -62,7 +67,7 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
o->type = RESPT_ERROR;
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o);
- return NULL;
+ return -1;
}
o->u.s = strdup(line);
break;
@@ -70,7 +75,7 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
{
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o);
- return NULL;
+ return -1;
}
o->type = RESPT_INT;
o->u.i = (long long)strtoll(line, NULL, 10);
@@ -80,12 +85,12 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
{
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o);
- return NULL;
+ return -1;
}
long blen = strtol(line, NULL, 10);
if (blen < 0 || blen > (long)MAX_BULK_LEN) {
free(o);
- return NULL;
+ return -1;
}
o->type = RESPT_BULK;
if (blen == 0) {
@@ -93,17 +98,17 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o->u.s);
free(o);
- return NULL;
+ return -1;
}
} else {
if ((size_t)blen > remaining) {
free(o);
- return NULL;
+ return -1;
}
o->u.s = malloc((size_t)blen + 1);
if (!o->u.s) {
free(o);
- return NULL;
+ return -1;
}
memcpy(o->u.s, p, (size_t)blen);
p += blen;
@@ -112,12 +117,12 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
if (remaining < 2) {
free(o->u.s);
free(o);
- return NULL;
+ return -1;
}
if (p[0] != '\r' || p[1] != '\n') {
free(o->u.s);
free(o);
- return NULL;
+ return -1;
}
p += 2;
remaining -= 2;
@@ -128,30 +133,31 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
{
if (resp_read_line_from_buf(&p, &remaining, line, sizeof(line)) != 0) {
free(o);
- return NULL;
+ return -1;
}
long n = strtol(line, NULL, 10);
if (n < 0 || n > 65536) {
free(o);
- return NULL;
+ return -1;
}
o->type = RESPT_ARRAY;
o->u.arr.n = (size_t)n;
o->u.arr.elem = n ? calloc((size_t)n, sizeof(resp_object)) : NULL;
if (n && !o->u.arr.elem) {
free(o);
- return NULL;
+ return -1;
}
for (size_t i = 0; i < (size_t)n; i++) {
- resp_object *sub = resp_read_buf(p, remaining);
- if (!sub) {
+ resp_object *sub = NULL;
+ int consumed = resp_read_buf(p, remaining, &sub);
+ if (consumed <= 0) {
for (size_t j = 0; j < i; j++) resp_free_internal(&o->u.arr.elem[j]);
free(o->u.arr.elem);
free(o);
- return NULL;
+ return -1;
}
- p += remaining - (sub ? remaining : 0);
- remaining = 0;
+ p += consumed;
+ remaining -= (size_t)consumed;
o->u.arr.elem[i] = *sub;
free(sub);
}
@@ -159,9 +165,11 @@ resp_object *resp_read_buf(const char *buf, size_t len) {
}
default:
free(o);
- return NULL;
+ return -1;
}
- return o;
+
+ *out_obj = o;
+ return (int)(p - start);
}
static int resp_read_byte(int fd) {
diff --git a/src/common/resp.h b/src/common/resp.h
@@ -40,8 +40,11 @@ void resp_map_set(resp_object *map, const char *key, resp_object *value);
resp_object *resp_read(int fd);
/* Returns new object: caller owns the result, must call resp_free() */
-resp_object *resp_read_buf(const char *buf, size_t len);
-/* Returns new object: caller owns the result, must call resp_free() */
+int resp_read_buf(const char *buf, size_t len, resp_object **out_obj);
+/* Returns 0=no data yet, <0=incomplete (need more data), >0=bytes consumed from buffer */
+/* If out_obj is NULL, returns -1 */
+/* If *out_obj is NULL, creates new object */
+/* If *out_obj exists, appends to it (for arrays) */
int resp_encode_array(int argc, const resp_object *const *argv, char **out_buf, size_t *out_len);
/* Returns allocated string in out_buf: caller must free() the string */
diff --git a/src/interface/api/server.c b/src/interface/api/server.c
@@ -46,6 +46,7 @@ struct api_client_state {
char *username;
char rbuf[READ_BUF_SIZE];
size_t rlen;
+ resp_object *partial_obj;
char *wbuf;
size_t wlen;
size_t wcap;
@@ -603,19 +604,43 @@ int api_client_pt(int64_t timestamp, struct pt_task *task) {
return SCHED_RUNNING;
}
- char buf[1];
- ssize_t n = recv(state->fd, buf, 1, MSG_PEEK);
- if (n <= 0) {
+ // Read data into receive buffer
+ if (state->rlen >= READ_BUF_SIZE) {
+ // Buffer full, protocol error
goto cleanup;
}
- resp_object *cmd = resp_read(state->fd);
- if (!cmd) {
+ ssize_t n = recv(state->fd, state->rbuf + state->rlen, READ_BUF_SIZE - state->rlen, 0);
+ if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return SCHED_RUNNING;
}
goto cleanup;
}
+ if (n == 0) {
+ goto cleanup;
+ }
+ state->rlen += (size_t)n;
+
+ // Try to parse RESP object from buffer
+ resp_object *cmd = NULL;
+ int consumed = resp_read_buf(state->rbuf, state->rlen, &cmd);
+ if (consumed > 0) {
+ // Successfully parsed - consume bytes from buffer
+ memmove(state->rbuf, state->rbuf + consumed, state->rlen - consumed);
+ state->rlen -= consumed;
+ state->partial_obj = NULL;
+ } else if (consumed < 0) {
+ // Incomplete - need more data
+ return SCHED_RUNNING;
+ } else {
+ // No data - shouldn't happen, but continue
+ return SCHED_RUNNING;
+ }
+
+ if (!cmd) {
+ return SCHED_RUNNING;
+ }
if (cmd->type != RESPT_ARRAY || cmd->u.arr.n == 0) {
resp_free(cmd);
@@ -660,6 +685,7 @@ cleanup:
free(state->fds);
free(state->wbuf);
free(state->username);
+ resp_free(state->partial_obj);
free(state);
return SCHED_DONE;
}