lighttpd1.4/tests/scgi-responder.c

223 lines
6.6 KiB
C
Raw Normal View History

/*
* 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
*
* Copyright(c) 2017 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 <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;
}