amduat-api/tests/tools/amduatd_http_unix.c

385 lines
8.4 KiB
C
Raw Permalink Normal View History

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
typedef struct {
char *data;
size_t len;
size_t cap;
} amduatd_buf_t;
static void amduatd_buf_free(amduatd_buf_t *b) {
if (b == NULL) {
return;
}
free(b->data);
b->data = NULL;
b->len = 0u;
b->cap = 0u;
}
static bool amduatd_buf_reserve(amduatd_buf_t *b, size_t extra) {
size_t need;
size_t next;
char *mem;
if (b == NULL) {
return false;
}
if (extra > SIZE_MAX - b->len) {
return false;
}
need = b->len + extra;
if (need <= b->cap) {
return true;
}
next = b->cap == 0u ? 512u : b->cap;
while (next < need) {
if (next > SIZE_MAX / 2u) {
next = need;
break;
}
next *= 2u;
}
mem = (char *)realloc(b->data, next);
if (mem == NULL) {
return false;
}
b->data = mem;
b->cap = next;
return true;
}
static bool amduatd_buf_append(amduatd_buf_t *b, const char *s, size_t n) {
if (b == NULL) {
return false;
}
if (n == 0u) {
return true;
}
if (s == NULL) {
return false;
}
if (!amduatd_buf_reserve(b, n)) {
return false;
}
memcpy(b->data + b->len, s, n);
b->len += n;
return true;
}
static bool amduatd_buf_append_cstr(amduatd_buf_t *b, const char *s) {
return amduatd_buf_append(b, s, s != NULL ? strlen(s) : 0u);
}
static void amduatd_usage(const char *prog) {
fprintf(stderr,
"usage: %s --sock PATH --method METHOD --path PATH "
"[--header \"K: V\"]... [--data STRING|--data-file PATH] "
"[--allow-status]\n",
prog);
}
static bool amduatd_read_file(const char *path, uint8_t **out_data,
size_t *out_len) {
FILE *fp;
long size;
uint8_t *buf;
if (out_data == NULL || out_len == NULL || path == NULL) {
return false;
}
*out_data = NULL;
*out_len = 0u;
fp = fopen(path, "rb");
if (fp == NULL) {
return false;
}
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return false;
}
size = ftell(fp);
if (size < 0) {
fclose(fp);
return false;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return false;
}
buf = (uint8_t *)malloc((size_t)size);
if (buf == NULL) {
fclose(fp);
return false;
}
if (size != 0 &&
fread(buf, 1u, (size_t)size, fp) != (size_t)size) {
free(buf);
fclose(fp);
return false;
}
fclose(fp);
*out_data = buf;
*out_len = (size_t)size;
return true;
}
static int amduatd_parse_status(const char *line) {
const char *p;
char *end = NULL;
long code;
if (line == NULL) {
return -1;
}
p = strchr(line, ' ');
if (p == NULL) {
return -1;
}
while (*p == ' ') {
p++;
}
errno = 0;
code = strtol(p, &end, 10);
if (errno != 0 || end == p || code < 0 || code > 999) {
return -1;
}
return (int)code;
}
int main(int argc, char **argv) {
const char *sock_path = NULL;
const char *method = NULL;
const char *path = NULL;
bool allow_status = false;
char **headers = NULL;
size_t header_count = 0u;
uint8_t *body = NULL;
size_t body_len = 0u;
int fd = -1;
struct sockaddr_un addr;
amduatd_buf_t req;
amduatd_buf_t resp;
ssize_t nread;
int status;
char *header_end;
size_t body_offset;
size_t i;
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
for (i = 1; i < (size_t)argc; ++i) {
const char *arg = argv[i];
if (strcmp(arg, "--sock") == 0 && i + 1 < (size_t)argc) {
sock_path = argv[++i];
} else if (strcmp(arg, "--method") == 0 && i + 1 < (size_t)argc) {
method = argv[++i];
} else if (strcmp(arg, "--path") == 0 && i + 1 < (size_t)argc) {
path = argv[++i];
} else if (strcmp(arg, "--header") == 0 && i + 1 < (size_t)argc) {
char **next = (char **)realloc(headers,
(header_count + 1u) * sizeof(*headers));
if (next == NULL) {
fprintf(stderr, "oom\n");
goto fail;
}
headers = next;
headers[header_count++] = argv[++i];
} else if (strcmp(arg, "--data") == 0 && i + 1 < (size_t)argc) {
const char *data = argv[++i];
body_len = strlen(data);
if (body_len == 0u) {
body = NULL;
} else {
body = (uint8_t *)malloc(body_len);
if (body == NULL) {
fprintf(stderr, "oom\n");
goto fail;
}
memcpy(body, data, body_len);
}
} else if (strcmp(arg, "--data-file") == 0 && i + 1 < (size_t)argc) {
if (!amduatd_read_file(argv[++i], &body, &body_len)) {
fprintf(stderr, "failed to read data file\n");
goto fail;
}
} else if (strcmp(arg, "--allow-status") == 0) {
allow_status = true;
} else {
amduatd_usage(argv[0]);
goto fail;
}
}
if (sock_path == NULL || method == NULL || path == NULL) {
amduatd_usage(argv[0]);
goto fail;
}
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
goto fail;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (strlen(sock_path) >= sizeof(addr.sun_path)) {
fprintf(stderr, "socket path too long\n");
goto fail;
}
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1u);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("connect");
goto fail;
}
if (!amduatd_buf_append_cstr(&req, method) ||
!amduatd_buf_append_cstr(&req, " ") ||
!amduatd_buf_append_cstr(&req, path) ||
!amduatd_buf_append_cstr(&req, " HTTP/1.1\r\n") ||
!amduatd_buf_append_cstr(&req, "Host: localhost\r\n") ||
!amduatd_buf_append_cstr(&req, "Connection: close\r\n")) {
fprintf(stderr, "oom\n");
goto fail;
}
for (i = 0; i < header_count; ++i) {
if (!amduatd_buf_append_cstr(&req, headers[i]) ||
!amduatd_buf_append_cstr(&req, "\r\n")) {
fprintf(stderr, "oom\n");
goto fail;
}
}
if (body != NULL) {
char len_buf[64];
int len;
len = snprintf(len_buf, sizeof(len_buf), "Content-Length: %zu\r\n",
body_len);
if (len < 0 || (size_t)len >= sizeof(len_buf) ||
!amduatd_buf_append(&req, len_buf, (size_t)len)) {
fprintf(stderr, "oom\n");
goto fail;
}
}
if (!amduatd_buf_append_cstr(&req, "\r\n")) {
fprintf(stderr, "oom\n");
goto fail;
}
{
size_t off = 0u;
while (off < req.len) {
ssize_t n = write(fd, req.data + off, req.len - off);
if (n <= 0) {
perror("write");
goto fail;
}
off += (size_t)n;
}
}
if (body != NULL && body_len != 0u) {
size_t off = 0u;
while (off < body_len) {
ssize_t n = write(fd, body + off, body_len - off);
if (n <= 0) {
perror("write");
goto fail;
}
off += (size_t)n;
}
}
while (true) {
char tmp[4096];
nread = read(fd, tmp, sizeof(tmp));
if (nread < 0) {
perror("read");
goto fail;
}
if (nread == 0) {
break;
}
if (!amduatd_buf_append(&resp, tmp, (size_t)nread)) {
fprintf(stderr, "oom\n");
goto fail;
}
}
if (resp.len == 0u) {
fprintf(stderr, "empty response\n");
goto fail;
}
if (!amduatd_buf_append(&resp, "\0", 1u)) {
fprintf(stderr, "oom\n");
goto fail;
}
{
char *line_end = strstr(resp.data, "\r\n");
if (line_end == NULL) {
fprintf(stderr, "invalid response\n");
goto fail;
}
*line_end = '\0';
status = amduatd_parse_status(resp.data);
*line_end = '\r';
if (status < 0) {
fprintf(stderr, "invalid status\n");
goto fail;
}
}
header_end = strstr(resp.data, "\r\n\r\n");
if (header_end == NULL) {
body_offset = resp.len;
} else {
body_offset = (size_t)(header_end - resp.data) + 4u;
}
if (body_offset < resp.len) {
size_t out_len = resp.len - body_offset;
if (resp.len > 0u && resp.data[resp.len - 1u] == '\0') {
if (out_len > 0u) {
out_len -= 1u;
}
}
if (out_len > 0u) {
fwrite(resp.data + body_offset, 1u, out_len, stdout);
}
}
fflush(stdout);
amduatd_buf_free(&req);
amduatd_buf_free(&resp);
free(body);
free(headers);
if (fd >= 0) {
close(fd);
}
if (!allow_status && (status < 200 || status >= 300)) {
return 1;
}
return 0;
fail:
amduatd_buf_free(&req);
amduatd_buf_free(&resp);
free(body);
free(headers);
if (fd >= 0) {
close(fd);
}
return 1;
}