summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/Makefile.am5
-rw-r--r--tests/SConscript1
-rwxr-xr-xtests/mod-scgi.t91
-rw-r--r--tests/scgi-responder.c219
-rw-r--r--tests/scgi-responder.conf154
6 files changed, 470 insertions, 1 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5b0330c5..993ef38a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -9,6 +9,7 @@ if(HAVE_FASTCGI_H OR HAVE_FASTCGI_FASTCGI_H)
target_link_libraries(fcgi-responder fcgi)
endif()
endif()
+add_executable(scgi-responder scgi-responder.c)
set(T_FILES
prepare.sh
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 57d3ea53..671be745 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,7 +2,7 @@
testdir=$(srcdir)/tmp/lighttpd/
if CHECK_WITH_FASTCGI
-check_PROGRAMS=fcgi-auth fcgi-responder
+check_PROGRAMS=fcgi-auth fcgi-responder scgi-responder
fcgi_auth_SOURCES=fcgi-auth.c
fcgi_auth_LDADD=-lfcgi
@@ -11,6 +11,8 @@ fcgi_responder_SOURCES=fcgi-responder.c
fcgi_responder_LDADD=-lfcgi
endif
+scgi_responder_SOURCES=scgi-responder.c
+
TESTS=\
prepare.sh \
run-tests.pl \
@@ -57,6 +59,7 @@ CONFS=\
mod-userdir.t \
proxy.conf \
request.t \
+ scgi-responder.conf \
symlink.t \
var-include-sub.conf \
var-include.conf
diff --git a/tests/SConscript b/tests/SConscript
index a4a5eb56..003a69c4 100644
--- a/tests/SConscript
+++ b/tests/SConscript
@@ -35,6 +35,7 @@ extra_dist = Split('fastcgi-10.conf \
fcgi_auth = None
fcgi_responder = None
+scgi_responder = env.Program("scgi-responder", "scgi-responder.c")
if env['LIBFCGI']:
fcgi_auth = env.Program("fcgi-auth", "fcgi-auth.c", LIBS=[env['LIBFCGI'], env['APPEND_LIBS']])
diff --git a/tests/mod-scgi.t b/tests/mod-scgi.t
new file mode 100755
index 00000000..684b012b
--- /dev/null
+++ b/tests/mod-scgi.t
@@ -0,0 +1,91 @@
+#!/usr/bin/env perl
+BEGIN {
+ # add current source dir to the include-path
+ # we need this for make distcheck
+ (my $srcdir = $0) =~ s,/[^/]+$,/,;
+ unshift @INC, $srcdir;
+}
+
+use strict;
+use Test::More tests => 10;
+use LightyTest;
+
+my $tf = LightyTest->new();
+my $t;
+
+SKIP: {
+ skip "no scgi-responder found", 11 unless -x $tf->{BASEDIR}."/tests/scgi-responder" || -x $tf->{BASEDIR}."/tests/scgi-responder.exe";
+
+ $tf->{CONFIGFILE} = 'scgi-responder.conf';
+ ok($tf->start_proc == 0, "Starting lighttpd with $tf->{CONFIGFILE}") or die();
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?lf HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'line-ending \n\n');
+
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?crlf HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'line-ending \r\n\r\n');
+
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?slow-lf HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'line-ending \n + \n');
+
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?slow-crlf HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'line-ending \r\n + \r\n');
+
+ $t->{REQUEST} = ( <<EOF
+GET /abc/def/ghi?path_info HTTP/1.0
+Host: wsgi.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/abc/def/ghi' } ];
+ ok($tf->handle_http($t) == 0, 'PATH_INFO (wsgi)');
+
+ $t->{REQUEST} = ( <<EOF
+GET /abc/def/ghi?script_name HTTP/1.0
+Host: wsgi.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '' } ];
+ ok($tf->handle_http($t) == 0, 'SCRIPT_NAME (wsgi)');
+
+
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?die-at-end HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'killing scgi and wait for restart');
+
+ # (might take lighttpd 1 sec to detect backend exit)
+ select(undef, undef, undef, .9);
+ select(undef, undef, undef, .1) while (!$tf->listening_on(10000));
+ $t->{REQUEST} = ( <<EOF
+GET /index.scgi?crlf HTTP/1.0
+Host: www.example.org
+EOF
+ );
+ $t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'test123' } ];
+ ok($tf->handle_http($t) == 0, 'regular response of after restart');
+
+
+ ok($tf->stop_proc == 0, "Stopping lighttpd");
+}
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;
+}
diff --git a/tests/scgi-responder.conf b/tests/scgi-responder.conf
new file mode 100644
index 00000000..68a72c7a
--- /dev/null
+++ b/tests/scgi-responder.conf
@@ -0,0 +1,154 @@
+server.document-root = env.SRCDIR + "/tmp/lighttpd/servers/www.example.org/pages/"
+
+#debug.log-request-header = "enable"
+#debug.log-response-header = "enable"
+#debug.log-request-handling = "enable"
+#debug.log-state-handling = "enable"
+
+#scgi.debug = 1
+
+## bind to port (default: 80)
+server.port = 2048
+
+## bind to localhost (default: all interfaces)
+server.bind = "localhost"
+server.errorlog = env.SRCDIR + "/tmp/lighttpd/logs/lighttpd.error.log"
+server.breakagelog = env.SRCDIR + "/tmp/lighttpd/logs/lighttpd.breakage.log"
+server.name = "www.example.org"
+server.tag = "Apache 1.3.29"
+
+server.dir-listing = "enable"
+
+server.modules = (
+ "mod_rewrite",
+ "mod_access",
+ "mod_auth",
+ "mod_authn_file",
+ "mod_status",
+ "mod_expire",
+ "mod_redirect",
+ "mod_scgi",
+ "mod_cgi",
+ "mod_compress",
+ "mod_accesslog",
+)
+
+server.indexfiles = (
+ "index.php",
+ "index.html",
+ "index.htm",
+ "default.htm",
+)
+
+######################## MODULE CONFIG ############################
+
+
+accesslog.filename = env.SRCDIR + "/tmp/lighttpd/logs/lighttpd.access.log"
+
+mimetype.assign = (
+ ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".pdf" => "application/pdf",
+ ".swf" => "application/x-shockwave-flash",
+ ".spl" => "application/futuresplash",
+ ".txt" => "text/plain",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".gz" => "application/x-gzip",
+ ".c" => "text/plain",
+ ".conf" => "text/plain",
+)
+
+compress.cache-dir = env.SRCDIR + "/tmp/lighttpd/cache/compress/"
+compress.filetype = (
+ "text/plain",
+ "text/html",
+)
+
+scgi.debug = 0
+scgi.server = (
+ ".scgi" => (
+ "grisu" => (
+ "host" => "127.0.0.1",
+ "port" => 10000,
+ "bin-path" => env.SRCDIR + "/scgi-responder",
+ "check-local" => "disable",
+ "max-procs" => 1,
+ "min-procs" => 1,
+ ),
+ ),
+)
+
+cgi.assign = (
+ ".pl" => env.PERL,
+ ".cgi" => env.PERL,
+)
+
+auth.backend = "plain"
+auth.backend.plain.userfile = env.SRCDIR + "/tmp/lighttpd/lighttpd.user"
+auth.backend.plain.groupfile = "lighttpd.group"
+
+#auth.backend.ldap.hostname = "localhost"
+#auth.backend.ldap.base-dn = "dc=my-domain,dc=com"
+#auth.backend.ldap.filter = "(uid=$)"
+
+auth.require = (
+ "/server-status" => (
+ "method" => "digest",
+ "realm" => "download archiv",
+ "require" => "group=www|user=jan|host=192.168.2.10",
+ ),
+ "/auth.php" => (
+ "method" => "basic",
+ "realm" => "download archiv",
+ "require" => "user=jan",
+ ),
+ "/server-config" => (
+ "method" => "basic",
+ "realm" => "download archiv",
+ "require" => "group=www|user=jan|host=192.168.2.10",
+ ),
+)
+
+url.access-deny = (
+ "~",
+ ".inc",
+)
+
+url.redirect = (
+ "^/redirect/$" => "http://localhost:2048/",
+)
+
+expire.url = (
+ "/buggy/" => "access 2 hours",
+ "/asdhas/" => "access plus 1 seconds 2 minutes",
+)
+
+#### status module
+status.status-url = "/server-status"
+status.config-url = "/server-config"
+
+$HTTP["host"] == "vvv.example.org" {
+ server.document-root = env.SRCDIR + "/tmp/lighttpd/servers/www.example.org/pages/"
+}
+
+$HTTP["host"] == "zzz.example.org" {
+ server.document-root = env.SRCDIR + "/tmp/lighttpd/servers/www.example.org/pages/"
+ server.name = "zzz.example.org"
+}
+
+$HTTP["host"] == "wsgi.example.org" {
+ scgi.server = (
+ "/" => ( (
+ "host" => "127.0.0.1", "port" => 10000,
+ "fix-root-scriptname" => "enable",
+ "check-local" => "disable",
+ "bin-path" => env.SRCDIR + "/scgi-responder",
+ "max-procs" => 1,
+ ) ),
+ )
+}