385 lines
8.4 KiB
C
385 lines
8.4 KiB
C
#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;
|
|
}
|