#include #include #include #include #include #include #include #include #define HTTP_FETCH_MAX (8u * 1024u * 1024u) typedef struct { char* method; char* url; char* headers; char* body; char hdr_path[64]; char out_path[64]; char in_path[64]; char err_path[64]; char* resp_hdr; char* resp_body; char err[256]; u32 status; int curl_code; int in_used; } HttpFetch; static int http_fetch_temp(char* path, const char* prefix) { snprintf(path, 64, "%sXXXXXX", prefix); int fd = mkstemp(path); if (fd >= 0) { fchmod(fd, 0600); } return fd; } static char* http_fetch_read(const char* path, u64 cap) { FILE* f = fopen(path, "rb"); if (f == NULL) { return NULL; } char* buf = io_mem(malloc((size_t)cap + 1)); size_t n = fread(buf, 1, (size_t)cap, f); buf[n] = 0; fclose(f); return buf; } static u32 http_fetch_status(const char* hdr) { u32 status = 0; const char* p = hdr; while (p != NULL && *p != 0) { if ((p == hdr || p[-1] == '\n') && strncmp(p, "HTTP/", 5) == 0) { const char* sp = strchr(p, ' '); if (sp != NULL) { status = (u32)atoi(sp + 1); } } p = strchr(p, '\n'); if (p != NULL) { p += 1; } } return status; } static int http_fetch_write(const char* path, const char* data) { FILE* f = fopen(path, "wb"); if (f == NULL) { return -1; } size_t n = strlen(data); size_t w = fwrite(data, 1, n, f); fclose(f); return w == n ? 0 : -1; } static void http_fetch_push(char** argv, int* n, int max, char* s) { if (*n + 1 < max) { argv[(*n)++] = s; } } static int http_fetch_run_curl(HttpFetch* st) { char* argv[96]; int n = 0; char in_ref[72]; http_fetch_push(argv, &n, 96, "curl"); http_fetch_push(argv, &n, 96, "-sS"); http_fetch_push(argv, &n, 96, "--max-time"); http_fetch_push(argv, &n, 96, "30"); http_fetch_push(argv, &n, 96, "-D"); http_fetch_push(argv, &n, 96, st->hdr_path); http_fetch_push(argv, &n, 96, "-o"); http_fetch_push(argv, &n, 96, st->out_path); http_fetch_push(argv, &n, 96, "-X"); http_fetch_push(argv, &n, 96, st->method); char* headers = st->headers; while (headers != NULL && headers[0] != 0 && n + 3 < 96) { char* nl = strchr(headers, '\n'); if (nl != NULL) { *nl = 0; } if (headers[0] != 0) { http_fetch_push(argv, &n, 96, "-H"); http_fetch_push(argv, &n, 96, headers); } headers = nl == NULL ? NULL : nl + 1; } if (st->in_used) { snprintf(in_ref, sizeof in_ref, "@%s", st->in_path); http_fetch_push(argv, &n, 96, "--data-binary"); http_fetch_push(argv, &n, 96, in_ref); } http_fetch_push(argv, &n, 96, "--url"); http_fetch_push(argv, &n, 96, st->url); argv[n] = NULL; int errfd = open(st->err_path, O_WRONLY | O_TRUNC); pid_t pid = fork(); if (pid < 0) { if (errfd >= 0) { close(errfd); } return -1; } if (pid == 0) { if (errfd >= 0) { dup2(errfd, STDERR_FILENO); close(errfd); } int nulfd = open("/dev/null", O_RDONLY); if (nulfd >= 0) { dup2(nulfd, STDIN_FILENO); close(nulfd); } execvp("curl", argv); _exit(127); } if (errfd >= 0) { close(errfd); } int stcode = 0; if (waitpid(pid, &stcode, 0) < 0) { return -1; } if (WIFEXITED(stcode)) { return WEXITSTATUS(stcode); } return 1; } static void http_fetch_call(IoWork* w) { HttpFetch* st = (HttpFetch*)w->data; if (st->in_used && http_fetch_write(st->in_path, st->body) != 0) { w->code = EIO; snprintf(st->err, sizeof st->err, "could not write request body"); return; } int code = http_fetch_run_curl(st); st->curl_code = code; if (code < 0) { w->code = errno != 0 ? (u32)errno : EIO; snprintf(st->err, sizeof st->err, "could not start curl"); return; } if (code == 127) { w->code = ENOENT; snprintf(st->err, sizeof st->err, "curl is not installed"); return; } if (code != 0) { w->code = EIO; char* msg = http_fetch_read(st->err_path, 255); if (msg != NULL && msg[0] != 0) { snprintf(st->err, sizeof st->err, "%s", msg); } else { snprintf(st->err, sizeof st->err, "curl failed (%d)", code); } free(msg); return; } st->resp_hdr = http_fetch_read(st->hdr_path, HTTP_FETCH_MAX); st->resp_body = http_fetch_read(st->out_path, HTTP_FETCH_MAX); if (st->resp_hdr == NULL) { st->resp_hdr = io_mem(calloc(1, 1)); } if (st->resp_body == NULL) { st->resp_body = io_mem(calloc(1, 1)); } st->status = http_fetch_status(st->resp_hdr); w->code = 0; } static Term http_fetch_pack(Env e, IoWork* w) { HttpFetch* st = (HttpFetch*)w->data; Term r; if (w->code != 0) { r = io_fail(e, w->code, st->err[0] != 0 ? st->err : NULL); } else { r = io_done(e, io_tup(e, st->status, io_tup(e, io_str(e, st->resp_hdr, strlen(st->resp_hdr)), io_str(e, st->resp_body, strlen(st->resp_body))))); } unlink(st->hdr_path); unlink(st->out_path); unlink(st->in_path); unlink(st->err_path); free(st->method); free(st->url); free(st->headers); free(st->body); free(st->resp_hdr); free(st->resp_body); free(st); return r; } Term http_fetch_run(Env e, Term* f, IoWork* w) { HttpFetch* st = io_mem(calloc(1, sizeof *st)); u64 n = 0; st->method = io_cstr(e, f[0], &n); st->url = io_cstr(e, f[1], &n); st->headers = io_cstr(e, f[2], &n); st->body = io_cstr(e, f[3], &n); st->in_used = st->body != NULL && st->body[0] != 0; int hdr_fd = http_fetch_temp(st->hdr_path, "/tmp/bend-http-h-"); int out_fd = http_fetch_temp(st->out_path, "/tmp/bend-http-b-"); int in_fd = http_fetch_temp(st->in_path, "/tmp/bend-http-i-"); int err_fd = http_fetch_temp(st->err_path, "/tmp/bend-http-e-"); if (hdr_fd >= 0) close(hdr_fd); if (out_fd >= 0) close(out_fd); if (in_fd >= 0) close(in_fd); if (err_fd >= 0) close(err_fd); w->data = st; if (hdr_fd < 0 || out_fd < 0 || in_fd < 0 || err_fd < 0) { w->code = EIO; snprintf(st->err, sizeof st->err, "could not create temp files"); return http_fetch_pack(e, w); } if (io_nul(st->method, strlen(st->method)) || io_nul(st->url, strlen(st->url))) { w->code = EINVAL; snprintf(st->err, sizeof st->err, "method and url are required"); return http_fetch_pack(e, w); } return io_work(w, http_fetch_call, http_fetch_pack); } static void __attribute__((constructor)) http_fetch_use(void) { io_eff(CID_HTTP_FETCH, http_fetch_run, 0); }