use strict; use warnings FATAL => 'all'; use Apache::Test; use Apache::TestRequest; use Apache::TestUtil; use File::Spec::Functions qw(catfile splitpath); Apache::TestRequest::scheme('http'); #ssl not listening on this vhost Apache::TestRequest::module('mod_include'); #use this module's port use constant WINFU => Apache::TestConfig::WINFU; ## mod_include tests my($res, $str, $doc); my $dir = "/modules/include/"; my $have_apache_1 = have_apache 1; my $have_apache_2 = have_apache 2; my $have_apache_21 = have_min_apache_version "2.1.0"; my $have_apache_20 = $have_apache_2 && ! $have_apache_21; my $htdocs = Apache::Test::vars('documentroot'); # these match the SSI files with their expected results. # the expectations are set by the current 2.1 mod_include # implementation. my %test = ( "echo.shtml" => "echo.shtml", "set.shtml" => "set works", "comment.shtml" => "No comment here", "include1.shtml" => "inc-two.shtml body include.shtml body", "include2.shtml" => "inc-two.shtml body include.shtml body", "include3.shtml" => "inc-two.shtml body inc-one.shtml body ". "include.shtml body", "include4.shtml" => "inc-two.shtml body inc-one.shtml body ". "include.shtml body", "include5.shtml" => "inc-two.shtml body inc-one.shtml body ". "inc-three.shtml body include.shtml body", "include6.shtml" => "inc-two.shtml body inc-one.shtml body ". "inc-three.shtml body include.shtml body", "foo.shtml" => "[an error occurred while processing this ". "directive] foo.shtml body", "foo1.shtml" => "[an error occurred while processing this ". "directive] foo.shtml body", "foo2.shtml" => "[an error occurred while processing this ". "directive] foo.shtml body", "encode.shtml" => "\# \%\^ \%23\%20\%25\%5e", "errmsg1.shtml" => "errmsg", "errmsg2.shtml" => "errmsg", "errmsg3.shtml" => "errmsg", "errmsg4.shtml" => "pass errmsg", "errmsg5.shtml" => "", "if1.shtml" => "pass", "if2.shtml" => "pass pass", "if3.shtml" => "pass pass pass", "if4.shtml" => "pass pass", "if5.shtml" => "pass pass pass", "if6.shtml" => "[an error occurred while processing this ". "directive]", "if7.shtml" => "[an error occurred while processing this ". "directive]", "if8.shtml" => "pass", "if9.shtml" => "pass pass", "if10.shtml" => "pass", "if11.shtml" => "pass", "big.shtml" => "hello pass pass pass hello", "newline.shtml" => "inc-two.shtml body", "inc-rfile.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". "inc-rfile.shtml body", "inc-rvirtual.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". "inc-rvirtual.shtml body", "extra/inc-bogus.shtml" => "[an error occurred while processing this ". "directive] inc-bogus.shtml body", "abs-path.shtml" => "inc-extra2.shtml body inc-extra1.shtml body ". "abs-path.shtml body", "parse1.shtml" => "-->", "parse2.shtml" => '"', "regex.shtml" => "(none) 1 (none)", "retagged1.shtml" => ["retagged1.shtml", "retagged1"], "retagged2.shtml" => ["----retagged2.shtml", "retagged1"], "echo1.shtml" => ["", "echo1" ], "echo2.shtml" => [" pass config ". " echomsg pass", "echo1"], "echo3.shtml" => ['', "retagged1"], "notreal.shtml" => "pass ) if (WINFU) { delete $test{'exec/on/cmd.shtml'}; } my @patterns = ( 'mod_include test', 'Hello World', 'footer', ); # with the tweaks out of the way, we can get on # with planning the tests # first, total the number of hashed tests # note that some legacy tests will redefine the main # %test hash, so the total is not necessarily the sum # of all the keys my %tests = (); if ($have_apache_21) { %tests = (%test, %todo); } elsif ($have_apache_2) { %tests = (%test, %todo, %legacy_2_0); } else { %tests = (%test, %todo, %legacy_1_3); } # now for the TODO tests my @todo = (); unless ($have_apache_21) { # if 1.3 or 2.0, dynamically determine which of %test # will end up being TODO tests. my $counter = 0; foreach my $test (sort keys %tests) { $counter++; push @todo, $counter if $todo{$test}; } } unless ($have_apache_2) { # fsize comes immediately after the hashed tests push @todo, (scalar keys %tests) + 1; } # in addition to %tests, there are 1 mod_request expected failure, # 1 fsize and 1 flastmod test, # 1 GET test, 2 query string tests, 14 XBitHack tests and 14 # tests that use mod_bucketeer to construct brigades for mod_include my $tests = (scalar keys %tests) + 1 + @patterns + 1 + 1 + 1 + 2 + 14 + 14; plan tests => $tests, todo => \@todo, need 'DateTime', need_lwp, need_module 'include'; foreach $doc (sort keys %tests) { # do as much from %test as we can if (ref $tests{$doc}) { ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => $tests{$doc}[1]), $tests{$doc}[0], "GET $dir$doc" ); } elsif ($doc =~ m/ranged/) { if (have_cgi) { ok t_cmp(GET_BODY("$dir$doc", Range => "bytes=0-"), $tests{$doc}, "GET $dir$doc with Range" ); } else { skip "Skipping virtual-range test; no cgi module", 1; } } elsif ($doc =~ m/cgi/) { if (have_cgi) { ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $tests{$doc}, "GET $dir$doc" ); } else { skip "Skipping 'exec cgi' test; no cgi module.", 1; } } elsif ($doc =~ m/mod_request.*\?/) { # param in the url ==> use GET if (have_cgi) { ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $tests{$doc}, "GET $dir$doc" ); } else { skip "Skipping 'exec cgi' test; no cgi module.", 1; } } elsif ($doc =~ m/mod_request/) { # no param in the url ==> use POST with a content if (have_cgi) { ok t_cmp(super_chomp(POST_BODY "$dir$doc", content => "foo=bar&foo2=bar2"), $tests{$doc}, "POST $dir$doc" ); if ($doc =~ m/mod_request.*post/) { # KeptBodySize is 32 my $r = POST("$dir$doc", content => "foo=bar&foo2=bar2&foo3=bar3&foo4=bar4"); ok t_cmp($r->code, 413, "sizeof(body) > KeptBodySize"); } } else { skip "Skipping 'exec cgi' test; no cgi module.", 2; } } else { ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $tests{$doc}, "GET $dir$doc" ); } } ### FLASTMOD/FSIZE TESTS # marked as TODO in 1.3 - hoping for a format backport { my $file = catfile($htdocs, splitpath($dir), "size.shtml"); my $size = (stat $file)[7]; # round perl's stat size for # this assumes the size of size.shtml is such that it is # rendered in K (which it is). if size.shtml is made much # larger or smaller this formatting will need to change too my $abbrev = sprintf("%.1fK", $size/1024); # and commify for my $bytes = commify($size); my $expected = join ' ', $bytes, $bytes, $abbrev, $abbrev; my $result = super_chomp(GET_BODY "${dir}size.shtml"); # trim output $result =~ s/X//g; # the Xs were there just to pad the filesiez $result = single_space($result); ok t_cmp("$result", "$expected", "GET ${dir}size.shtml" ); } unless(eval "require POSIX") { skip "POSIX module not found", 1; } else { # use DateTime and avoid the system locale messing things up use DateTime; # Only for checking, whether system strftime supports %s use POSIX; my $strftime_gnu = (POSIX::strftime("%s", gmtime()) eq '%s' ? 0 : 1); my $result = super_chomp(GET_BODY "${dir}file.shtml"); $result = single_space($result); my $httpdtz = $1 if $result =~ /\w+, \d+-\w+-\d+ \d+:\d+:\d+ (\w+) /; my $file = catfile($htdocs, splitpath($dir), "file.shtml"); my $mtime = (stat $file)[9]; my $dt = DateTime->from_epoch( epoch => $mtime, locale => 'en_US', time_zone => $httpdtz||'UTC' ); my $expected = join ' ' => $dt->strftime("%A, %B %e, %G"), $dt->strftime("%A, %B %e, %G"), $strftime_gnu ? $dt->strftime("%s") : '%s', $strftime_gnu ? $dt->strftime("%s") : '%s'; # trim output $expected = single_space($expected); ok t_cmp("$result", "$expected", "GET ${dir}file.shtml" ); } # some tests that can't be easily assimilated $doc = "printenv.shtml"; ok t_cmp(GET("$dir$doc")->code, "200", "GET $dir$doc" ); ### test include + query string $res = GET "${dir}virtual.shtml"; ok $res->is_success; $str = $res->content; ok $str; for my $pat (@patterns) { ok t_cmp($str, qr/$pat/, "/$pat/"); } ### MOD_BUCKETEER+MOD_INCLUDE TESTS if (WINFU) { for (1..13) { skip "Skipping XBitHack tests on this platform", 1; } } else { ### XBITHACK TESTS # test xbithack off $doc = "xbithack/off/test.html"; foreach ("0444", "0544", "0554") { chmod oct($_), "$htdocs/$dir$doc"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"),, " ", "XBitHack off [$_]" ); } # test xbithack on $doc = "xbithack/on/test.html"; chmod 0444, "$htdocs$dir$doc"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), " ", "XBitHack on [0444]" ); foreach ("0544", "0554") { chmod oct($_), "$htdocs/$dir$doc"; ok t_cmp(check_xbithack(GET "$dir$doc"), "No Last-modified date ; inc-two.shtml body ", "XBitHack on [$_]" ); } # test timefmt - make sure filter only inserted once # if Option Include and xbithack both say to process $doc = "xbithack/both/timefmt.shtml"; my @now = localtime(); my $year = $now[5] + 1900; chmod 0555, "$htdocs/$dir$doc"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), "xx${year}xx", "XBitHack both [timefmt]" ); # test xbithack full $doc = "xbithack/full/test.html"; chmod 0444, "$htdocs/$dir$doc"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), " ", "XBitHack full [0444]" ); chmod 0544, "$htdocs/$dir$doc"; ok t_cmp(check_xbithack(GET "$dir$doc"), "No Last-modified date ; inc-two.shtml body ", "XBitHack full [0544]" ); my $lm; chmod 0554, "$htdocs/$dir$doc"; ok t_cmp(check_xbithack(GET("$dir$doc"), \$lm), "Has Last-modified date ; inc-two.shtml body ", "XBitHack full [0554]" ); ok t_cmp(check_xbithack_etag(GET("$dir$doc", 'If-Modified-Since' => $lm)), "No ETag ; ", "XBitHack full [0554] / ETag" ); ok t_cmp(GET("$dir$doc", 'If-Modified-Since' => $lm)->code, 304, "XBitHack full [0554] / If-Modified-Since" ); chmod 0544, "$htdocs/$dir$doc"; ok t_cmp(GET("$dir$doc", 'If-Modified-Since' => $lm)->code, 200, "XBitHack full [0544] / If-Modified-Since" ); ok t_cmp(check_xbithack_etag(GET("$dir$doc", 'If-Modified-Since' => $lm)), "No ETag ; inc-two.shtml body ", "XBitHack full [0544] / ETag" ); } # we can use mod_bucketeer to create edge conditions for mod_include, since # it allows us to create bucket and brigade boundaries wherever we want if (have_module 'mod_bucketeer') { my $expected = "____ _____ _____ ___________________ ". "##################################1/8 ". "##################################2/8 ". "##################################3/8 ". "##################################4/8 ". "##################################5/8 ". "##################################6/8$htdocs ". "##################################7/8 ". "##################################8/8 ". "@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@"; $doc = "bucketeer/y.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected = "____ ___________________________________". "________________________________________". "___ ____________________________________". "________________________________________". "__________ ___________________ ". "##################################### ". "##################################### ". "##################################### ". "##################################### ". "##################################### ". "##################################### ". "##################################### ". "##################################### ". "@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@"; for (0..3) { $doc = "bucketeer/y$_.shtml"; my ($body) = super_chomp(GET_BODY "$dir$doc"); $body =~ s/\002/^B/g; $body =~ s/\006/^F/g; $body =~ s/\020/^P/g; ok t_cmp($body, $expected, "GET $dir$doc" ); } $expected = "[an error occurred while processing this directive]"; $doc = "bucketeer/y4.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "pass [an error occurred while processing this directive] ". "pass pass1"; $doc = "bucketeer/y5.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "BeforeIfElseBlockAfterIf"; $doc = "bucketeer/y6.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "Before If SomethingElse". "After if"; $doc = "bucketeer/y7.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "FalseSetDone"; $doc = "bucketeer/y8.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "FalseSetDone"; $doc = "bucketeer/y9.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); $expected= "\"pass\""; $doc = "bucketeer/y10.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc"), $expected, "GET $dir$doc" ); ### exotic SSI(Start|End)Tags $expected= "----retagged3.shtml"; $doc = "bucketeer/retagged3.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => 'retagged1'), $expected, "GET $dir$doc" ); $expected= "---pass"; $doc = "bucketeer/retagged4.shtml"; ok t_cmp(super_chomp(GET_BODY "$dir$doc", Host => 'retagged2'), $expected, "GET $dir$doc" ); } else { for (1..14) { skip "Skipping bucket boundary tests, no mod_bucketeer", 1; } } sub super_chomp { my ($body) = shift; ## super chomp - all leading and trailing \n (and \r for win32) $body =~ s/^[\n\r]*//; $body =~ s/[\n\r]*$//; ## and all the rest change to spaces $body =~ s/\n/ /g; $body =~ s/\r//g; #rip out all remaining \r's $body; } sub check_xbithack { my ($resp) = shift; my ($body) = super_chomp($resp->content); my ($lastmod) = ($resp->last_modified) ? "Has Last-modified date" : "No Last-modified date"; my $data = shift; $$data = $resp->header('Last-Modified') if $data; "$lastmod ; $body"; } sub check_xbithack_etag { my ($resp) = shift; my ($body) = super_chomp($resp->content); my ($etag) = ($resp->header('ETag')) ? "Has ETag" : "No ETag"; my $data = shift; $$data = $etag if $data; "$etag ; $body"; } sub commify { # add standard commas to numbers. from perlfaq5 local $_ = shift; 1 while s/^([-+]?\d+)(\d{3})/$1,$2/; return $_; } sub single_space { # condense multiple spaces between values to a single # space. also trim initial and trailing whitespace local $_ = shift; s/\s+/ /g; s/(^ )|( $)//; return $_; }