quark-basecgi-20190317-4677877.diff - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) 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