#!/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 IO::Socket; use Test::More tests => 164; use LightyTest; my $tf = LightyTest->new(); my $t; ok($tf->start_proc == 0, "Starting lighttpd") or die(); ## Basic Request-Handling $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Valid HTTP/1.0 Request') or ($tf->stop_proc, die()); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'OPTIONS'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'OPTIONS'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'URL-encoding, %00'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 413 } ]; ok($tf->handle_http($t) == 0, 'Content-Length > max-request-size'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Type' => 'image/jpeg' } ]; ok($tf->handle_http($t) == 0, 'Content-Type - image/jpeg'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Type' => 'image/jpeg' } ]; ok($tf->handle_http($t) == 0, 'Content-Type - image/jpeg (upper case)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'uppercase filenames'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'file not found + querystring'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain' } ]; ok($tf->handle_http($t) == 0, 'GET, content == 12345, mimetype text/plain'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/html' } ]; ok($tf->handle_http($t) == 0, 'GET, content == 12345, mimetype text/html'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST request, empty request-body'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => ''} ]; ok($tf->handle_http($t) == 0, 'HEAD request, no content'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ]; ok($tf->handle_http($t) == 0, 'HEAD request, mimetype text/html, content-length'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ]; ok($tf->handle_http($t) == 0, 'Hostname in first line, HTTP/1.1'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ]; ok($tf->handle_http($t) == 0, 'Hostname in first line as https url'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, '-HTTP-Content' => '' } ]; ok($tf->handle_http($t) == 0, 'HEAD request, file-not-found, query-string'); # (expect 200 OK instead of 100 Continue since request body sent with request) # (if we waited to send request body, would expect 100 Continue, first) $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Continue, Expect'); # note Transfer-Encoding: chunked tests will fail with 411 Length Required if # server.stream-request-body != 0 in lighttpd.conf $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, lc hex'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, uc hex'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, two hex'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, with trailer'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked, chunked header comment'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; bad chunked header'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; mismatch chunked header size and chunked data size'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'POST via Transfer-Encoding: chunked; chunked header too long'); ## ranges $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'HTTP-Content' => '1234' } ]; ok($tf->handle_http($t) == 0, 'GET, Range 0-3'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'HTTP-Content' => '45'."\n" } ]; ok($tf->handle_http($t) == 0, 'GET, Range -3'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'HTTP-Content' => '45'."\n" } ]; ok($tf->handle_http($t) == 0, 'GET, Range 3-'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'HTTP-Content' => '12345' } ]; ok($tf->handle_http($t) == 0, 'GET, Range 0-1,3-4 (ranges merged)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'HTTP-Content' => <handle_http($t) == 0, 'GET, Range 0-1,97-98 (ranges not merged)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206, 'Content-Range' => 'bytes 0-5/6' } ]; ok($tf->handle_http($t) == 0, 'GET, Range 0-'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 416 } ]; ok($tf->handle_http($t) == 0, 'GET, Range 0--'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 416 } ]; ok($tf->handle_http($t) == 0, 'GET, Range -2-3'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 416, 'HTTP-Content' => < 416 Range Not Satisfiable

416 Range Not Satisfiable

