sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

commit b27a0a8809008352e29a13beb35938a19db2893a
parent c5f28fe70e77b3a7e31b7d54a796d70012d7255c
Author: Platon Ryzhikov <ihummer63@yandex.ru>
Date:   Sun, 17 Mar 2019 12:26:21 +0300

Add patch for basic cgi support for quark

Diffstat:
Atools.suckless.org/quark/patches/basecgi/index.md | 16++++++++++++++++
Atools.suckless.org/quark/patches/basecgi/quark-basecgi-20190317-4677877.diff | 408+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools.suckless.org/quark/patches/index.md | 4++++
3 files changed, 428 insertions(+), 0 deletions(-)

diff --git a/tools.suckless.org/quark/patches/basecgi/index.md b/tools.suckless.org/quark/patches/basecgi/index.md @@ -0,0 +1,16 @@ +Base cgi support +================ + +Description +----------- +This patch adds basic cgi support for quark. It directly executes given script sending data via pipe in case of POST method and then reads script's stdout and sends it to client. This patch allows quark to accept any input data except for uploading files. Scripts are searched relatively to server root (-d option). This option is proceed before vhosts. + +This patch is not tested on UDS. + +Download +-------- +* [quark-basecgi-20190317-4677877.diff](quark-basecgi-20190317-4677877.diff) + +Author +------ +* Platon Ryzhikov <ihummer63@yandex.ru> diff --git a/tools.suckless.org/quark/patches/basecgi/quark-basecgi-20190317-4677877.diff b/tools.suckless.org/quark/patches/basecgi/quark-basecgi-20190317-4677877.diff @@ -0,0 +1,408 @@ +From 4677877693196823e8d806b0a0f520a35dd08533 Mon Sep 17 00:00:00 2001 +From: Platon Ryzhikov <ihummer63@yandex.ru> +Date: Sun, 17 Mar 2019 11:44:36 +0300 +Subject: [PATCH] Add basic cgi support + +--- + http.c | 67 ++++++++++++++++++++++++++++++++++++++++---------- + http.h | 3 +++ + main.c | 25 +++++++++++++++++-- + quark.1 | 20 ++++++++++++++- + resp.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + resp.h | 1 + + util.h | 8 ++++++ + 7 files changed, 184 insertions(+), 16 deletions(-) + +diff --git a/http.c b/http.c +index efc4136..d3af686 100644 +--- a/http.c ++++ b/http.c +@@ -8,6 +8,7 @@ + #include <stddef.h> + #include <stdint.h> + #include <stdio.h> ++#include <stdlib.h> + #include <string.h> + #include <strings.h> + #include <sys/socket.h> +@@ -30,10 +31,12 @@ const char *req_field_str[] = { + const char *req_method_str[] = { + [M_GET] = "GET", + [M_HEAD] = "HEAD", ++ [M_POST] = "POST", + }; + + const char *status_str[] = { + [S_OK] = "OK", ++ [S_NO_CONTENT] = "No content", + [S_PARTIAL_CONTENT] = "Partial Content", + [S_MOVED_PERMANENTLY] = "Moved Permanently", + [S_NOT_MODIFIED] = "Not Modified", +@@ -97,6 +100,7 @@ http_get_request(int fd, struct request *r) + size_t hlen, i, mlen; + ssize_t off; + char h[HEADER_MAX], *p, *q; ++ size_t clen; + + /* empty all fields */ + memset(r, 0, sizeof(*r)); +@@ -111,23 +115,23 @@ http_get_request(int fd, struct request *r) + break; + } + hlen += off; +- if (hlen >= 4 && !memcmp(h + hlen - 4, "\r\n\r\n", 4)) { +- break; ++ if (hlen >= 4 && strstr(h, "\r\n\r\n")) { ++ if (strstr(h, "Content-Length:")) { ++ /* Make sure that all data is read */ ++ sscanf(strstr(h, "Content-Length:"), "Content-Length: %lu", &clen); ++ if (strlen(strstr(h, "\r\n\r\n")) == 4 + clen) { ++ break; ++ } ++ } ++ else { ++ break; ++ } + } + if (hlen == sizeof(h)) { + return http_send_status(fd, S_REQUEST_TOO_LARGE); + } + } + +- /* remove terminating empty line */ +- if (hlen < 2) { +- return http_send_status(fd, S_BAD_REQUEST); +- } +- hlen -= 2; +- +- /* null-terminate the header */ +- h[hlen] = '\0'; +- + /* + * parse request line + */ +@@ -137,6 +141,7 @@ http_get_request(int fd, struct request *r) + mlen = strlen(req_method_str[i]); + if (!strncmp(req_method_str[i], h, mlen)) { + r->method = i; ++ setenv("REQUEST_METHOD", req_method_str[i], 1); + break; + } + } +@@ -161,7 +166,6 @@ http_get_request(int fd, struct request *r) + return http_send_status(fd, S_REQUEST_TOO_LARGE); + } + memcpy(r->target, p, q - p + 1); +- decode(r->target, r->target); + + /* basis for next step */ + p = q + 1; +@@ -200,7 +204,11 @@ http_get_request(int fd, struct request *r) + if (i == NUM_REQ_FIELDS) { + /* unmatched field, skip this line */ + if (!(q = strstr(p, "\r\n"))) { +- return http_send_status(fd, S_BAD_REQUEST); ++ if (r->method == M_POST) { ++ break; ++ } else { ++ return http_send_status(fd, S_BAD_REQUEST); ++ } + } + p = q + (sizeof("\r\n") - 1); + continue; +@@ -230,6 +238,9 @@ http_get_request(int fd, struct request *r) + /* go to next line */ + p = q + (sizeof("\r\n") - 1); + } ++ ++ /* all other data will be later passed to script */ ++ sprintf(r->cgicont, "%s", p); + + /* + * clean up host +@@ -361,6 +372,36 @@ http_send_response(int fd, struct request *r) + /* make a working copy of the target */ + memcpy(realtarget, r->target, sizeof(realtarget)); + ++ /* check if there is some query string */ ++ if (strrchr(realtarget, '?')) { ++ snprintf(tmptarget, sizeof(realtarget), "%s", strtok(realtarget, "?")); ++ setenv("QUERY_STRING", strtok(NULL, "?"), 1); ++ memcpy(realtarget, tmptarget, sizeof(tmptarget)); ++ } ++ decode(realtarget, tmptarget); ++ ++ /* match cgi */ ++ if (s.cgi) { ++ for (i = 0; i < s.cgi_len; i++) { ++ if (!regexec(&s.cgi[i].re, realtarget, 0, ++ NULL, 0)) { ++ snprintf(realtarget, sizeof(tmptarget) + sizeof(s.cgi[i].dir) - 1, "%s%s", s.cgi[i].dir, tmptarget); ++ if (stat(RELPATH(realtarget), &st) < 0) { ++ return http_send_status(fd, (errno == EACCES) ? ++ S_FORBIDDEN : S_NO_CONTENT); ++ } ++ setenv("SERVER_NAME", r->field[REQ_HOST], 1); ++ if (s.port) { ++ setenv("SERVER_PORT", s.port, 1); ++ } ++ setenv("SCRIPT_NAME", realtarget, 1); ++ return resp_cgi(fd, RELPATH(realtarget), r, &st); ++ } ++ } ++ } ++ ++ memcpy(realtarget, tmptarget, sizeof(tmptarget)); ++ + /* match vhost */ + vhostmatch = NULL; + if (s.vhost) { +diff --git a/http.h b/http.h +index cd1ba22..b438759 100644 +--- a/http.h ++++ b/http.h +@@ -19,6 +19,7 @@ extern const char *req_field_str[]; + enum req_method { + M_GET, + M_HEAD, ++ M_POST, + NUM_REQ_METHODS, + }; + +@@ -28,10 +29,12 @@ struct request { + enum req_method method; + char target[PATH_MAX]; + char field[NUM_REQ_FIELDS][FIELD_MAX]; ++ char cgicont[PATH_MAX]; + }; + + enum status { + S_OK = 200, ++ S_NO_CONTENT = 204, + S_PARTIAL_CONTENT = 206, + S_MOVED_PERMANENTLY = 301, + S_NOT_MODIFIED = 304, +diff --git a/main.c b/main.c +index 9e7788f..471a3a7 100644 +--- a/main.c ++++ b/main.c +@@ -165,7 +165,7 @@ static void + usage(void) + { + const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] " +- "[-i file] [-v vhost] ... [-m map] ..."; ++ "[-i file] [-v vhost] ... [-m map] ... [-c cgi] ..."; + + die("usage: %s -h host -p port %s\n" + " %s -U file [-p port] %s", argv0, +@@ -195,11 +195,23 @@ main(int argc, char *argv[]) + s.host = s.port = NULL; + s.vhost = NULL; + s.map = NULL; +- s.vhost_len = s.map_len = 0; ++ s.cgi = NULL; ++ s.vhost_len = s.map_len = s.cgi_len = 0; + s.docindex = "index.html"; + s.listdirs = 0; + + ARGBEGIN { ++ case 'c': ++ if (spacetok(EARGF(usage()), tok, 2) || !tok[0] || !tok[1]) { ++ usage(); ++ } ++ if (!(s.cgi = reallocarray(s.cgi, ++s.cgi_len, ++ sizeof(struct cgi)))) { ++ die("reallocarray:"); ++ } ++ s.cgi[s.cgi_len - 1].regex = tok[0]; ++ s.cgi[s.cgi_len - 1].dir = tok[1]; ++ break; + case 'd': + servedir = EARGF(usage()); + break; +@@ -286,6 +298,15 @@ main(int argc, char *argv[]) + } + } + ++ /* compile and check the supplied cgi regexes */ ++ for (i = 0; i < s.cgi_len; i++) { ++ if (regcomp(&s.cgi[i].re, s.cgi[i].regex, ++ REG_EXTENDED | REG_ICASE | REG_NOSUB)) { ++ die("regcomp '%s': invalid regex", ++ s.cgi[i].regex); ++ } ++ } ++ + /* raise the process limit */ + rlim.rlim_cur = rlim.rlim_max = maxnprocs; + if (setrlimit(RLIMIT_NPROC, &rlim) < 0) { +diff --git a/quark.1 b/quark.1 +index ce315b5..cbbcff3 100644 +--- a/quark.1 ++++ b/quark.1 +@@ -16,6 +16,7 @@ + .Op Fl i Ar file + .Oo Fl v Ar vhost Oc ... + .Oo Fl m Ar map Oc ... ++.Oo Fl c Ar cgi Oc ... + .Nm + .Fl U Ar file + .Op Fl p Ar port +@@ -27,11 +28,28 @@ + .Op Fl i Ar file + .Oo Fl v Ar vhost Oc ... + .Oo Fl m Ar map Oc ... ++.Oo Fl c Ar cgi Oc ... + .Sh DESCRIPTION + .Nm +-is a simple HTTP GET/HEAD-only web server for static content. ++is a simple HTTP web server. + .Sh OPTIONS + .Bl -tag -width Ds ++.It Fl c Ar cgi ++Add the target prefix mapping rule for dynamic content specified by ++.Ar cgi , ++which has the form ++.Qq Pa regex dir , ++where each element is separated with spaces (0x20) that can be ++escaped with '\\'. ++.Pp ++A request matching cgi regular expression ++.Pa regex ++(see ++.Xr regex 3 ) ++executes script located in ++.Pa dir ++passing data to it via QUERY_STRING environment variable ++or via stdout and then sends its stdout. + .It Fl d Ar dir + Serve + .Ar dir +diff --git a/resp.c b/resp.c +index 3075c28..dccdc3f 100644 +--- a/resp.c ++++ b/resp.c +@@ -38,6 +38,82 @@ suffix(int t) + return ""; + } + ++enum status ++resp_cgi(int fd, char *name, struct request *r, struct stat *st) ++{ ++ enum status sta; ++ int tocgi[2], fromcgi[2]; ++ pid_t script; ++ ssize_t bread, bwritten; ++ static char buf[BUFSIZ], t[TIMESTAMP_LEN]; ++ ++ /* check if script is executable */ ++ if (!(st->st_mode & S_IXOTH)) { ++ return http_send_status(fd, S_FORBIDDEN); ++ } ++ ++ /* open two pipes in case for POST method; this doesn't break operation if GET method is used */ ++ if (pipe(fromcgi) < 0) { ++ return http_send_status(fd, S_INTERNAL_SERVER_ERROR); ++ } ++ ++ if (pipe(tocgi) < 0) { ++ return http_send_status(fd, S_INTERNAL_SERVER_ERROR); ++ } ++ ++ /* start script */ ++ if (!(script = fork())) { ++ close(0); ++ close(1); ++ close(fromcgi[0]); ++ close(tocgi[1]); ++ dup2(fromcgi[1], 1); ++ dup2(tocgi[0], 0); ++ execlp(name, name, (char*) NULL); ++ } ++ ++ if (script < 0) { ++ return http_send_status(fd, S_INTERNAL_SERVER_ERROR); ++ } ++ close(fromcgi[1]); ++ close(tocgi[0]); ++ ++ /* POST method should obtain its data */ ++ if (dprintf(tocgi[1], "%s\n", r->cgicont) < 0) { ++ return http_send_status(fd, S_INTERNAL_SERVER_ERROR); ++ } ++ close(tocgi[1]); ++ ++ /* send header as late as possible */ ++ if (dprintf(fd, ++ "HTTP/1.1 %d %s\r\n" ++ "Date: %s\r\n" ++ "Connection: close\r\n", ++ S_OK, status_str[S_OK], timestamp(time(NULL), t)) < 0) { ++ sta = S_REQUEST_TIMEOUT; ++ goto cleanup; ++ } ++ ++ while ((bread = read(fromcgi[0], buf, BUFSIZ)) > 0) { ++ if (bread < 0) { ++ return S_INTERNAL_SERVER_ERROR; ++ } ++ ++ bwritten = write(fd, buf, bread); ++ ++ if (bwritten < 0) { ++ return S_REQUEST_TIMEOUT; ++ } ++ } ++ sta = S_OK; ++cleanup: ++ if (fromcgi[0]) { ++ close(fromcgi[0]); ++ } ++ ++ return sta; ++} ++ + enum status + resp_dir(int fd, char *name, struct request *r) + { +diff --git a/resp.h b/resp.h +index d5928ef..2705364 100644 +--- a/resp.h ++++ b/resp.h +@@ -7,6 +7,7 @@ + + #include "http.h" + ++enum status resp_cgi(int, char *, struct request *, struct stat *); + enum status resp_dir(int, char *, struct request *); + enum status resp_file(int, char *, struct request *, struct stat *, char *, + off_t, off_t); +diff --git a/util.h b/util.h +index 12b7bd8..ef1a8b3 100644 +--- a/util.h ++++ b/util.h +@@ -23,6 +23,12 @@ struct map { + char *to; + }; + ++struct cgi { ++ char *regex; ++ char *dir; ++ regex_t re; ++}; ++ + extern struct server { + char *host; + char *port; +@@ -32,6 +38,8 @@ extern struct server { + size_t vhost_len; + struct map *map; + size_t map_len; ++ struct cgi *cgi; ++ size_t cgi_len; + } s; + + #undef MIN +-- +2.21.0 + diff --git a/tools.suckless.org/quark/patches/index.md b/tools.suckless.org/quark/patches/index.md @@ -0,0 +1,4 @@ +patches +======= +For instructions on how to submit and format patches, take a look at the +[hacking guidelines](//suckless.org/hacking).