From 1958c2f4a6df513019977baa4af4f5f757ce9648 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Thu, 23 Sep 2021 21:35:34 -0400 Subject: Add quick --- bin/.gitignore | 1 + bin/Makefile | 1 + bin/README.7 | 4 +- bin/man1/quick.1 | 66 +++++++++++++++++++++ bin/quick.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 bin/man1/quick.1 create mode 100644 bin/quick.c (limited to 'bin') diff --git a/bin/.gitignore b/bin/.gitignore index 5f3bf0c7..a8d641b5 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -23,6 +23,7 @@ pbd pngo psf2png ptee +quick relay scheme scheme.h diff --git a/bin/Makefile b/bin/Makefile index 9fe60278..f44d3026 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -25,6 +25,7 @@ BINS += pbd BINS += pngo BINS += psf2png BINS += ptee +BINS += quick BINS += scheme BINS += shotty BINS += sup diff --git a/bin/README.7 b/bin/README.7 index be4c6a31..6d4e4e8a 100644 --- a/bin/README.7 +++ b/bin/README.7 @@ -1,4 +1,4 @@ -.Dd September 22, 2021 +.Dd September 23, 2021 .Dt BIN 7 .Os "Causal Agency" . @@ -56,6 +56,8 @@ PNG optimizer PSF2 to PNG renderer .It Xr ptee 1 tee for PTYs +.It Xr quick 1 +terrible HTTP/CGI server .It Xr relay 1 IRC relay bot .It Xr scheme 1 diff --git a/bin/man1/quick.1 b/bin/man1/quick.1 new file mode 100644 index 00000000..96f1766a --- /dev/null +++ b/bin/man1/quick.1 @@ -0,0 +1,66 @@ +.Dd September 23, 2021 +.Dt QUICK 1 +.Os +. +.Sh NAME +.Nm quick +.Nd (and dirty) HTTP/CGI server +. +.Sh SYNOPSIS +.Nm +.Op Fl p Ar port +.Ar script +.Op Ar args ... +. +.Sh DESCRIPTION +.Nm +is a barely functional HTTP server +for running CGI scripts. +It listens only on localhost, +on a randomly assigned port, +unless +.Fl p +is used. +The URL of the server +is printed to standard output. +. +.Sh EXAMPLES +.Dl quick cgit | xargs -n1 open +. +.Sh STANDARDS +.Nm +does +.Em not +implement the following: +.Bl -item +.It +.Rs +.%A T. Berners-Lee +.%A R. Fielding +.%A H. Frystyk +.%A J. Gettys +.%A J. Mogul +.%T Hypertext Transfer Protocol -- HTTP/1.1 +.%R RFC 2068 +.%U https://tools.ietf.org/html/rfc2068 +.%D January 1997 +.Re +.It +.Rs +.%A K. Coar +.%A D. Robinson +.%T The Common Gateway Interface (CGI) Version 1.1 +.%R RFC 3875 +.%U https://tools.ietf.org/html/rfc3875 +.%D October 2004 +.Re +.El +. +.Sh CAVEATS +Oh, so many. +No error handling, +no validation, +no security. +This is a local testing tool only. +.Pp +Every response is served as a 200 OK. diff --git a/bin/quick.c b/bin/quick.c new file mode 100644 index 00000000..150eaf62 --- /dev/null +++ b/bin/quick.c @@ -0,0 +1,174 @@ +/* Copyright (C) 2021 C. McEnroe + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void request(int sock, char *argv[]) { + FILE *req = fdopen(dup(sock), "r"); + if (!req) err(EX_OSERR, "fdopen"); + fcntl(fileno(req), F_SETFD, FD_CLOEXEC); + + size_t cap = 0; + char *buf = NULL; + ssize_t len = getline(&buf, &cap, req); + if (len < 0) goto close; + + char *ptr = buf; + char *method = strsep(&ptr, " "); + char *query = strsep(&ptr, " "); + char *path = strsep(&query, "?"); + char *proto = strsep(&ptr, "\r\n"); + if (!method || !path || !proto) goto close; + + setenv("REQUEST_METHOD", method, 1); + setenv("PATH_INFO", path, 1); + setenv("QUERY_STRING", (query ? query : ""), 1); + setenv("SERVER_PROTOCOL", proto, 1); + unsetenv("CONTENT_TYPE"); + unsetenv("CONTENT_LENGTH"); + unsetenv("HTTP_HOST"); + + size_t bodyLen = 0; + while (0 <= (len = getline(&buf, &cap, req))) { + if (len && buf[len-1] == '\n') buf[--len] = '\0'; + if (len && buf[len-1] == '\r') buf[--len] = '\0'; + if (!len) break; + + char *value = buf; + char *header = strsep(&value, ":"); + if (!header || !value++) goto close; + + if (!strcasecmp(header, "Content-Type")) { + setenv("CONTENT_TYPE", value, 1); + } else if (!strcasecmp(header, "Content-Length")) { + bodyLen = strtoull(value, NULL, 10); + setenv("CONTENT_LENGTH", value, 1); + } else { + char buf[256]; + for (char *ch = header; *ch; ++ch) { + *ch = (*ch == '-' ? '_' : toupper(*ch)); + } + snprintf(buf, sizeof(buf), "HTTP_%s", header); + setenv(buf, value, 1); + } + } + + int rw[2]; + int error = pipe(rw); + if (error) err(EX_OSERR, "pipe"); + fcntl(rw[0], F_SETFD, FD_CLOEXEC); + fcntl(rw[1], F_SETFD, FD_CLOEXEC); + + dprintf(sock, "HTTP/1.1 200 OK\nConnection: close\n"); + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + if (!pid) { + dup2(rw[0], STDIN_FILENO); + dup2(sock, STDOUT_FILENO); + execv(argv[0], argv); + warn("%s", argv[0]); + _exit(127); + } + + close(rw[0]); + char body[4096]; + while (bodyLen) { + size_t cap = (bodyLen < sizeof(body) ? bodyLen : sizeof(body)); + size_t len = fread(&body, 1, cap, req); + if (!len) break; + write(rw[1], body, len); + bodyLen -= len; + } + close(rw[1]); + + int status; + pid = wait(&status); + if (pid < 0) err(EX_OSERR, "wait"); + if (WIFEXITED(status) && WEXITSTATUS(status)) { + warnx("%s exited %d", argv[0], WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + warnx("%s killed %d", argv[0], WTERMSIG(status)); + } + +close: + fclose(req); + free(buf); +} + +int main(int argc, char *argv[]) { + short port = 0; + for (int opt; 0 < (opt = getopt(argc, argv, "p:"));) { + switch (opt) { + break; case 'p': port = atoi(optarg); + break; default: return EX_USAGE; + } + } + if (optind == argc) errx(EX_USAGE, "script required"); + + int server = socket(AF_INET, SOCK_STREAM, 0); + if (server < 0) err(EX_OSERR, "socket"); + fcntl(server, F_SETFD, FD_CLOEXEC); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + socklen_t addrlen = sizeof(addr); + int error = 0 + || bind(server, (struct sockaddr *)&addr, addrlen) + || getsockname(server, (struct sockaddr *)&addr, &addrlen) + || listen(server, -1); + if (error) err(EX_UNAVAILABLE, "%hd", port); + + char host[NI_MAXHOST], serv[NI_MAXSERV]; + error = getnameinfo( + (struct sockaddr *)&addr, addrlen, + host, sizeof(host), serv, sizeof(serv), + NI_NOFQDN + ); + if (error) errx(EX_UNAVAILABLE, "getnameinfo: %s", gai_strerror(error)); + printf("http://%s:%s/\n", host, serv); + fflush(stdout); + + setenv("SERVER_SOFTWARE", "quick (and dirty)", 1); + setenv("GATEWAY_INTERFACE", "CGI/1.1", 1); + setenv("SERVER_NAME", host, 1); + setenv("SERVER_PORT", serv, 1); + setenv("REMOTE_ADDR", "127.0.0.1", 1); + setenv("REMOTE_HOST", host, 1); + setenv("SCRIPT_NAME", "/", 1); + + signal(SIGPIPE, SIG_IGN); + for (int sock; 0 <= (sock = accept(server, NULL, NULL)); close(sock)) { + request(sock, &argv[optind]); + } + err(EX_IOERR, "accept"); +} -- cgit 1.4.1