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