EOF } ]; ok($tf->handle_http($t) == 0, 'GET, Range -0'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 416, 'HTTP-Content' => < 416 Range Not Satisfiable

416 Range Not Satisfiable

EOF } ]; ok($tf->handle_http($t) == 0, 'GET, Range start out of range'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'GET, Range with range-requests-disabled'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => "12345\n" } ]; ok($tf->handle_http($t) == 0, 'GET, Range invalid range-unit (first)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 206 } ]; ok($tf->handle_http($t) == 0, 'GET, Range ignore invalid range (second)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'OPTIONS with Content-Length'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'OPTIONS for RTSP'); my $nextyr = (gmtime(time()))[5] + 1900 + 1; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304 } ]; ok($tf->handle_http($t) == 0, 'Similar Headers (bug #1287)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304, '-Content-Length' => '', 'Content-Type' => 'text/html' } ]; ok($tf->handle_http($t) == 0, 'Status 304 has no Content-Length (#1002)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain' } ]; $t->{SLOWREQUEST} = 1; ok($tf->handle_http($t) == 0, 'GET, slow \\r\\n\\r\\n (#2105)'); undef $t->{SLOWREQUEST}; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'pathinfo on a directory'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ]; ok($tf->handle_http($t) == 0, 'Connection-header, leading comma'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ]; ok($tf->handle_http($t) == 0, 'Connection-header, no value between two commas'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ]; ok($tf->handle_http($t) == 0, 'Connection-header, space between two commas'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ]; ok($tf->handle_http($t) == 0, 'Connection-header, comma after value'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ]; ok($tf->handle_http($t) == 0, 'Connection-header, comma and space after value'); ## Low-Level Response-Header Parsing - HTTP/1.1 $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '+Date' => '' } ]; ok($tf->handle_http($t) == 0, 'Date header'); ## Low-Level Response-Header Parsing - Content-Length $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Length' => '6' } ]; ok($tf->handle_http($t) == 0, 'Content-Length for text/html'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Length' => '6' } ]; ok($tf->handle_http($t) == 0, 'Content-Length for text/plain'); ## Low-Level Response-Header Parsing - Location $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => '/dummydir/' } ]; ok($tf->handle_http($t) == 0, 'internal redirect in directory'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => '/dummydir/?foo' } ]; ok($tf->handle_http($t) == 0, 'internal redirect in directory + querystring'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => '/~test%20%C3%A4_/' } ]; ok($tf->handle_http($t) == 0, 'internal redirect in directory with special characters'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => '/~test%20%C3%A4_/?foo' } ]; ok($tf->handle_http($t) == 0, 'internal redirect in directory with special characters + querystring'); ## simple-vhost $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Length' => '6' } ]; ok($tf->handle_http($t) == 0, 'disabling simple-vhost via conditionals'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'simple-vhost via conditionals'); ## keep-alive $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Explicit HTTP/1.0 Keep-Alive'); undef $t->{RESPONSE}; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Explicit HTTP/1.0 Keep-Alive'); undef $t->{RESPONSE}; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Implicit HTTP/1.0 Keep-Alive'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Explicit HTTP/1.1 Keep-Alive'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Implicit HTTP/1.1 Keep-Alive'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Implicit HTTP/1.1 Keep-Alive w/ extra blank b/w requests'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200 } , { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'Implicit HTTP/1.1 Keep-Alive w/ excess blank b/w requests'); ## 404 handlers $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => "static not found\n" } ]; ok($tf->handle_http($t) == 0, '404 handler => static'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => "found here\n" } ]; ok($tf->handle_http($t) == 0, '404 handler => dynamic(200)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => "http://www.example.org/" } ]; ok($tf->handle_http($t) == 0, '404 handler => dynamic(302)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, 'HTTP-Content' => "Not found here\n" } ]; ok($tf->handle_http($t) == 0, '404 handler => dynamic(404)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, 'HTTP-Content' => "REDIRECT_STATUS\n" } ]; ok($tf->handle_http($t) == 0, 'error handler => dynamic(REDIRECT_STATUS)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => "found here\n" } ]; ok($tf->handle_http($t) == 0, '404 handler => dynamic(nostatus)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, 'HTTP-Content' => "send404\n" } ]; ok($tf->handle_http($t) == 0, '404 generated by CGI should stay 404'); ## config conditions $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'condition: Referer - no referer'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'condition: Referer - referer matches regex'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'condition: Referer - no referer'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'condition: Referer - referer matches regex'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'condition: Referer - referer doesn\'t match'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 403 }, { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'remote ip cache (#255)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'condition: $HTTP["referer"] == "" and Referer is no set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'condition: $HTTP["referer"] == "" and Referer is empty'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'condition: $HTTP["referer"] == "" and Referer: foobar'); ## case-insensitive filesystem policy ## check if lower-casing works $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'uppercase access'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'lowercase access'); ## check that mod_auth works $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'uppercase access'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'lowercase access'); ## check that mod_staticfile exclude works $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'upper case access to staticfile.exclude-extension'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'lowercase access'); ## check that mod_access exclude works $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'uppercase access to url.access-deny protected location'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'lowercase access'); ## symlink policy my $docroot = $tf->{'TESTDIR'}."/tmp/lighttpd/servers/www.example.org/pages"; sub init_testbed { return 0 if $tf->{'win32native'}; # win32native lighttpd.exe return 0 unless eval { symlink("",""); 1 }; my $f = "$docroot/index.html"; my $l = "$docroot/index.xhtml"; my $rc = undef; unless (-l $l) { return 0 unless symlink($f,$l); }; $f = "$docroot/expire"; $l = "$docroot/symlinked"; $rc = undef; unless (-l $l) { return 0 unless symlink($f,$l); } return 1; }; SKIP: { skip "perl does not support symlinking or setting up the symlinks failed.", 8 unless init_testbed; # allow case # simple file $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'allow: simple file'); # symlinked file $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'allow: symlinked file'); # directly symlinked dir $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'allow: directly symlinked dir'); # symlinked dir in path $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'allow: symlinked dir in path'); # deny case # simple file $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'deny: simple file'); # symlinked file $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'deny: symlinked file'); # directly symlinked dir $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'deny: directly symlinked dir'); # symlinked dir in path $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ]; ok($tf->handle_http($t) == 0, 'deny: symlinked dir in path'); }; ## mod_auth $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Missing Auth-token'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Invalid base64 Auth-token'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Wrong Auth-token'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - plain'); SKIP: { skip "no crypt-des under openbsd or MS Visual Studio", 2 if $^O eq 'openbsd' || $tf->{'win32native'}; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (des)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (des) (lowercase)'); } $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (sha)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (sha, wrong password)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (apr-md5)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (apr-md5, wrong password)'); SKIP: { skip "no crypt-md5 under cygwin", 1 if $^O eq 'cygwin'; skip "no crypt-md5 under darwin", 1 if $^O eq 'darwin'; skip "no crypt-md5 under openbsd",1 if $^O eq 'openbsd'; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token - htpasswd (crypt-md5)'); } $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Valid Auth-token'); ## this should not crash $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401 } ]; ok($tf->handle_http($t) == 0, 'Digest-Auth: missing qop, no crash'); # (Note: test case is invalid; mismatch between request line and uri="..." # is not what is intended to be tested here, but that is what is invalid) # https://redmine.lighttpd.net/issues/477 ## this should not crash $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'Digest-Auth: missing nc (noncecount instead), no crash'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'Basic-Auth: Invalid Base64'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ]; ok($tf->handle_http($t) == 0, 'Digest-Auth: md5-sess + missing cnonce'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401, 'WWW-Authenticate' => '/, stale=true$/' } ]; ok($tf->handle_http($t) == 0, 'Digest-Auth: stale nonce'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 401, 'WWW-Authenticate' => '/, stale=true$/' } ]; ok($tf->handle_http($t) == 0, 'Digest-Auth: BWS, trailing WS, stale nonce'); ## mod_cgi $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'perl via cgi'); if ($^O ne "cygwin") { $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ]; ok($tf->handle_http($t) == 0, 'No source retrieval'); } else { ok(1, 'No source retrieval; skipped on cygwin; see response.c'); } $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/cgi.pl' } ]; ok($tf->handle_http($t) == 0, 'perl via cgi + pathinfo'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'perl via cgi and internal redirect from CGI'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Length' => 4348 } ]; ok($tf->handle_http($t) == 0, 'X-Sendfile'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => 'http://www.example.org:2048/' } ]; ok($tf->handle_http($t) == 0, 'Status + Location via FastCGI'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => 'http://www.example.org:2048/' } ]; ok($tf->handle_http($t) == 0, 'Trailing slash as path-info (#1989: workaround broken operating systems)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 502 } ]; ok($tf->handle_http($t) == 0, 'NPH + perl, invalid status-code (#14)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304 } ]; ok($tf->handle_http($t) == 0, 'NPH + perl, setting status-code (#1125)'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf->handle_http($t) == 0, 'NPH + perl, setting status-code'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'CGI/1.1' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: GATEWAY_INTERFACE'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'query_string', 'Content-Type' => 'text/plain' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: QUERY_STRING'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/cgi.pl' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: SCRIPT_NAME'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/cgi.pl' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: SCRIPT_NAME w/ PATH_INFO'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/path/info' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: PATH_INFO'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'foo' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: quoting headers with numbers'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'www.example.org' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: HTTP_HOST'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '+Content-Length' => '' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: HTTP_HOST'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '[ABSENT not found]' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: ABSENT'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '' } ]; ok($tf->handle_http($t) == 0, 'cgi-env: BLANK_VALUE'); # broken header crash $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => 'http://www.example.org/' } ]; ok($tf->handle_http($t) == 0, 'broken header via perl cgi'); ## mod_deflate $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '' } ]; ok($tf->handle_http($t) == 0, 'Vary is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', 'Content-Length' => '1294', '+Content-Encoding' => '' } ]; ok($tf->handle_http($t) == 0, 'deflate - Content-Length and Content-Encoding is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', 'Content-Length' => '1294', '+Content-Encoding' => '' } ]; ok($tf->handle_http($t) == 0, 'deflate - Content-Length and Content-Encoding is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', 'Content-Length' => '1306', '+Content-Encoding' => '' } ]; ok($tf->handle_http($t) == 0, 'gzip - Content-Length and Content-Encoding is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', 'Content-Length' => '1306', '+Content-Encoding' => '' } ]; ok($tf->handle_http($t) == 0, 'gzip - Content-Length and Content-Encoding is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', '+Content-Encoding' => '' } ]; ok($tf->handle_http($t) == 0, 'gzip, deflate - Content-Length and Content-Encoding is set'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', '+Content-Encoding' => '', 'Content-Type' => "text/plain; charset=utf-8" } ]; ok($tf->handle_http($t) == 0, 'Content-Type is from the original file'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-Content-Encoding' => '', 'Content-Type' => "text/plain; charset=utf-8" } ]; ok($tf->handle_http($t) == 0, 'Empty Accept-Encoding'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '+Vary' => '', 'Content-Encoding' => 'gzip', 'Content-Type' => "text/plain" } ]; ok($tf->handle_http($t) == 0, 'bzip2 requested but disabled'); ## mod_extforward $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '127.0.10.1' } ]; ok($tf->handle_http($t) == 0, 'expect 127.0.10.1, from single ip'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '127.0.20.1' } ]; ok($tf->handle_http($t) == 0, 'expect 127.0.20.1, from two ips'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '127.0.20.1' } ]; ok($tf->handle_http($t) == 0, 'expect 127.0.20.1, from chained proxies'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '127.0.20.1' } ]; ok($tf->handle_http($t) == 0, 'expect 127.0.20.1, from chained proxies'); ## mod_proxy do { my $tf_proxy = LightyTest->new(); $tf_proxy->{CONFIGFILE} = 'proxy.conf'; local $ENV{EPHEMERAL_PORT} = $tf->{PORT}; ok($tf_proxy->start_proc == 0, "Starting lighttpd as proxy") or last; $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ]; ok($tf_proxy->handle_http($t) == 0, 'valid request'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Server' => 'lighttpd-1.4.x' } ]; ok($tf_proxy->handle_http($t) == 0, 'drop Server from real server'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '/some+test%3Axxx%20with%20space' } ]; ok($tf_proxy->handle_http($t) == 0, 'rewrited urls work with encoded path'); ok($tf_proxy->stop_proc == 0, "Stopping lighttpd proxy"); } while (0); ## mod_setenv $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'tracenv' } ]; ok($tf->handle_http($t) == 0, 'query first setenv'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'setenv' } ]; ok($tf->handle_http($t) == 0, 'query second setenv'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'newenv' } ]; ok($tf->handle_http($t) == 0, 'query set-environment'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'foo' } ]; ok($tf->handle_http($t) == 0, 'query add-request-header'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => 'foo2' } ]; ok($tf->handle_http($t) == 0, 'query set-request-header'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'BAR' => 'foo' } ]; ok($tf->handle_http($t) == 0, 'query add-response-header'); $t->{REQUEST} = ( <{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'BAR2' => 'bar2' } ]; ok($tf->handle_http($t) == 0, 'query set-response-header'); ok($tf->stop_proc == 0, "Stopping lighttpd");