diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8316992..90ef5d9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,8 +24,8 @@ target_link_libraries(amduat_federation
PRIVATE amduat_asl amduat_enc amduat_util amduat_fed
)
-set(amduatd_sources src/amduatd.c src/amduatd_caps.c src/amduatd_space.c
- src/amduatd_concepts.c)
+set(amduatd_sources src/amduatd.c src/amduatd_http.c src/amduatd_caps.c
+ src/amduatd_space.c src/amduatd_concepts.c)
if(AMDUATD_ENABLE_UI)
list(APPEND amduatd_sources src/amduatd_ui.c)
endif()
diff --git a/src/amduatd.c b/src/amduatd.c
index 0df8103..2e9a1cb 100644
--- a/src/amduatd.c
+++ b/src/amduatd.c
@@ -34,6 +34,7 @@
#include "amduat/util/log.h"
#include "amduat/hash/asl1.h"
#include "amduatd_concepts.h"
+#include "amduatd_http.h"
#include "amduatd_ui.h"
#include "amduatd_caps.h"
#include "amduatd_space.h"
@@ -64,17 +65,6 @@ typedef struct amduatd_strbuf {
size_t cap;
} amduatd_strbuf_t;
-bool amduatd_http_send_json(int fd,
- int code,
- const char *reason,
- const char *json,
- bool head_only);
-
-bool amduatd_send_json_error(int fd,
- int code,
- const char *reason,
- const char *msg);
-
amduatd_ref_status_t amduatd_decode_ref_or_name_latest(
amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg,
@@ -282,97 +272,6 @@ static void amduatd_on_signal(int signo) {
amduatd_should_exit = 1;
}
-bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) {
- size_t off = 0;
- while (off < len) {
- ssize_t n = write(fd, buf + off, len - off);
- if (n < 0) {
- if (errno == EINTR) {
- continue;
- }
- return false;
- }
- if (n == 0) {
- return false;
- }
- off += (size_t)n;
- }
- return true;
-}
-
-bool amduatd_read_exact(int fd, uint8_t *buf, size_t len);
-
-bool amduatd_read_urandom(uint8_t *out, size_t len) {
- int fd;
- if (out == NULL || len == 0) {
- return false;
- }
- fd = open("/dev/urandom", O_RDONLY);
- if (fd < 0) {
- return false;
- }
- if (!amduatd_read_exact(fd, out, len)) {
- close(fd);
- return false;
- }
- close(fd);
- return true;
-}
-
-bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) {
- size_t off = 0;
- while (off < len) {
- ssize_t n = read(fd, buf + off, len - off);
- if (n < 0) {
- if (errno == EINTR) {
- continue;
- }
- return false;
- }
- if (n == 0) {
- return false;
- }
- off += (size_t)n;
- }
- return true;
-}
-
-static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) {
- size_t len = 0;
- while (len + 1 < cap) {
- char c = 0;
- ssize_t n = read(fd, &c, 1);
- if (n < 0) {
- if (errno == EINTR) {
- continue;
- }
- return false;
- }
- if (n == 0) {
- return false;
- }
- if (c == '\n') {
- break;
- }
- buf[len++] = c;
- }
- if (len == 0) {
- return false;
- }
- if (len > 0 && buf[len - 1] == '\r') {
- len--;
- }
- buf[len] = '\0';
- if (out_len != NULL) {
- *out_len = len;
- }
- return true;
-}
-
-static void amduatd_http_req_init(amduatd_http_req_t *req) {
- memset(req, 0, sizeof(*req));
-}
-
typedef struct {
uid_t *uids;
size_t len;
@@ -493,258 +392,6 @@ static bool amduatd_get_peer_actor(int client_fd,
return true;
}
-static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) {
- char line[2048];
- size_t line_len = 0;
- char *sp1 = NULL;
- char *sp2 = NULL;
-
- if (out_req == NULL) {
- return false;
- }
- amduatd_http_req_init(out_req);
-
- if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
- return false;
- }
-
- sp1 = strchr(line, ' ');
- if (sp1 == NULL) {
- return false;
- }
- *sp1++ = '\0';
- sp2 = strchr(sp1, ' ');
- if (sp2 == NULL) {
- return false;
- }
- *sp2++ = '\0';
-
- if (strlen(line) >= sizeof(out_req->method)) {
- return false;
- }
- strncpy(out_req->method, line, sizeof(out_req->method) - 1);
-
- if (strlen(sp1) >= sizeof(out_req->path)) {
- return false;
- }
- strncpy(out_req->path, sp1, sizeof(out_req->path) - 1);
-
- for (;;) {
- if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
- return false;
- }
- if (line_len == 0) {
- break;
- }
-
- if (strncasecmp(line, "Content-Length:", 15) == 0) {
- const char *v = line + 15;
- while (*v == ' ' || *v == '\t') {
- v++;
- }
- out_req->content_length = (size_t)strtoull(v, NULL, 10);
- } else if (strncasecmp(line, "Content-Type:", 13) == 0) {
- const char *v = line + 13;
- while (*v == ' ' || *v == '\t') {
- v++;
- }
- strncpy(out_req->content_type, v, sizeof(out_req->content_type) - 1);
- } else if (strncasecmp(line, "Accept:", 7) == 0) {
- const char *v = line + 7;
- while (*v == ' ' || *v == '\t') {
- v++;
- }
- strncpy(out_req->accept, v, sizeof(out_req->accept) - 1);
- } else if (strncasecmp(line, "X-Amduat-Type-Tag:", 18) == 0) {
- const char *v = line + 18;
- while (*v == ' ' || *v == '\t') {
- v++;
- }
- strncpy(out_req->x_type_tag, v, sizeof(out_req->x_type_tag) - 1);
- } else if (strncasecmp(line, "X-Amduat-Capability:", 20) == 0) {
- const char *v = line + 20;
- while (*v == ' ' || *v == '\t') {
- v++;
- }
- strncpy(out_req->x_capability, v, sizeof(out_req->x_capability) - 1);
- }
- }
-
- return true;
-}
-
-bool amduatd_http_send_status(int fd,
- int code,
- const char *reason,
- const char *content_type,
- const uint8_t *body,
- size_t body_len,
- bool head_only) {
- char hdr[512];
- int n = snprintf(hdr,
- sizeof(hdr),
- "HTTP/1.1 %d %s\r\n"
- "Content-Length: %zu\r\n"
- "Content-Type: %s\r\n"
- "Connection: close\r\n"
- "\r\n",
- code,
- reason != NULL ? reason : "",
- body_len,
- content_type != NULL ? content_type : "text/plain");
- if (n <= 0 || (size_t)n >= sizeof(hdr)) {
- return false;
- }
- if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) {
- return false;
- }
- if (!head_only && body != NULL && body_len != 0) {
- return amduatd_write_all(fd, body, body_len);
- }
- return true;
-}
-
-bool amduatd_http_send_text(int fd,
- int code,
- const char *reason,
- const char *text,
- bool head_only) {
- const uint8_t *body = (const uint8_t *)(text != NULL ? text : "");
- size_t len = text != NULL ? strlen(text) : 0;
- return amduatd_http_send_status(
- fd, code, reason, "text/plain; charset=utf-8", body, len, head_only);
-}
-
-bool amduatd_http_send_json(int fd,
- int code,
- const char *reason,
- const char *json,
- bool head_only) {
- const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}");
- size_t len = json != NULL ? strlen(json) : 2;
- return amduatd_http_send_status(
- fd, code, reason, "application/json", body, len, head_only);
-}
-
-static bool amduatd_http_req_wants_html(const amduatd_http_req_t *req) {
- if (req == NULL) {
- return false;
- }
- if (req->accept[0] == '\0') {
- return false;
- }
- return strstr(req->accept, "text/html") != NULL;
-}
-
-static bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req) {
- if (amduatd_http_req_wants_html(req)) {
- const char *path = (req != NULL && req->path[0] != '\0') ? req->path : "/";
- const char *method =
- (req != NULL && req->method[0] != '\0') ? req->method : "GET";
- char html[4096];
- int n = snprintf(
- html,
- sizeof(html),
- "\n"
- "\n"
- "
\n"
- " \n"
- " \n"
- " amduatd — Not Found\n"
- " \n"
- "\n"
- "\n"
- " \n"
- "
\n"
- "
404 — Not Found
\n"
- "
amduatd didn’t recognize %s %s.
\n"
- "
Try one of these:
\n"
- "
\n"
- "
Artifact bytes: /v1/artifacts/<ref>
\n"
- "
\n"
- "
\n"
- "\n"
- "\n",
- method,
- path);
- if (n <= 0 || (size_t)n >= sizeof(html)) {
- return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
- }
- return amduatd_http_send_status(fd,
- 404,
- "Not Found",
- "text/html; charset=utf-8",
- (const uint8_t *)html,
- (size_t)n,
- false);
- }
- return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
-}
-
-const char *amduatd_query_param(const char *path,
- const char *key,
- char *out_value,
- size_t out_cap) {
- const char *q = strchr(path, '?');
- size_t key_len = 0;
- if (out_value != NULL && out_cap != 0) {
- out_value[0] = '\0';
- }
- if (q == NULL || key == NULL || out_value == NULL || out_cap == 0) {
- return NULL;
- }
- q++;
- key_len = strlen(key);
-
- while (*q != '\0') {
- const char *k = q;
- const char *eq = strchr(k, '=');
- const char *amp = strchr(k, '&');
- size_t klen = 0;
- const char *v = NULL;
- size_t vlen = 0;
-
- if (amp == NULL) {
- amp = q + strlen(q);
- }
- if (eq == NULL || eq > amp) {
- q = (*amp == '&') ? (amp + 1) : amp;
- continue;
- }
- klen = (size_t)(eq - k);
- if (klen == key_len && strncmp(k, key, key_len) == 0) {
- v = eq + 1;
- vlen = (size_t)(amp - v);
- if (vlen >= out_cap) {
- vlen = out_cap - 1;
- }
- memcpy(out_value, v, vlen);
- out_value[vlen] = '\0';
- return out_value;
- }
- q = (*amp == '&') ? (amp + 1) : amp;
- }
-
- return NULL;
-}
-
static void amduatd_strbuf_free(amduatd_strbuf_t *b) {
if (b == NULL) {
return;
@@ -816,73 +463,6 @@ static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c) {
return amduatd_strbuf_append(b, &c, 1u);
}
-const char *amduatd_json_skip_ws(const char *p, const char *end) {
- while (p < end) {
- char c = *p;
- if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
- break;
- }
- p++;
- }
- return p;
-}
-
-bool amduatd_json_expect(const char **p,
- const char *end,
- char expected) {
- const char *cur;
- if (p == NULL || *p == NULL) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end || *cur != expected) {
- return false;
- }
- *p = cur + 1;
- return true;
-}
-
-bool amduatd_json_parse_string_noesc(const char **p,
- const char *end,
- const char **out_start,
- size_t *out_len) {
- const char *cur;
- const char *s;
- if (out_start != NULL) {
- *out_start = NULL;
- }
- if (out_len != NULL) {
- *out_len = 0;
- }
- if (p == NULL || *p == NULL) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end || *cur != '"') {
- return false;
- }
- cur++;
- s = cur;
- while (cur < end) {
- unsigned char c = (unsigned char)*cur;
- if (c == '"') {
- if (out_start != NULL) {
- *out_start = s;
- }
- if (out_len != NULL) {
- *out_len = (size_t)(cur - s);
- }
- *p = cur + 1;
- return true;
- }
- if (c == '\\' || c < 0x20u) {
- return false;
- }
- cur++;
- }
- return false;
-}
-
static bool amduatd_json_parse_u32(const char **p,
const char *end,
uint32_t *out) {
@@ -930,53 +510,6 @@ static bool amduatd_json_parse_u32(const char **p,
return true;
}
-bool amduatd_json_parse_u64(const char **p,
- const char *end,
- uint64_t *out) {
- const char *cur;
- const char *start;
- unsigned long long v;
- char *tmp = NULL;
- char *endp = NULL;
- size_t n;
-
- if (out != NULL) {
- *out = 0;
- }
- if (p == NULL || *p == NULL || out == NULL) {
- return false;
- }
-
- cur = amduatd_json_skip_ws(*p, end);
- start = cur;
- if (cur >= end) {
- return false;
- }
- if (*cur < '0' || *cur > '9') {
- return false;
- }
- cur++;
- while (cur < end && *cur >= '0' && *cur <= '9') {
- cur++;
- }
- n = (size_t)(cur - start);
- tmp = (char *)malloc(n + 1u);
- if (tmp == NULL) {
- return false;
- }
- memcpy(tmp, start, n);
- tmp[n] = '\0';
- errno = 0;
- v = strtoull(tmp, &endp, 10);
- free(tmp);
- if (errno != 0 || endp == NULL || *endp != '\0') {
- return false;
- }
- *out = (uint64_t)v;
- *p = cur;
- return true;
-}
-
static bool amduatd_json_parse_u32_loose(const char **p,
const char *end,
uint32_t *out) {
@@ -1070,183 +603,6 @@ static void amduatd_json_peek_token(const char *p,
out[n] = '\0';
}
-static bool amduatd_json_skip_string(const char **p, const char *end) {
- const char *cur;
- if (p == NULL || *p == NULL) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end || *cur != '"') {
- return false;
- }
- cur++;
- while (cur < end) {
- unsigned char c = (unsigned char)*cur++;
- if (c == '"') {
- *p = cur;
- return true;
- }
- if (c == '\\') {
- if (cur >= end) {
- return false;
- }
- cur++;
- } else if (c < 0x20u) {
- return false;
- }
- }
- return false;
-}
-
-bool amduatd_json_skip_value(const char **p,
- const char *end,
- int depth);
-
-static bool amduatd_json_skip_array(const char **p,
- const char *end,
- int depth) {
- const char *cur;
- if (!amduatd_json_expect(p, end, '[')) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur < end && *cur == ']') {
- *p = cur + 1;
- return true;
- }
- for (;;) {
- if (!amduatd_json_skip_value(p, end, depth + 1)) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end) {
- return false;
- }
- if (*cur == ',') {
- *p = cur + 1;
- continue;
- }
- if (*cur == ']') {
- *p = cur + 1;
- return true;
- }
- return false;
- }
-}
-
-static bool amduatd_json_skip_object(const char **p,
- const char *end,
- int depth) {
- const char *cur;
- if (!amduatd_json_expect(p, end, '{')) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur < end && *cur == '}') {
- *p = cur + 1;
- return true;
- }
- for (;;) {
- if (!amduatd_json_skip_string(p, end)) {
- return false;
- }
- if (!amduatd_json_expect(p, end, ':')) {
- return false;
- }
- if (!amduatd_json_skip_value(p, end, depth + 1)) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end) {
- return false;
- }
- if (*cur == ',') {
- *p = cur + 1;
- continue;
- }
- if (*cur == '}') {
- *p = cur + 1;
- return true;
- }
- return false;
- }
-}
-
-bool amduatd_json_skip_value(const char **p,
- const char *end,
- int depth) {
- const char *cur;
- if (p == NULL || *p == NULL) {
- return false;
- }
- if (depth > 32) {
- return false;
- }
- cur = amduatd_json_skip_ws(*p, end);
- if (cur >= end) {
- return false;
- }
- if (*cur == '"') {
- return amduatd_json_skip_string(p, end);
- }
- if (*cur == '{') {
- return amduatd_json_skip_object(p, end, depth);
- }
- if (*cur == '[') {
- return amduatd_json_skip_array(p, end, depth);
- }
- if (*cur == 't' && (end - cur) >= 4 && memcmp(cur, "true", 4) == 0) {
- *p = cur + 4;
- return true;
- }
- if (*cur == 'f' && (end - cur) >= 5 && memcmp(cur, "false", 5) == 0) {
- *p = cur + 5;
- return true;
- }
- if (*cur == 'n' && (end - cur) >= 4 && memcmp(cur, "null", 4) == 0) {
- *p = cur + 4;
- return true;
- }
- if (*cur == '-' || (*cur >= '0' && *cur <= '9')) {
- cur++;
- while (cur < end) {
- char c = *cur;
- if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' ||
- c == '+' || c == '-') {
- cur++;
- continue;
- }
- break;
- }
- *p = cur;
- return true;
- }
- return false;
-}
-
-bool amduatd_copy_json_str(const char *s,
- size_t len,
- char **out) {
- char *buf;
- if (out == NULL) {
- return false;
- }
- *out = NULL;
- if (len > (SIZE_MAX - 1u)) {
- return false;
- }
- buf = (char *)malloc(len + 1u);
- if (buf == NULL) {
- return false;
- }
- if (len != 0) {
- memcpy(buf, s, len);
- }
- buf[len] = '\0';
- *out = buf;
- return true;
-}
-
static bool amduatd_decode_ref_hex_str(const char *s,
size_t len,
amduat_reference_t *out_ref) {
@@ -1264,47 +620,6 @@ static bool amduatd_decode_ref_hex_str(const char *s,
return ok;
}
-bool amduatd_send_json_error(int fd,
- int code,
- const char *reason,
- const char *msg) {
- amduatd_strbuf_t b;
- memset(&b, 0, sizeof(b));
- if (!amduatd_strbuf_append_cstr(&b, "{\"error\":\"") ||
- !amduatd_strbuf_append_cstr(&b, msg != NULL ? msg : "error") ||
- !amduatd_strbuf_append_cstr(&b, "\"}\n")) {
- amduatd_strbuf_free(&b);
- return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n",
- false);
- }
- {
- bool ok = amduatd_http_send_json(fd, code, reason, b.data, false);
- amduatd_strbuf_free(&b);
- return ok;
- }
-}
-
-static void amduatd_path_without_query(const char *path,
- char *out,
- size_t cap) {
- const char *q = NULL;
- size_t n = 0;
- if (out == NULL || cap == 0) {
- return;
- }
- out[0] = '\0';
- if (path == NULL) {
- return;
- }
- q = strchr(path, '?');
- n = q != NULL ? (size_t)(q - path) : strlen(path);
- if (n >= cap) {
- n = cap - 1;
- }
- memcpy(out, path, n);
- out[n] = '\0';
-}
-
static bool amduatd_parse_type_tag_hex(const char *text,
bool *out_has_type_tag,
amduat_type_tag_t *out_type_tag) {
@@ -4695,7 +4010,7 @@ static bool amduatd_handle_conn(int fd,
ok = amduatd_http_send_not_found(fd, &req);
conn_cleanup:
- free((void *)req.actor.data);
+ amduatd_http_req_free(&req);
return ok;
}
diff --git a/src/amduatd_caps.c b/src/amduatd_caps.c
index bf5da80..4f8b818 100644
--- a/src/amduatd_caps.c
+++ b/src/amduatd_caps.c
@@ -1,4 +1,5 @@
#include "amduatd_caps.h"
+#include "amduatd_http.h"
#include "amduat/asl/asl_pointer_fs.h"
#include "amduat/asl/ref_text.h"
@@ -23,45 +24,6 @@
#include
#include
-bool amduatd_write_all(int fd, const uint8_t *buf, size_t len);
-bool amduatd_read_exact(int fd, uint8_t *buf, size_t len);
-bool amduatd_read_urandom(uint8_t *out, size_t len);
-
-bool amduatd_http_send_json(int fd,
- int code,
- const char *reason,
- const char *json,
- bool head_only);
-
-bool amduatd_send_json_error(int fd,
- int code,
- const char *reason,
- const char *msg);
-
-const char *amduatd_query_param(const char *path,
- const char *key,
- char *out_value,
- size_t out_cap);
-
-const char *amduatd_json_skip_ws(const char *p, const char *end);
-
-bool amduatd_json_expect(const char **p,
- const char *end,
- char expected);
-
-bool amduatd_json_parse_string_noesc(const char **p,
- const char *end,
- const char **out,
- size_t *out_len);
-
-bool amduatd_json_parse_u64(const char **p, const char *end, uint64_t *out);
-
-bool amduatd_json_skip_value(const char **p,
- const char *end,
- int depth);
-
-bool amduatd_copy_json_str(const char *s, size_t len, char **out);
-
amduatd_ref_status_t amduatd_decode_ref_or_name_latest(
amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg,
@@ -1152,28 +1114,6 @@ static bool amduatd_cap_verify_token(const amduatd_caps_t *cap,
return true;
}
-static bool amduatd_caps_path_without_query(const char *path,
- char *out,
- size_t cap) {
- const char *q = NULL;
- size_t n = 0;
- if (out == NULL || cap == 0) {
- return false;
- }
- out[0] = '\0';
- if (path == NULL) {
- return false;
- }
- q = strchr(path, '?');
- n = q != NULL ? (size_t)(q - path) : strlen(path);
- if (n >= cap) {
- n = cap - 1;
- }
- memcpy(out, path, n);
- out[n] = '\0';
- return true;
-}
-
static bool amduatd_handle_post_capabilities(
int fd,
amduat_asl_store_t *store,
@@ -1862,7 +1802,7 @@ bool amduatd_caps_can_handle(const amduatd_http_req_t *req) {
if (req == NULL) {
return false;
}
- if (!amduatd_caps_path_without_query(req->path, no_query, sizeof(no_query))) {
+ if (!amduatd_path_without_query(req->path, no_query, sizeof(no_query))) {
return false;
}
if (strcmp(req->method, "POST") == 0 &&
@@ -1887,11 +1827,11 @@ bool amduatd_caps_handle(amduatd_ctx_t *ctx,
if (!amduatd_caps_can_handle(req)) {
return false;
}
- if (!amduatd_caps_path_without_query(req->path, no_query, sizeof(no_query))) {
+ if (!amduatd_path_without_query(req->path, no_query, sizeof(no_query))) {
resp->ok = amduatd_send_json_error(resp->fd,
- 400,
- "Bad Request",
- "invalid path");
+ 400,
+ "Bad Request",
+ "invalid path");
return true;
}
diff --git a/src/amduatd_concepts.c b/src/amduatd_concepts.c
index 50823ca..227a0c7 100644
--- a/src/amduatd_concepts.c
+++ b/src/amduatd_concepts.c
@@ -4,6 +4,7 @@
#define _POSIX_C_SOURCE 200809L
#include "amduatd_concepts.h"
+#include "amduatd_http.h"
#include "amduatd_caps.h"
@@ -34,37 +35,6 @@
#include
#include
-bool amduatd_read_exact(int fd, uint8_t *buf, size_t len);
-bool amduatd_read_urandom(uint8_t *out, size_t len);
-
-bool amduatd_http_send_json(int fd,
- int code,
- const char *reason,
- const char *json,
- bool head_only);
-
-bool amduatd_send_json_error(int fd,
- int code,
- const char *reason,
- const char *msg);
-
-const char *amduatd_json_skip_ws(const char *p, const char *end);
-
-bool amduatd_json_expect(const char **p,
- const char *end,
- char expected);
-
-bool amduatd_json_parse_string_noesc(const char **p,
- const char *end,
- const char **out,
- size_t *out_len);
-
-bool amduatd_json_skip_value(const char **p,
- const char *end,
- int depth);
-
-bool amduatd_copy_json_str(const char *s, size_t len, char **out);
-
typedef struct amduatd_strbuf {
char *data;
size_t len;
@@ -2995,37 +2965,6 @@ concepts_cleanup:
return ok;
}
-static bool amduatd_path_extract_name(const char *path,
- const char *prefix,
- char *out,
- size_t cap) {
- size_t plen;
- const char *p;
- size_t len;
-
- if (out != NULL && cap != 0) {
- out[0] = '\0';
- }
- if (path == NULL || prefix == NULL || out == NULL || cap == 0) {
- return false;
- }
- plen = strlen(prefix);
- if (strncmp(path, prefix, plen) != 0) {
- return false;
- }
- p = path + plen;
- if (*p == '\0') {
- return false;
- }
- len = strlen(p);
- if (len >= cap) {
- len = cap - 1;
- }
- memcpy(out, p, len);
- out[len] = '\0';
- return true;
-}
-
static bool amduatd_handle_post_concept_publish(int fd,
amduat_asl_store_t *store,
amduatd_concepts_t *concepts,
@@ -3219,34 +3158,13 @@ static bool amduatd_handle_get_resolve(int fd,
return amduatd_http_send_json(fd, 200, "OK", json, false);
}
-static void amduatd_concepts_path_without_query(const char *path,
- char *out,
- size_t cap) {
- const char *q = NULL;
- size_t n = 0;
- if (out == NULL || cap == 0) {
- return;
- }
- out[0] = '\0';
- if (path == NULL) {
- return;
- }
- q = strchr(path, '?');
- n = q != NULL ? (size_t)(q - path) : strlen(path);
- if (n >= cap) {
- n = cap - 1;
- }
- memcpy(out, path, n);
- out[n] = '\0';
-}
-
bool amduatd_concepts_can_handle(const amduatd_http_req_t *req) {
char no_query[1024];
if (req == NULL) {
return false;
}
- amduatd_concepts_path_without_query(req->path, no_query, sizeof(no_query));
+ amduatd_path_without_query(req->path, no_query, sizeof(no_query));
if (strcmp(req->method, "POST") == 0 &&
strcmp(no_query, "/v1/concepts") == 0) {
@@ -3286,7 +3204,7 @@ bool amduatd_concepts_handle(amduatd_ctx_t *ctx,
return false;
}
- amduatd_concepts_path_without_query(req->path, no_query, sizeof(no_query));
+ amduatd_path_without_query(req->path, no_query, sizeof(no_query));
if (strcmp(req->method, "POST") == 0 && strcmp(no_query, "/v1/concepts") == 0) {
resp->ok = amduatd_handle_post_concepts(resp->fd,
diff --git a/src/amduatd_http.c b/src/amduatd_http.c
new file mode 100644
index 0000000..1fd40bb
--- /dev/null
+++ b/src/amduatd_http.c
@@ -0,0 +1,819 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#define _POSIX_C_SOURCE 200809L
+
+#include "amduatd_http.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef struct amduatd_http_strbuf {
+ char *data;
+ size_t len;
+ size_t cap;
+} amduatd_http_strbuf_t;
+
+static void amduatd_http_strbuf_free(amduatd_http_strbuf_t *b) {
+ if (b == NULL) {
+ return;
+ }
+ free(b->data);
+ b->data = NULL;
+ b->len = 0;
+ b->cap = 0;
+}
+
+static bool amduatd_http_strbuf_reserve(amduatd_http_strbuf_t *b, size_t extra) {
+ size_t need;
+ size_t next_cap;
+ char *next;
+
+ if (b == NULL) {
+ return false;
+ }
+ if (extra > (SIZE_MAX - b->len)) {
+ return false;
+ }
+ need = b->len + extra;
+ if (need <= b->cap) {
+ return true;
+ }
+ next_cap = b->cap != 0 ? b->cap : 256u;
+ while (next_cap < need) {
+ if (next_cap > (SIZE_MAX / 2u)) {
+ next_cap = need;
+ break;
+ }
+ next_cap *= 2u;
+ }
+ next = (char *)realloc(b->data, next_cap);
+ if (next == NULL) {
+ return false;
+ }
+ b->data = next;
+ b->cap = next_cap;
+ return true;
+}
+
+static bool amduatd_http_strbuf_append(amduatd_http_strbuf_t *b,
+ const char *s,
+ size_t n) {
+ if (b == NULL) {
+ return false;
+ }
+ if (n == 0) {
+ return true;
+ }
+ if (s == NULL) {
+ return false;
+ }
+ if (!amduatd_http_strbuf_reserve(b, n + 1u)) {
+ return false;
+ }
+ memcpy(b->data + b->len, s, n);
+ b->len += n;
+ b->data[b->len] = '\0';
+ return true;
+}
+
+static bool amduatd_http_strbuf_append_cstr(amduatd_http_strbuf_t *b,
+ const char *s) {
+ return amduatd_http_strbuf_append(
+ b, s != NULL ? s : "", s != NULL ? strlen(s) : 0u);
+}
+
+bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) {
+ size_t off = 0;
+ while (off < len) {
+ ssize_t n = write(fd, buf + off, len - off);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return false;
+ }
+ if (n == 0) {
+ return false;
+ }
+ off += (size_t)n;
+ }
+ return true;
+}
+
+bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) {
+ size_t off = 0;
+ while (off < len) {
+ ssize_t n = read(fd, buf + off, len - off);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return false;
+ }
+ if (n == 0) {
+ return false;
+ }
+ off += (size_t)n;
+ }
+ return true;
+}
+
+bool amduatd_read_urandom(uint8_t *out, size_t len) {
+ int fd;
+ if (out == NULL || len == 0) {
+ return false;
+ }
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ return false;
+ }
+ if (!amduatd_read_exact(fd, out, len)) {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+static bool amduatd_read_line(int fd, char *buf, size_t cap, size_t *out_len) {
+ size_t len = 0;
+ while (len + 1 < cap) {
+ char c = 0;
+ ssize_t n = read(fd, &c, 1);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return false;
+ }
+ if (n == 0) {
+ return false;
+ }
+ if (c == '\n') {
+ break;
+ }
+ buf[len++] = c;
+ }
+ if (len == 0) {
+ return false;
+ }
+ if (len > 0 && buf[len - 1] == '\r') {
+ len--;
+ }
+ buf[len] = '\0';
+ if (out_len != NULL) {
+ *out_len = len;
+ }
+ return true;
+}
+
+static void amduatd_http_req_init(amduatd_http_req_t *req) {
+ memset(req, 0, sizeof(*req));
+}
+
+bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) {
+ char line[2048];
+ size_t line_len = 0;
+ char *sp1 = NULL;
+ char *sp2 = NULL;
+
+ if (out_req == NULL) {
+ return false;
+ }
+ amduatd_http_req_init(out_req);
+
+ if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
+ return false;
+ }
+
+ sp1 = strchr(line, ' ');
+ if (sp1 == NULL) {
+ return false;
+ }
+ *sp1++ = '\0';
+ sp2 = strchr(sp1, ' ');
+ if (sp2 == NULL) {
+ return false;
+ }
+ *sp2++ = '\0';
+
+ if (strlen(line) >= sizeof(out_req->method)) {
+ return false;
+ }
+ strncpy(out_req->method, line, sizeof(out_req->method) - 1);
+
+ if (strlen(sp1) >= sizeof(out_req->path)) {
+ return false;
+ }
+ strncpy(out_req->path, sp1, sizeof(out_req->path) - 1);
+
+ for (;;) {
+ if (!amduatd_read_line(fd, line, sizeof(line), &line_len)) {
+ return false;
+ }
+ if (line_len == 0) {
+ break;
+ }
+
+ if (strncasecmp(line, "Content-Length:", 15) == 0) {
+ const char *v = line + 15;
+ while (*v == ' ' || *v == '\t') {
+ v++;
+ }
+ out_req->content_length = (size_t)strtoull(v, NULL, 10);
+ } else if (strncasecmp(line, "Content-Type:", 13) == 0) {
+ const char *v = line + 13;
+ while (*v == ' ' || *v == '\t') {
+ v++;
+ }
+ strncpy(out_req->content_type, v, sizeof(out_req->content_type) - 1);
+ } else if (strncasecmp(line, "Accept:", 7) == 0) {
+ const char *v = line + 7;
+ while (*v == ' ' || *v == '\t') {
+ v++;
+ }
+ strncpy(out_req->accept, v, sizeof(out_req->accept) - 1);
+ } else if (strncasecmp(line, "X-Amduat-Type-Tag:", 18) == 0) {
+ const char *v = line + 18;
+ while (*v == ' ' || *v == '\t') {
+ v++;
+ }
+ strncpy(out_req->x_type_tag, v, sizeof(out_req->x_type_tag) - 1);
+ } else if (strncasecmp(line, "X-Amduat-Capability:", 20) == 0) {
+ const char *v = line + 20;
+ while (*v == ' ' || *v == '\t') {
+ v++;
+ }
+ strncpy(out_req->x_capability, v, sizeof(out_req->x_capability) - 1);
+ }
+ }
+
+ return true;
+}
+
+void amduatd_http_req_free(amduatd_http_req_t *req) {
+ if (req == NULL) {
+ return;
+ }
+ free((void *)req->actor.data);
+ req->actor = amduat_octets(NULL, 0u);
+ req->has_actor = false;
+ req->has_uid = false;
+ req->uid = 0;
+}
+
+bool amduatd_http_send_response(int fd, const amduatd_http_resp_t *resp) {
+ if (resp == NULL) {
+ return false;
+ }
+ return amduatd_http_send_status(fd,
+ resp->status,
+ resp->reason,
+ resp->content_type,
+ resp->body,
+ resp->body_len,
+ resp->head_only);
+}
+
+bool amduatd_http_send_status(int fd,
+ int code,
+ const char *reason,
+ const char *content_type,
+ const uint8_t *body,
+ size_t body_len,
+ bool head_only) {
+ char hdr[512];
+ int n = snprintf(hdr,
+ sizeof(hdr),
+ "HTTP/1.1 %d %s\r\n"
+ "Content-Length: %zu\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ code,
+ reason != NULL ? reason : "",
+ body_len,
+ content_type != NULL ? content_type : "text/plain");
+ if (n <= 0 || (size_t)n >= sizeof(hdr)) {
+ return false;
+ }
+ if (!amduatd_write_all(fd, (const uint8_t *)hdr, (size_t)n)) {
+ return false;
+ }
+ if (!head_only && body != NULL && body_len != 0) {
+ return amduatd_write_all(fd, body, body_len);
+ }
+ return true;
+}
+
+bool amduatd_http_send_text(int fd,
+ int code,
+ const char *reason,
+ const char *text,
+ bool head_only) {
+ const uint8_t *body = (const uint8_t *)(text != NULL ? text : "");
+ size_t len = text != NULL ? strlen(text) : 0;
+ return amduatd_http_send_status(
+ fd, code, reason, "text/plain; charset=utf-8", body, len, head_only);
+}
+
+bool amduatd_http_send_json(int fd,
+ int code,
+ const char *reason,
+ const char *json,
+ bool head_only) {
+ const uint8_t *body = (const uint8_t *)(json != NULL ? json : "{}");
+ size_t len = json != NULL ? strlen(json) : 2;
+ return amduatd_http_send_status(
+ fd, code, reason, "application/json", body, len, head_only);
+}
+
+static bool amduatd_http_req_wants_html(const amduatd_http_req_t *req) {
+ if (req == NULL) {
+ return false;
+ }
+ if (req->accept[0] == '\0') {
+ return false;
+ }
+ return strstr(req->accept, "text/html") != NULL;
+}
+
+bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req) {
+ if (amduatd_http_req_wants_html(req)) {
+ const char *path = (req != NULL && req->path[0] != '\0') ? req->path : "/";
+ const char *method =
+ (req != NULL && req->method[0] != '\0') ? req->method : "GET";
+ char html[4096];
+ int n = snprintf(
+ html,
+ sizeof(html),
+ "\n"
+ "\n"
+ "\n"
+ " \n"
+ " \n"
+ " amduatd — Not Found\n"
+ " \n"
+ "\n"
+ "\n"
+ " \n"
+ "
\n"
+ "
404 — Not Found
\n"
+ "
amduatd didn’t recognize %s %s.
\n"
+ "
Try one of these:
\n"
+ "
\n"
+ "
Artifact bytes: /v1/artifacts/<ref>
\n"
+ "
\n"
+ "
\n"
+ "\n"
+ "\n",
+ method,
+ path);
+ if (n <= 0 || (size_t)n >= sizeof(html)) {
+ return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
+ }
+ return amduatd_http_send_status(fd,
+ 404,
+ "Not Found",
+ "text/html; charset=utf-8",
+ (const uint8_t *)html,
+ (size_t)n,
+ false);
+ }
+ return amduatd_http_send_text(fd, 404, "Not Found", "not found\n", false);
+}
+
+bool amduatd_send_json_error(int fd,
+ int code,
+ const char *reason,
+ const char *msg) {
+ amduatd_http_strbuf_t b;
+ memset(&b, 0, sizeof(b));
+ if (!amduatd_http_strbuf_append_cstr(&b, "{\"error\":\"") ||
+ !amduatd_http_strbuf_append_cstr(&b, msg != NULL ? msg : "error") ||
+ !amduatd_http_strbuf_append_cstr(&b, "\"}\n")) {
+ amduatd_http_strbuf_free(&b);
+ return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n",
+ false);
+ }
+ {
+ bool ok = amduatd_http_send_json(fd, code, reason, b.data, false);
+ amduatd_http_strbuf_free(&b);
+ return ok;
+ }
+}
+
+const char *amduatd_query_param(const char *path,
+ const char *key,
+ char *out_value,
+ size_t out_cap) {
+ const char *q = strchr(path, '?');
+ size_t key_len = 0;
+ if (out_value != NULL && out_cap != 0) {
+ out_value[0] = '\0';
+ }
+ if (q == NULL || key == NULL || out_value == NULL || out_cap == 0) {
+ return NULL;
+ }
+ q++;
+ key_len = strlen(key);
+
+ while (*q != '\0') {
+ const char *k = q;
+ const char *eq = strchr(k, '=');
+ const char *amp = strchr(k, '&');
+ size_t klen = 0;
+ const char *v = NULL;
+ size_t vlen = 0;
+
+ if (amp == NULL) {
+ amp = q + strlen(q);
+ }
+ if (eq == NULL || eq > amp) {
+ q = (*amp == '&') ? (amp + 1) : amp;
+ continue;
+ }
+ klen = (size_t)(eq - k);
+ if (klen == key_len && strncmp(k, key, key_len) == 0) {
+ v = eq + 1;
+ vlen = (size_t)(amp - v);
+ if (vlen >= out_cap) {
+ vlen = out_cap - 1;
+ }
+ memcpy(out_value, v, vlen);
+ out_value[vlen] = '\0';
+ return out_value;
+ }
+ q = (*amp == '&') ? (amp + 1) : amp;
+ }
+
+ return NULL;
+}
+
+bool amduatd_path_without_query(const char *path, char *out, size_t cap) {
+ const char *q = NULL;
+ size_t n = 0;
+ if (out == NULL || cap == 0) {
+ return false;
+ }
+ out[0] = '\0';
+ if (path == NULL) {
+ return false;
+ }
+ q = strchr(path, '?');
+ n = q != NULL ? (size_t)(q - path) : strlen(path);
+ if (n >= cap) {
+ n = cap - 1;
+ }
+ memcpy(out, path, n);
+ out[n] = '\0';
+ return true;
+}
+
+bool amduatd_path_extract_name(const char *path,
+ const char *prefix,
+ char *out,
+ size_t cap) {
+ size_t plen;
+ const char *p;
+ size_t len;
+
+ if (out != NULL && cap != 0) {
+ out[0] = '\0';
+ }
+ if (path == NULL || prefix == NULL || out == NULL || cap == 0) {
+ return false;
+ }
+ plen = strlen(prefix);
+ if (strncmp(path, prefix, plen) != 0) {
+ return false;
+ }
+ p = path + plen;
+ if (*p == '\0') {
+ return false;
+ }
+ len = strlen(p);
+ if (len >= cap) {
+ len = cap - 1;
+ }
+ memcpy(out, p, len);
+ out[len] = '\0';
+ return true;
+}
+
+const char *amduatd_json_skip_ws(const char *p, const char *end) {
+ while (p < end) {
+ char c = *p;
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
+ break;
+ }
+ p++;
+ }
+ return p;
+}
+
+bool amduatd_json_expect(const char **p,
+ const char *end,
+ char expected) {
+ const char *cur;
+ if (p == NULL || *p == NULL) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end || *cur != expected) {
+ return false;
+ }
+ *p = cur + 1;
+ return true;
+}
+
+bool amduatd_json_parse_string_noesc(const char **p,
+ const char *end,
+ const char **out_start,
+ size_t *out_len) {
+ const char *cur;
+ const char *s;
+ if (out_start != NULL) {
+ *out_start = NULL;
+ }
+ if (out_len != NULL) {
+ *out_len = 0;
+ }
+ if (p == NULL || *p == NULL) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end || *cur != '"') {
+ return false;
+ }
+ cur++;
+ s = cur;
+ while (cur < end) {
+ unsigned char c = (unsigned char)*cur;
+ if (c == '"') {
+ if (out_start != NULL) {
+ *out_start = s;
+ }
+ if (out_len != NULL) {
+ *out_len = (size_t)(cur - s);
+ }
+ *p = cur + 1;
+ return true;
+ }
+ if (c == '\\' || c < 0x20u) {
+ return false;
+ }
+ cur++;
+ }
+ return false;
+}
+
+bool amduatd_json_parse_u64(const char **p,
+ const char *end,
+ uint64_t *out) {
+ const char *cur;
+ const char *start;
+ unsigned long long v;
+ char *tmp = NULL;
+ char *endp = NULL;
+ size_t n;
+
+ if (out != NULL) {
+ *out = 0;
+ }
+ if (p == NULL || *p == NULL || out == NULL) {
+ return false;
+ }
+
+ cur = amduatd_json_skip_ws(*p, end);
+ start = cur;
+ if (cur >= end) {
+ return false;
+ }
+ if (*cur < '0' || *cur > '9') {
+ return false;
+ }
+ cur++;
+ while (cur < end && *cur >= '0' && *cur <= '9') {
+ cur++;
+ }
+ n = (size_t)(cur - start);
+ tmp = (char *)malloc(n + 1u);
+ if (tmp == NULL) {
+ return false;
+ }
+ memcpy(tmp, start, n);
+ tmp[n] = '\0';
+ errno = 0;
+ v = strtoull(tmp, &endp, 10);
+ free(tmp);
+ if (errno != 0 || endp == NULL || *endp != '\0') {
+ return false;
+ }
+ *out = (uint64_t)v;
+ *p = cur;
+ return true;
+}
+
+static bool amduatd_json_skip_string(const char **p, const char *end) {
+ const char *cur;
+ if (p == NULL || *p == NULL) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end || *cur != '"') {
+ return false;
+ }
+ cur++;
+ while (cur < end) {
+ unsigned char c = (unsigned char)*cur++;
+ if (c == '"') {
+ *p = cur;
+ return true;
+ }
+ if (c == '\\') {
+ if (cur >= end) {
+ return false;
+ }
+ cur++;
+ } else if (c < 0x20u) {
+ return false;
+ }
+ }
+ return false;
+}
+
+bool amduatd_json_skip_value(const char **p,
+ const char *end,
+ int depth);
+
+static bool amduatd_json_skip_array(const char **p,
+ const char *end,
+ int depth) {
+ const char *cur;
+ if (!amduatd_json_expect(p, end, '[')) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur < end && *cur == ']') {
+ *p = cur + 1;
+ return true;
+ }
+ for (;;) {
+ if (!amduatd_json_skip_value(p, end, depth + 1)) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end) {
+ return false;
+ }
+ if (*cur == ',') {
+ *p = cur + 1;
+ continue;
+ }
+ if (*cur == ']') {
+ *p = cur + 1;
+ return true;
+ }
+ return false;
+ }
+}
+
+static bool amduatd_json_skip_object(const char **p,
+ const char *end,
+ int depth) {
+ const char *cur;
+ if (!amduatd_json_expect(p, end, '{')) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur < end && *cur == '}') {
+ *p = cur + 1;
+ return true;
+ }
+ for (;;) {
+ if (!amduatd_json_skip_string(p, end)) {
+ return false;
+ }
+ if (!amduatd_json_expect(p, end, ':')) {
+ return false;
+ }
+ if (!amduatd_json_skip_value(p, end, depth + 1)) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end) {
+ return false;
+ }
+ if (*cur == ',') {
+ *p = cur + 1;
+ continue;
+ }
+ if (*cur == '}') {
+ *p = cur + 1;
+ return true;
+ }
+ return false;
+ }
+}
+
+bool amduatd_json_skip_value(const char **p,
+ const char *end,
+ int depth) {
+ const char *cur;
+ if (p == NULL || *p == NULL) {
+ return false;
+ }
+ if (depth > 32) {
+ return false;
+ }
+ cur = amduatd_json_skip_ws(*p, end);
+ if (cur >= end) {
+ return false;
+ }
+ if (*cur == '"') {
+ return amduatd_json_skip_string(p, end);
+ }
+ if (*cur == '{') {
+ return amduatd_json_skip_object(p, end, depth);
+ }
+ if (*cur == '[') {
+ return amduatd_json_skip_array(p, end, depth);
+ }
+ if (*cur == 't' && (end - cur) >= 4 && memcmp(cur, "true", 4) == 0) {
+ *p = cur + 4;
+ return true;
+ }
+ if (*cur == 'f' && (end - cur) >= 5 && memcmp(cur, "false", 5) == 0) {
+ *p = cur + 5;
+ return true;
+ }
+ if (*cur == 'n' && (end - cur) >= 4 && memcmp(cur, "null", 4) == 0) {
+ *p = cur + 4;
+ return true;
+ }
+ if (*cur == '-' || (*cur >= '0' && *cur <= '9')) {
+ cur++;
+ while (cur < end) {
+ char c = *cur;
+ if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' ||
+ c == '+' || c == '-') {
+ cur++;
+ continue;
+ }
+ break;
+ }
+ *p = cur;
+ return true;
+ }
+ return false;
+}
+
+bool amduatd_copy_json_str(const char *s,
+ size_t len,
+ char **out) {
+ char *buf;
+ if (out == NULL) {
+ return false;
+ }
+ *out = NULL;
+ if (len > (SIZE_MAX - 1u)) {
+ return false;
+ }
+ buf = (char *)malloc(len + 1u);
+ if (buf == NULL) {
+ return false;
+ }
+ if (len != 0) {
+ memcpy(buf, s, len);
+ }
+ buf[len] = '\0';
+ *out = buf;
+ return true;
+}
diff --git a/src/amduatd_http.h b/src/amduatd_http.h
new file mode 100644
index 0000000..f6c2a60
--- /dev/null
+++ b/src/amduatd_http.h
@@ -0,0 +1,116 @@
+#ifndef AMDUATD_HTTP_H
+#define AMDUATD_HTTP_H
+
+#include "amduat/asl/core.h"
+
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ char method[8];
+ char path[1024];
+ char content_type[128];
+ char accept[128];
+ char x_type_tag[64];
+ char x_capability[2048];
+ size_t content_length;
+ bool has_actor;
+ amduat_octets_t actor;
+ bool has_uid;
+ uid_t uid;
+} amduatd_http_req_t;
+
+typedef struct {
+ int fd;
+ int status;
+ const char *reason;
+ const char *content_type;
+ const uint8_t *body;
+ size_t body_len;
+ bool head_only;
+ bool ok;
+} amduatd_http_resp_t;
+
+bool amduatd_write_all(int fd, const uint8_t *buf, size_t len);
+
+bool amduatd_read_exact(int fd, uint8_t *buf, size_t len);
+
+bool amduatd_read_urandom(uint8_t *out, size_t len);
+
+bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req);
+
+void amduatd_http_req_free(amduatd_http_req_t *req);
+
+bool amduatd_http_send_response(int fd, const amduatd_http_resp_t *resp);
+
+bool amduatd_http_send_status(int fd,
+ int code,
+ const char *reason,
+ const char *content_type,
+ const uint8_t *body,
+ size_t body_len,
+ bool head_only);
+
+bool amduatd_http_send_text(int fd,
+ int code,
+ const char *reason,
+ const char *text,
+ bool head_only);
+
+bool amduatd_http_send_json(int fd,
+ int code,
+ const char *reason,
+ const char *json,
+ bool head_only);
+
+bool amduatd_http_send_not_found(int fd, const amduatd_http_req_t *req);
+
+bool amduatd_send_json_error(int fd,
+ int code,
+ const char *reason,
+ const char *msg);
+
+const char *amduatd_query_param(const char *path,
+ const char *key,
+ char *out_value,
+ size_t out_cap);
+
+bool amduatd_path_without_query(const char *path,
+ char *out,
+ size_t cap);
+
+bool amduatd_path_extract_name(const char *path,
+ const char *prefix,
+ char *out,
+ size_t cap);
+
+const char *amduatd_json_skip_ws(const char *p, const char *end);
+
+bool amduatd_json_expect(const char **p,
+ const char *end,
+ char expected);
+
+bool amduatd_json_parse_string_noesc(const char **p,
+ const char *end,
+ const char **out_start,
+ size_t *out_len);
+
+bool amduatd_json_parse_u64(const char **p, const char *end, uint64_t *out);
+
+bool amduatd_json_skip_value(const char **p,
+ const char *end,
+ int depth);
+
+bool amduatd_copy_json_str(const char *s, size_t len, char **out);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* AMDUATD_HTTP_H */
diff --git a/src/amduatd_ui.c b/src/amduatd_ui.c
index c4a17a5..b83d994 100644
--- a/src/amduatd_ui.c
+++ b/src/amduatd_ui.c
@@ -1,4 +1,5 @@
#include "amduatd_ui.h"
+#include "amduatd_http.h"
#include "amduat/asl/artifact_io.h"
#include "amduat/asl/asl_store_fs.h"
@@ -10,20 +11,6 @@
#include
#include
-bool amduatd_http_send_text(int fd,
- int code,
- const char *reason,
- const char *text,
- bool head_only);
-
-bool amduatd_http_send_status(int fd,
- int code,
- const char *reason,
- const char *content_type,
- const uint8_t *body,
- size_t body_len,
- bool head_only);
-
static const char k_amduatd_ui_html[] =
"\n"
"\n"
@@ -337,27 +324,6 @@ static const char k_amduatd_ui_html[] =
"\n"
"