sites

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

quark-basecgi-20190317-4677877.diff (10208B)


      1 From 4677877693196823e8d806b0a0f520a35dd08533 Mon Sep 17 00:00:00 2001
      2 From: Platon Ryzhikov <ihummer63@yandex.ru>
      3 Date: Sun, 17 Mar 2019 11:44:36 +0300
      4 Subject: [PATCH] Add basic cgi support
      5 
      6 ---
      7  http.c  | 67 ++++++++++++++++++++++++++++++++++++++++----------
      8  http.h  |  3 +++
      9  main.c  | 25 +++++++++++++++++--
     10  quark.1 | 20 ++++++++++++++-
     11  resp.c  | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     12  resp.h  |  1 +
     13  util.h  |  8 ++++++
     14  7 files changed, 184 insertions(+), 16 deletions(-)
     15 
     16 diff --git a/http.c b/http.c
     17 index efc4136..d3af686 100644
     18 --- a/http.c
     19 +++ b/http.c
     20 @@ -8,6 +8,7 @@
     21  #include <stddef.h>
     22  #include <stdint.h>
     23  #include <stdio.h>
     24 +#include <stdlib.h>
     25  #include <string.h>
     26  #include <strings.h>
     27  #include <sys/socket.h>
     28 @@ -30,10 +31,12 @@ const char *req_field_str[] = {
     29  const char *req_method_str[] = {
     30  	[M_GET]  = "GET",
     31  	[M_HEAD] = "HEAD",
     32 +	[M_POST] = "POST",
     33  };
     34  
     35  const char *status_str[] = {
     36  	[S_OK]                    = "OK",
     37 +	[S_NO_CONTENT]            = "No content",
     38  	[S_PARTIAL_CONTENT]       = "Partial Content",
     39  	[S_MOVED_PERMANENTLY]     = "Moved Permanently",
     40  	[S_NOT_MODIFIED]          = "Not Modified",
     41 @@ -97,6 +100,7 @@ http_get_request(int fd, struct request *r)
     42  	size_t hlen, i, mlen;
     43  	ssize_t off;
     44  	char h[HEADER_MAX], *p, *q;
     45 +	size_t clen;
     46  
     47  	/* empty all fields */
     48  	memset(r, 0, sizeof(*r));
     49 @@ -111,23 +115,23 @@ http_get_request(int fd, struct request *r)
     50  			break;
     51  		}
     52  		hlen += off;
     53 -		if (hlen >= 4 && !memcmp(h + hlen - 4, "\r\n\r\n", 4)) {
     54 -			break;
     55 +		if (hlen >= 4 && strstr(h, "\r\n\r\n")) {
     56 +			if (strstr(h, "Content-Length:")) {
     57 +				/* Make sure that all data is read */
     58 +				sscanf(strstr(h, "Content-Length:"), "Content-Length: %lu", &clen);
     59 +				if (strlen(strstr(h, "\r\n\r\n")) == 4 + clen) {
     60 +					break;
     61 +				}
     62 +			}
     63 +			else {
     64 +				break;
     65 +			}
     66  		}
     67  		if (hlen == sizeof(h)) {
     68  			return http_send_status(fd, S_REQUEST_TOO_LARGE);
     69  		}
     70  	}
     71  
     72 -	/* remove terminating empty line */
     73 -	if (hlen < 2) {
     74 -		return http_send_status(fd, S_BAD_REQUEST);
     75 -	}
     76 -	hlen -= 2;
     77 -
     78 -	/* null-terminate the header */
     79 -	h[hlen] = '\0';
     80 -
     81  	/*
     82  	 * parse request line
     83  	 */
     84 @@ -137,6 +141,7 @@ http_get_request(int fd, struct request *r)
     85  		mlen = strlen(req_method_str[i]);
     86  		if (!strncmp(req_method_str[i], h, mlen)) {
     87  			r->method = i;
     88 +			setenv("REQUEST_METHOD", req_method_str[i], 1);
     89  			break;
     90  		}
     91  	}
     92 @@ -161,7 +166,6 @@ http_get_request(int fd, struct request *r)
     93  		return http_send_status(fd, S_REQUEST_TOO_LARGE);
     94  	}
     95  	memcpy(r->target, p, q - p + 1);
     96 -	decode(r->target, r->target);
     97  
     98  	/* basis for next step */
     99  	p = q + 1;
    100 @@ -200,7 +204,11 @@ http_get_request(int fd, struct request *r)
    101  		if (i == NUM_REQ_FIELDS) {
    102  			/* unmatched field, skip this line */
    103  			if (!(q = strstr(p, "\r\n"))) {
    104 -				return http_send_status(fd, S_BAD_REQUEST);
    105 +				if (r->method == M_POST) {
    106 +					break;
    107 +				} else {
    108 +					return http_send_status(fd, S_BAD_REQUEST);
    109 +				}
    110  			}
    111  			p = q + (sizeof("\r\n") - 1);
    112  			continue;
    113 @@ -230,6 +238,9 @@ http_get_request(int fd, struct request *r)
    114  		/* go to next line */
    115  		p = q + (sizeof("\r\n") - 1);
    116  	}
    117 +
    118 +	/* all other data will be later passed to script */
    119 +	sprintf(r->cgicont, "%s", p);
    120  
    121  	/*
    122  	 * clean up host
    123 @@ -361,6 +372,36 @@ http_send_response(int fd, struct request *r)
    124  	/* make a working copy of the target */
    125  	memcpy(realtarget, r->target, sizeof(realtarget));
    126  
    127 +	/* check if there is some query string */
    128 +	if (strrchr(realtarget, '?')) {
    129 +		snprintf(tmptarget, sizeof(realtarget), "%s", strtok(realtarget, "?"));
    130 +		setenv("QUERY_STRING", strtok(NULL, "?"), 1);
    131 +		memcpy(realtarget, tmptarget, sizeof(tmptarget));
    132 +	}
    133 +	decode(realtarget, tmptarget);
    134 +
    135 +	/* match cgi */
    136 +	if (s.cgi) {
    137 +		for (i = 0; i < s.cgi_len; i++) {
    138 +			if (!regexec(&s.cgi[i].re, realtarget, 0,
    139 +			             NULL, 0)) {
    140 +				snprintf(realtarget, sizeof(tmptarget) + sizeof(s.cgi[i].dir) - 1, "%s%s", s.cgi[i].dir, tmptarget);
    141 +				if (stat(RELPATH(realtarget), &st) < 0) {
    142 +					return http_send_status(fd, (errno == EACCES) ?
    143 +					                        S_FORBIDDEN : S_NO_CONTENT);
    144 +				}
    145 +				setenv("SERVER_NAME", r->field[REQ_HOST], 1);
    146 +				if (s.port) {
    147 +					setenv("SERVER_PORT", s.port, 1);
    148 +				}
    149 +				setenv("SCRIPT_NAME", realtarget, 1);
    150 +				return resp_cgi(fd, RELPATH(realtarget), r, &st);
    151 +			}
    152 +		}
    153 +	}
    154 +
    155 +	memcpy(realtarget, tmptarget, sizeof(tmptarget));
    156 +
    157  	/* match vhost */
    158  	vhostmatch = NULL;
    159  	if (s.vhost) {
    160 diff --git a/http.h b/http.h
    161 index cd1ba22..b438759 100644
    162 --- a/http.h
    163 +++ b/http.h
    164 @@ -19,6 +19,7 @@ extern const char *req_field_str[];
    165  enum req_method {
    166  	M_GET,
    167  	M_HEAD,
    168 +	M_POST,
    169  	NUM_REQ_METHODS,
    170  };
    171  
    172 @@ -28,10 +29,12 @@ struct request {
    173  	enum req_method method;
    174  	char target[PATH_MAX];
    175  	char field[NUM_REQ_FIELDS][FIELD_MAX];
    176 +	char cgicont[PATH_MAX];
    177  };
    178  
    179  enum status {
    180  	S_OK                    = 200,
    181 +	S_NO_CONTENT            = 204,
    182  	S_PARTIAL_CONTENT       = 206,
    183  	S_MOVED_PERMANENTLY     = 301,
    184  	S_NOT_MODIFIED          = 304,
    185 diff --git a/main.c b/main.c
    186 index 9e7788f..471a3a7 100644
    187 --- a/main.c
    188 +++ b/main.c
    189 @@ -165,7 +165,7 @@ static void
    190  usage(void)
    191  {
    192  	const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
    193 -	                   "[-i file] [-v vhost] ... [-m map] ...";
    194 +	                   "[-i file] [-v vhost] ... [-m map] ... [-c cgi] ...";
    195  
    196  	die("usage: %s -h host -p port %s\n"
    197  	    "       %s -U file [-p port] %s", argv0,
    198 @@ -195,11 +195,23 @@ main(int argc, char *argv[])
    199  	s.host = s.port = NULL;
    200  	s.vhost = NULL;
    201  	s.map = NULL;
    202 -	s.vhost_len = s.map_len = 0;
    203 +	s.cgi = NULL;
    204 +	s.vhost_len = s.map_len = s.cgi_len = 0;
    205  	s.docindex = "index.html";
    206  	s.listdirs = 0;
    207  
    208  	ARGBEGIN {
    209 +	case 'c':
    210 +		if (spacetok(EARGF(usage()), tok, 2) || !tok[0] || !tok[1]) {
    211 +			usage();
    212 +		}
    213 +		if (!(s.cgi = reallocarray(s.cgi, ++s.cgi_len,
    214 +		                             sizeof(struct cgi)))) {
    215 +			die("reallocarray:");
    216 +		}
    217 +		s.cgi[s.cgi_len - 1].regex  = tok[0];
    218 +		s.cgi[s.cgi_len - 1].dir  = tok[1];
    219 +		break;
    220  	case 'd':
    221  		servedir = EARGF(usage());
    222  		break;
    223 @@ -286,6 +298,15 @@ main(int argc, char *argv[])
    224  		}
    225  	}
    226  
    227 +	/* compile and check the supplied cgi regexes */
    228 +	for (i = 0; i < s.cgi_len; i++) {
    229 +		if (regcomp(&s.cgi[i].re, s.cgi[i].regex,
    230 +		            REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
    231 +			die("regcomp '%s': invalid regex",
    232 +			    s.cgi[i].regex);
    233 +		}
    234 +	}
    235 +
    236  	/* raise the process limit */
    237  	rlim.rlim_cur = rlim.rlim_max = maxnprocs;
    238  	if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
    239 diff --git a/quark.1 b/quark.1
    240 index ce315b5..cbbcff3 100644
    241 --- a/quark.1
    242 +++ b/quark.1
    243 @@ -16,6 +16,7 @@
    244  .Op Fl i Ar file
    245  .Oo Fl v Ar vhost Oc ...
    246  .Oo Fl m Ar map Oc ...
    247 +.Oo Fl c Ar cgi Oc ...
    248  .Nm
    249  .Fl U Ar file
    250  .Op Fl p Ar port
    251 @@ -27,11 +28,28 @@
    252  .Op Fl i Ar file
    253  .Oo Fl v Ar vhost Oc ...
    254  .Oo Fl m Ar map Oc ...
    255 +.Oo Fl c Ar cgi Oc ...
    256  .Sh DESCRIPTION
    257  .Nm
    258 -is a simple HTTP GET/HEAD-only web server for static content.
    259 +is a simple HTTP web server.
    260  .Sh OPTIONS
    261  .Bl -tag -width Ds
    262 +.It Fl c Ar cgi
    263 +Add the target prefix mapping rule for dynamic content specified by
    264 +.Ar cgi ,
    265 +which has the form
    266 +.Qq Pa regex dir ,
    267 +where each element is separated with spaces (0x20) that can be
    268 +escaped with '\\'.
    269 +.Pp
    270 +A request matching cgi regular expression
    271 +.Pa regex
    272 +(see
    273 +.Xr regex 3 )
    274 +executes script located in
    275 +.Pa dir
    276 +passing data to it via QUERY_STRING environment variable
    277 +or via stdout and then sends its stdout.
    278  .It Fl d Ar dir
    279  Serve
    280  .Ar dir
    281 diff --git a/resp.c b/resp.c
    282 index 3075c28..dccdc3f 100644
    283 --- a/resp.c
    284 +++ b/resp.c
    285 @@ -38,6 +38,82 @@ suffix(int t)
    286  	return "";
    287  }
    288  
    289 +enum status
    290 +resp_cgi(int fd, char *name, struct request *r, struct stat *st)
    291 +{
    292 +	enum status sta;
    293 +	int tocgi[2], fromcgi[2];
    294 +	pid_t script;
    295 +	ssize_t bread, bwritten;
    296 +	static char buf[BUFSIZ], t[TIMESTAMP_LEN];
    297 +
    298 +	/* check if script is executable */
    299 +	if (!(st->st_mode & S_IXOTH)) {
    300 +		return http_send_status(fd, S_FORBIDDEN);
    301 +	}
    302 +
    303 +	/* open two pipes in case for POST method; this doesn't break operation if GET method is used */
    304 +	if (pipe(fromcgi) < 0) {
    305 +		return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
    306 +	}
    307 +
    308 +	if (pipe(tocgi) < 0) {
    309 +			return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
    310 +	}
    311 +
    312 +	/* start script */
    313 +	if (!(script = fork())) {
    314 +		close(0);
    315 +		close(1);
    316 +		close(fromcgi[0]);
    317 +		close(tocgi[1]);
    318 +		dup2(fromcgi[1], 1);
    319 +		dup2(tocgi[0], 0);
    320 +		execlp(name, name, (char*) NULL);
    321 +	}
    322 +
    323 +	if (script < 0) {
    324 +		return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
    325 +	}
    326 +	close(fromcgi[1]);
    327 +	close(tocgi[0]);
    328 +
    329 +	/* POST method should obtain its data */
    330 +	if (dprintf(tocgi[1], "%s\n", r->cgicont) < 0) {
    331 +		return http_send_status(fd, S_INTERNAL_SERVER_ERROR);
    332 +	}
    333 +	close(tocgi[1]);
    334 +
    335 +	/* send header as late as possible */
    336 +	if (dprintf(fd,
    337 +	            "HTTP/1.1 %d %s\r\n"
    338 +	            "Date: %s\r\n"
    339 +	            "Connection: close\r\n",
    340 +	            S_OK, status_str[S_OK], timestamp(time(NULL), t)) < 0) {
    341 +		sta = S_REQUEST_TIMEOUT;
    342 +		goto cleanup;
    343 +	}
    344 +
    345 +	while ((bread = read(fromcgi[0], buf, BUFSIZ)) > 0) {
    346 +		if (bread < 0) {
    347 +			return S_INTERNAL_SERVER_ERROR;
    348 +		}
    349 +
    350 +		bwritten = write(fd, buf, bread);
    351 +
    352 +		if (bwritten < 0) {
    353 +			return S_REQUEST_TIMEOUT;
    354 +		}
    355 +	}
    356 +	sta = S_OK;
    357 +cleanup:
    358 +	if (fromcgi[0]) {
    359 +		close(fromcgi[0]);
    360 +	}
    361 +
    362 +	return sta;
    363 +}
    364 +
    365  enum status
    366  resp_dir(int fd, char *name, struct request *r)
    367  {
    368 diff --git a/resp.h b/resp.h
    369 index d5928ef..2705364 100644
    370 --- a/resp.h
    371 +++ b/resp.h
    372 @@ -7,6 +7,7 @@
    373  
    374  #include "http.h"
    375  
    376 +enum status resp_cgi(int, char *, struct request *, struct stat *);
    377  enum status resp_dir(int, char *, struct request *);
    378  enum status resp_file(int, char *, struct request *, struct stat *, char *,
    379                        off_t, off_t);
    380 diff --git a/util.h b/util.h
    381 index 12b7bd8..ef1a8b3 100644
    382 --- a/util.h
    383 +++ b/util.h
    384 @@ -23,6 +23,12 @@ struct map {
    385  	char *to;
    386  };
    387  
    388 +struct cgi {
    389 +	char *regex;
    390 +	char *dir;
    391 +	regex_t re;
    392 +};
    393 +
    394  extern struct server {
    395  	char *host;
    396  	char *port;
    397 @@ -32,6 +38,8 @@ extern struct server {
    398  	size_t vhost_len;
    399  	struct map *map;
    400  	size_t map_len;
    401 +	struct cgi *cgi;
    402 +	size_t cgi_len;
    403  } s;
    404  
    405  #undef MIN
    406 -- 
    407 2.21.0
    408