| 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 |