diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2017-01-04 11:38:46 -0500 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2017-01-31 14:36:15 -0500 |
commit | 4a68780e1b1d5388d2d67ab518d1d3c424b792d3 (patch) | |
tree | 8b34a6fe838022085ce3216d0dab942b2e9fd45e /tests/scgi-responder.c | |
parent | d246656f5bf640d1c806fa1a4c28e7df1fc344da (diff) | |
download | lighttpd-git-4a68780e1b1d5388d2d67ab518d1d3c424b792d3.tar.gz |
[mod_scgi] tests/mod-scgi.t unit tests
(copied from tests/mod-fastcgi.t fcgi-responder tests)
Diffstat (limited to 'tests/scgi-responder.c')
-rw-r--r-- | tests/scgi-responder.c | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/tests/scgi-responder.c b/tests/scgi-responder.c new file mode 100644 index 00000000..0c4244b7 --- /dev/null +++ b/tests/scgi-responder.c @@ -0,0 +1,219 @@ +/* + * simple and trivial SCGI server with hard-coded results for use in unit tests + * - listens on STDIN_FILENO (socket on STDIN_FILENO must be set up by caller) + * - processes a single SCGI request at a time + * - arbitrary limitation: reads request headers netstring up to 64k in size + * - expect recv data for request headers netstring every 10ms or less + * - no read timeouts for request body; might block reading request body + * - no write timeouts; might block writing response + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif + +static int finished; +static char buf[65536]; + + +static char * +scgi_getenv(char *r, const unsigned long rlen, const char * const name) +{ + /* simple search; + * if many lookups are done, then should use more efficient data structure*/ + char * const end = r+rlen; + char *z; + const size_t len = strlen(name); + do { + if (0 == strcmp(r, name)) return r+len+1; + + z = memchr(r, '\0', (size_t)(end-r)); + if (NULL == z) return NULL; + z = memchr(z+1, '\0', (size_t)(end-r)); + if (NULL == z) return NULL; + r = z+1; + } while (r < end); + return NULL; +} + + +static void +scgi_process (const int fd) +{ + ssize_t rd = 0, offset = 0; + int num_requests = 1; + char *p = NULL, *r; + unsigned long rlen; + long long cl; + + assert(fd == STDOUT_FILENO); /*(required for response sent with printf())*/ + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + + do { + struct pollfd pfd = { fd, POLLIN, 0 }; + switch (poll(&pfd, 1, 10)) { /* 10ms timeout */ + default: /* 1; the only pfd has revents */ + break; + case -1: /* error */ + case 0: /* timeout */ + pfd.revents |= POLLERR; + break; + } + if (!(pfd.revents & POLLIN)) + break; + do { + rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT); + } while (rd < 0 && errno == EINTR); + if (rd > 0) + offset += rd; + else if (0 == rd) { + p = memchr(buf, ':', offset); + break; + } + else if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + else + break; + } while (NULL == (p = memchr(buf,':',offset)) && offset < 21); + if (NULL == p) + return; /* timeout or error receiving start of netstring */ + rlen = strtoul(buf, &p, 10); + if (*buf == '-' || *p != ':' || p == buf || rlen == ULONG_MAX) + return; /* invalid netstring (and rlen == ULONG_MAX is too long)*/ + if (rlen > sizeof(buf) - (p - buf) - 2) + return; /* netstring longer than arbitrary limit we accept here */ + rlen += (p - buf) + 2; + + while ((ssize_t)rlen < offset) { + struct pollfd pfd = { fd, POLLIN, 0 }; + switch (poll(&pfd, 1, 10)) { /* 10ms timeout */ + default: /* 1; the only pfd has revents */ + break; + case -1: /* error */ + case 0: /* timeout */ + pfd.revents |= POLLERR; + break; + } + if (!(pfd.revents & POLLIN)) + break; + do { + rd = recv(fd, buf+offset, sizeof(buf)-offset, MSG_DONTWAIT); + } while (rd < 0 && errno == EINTR); + if (rd > 0) + offset += rd; + else if (0 == rd) + break; + else if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + else + break; + } + if (offset < (ssize_t)rlen) + return; /* timeout or error receiving netstring */ + if (buf[rlen-1] != ',') + return; /* invalid netstring */ + rlen -= (p - buf) + 2; + r = p+1; + + /* not checking for empty headers in SCGI request (empty values allowed) */ + + /* SCGI request must contain "SCGI" header with value "1" */ + p = scgi_getenv(r, rlen, "SCGI"); + if (NULL == p || p[0] != '1' || p[1] != '\0') + return; /* missing or invalid SCGI header */ + + /* CONTENT_LENGTH must be first header in SCGI request; always required */ + if (0 != strcmp(r, "CONTENT_LENGTH")) + return; /* missing CONTENT_LENGTH */ + + errno = 0; + cl = strtoll(r+sizeof("CONTENT_LENGTH"), &p, 10); + if (*p != '\0' || p == r+sizeof("CONTENT_LENGTH") || cl < 0 || 0 != errno) + return; /* invalid CONTENT_LENGTH */ + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + + /* read,discard request body (currently ignored in these SCGI unit tests) + * (make basic effort to read body; ignore any timeouts or errors here) */ + cl -= (offset - (r+rlen+1 - buf)); + while (cl > 0) { + char x[8192]; + do { + rd = recv(fd, x, (cl>(long long)sizeof(x)?sizeof(x):(size_t)cl), 0); + } while (rd < 0 && errno == EINTR); + if (rd <= 0) + break; + cl -= rd; + } + + /*(from fcgi-responder.c, substituting scgi_getenv() for getenv())*/ + { + if (NULL != (p = scgi_getenv(r, rlen, "QUERY_STRING"))) { + if (0 == strcmp(p, "lf")) { + printf("Status: 200 OK\n\n"); + } else if (0 == strcmp(p, "crlf")) { + printf("Status: 200 OK\r\n\r\n"); + } else if (0 == strcmp(p, "slow-lf")) { + printf("Status: 200 OK\n"); + fflush(stdout); + printf("\n"); + } else if (0 == strcmp(p,"slow-crlf")) { + printf("Status: 200 OK\r\n"); + fflush(stdout); + printf("\r\n"); + } else if (0 == strcmp(p, "die-at-end")) { + printf("Status: 200 OK\r\n\r\n"); + num_requests--; + } else { + printf("Status: 200 OK\r\n\r\n"); + } + } else { + printf("Status: 500 Internal Foo\r\n\r\n"); + } + + if (0 == strcmp(p, "path_info")) { + printf("%s", scgi_getenv(r, rlen, "PATH_INFO")); + } else if (0 == strcmp(p, "script_name")) { + printf("%s", scgi_getenv(r, rlen, "SCRIPT_NAME")); + } else if (0 == strcmp(p, "var")) { + p = scgi_getenv(r, rlen, "X_LIGHTTPD_FCGI_AUTH"); + printf("%s", p ? p : "(no value)"); + } else { + printf("test123"); + } + } + + fflush(stdout); + if (0 == num_requests) finished = 1; +} + + +int +main (void) +{ + int fd; + fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK); + close(STDOUT_FILENO); /*(so that accept() returns fd to STDOUT_FILENO)*/ + + do { + fd = accept(STDIN_FILENO, NULL, NULL); + if (fd < 0) + continue; + assert(fd == STDOUT_FILENO); + scgi_process(fd); + } while (fd > 0 ? 0 == close(fd) && !finished : errno == EINTR); + + return 0; +} |