#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)); req->effective_space = NULL; } 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); } else if (strncasecmp(line, "X-Amduat-Space:", 15) == 0) { const char *v = line + 15; while (*v == ' ' || *v == '\t') { v++; } strncpy(out_req->x_space, v, sizeof(out_req->x_space) - 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); if (errno != 0 || endp == NULL || *endp != '\0') { free(tmp); return false; } free(tmp); *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; }