summaryrefslogtreecommitdiff
path: root/tests/fcgi-responder.c
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2020-12-20 21:32:50 -0500
committerGlenn Strauss <gstrauss@gluelogic.com>2020-12-23 03:49:43 -0500
commitfc01b820ecf497c0b3bf4dbf82f83852d2c7aefd (patch)
tree968ef15af5d8bb195e9e124b96f723cf1a773b1a /tests/fcgi-responder.c
parentc68a7b4552b49052b3876a3e41d772ab051a1d3c (diff)
downloadlighttpd-git-fc01b820ecf497c0b3bf4dbf82f83852d2c7aefd.tar.gz
[tests] remove FastCGI test dependency on libfcgi
- rewrite fcgi-responder as standalone app fcgi-responder is now a minimal, standalone FastCGI server for tests - remove dependency on fcgi-devel package - merge fcgi-auth into fcgi-responder
Diffstat (limited to 'tests/fcgi-responder.c')
-rw-r--r--tests/fcgi-responder.c426
1 files changed, 374 insertions, 52 deletions
diff --git a/tests/fcgi-responder.c b/tests/fcgi-responder.c
index be626ee2..64bcc2a9 100644
--- a/tests/fcgi-responder.c
+++ b/tests/fcgi-responder.c
@@ -1,56 +1,378 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-#ifdef HAVE_FASTCGI_FASTCGI_H
-#include <fastcgi/fcgi_stdio.h>
-#else
-#include <fcgi_stdio.h>
-#endif
+/*
+ * simple and trivial FastCGI server w/ hard-coded results for use in unit tests
+ * - processes a single FastCGI request at a time (serially)
+ * - listens on FCGI_LISTENSOCK_FILENO
+ * (socket on FCGI_LISTENSOCK_FILENO must be set up by invoker)
+ * expects to be started w/ listening socket already on FCGI_LISTENSOCK_FILENO
+ * - expect recv data for request headers every 10ms or less
+ * - no read timeouts for request body; might block reading request body
+ * - no write timeouts; might block writing response
+ *
+ * Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
+ * License: BSD 3-clause (same as lighttpd)
+ */
+
+#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 <unistd.h>
+#include <stdio.h>
+#include <stdint.h>
#include <string.h>
+#include <unistd.h>
+
+#ifndef MSG_DONTWAIT
+#define MSG_DONTWAIT 0
+#endif
+
+#include "../src/compat/fastcgi.h"
+
+static int finished;
+static unsigned char buf[65536];
+
+
+static void
+fcgi_header (FCGI_Header * const header, const unsigned char type, const int request_id, const int contentLength, const unsigned char paddingLength)
+{
+ /*force_assert(contentLength <= FCGI_MAX_LENGTH);*/
+
+ header->version = FCGI_VERSION_1;
+ header->type = type;
+ header->requestIdB1 = (request_id >> 8) & 0xff;
+ header->requestIdB0 = request_id & 0xff;
+ header->contentLengthB1 = (contentLength >> 8) & 0xff;
+ header->contentLengthB0 = contentLength & 0xff;
+ header->paddingLength = paddingLength;
+ header->reserved = 0;
+}
+
+
+static void
+fcgi_unknown_type_rec (FCGI_UnknownTypeRecord * const rec, const int req_id, const unsigned char type)
+{
+ fcgi_header(&rec->header, FCGI_UNKNOWN_TYPE, req_id, sizeof(rec->header), 0);
+ memset(&rec->body.reserved, 0, sizeof(rec->body.reserved));
+ rec->body.type = type;
+}
+
+
+static void
+fcgi_end_request_rec (FCGI_EndRequestRecord * const rec, const int req_id, const uint32_t appStatus, const unsigned char protocolStatus)
+{
+ fcgi_header(&rec->header, FCGI_END_REQUEST, req_id, sizeof(rec->header), 0);
+ rec->body.appStatusB3 = (appStatus >> 24) & 0xff;
+ rec->body.appStatusB2 = (appStatus >> 16) & 0xff;
+ rec->body.appStatusB1 = (appStatus >> 8) & 0xff;
+ rec->body.appStatusB0 = appStatus & 0xff;
+ rec->body.protocolStatus = protocolStatus;
+ rec->body.reserved[0] = 0;
+ rec->body.reserved[1] = 0;
+ rec->body.reserved[2] = 0;
+}
+
+
+static int
+fcgi_puts (const int req_id, const char * const str, size_t len, FILE * const stream)
+{
+ if (NULL == str) return -1;
+
+ FCGI_Header header;
+
+ for (size_t offset = 0, part; offset != len; offset += part) {
+ part = len - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : len - offset;
+ fcgi_header(&header, FCGI_STDOUT, req_id, part, 0);
+ if (1 != fwrite(&header, sizeof(header), 1, stream))
+ return -1;
+ if (part != fwrite(str+offset, 1, part, stream))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static const char *
+fcgi_getenv(const unsigned char * const r, const uint32_t rlen, const char * const name, int nlen, int *len)
+{
+ /* simple search;
+ * if many lookups are done, then should use more efficient data structure*/
+ for (uint32_t i = 0; i < rlen; ) {
+ int klen = r[i];
+ if (!(r[i] & 0x80))
+ ++i;
+ else {
+ klen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
+ i += 4;
+ }
+ int vlen = r[i];
+ if (!(r[i] & 0x80))
+ ++i;
+ else {
+ vlen = ((r[i] & ~0x80)<<24) | (r[i+1]<<16) | (r[i+2]<<8) | r[i+3];
+ i += 4;
+ }
+
+ if (klen == nlen && 0 == memcmp(r+i, name, klen)) {
+ *len = vlen;
+ return (const char *)r+i+klen;
+ }
+
+ i += klen + vlen;
+ }
+
+ char s[256];
+ if (nlen > (int)sizeof(s)-1)
+ return NULL;
+ memcpy(s, name, nlen);
+ s[nlen] = '\0';
+ char *e = getenv(s);
+ if (e) *len = strlen(e);
+ return e;
+}
+
+
+static int
+fcgi_process_params (FILE * const stream, int req_id, int role, unsigned char * const r, uint32_t rlen)
+{
+ const char *p = NULL;
+ int len;
+
+ /* (FCGI_STDIN currently ignored in these FastCGI unit test responses, so
+ * generate response here based on query string values (indicating test) */
+
+ const char *cdata = NULL;
+
+ if (NULL != (p = fcgi_getenv(r, rlen, "QUERY_STRING", 12, &len))) {
+ if (2 == len && 0 == memcmp(p, "lf", 2))
+ cdata = "Status: 200 OK\n\n";
+ else if (4 == len && 0 == memcmp(p, "crlf", 4))
+ cdata = "Status: 200 OK\r\n\r\n";
+ else if (7 == len && 0 == memcmp(p, "slow-lf", 7)) {
+ cdata = "Status: 200 OK\n";
+ if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
+ return -1;
+ fflush(stdout);
+ cdata = "\n";
+ }
+ else if (9 == len && 0 == memcmp(p, "slow-crlf", 9)) {
+ cdata = "Status: 200 OK\r\n";
+ if (0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
+ return -1;
+ fflush(stdout);
+ cdata = "\r\n";
+ }
+ else if (10 == len && 0 == memcmp(p, "die-at-end", 10)) {
+ cdata = "Status: 200 OK\r\n\r\n";
+ finished = 1;
+ }
+ else if (role == FCGI_AUTHORIZER
+ && len >= 5 && 0 == memcmp(p, "auth-", 5)) {
+ if (7 == len && 0 == memcmp(p, "auth-ok", 7))
+ cdata = "Status: 200 OK\r\n\r\n";
+ else if (8 == len && 0 == memcmp(p, "auth-var", 8)) {
+ /* Status: 200 OK to allow access is implied
+ * if Status header is not included in response */
+ cdata = "Variable-X-LIGHTTPD-FCGI-AUTH: "
+ "LighttpdTestContent\r\n\r\n";
+ p = NULL;
+ }
+ else {
+ cdata = "Status: 403 Forbidden\r\n\r\n";
+ p = NULL;
+ }
+ }
+ else
+ cdata = "Status: 200 OK\r\n\r\n";
+ }
+ else {
+ cdata = "Status: 500 Internal Foo\r\n\r\n";
+ p = NULL;
+ }
+
+ if (cdata && 0 != fcgi_puts(req_id, cdata, strlen(cdata), stream))
+ return -1;
+
+ if (NULL == p)
+ cdata = NULL;
+ else if (9 == len && 0 == memcmp(p, "path_info", 9))
+ cdata = fcgi_getenv(r, rlen, "PATH_INFO", 9, &len);
+ else if (11 == len && 0 == memcmp(p, "script_name", 11))
+ cdata = fcgi_getenv(r, rlen, "SCRIPT_NAME", 11, &len);
+ else if (len > 4 && 0 == memcmp(p, "env=", 4))
+ cdata = fcgi_getenv(r, rlen, p+4, len-4, &len);
+ else if (8 == len && 0 == memcmp(p, "auth-var", 8))
+ cdata = fcgi_getenv(r, rlen, "X_LIGHTTPD_FCGI_AUTH", 20, &len);
+ else {
+ cdata = "test123";
+ len = sizeof("test123")-1;
+ }
+
+ if (cdata && 0 != fcgi_puts(req_id, cdata, (size_t)len, stream))
+ return -1;
+
+ /*(XXX: always sending appStatus 0)*/
+ FCGI_EndRequestRecord endrec;
+ fcgi_end_request_rec(&endrec, req_id, 0, FCGI_REQUEST_COMPLETE);
+ if (1 != fwrite(&endrec, sizeof(endrec), 1, stream))
+ return -1; /* error writing FCGI_END_REQUEST; ignore */
+
+ return -2; /* done */
+}
+
+
+static int
+fcgi_dispatch_packet (FILE *stream, ssize_t offset, uint32_t len)
+{
+ FCGI_Header * const h = (FCGI_Header *)(buf+offset);
+ int req_id = (h->requestIdB1 << 8) | h->requestIdB0;
+ int type = h->type;
+
+ if (type > FCGI_MAXTYPE) {
+ FCGI_UnknownTypeRecord unkrec;
+ fcgi_unknown_type_rec(&unkrec, req_id, type);
+ if (1 != fwrite(&unkrec, sizeof(unkrec), 1, stream))
+ return -1;
+ return 0;
+ }
+
+ if (0 == req_id) {
+ /* not implemented: FCGI_GET_VALUES
+ * FCGI_GET_VALUES_RESULT
+ * FCGI_MAX_CONNS: 1
+ * FCGI_MAX_REQS: 1
+ * FCGI_MPXS_CONNS: 0
+ * ...
+ */
+ return 0;
+ }
+
+ /* XXX: save role from FCGI_BEGIN_REQUEST; should keep independent state */
+ static int role;
+
+ switch (type) {
+ case FCGI_BEGIN_REQUEST:
+ role = (buf[offset+FCGI_HEADER_LEN] << 8)
+ | buf[offset+FCGI_HEADER_LEN+1];
+ return 0; /* ignore; could save req_id and match further packets */
+ case FCGI_ABORT_REQUEST:
+ return -2; /* done */
+ case FCGI_END_REQUEST:
+ return -1; /* unexpected; this server is not sending FastCGI requests */
+ case FCGI_PARAMS:
+ return fcgi_process_params(stream, req_id, role,
+ buf+offset+FCGI_HEADER_LEN, len);
+ case FCGI_STDIN:
+ /* XXX: TODO read and discard request body
+ * (currently ignored in these FastCGI unit tests)
+ * (make basic effort to read body; ignore any timeouts or errors) */
+ return -1; /* unexpected; this server is not expecting request body */
+ case FCGI_STDOUT:
+ return -1; /* unexpected; this server is not sending FastCGI requests */
+ case FCGI_STDERR:
+ return -1; /* unexpected; this server is not sending FastCGI requests */
+ case FCGI_DATA:
+ return -1; /* unexpected; this server is not sending FastCGI requests */
+ case FCGI_GET_VALUES:
+ return 0; /* ignore; not implemented */
+ case FCGI_GET_VALUES_RESULT:
+ return 0; /* ignore; not implemented */
+ default:
+ return -1; /* unexpected */
+ }
+}
+
+
+static ssize_t
+fcgi_recv_packet (FILE * const stream, ssize_t sz)
+{
+ ssize_t offset = 0;
+ while (sz - offset >= (ssize_t)FCGI_HEADER_LEN) {
+ FCGI_Header * const h = (FCGI_Header *)(buf+offset);
+ uint32_t pad = h->paddingLength;
+ uint32_t len = (h->contentLengthB1 << 8) | h->contentLengthB0;
+ if (sz - offset < (ssize_t)(FCGI_HEADER_LEN + len + pad))
+ break;
+ int rc = fcgi_dispatch_packet(stream, offset, len);
+ if (rc < 0)
+ return rc;
+ offset += (ssize_t)(FCGI_HEADER_LEN + len + pad);
+ }
+ return offset;
+}
+
+
+static int
+fcgi_recv (const int fd, FILE * const stream)
+{
+ ssize_t rd = 0, offset = 0;
+
+ /* XXX: remain blocking */
+ /*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;
+ rd = fcgi_recv_packet(stream, offset);
+ if (rd < 0)
+ return (-2 == rd) ? 0 : -1; /*(-2 indicates done)*/
+ if (rd > 0) {
+ offset -= rd;
+ if (offset)
+ memmove(buf, buf+rd, offset);
+ }
+ }
+ else if (0 == rd || (errno != EAGAIN && errno != EWOULDBLOCK))
+ break;
+ } while (offset < (ssize_t)sizeof(buf));
+
+ return -1;
+}
+
+
+int
+main (void)
+{
+ int fd;
+ fcntl(FCGI_LISTENSOCK_FILENO, F_SETFL,
+ fcntl(FCGI_LISTENSOCK_FILENO, F_GETFL) & ~O_NONBLOCK);
+
+ do {
+ fd = accept(FCGI_LISTENSOCK_FILENO, NULL, NULL);
+ if (fd < 0)
+ continue;
+ /* XXX: skip checking FCGI_WEB_SERVER_ADDRS; not implemented */
+
+ /* uses stdio to retain prior behavior of output buffering (default)
+ * and flushing with fflush() at specific points */
+ FILE *stream = fdopen(fd, "r+");
+ if (NULL == stream) {
+ close(fd);
+ continue;
+ }
+ fcgi_recv(fd, stream);
+ fflush(stream);
+ fclose(stream);
+ } while (fd > 0 ? !finished : errno == EINTR);
-int main (void) {
-
- while (FCGI_Accept() >= 0) {
- char* p;
-
- if (NULL != (p = getenv("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");
- printf("test123");
- FCGI_Finish();
- break;
- } 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", getenv("PATH_INFO"));
- } else if (0 == strcmp(p, "script_name")) {
- printf("%s", getenv("SCRIPT_NAME"));
- } else if (0 == strcmp(p, "var")) {
- p = getenv("X_LIGHTTPD_FCGI_AUTH");
- printf("%s", p ? p : "(no value)");
- } else {
- printf("test123");
- }
- }
-
- return 0;
+ return 0;
}