diff options
-rw-r--r-- | tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 5 | ||||
-rw-r--r-- | tests/SConscript | 1 | ||||
-rwxr-xr-x | tests/mod-scgi.t | 91 | ||||
-rw-r--r-- | tests/scgi-responder.c | 219 | ||||
-rw-r--r-- | tests/scgi-responder.conf | 154 |
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, + ) ), + ) +} |