quark-basecgi-20190317-4677877.diff - sites - public wiki contents of suckless.… | |
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 <[email protected]> | |
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:"), "C… | |
59 + if (strlen(strstr(h, "\r\n\r\n")) == 4 … | |
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_REQUE… | |
105 + if (r->method == M_POST) { | |
106 + break; | |
107 + } else { | |
108 + return http_send_status(fd, S_B… | |
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(re… | |
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) … | |
141 + if (stat(RELPATH(realtarget), &st) < 0)… | |
142 + return http_send_status(fd, (er… | |
143 + S_FORBI… | |
144 + } | |
145 + setenv("SERVER_NAME", r->field[REQ_HOST… | |
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)… | |
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 c… | |
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… | |
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 o… | |
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_E… | |
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)) <… | |
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 *, cha… | |
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 |