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