2010-05-04 04:46:42 +08:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
# This script is used to test Mongoose web server
|
|
|
|
# $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
|
|
|
|
|
|
|
|
use IO::Socket;
|
|
|
|
use File::Path;
|
2013-09-07 14:07:18 +08:00
|
|
|
use File::Basename;
|
2013-07-20 21:11:05 +08:00
|
|
|
use Cwd;
|
2010-05-04 04:46:42 +08:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
#use diagnostics;
|
|
|
|
|
|
|
|
sub on_windows { $^O =~ /win32/i; }
|
|
|
|
|
|
|
|
my $port = 23456;
|
|
|
|
my $pid = undef;
|
|
|
|
my $num_requests;
|
|
|
|
my $dir_separator = on_windows() ? '\\' : '/';
|
|
|
|
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
|
|
|
my $test_dir_uri = "test_dir";
|
2013-09-07 14:07:18 +08:00
|
|
|
my $root = '../test';
|
|
|
|
my $abs_root = Cwd::abs_path(dirname($0) . $dir_separator . $root);
|
|
|
|
my $test_dir = $abs_root . $dir_separator. $test_dir_uri;
|
|
|
|
#print "$test_dir\n"; exit 0;
|
2010-05-04 04:46:42 +08:00
|
|
|
my $config = 'mongoose.conf';
|
2013-07-17 15:24:52 +08:00
|
|
|
my $exe_ext = on_windows() ? '.exe' : '';
|
|
|
|
my $mongoose_exe = '.' . $dir_separator . 'mongoose' . $exe_ext;
|
|
|
|
my $embed_exe = '.' . $dir_separator . 'embed' . $exe_ext;
|
|
|
|
my $unit_test_exe = '.' . $dir_separator . 'unit_test' . $exe_ext;
|
2010-05-04 04:46:42 +08:00
|
|
|
my $exit_code = 0;
|
|
|
|
|
2011-08-03 17:39:55 +08:00
|
|
|
my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
|
|
|
|
"$root/a+.txt", "$root/.htpasswd", "$root/binary_file", "$root/a",
|
2012-01-30 03:21:43 +08:00
|
|
|
"$root/myperl", $embed_exe, $unit_test_exe);
|
2010-05-04 04:46:42 +08:00
|
|
|
|
|
|
|
END {
|
|
|
|
unlink @files_to_delete;
|
|
|
|
kill_spawned_child();
|
|
|
|
File::Path::rmtree($test_dir);
|
|
|
|
exit $exit_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub fail {
|
|
|
|
print "FAILED: @_\n";
|
|
|
|
$exit_code = 1;
|
|
|
|
exit 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_num_of_log_entries {
|
|
|
|
open FD, "access.log" or return 0;
|
|
|
|
my @lines = (<FD>);
|
|
|
|
close FD;
|
|
|
|
return scalar @lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Send the request to the 127.0.0.1:$port and return the reply
|
|
|
|
sub req {
|
2010-11-13 05:15:30 +08:00
|
|
|
my ($request, $inc, $timeout) = @_;
|
2013-07-17 15:24:52 +08:00
|
|
|
my $sock = IO::Socket::INET->new(Proto => 6,
|
|
|
|
PeerAddr => '127.0.0.1', PeerPort => $port);
|
|
|
|
fail("Cannot connect to http://127.0.0.1:$port : $!") unless $sock;
|
2010-05-04 04:46:42 +08:00
|
|
|
$sock->autoflush(1);
|
|
|
|
foreach my $byte (split //, $request) {
|
|
|
|
last unless print $sock $byte;
|
|
|
|
select undef, undef, undef, .001 if length($request) < 256;
|
|
|
|
}
|
2010-11-15 17:41:17 +08:00
|
|
|
my ($out, $buf) = ('', '');
|
2010-11-13 05:15:30 +08:00
|
|
|
eval {
|
|
|
|
alarm $timeout if $timeout;
|
2010-11-15 17:41:17 +08:00
|
|
|
$out .= $buf while (sysread($sock, $buf, 1024) > 0);
|
|
|
|
alarm 0 if $timeout;
|
2010-11-13 05:15:30 +08:00
|
|
|
};
|
2010-05-04 04:46:42 +08:00
|
|
|
close $sock;
|
|
|
|
|
|
|
|
$num_requests += defined($inc) ? $inc : 1;
|
|
|
|
my $num_logs = get_num_of_log_entries();
|
|
|
|
|
|
|
|
unless ($num_requests == $num_logs) {
|
|
|
|
fail("Request has not been logged: [$request], output: [$out]");
|
|
|
|
}
|
|
|
|
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Send the request. Compare with the expected reply. Fail if no match
|
|
|
|
sub o {
|
|
|
|
my ($request, $expected_reply, $message, $num_logs) = @_;
|
2010-08-23 14:26:33 +08:00
|
|
|
print "==> $message ... ";
|
2010-05-04 04:46:42 +08:00
|
|
|
my $reply = req($request, $num_logs);
|
|
|
|
if ($reply =~ /$expected_reply/s) {
|
|
|
|
print "OK\n";
|
|
|
|
} else {
|
2012-07-28 18:04:03 +08:00
|
|
|
#fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
|
|
|
|
fail("Expected: [$expected_reply], got: [$reply]");
|
2010-05-04 04:46:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Spawn a server listening on specified port
|
|
|
|
sub spawn {
|
|
|
|
my ($cmdline) = @_;
|
2010-06-25 23:23:17 +08:00
|
|
|
print 'Executing: ', @_, "\n";
|
2010-05-04 04:46:42 +08:00
|
|
|
if (on_windows()) {
|
|
|
|
my @args = split /\s+/, $cmdline;
|
|
|
|
my $executable = $args[0];
|
|
|
|
Win32::Spawn($executable, $cmdline, $pid);
|
|
|
|
die "Cannot spawn @_: $!" unless $pid;
|
|
|
|
} else {
|
|
|
|
unless ($pid = fork()) {
|
|
|
|
exec $cmdline;
|
|
|
|
die "cannot exec [$cmdline]: $!\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sleep 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub write_file {
|
|
|
|
open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
|
|
|
|
binmode FD;
|
|
|
|
print FD $_[1];
|
|
|
|
close FD;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub read_file {
|
|
|
|
open FD, $_[0] or fail "Cannot open $_[0]: $!";
|
|
|
|
my @lines = <FD>;
|
|
|
|
close FD;
|
|
|
|
return join '', @lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub kill_spawned_child {
|
|
|
|
if (defined($pid)) {
|
|
|
|
kill(9, $pid);
|
|
|
|
waitpid($pid, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
####################################################### ENTRY POINT
|
|
|
|
|
|
|
|
unlink @files_to_delete;
|
|
|
|
$SIG{PIPE} = 'IGNORE';
|
2010-11-13 05:15:30 +08:00
|
|
|
$SIG{ALRM} = sub { die "timeout\n" };
|
2010-05-04 04:46:42 +08:00
|
|
|
#local $| =1;
|
|
|
|
|
|
|
|
# Make sure we export only symbols that start with "mg_", and keep local
|
|
|
|
# symbols static.
|
|
|
|
if ($^O =~ /darwin|bsd|linux/) {
|
|
|
|
my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
|
|
|
|
foreach (split /\n/, $out) {
|
|
|
|
/T\s+_?mg_.+/ or fail("Exported symbol $_")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-24 17:30:01 +08:00
|
|
|
if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
|
|
|
|
do_unit_test();
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
2011-06-22 05:04:58 +08:00
|
|
|
# Make sure we load config file if no options are given.
|
|
|
|
# Command line options override config files settings
|
2013-01-22 20:26:56 +08:00
|
|
|
write_file($config, "access_log_file access.log\n" .
|
2013-09-07 14:07:18 +08:00
|
|
|
"document_root $root\n" .
|
2013-01-22 20:26:56 +08:00
|
|
|
"listening_ports 127.0.0.1:12345\n");
|
2013-07-17 15:24:52 +08:00
|
|
|
spawn("$mongoose_exe -listening_ports 127.0.0.1:$port");
|
2013-09-07 14:07:18 +08:00
|
|
|
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
|
2010-05-04 04:46:42 +08:00
|
|
|
unlink $config;
|
|
|
|
kill_spawned_child();
|
|
|
|
|
|
|
|
# Spawn the server on port $port
|
2013-07-17 15:24:52 +08:00
|
|
|
my $cmd = "$mongoose_exe ".
|
2013-01-24 05:54:27 +08:00
|
|
|
"-listening_ports 127.0.0.1:$port ".
|
2012-01-30 03:21:43 +08:00
|
|
|
"-access_log_file access.log ".
|
|
|
|
"-error_log_file debug.log ".
|
|
|
|
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
|
|
|
|
"-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
|
2013-09-07 14:07:18 +08:00
|
|
|
"-put_delete_auth_file $abs_root/passfile " .
|
2012-01-30 03:21:43 +08:00
|
|
|
'-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
|
|
|
|
"-document_root $root ".
|
2013-07-25 14:07:25 +08:00
|
|
|
"-hide_files_patterns **exploit.PL ".
|
2012-08-14 23:06:10 +08:00
|
|
|
"-enable_keep_alive yes ".
|
2012-01-30 03:21:43 +08:00
|
|
|
"-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
|
2010-08-30 05:30:49 +08:00
|
|
|
$cmd .= ' -cgi_interpreter perl' if on_windows();
|
2010-05-04 04:46:42 +08:00
|
|
|
spawn($cmd);
|
|
|
|
|
2012-09-30 01:56:23 +08:00
|
|
|
o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
|
|
|
|
'Content-Length: 15\s', 'Range past the file end');
|
|
|
|
|
2012-08-14 23:06:10 +08:00
|
|
|
o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
|
|
|
|
'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
|
|
|
|
'Request pipelining', 2);
|
|
|
|
|
|
|
|
my $x = 'x=' . 'A' x (200 * 1024);
|
|
|
|
my $len = length($x);
|
|
|
|
o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x",
|
|
|
|
'^HTTP/1.1 200 OK', 'Long POST');
|
2012-07-28 18:04:03 +08:00
|
|
|
|
2010-05-04 04:46:42 +08:00
|
|
|
# Try to overflow: Send very long request
|
|
|
|
req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
|
|
|
|
|
|
|
|
o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
|
2012-05-19 14:38:50 +08:00
|
|
|
o("GET /hello.txt HTTP/1.0\nContent-Length: -2147483648\n\n",
|
|
|
|
'HTTP/1.1 200 OK', 'Negative content length');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
|
|
|
|
'GET regular file Content-Length');
|
|
|
|
o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
|
|
|
|
'HTTP/1.1 200 OK', 'URL-decoding');
|
|
|
|
|
2010-11-15 17:41:17 +08:00
|
|
|
# Break CGI reading after 1 second. We must get full output.
|
|
|
|
# Since CGI script does sleep, we sleep as well and increase request count
|
|
|
|
# manually.
|
2010-12-07 19:38:11 +08:00
|
|
|
my $slow_cgi_reply;
|
|
|
|
print "==> Slow CGI output ... ";
|
|
|
|
fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
|
|
|
|
($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
|
|
|
|
print "OK\n";
|
2010-11-15 17:41:17 +08:00
|
|
|
sleep 3;
|
|
|
|
$num_requests++;
|
|
|
|
|
2010-05-04 04:46:42 +08:00
|
|
|
# '+' in URI must not be URL-decoded to space
|
|
|
|
write_file("$root/a+.txt", '');
|
|
|
|
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
|
|
|
|
|
|
|
|
# Test HTTP version parsing
|
2013-01-24 05:54:27 +08:00
|
|
|
o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 500', 'Bad HTTP Version', 0);
|
|
|
|
o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 0);
|
|
|
|
o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 0);
|
|
|
|
o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
|
2010-05-04 04:46:42 +08:00
|
|
|
|
|
|
|
# File with leading single dot
|
|
|
|
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
|
|
|
|
o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
|
2012-02-10 06:55:24 +08:00
|
|
|
o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3')
|
|
|
|
if on_windows();
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
|
|
|
|
|
|
|
|
mkdir $test_dir unless -d $test_dir;
|
|
|
|
o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
|
|
|
|
'HTTP/1.1 404', 'PATH_INFO loop problem');
|
|
|
|
o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
|
|
|
|
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
|
|
|
|
write_file("$test_dir/index.html", "tralala");
|
|
|
|
o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
|
|
|
|
o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
|
|
|
|
o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
|
|
|
|
o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
|
|
|
|
mkdir $test_dir . $dir_separator . 'x';
|
|
|
|
my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
|
|
|
|
write_file($path, read_file($root . $dir_separator . 'env.cgi'));
|
2012-01-30 03:21:43 +08:00
|
|
|
chmod(0755, $path);
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
|
|
|
|
'index.cgi execution');
|
2013-07-20 21:11:05 +08:00
|
|
|
|
2010-09-19 20:00:49 +08:00
|
|
|
o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
|
2013-09-07 14:07:18 +08:00
|
|
|
"SCRIPT_FILENAME=$test_dir/x/index.cgi", 'SCRIPT_FILENAME');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
|
|
|
|
'Aliases SCRIPT_NAME');
|
2012-08-14 23:06:10 +08:00
|
|
|
o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
|
|
|
|
'No keep-alive');
|
2012-01-30 03:21:43 +08:00
|
|
|
|
|
|
|
$path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
|
|
|
|
system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
|
|
|
|
write_file($path, "#!../../myperl\n" .
|
|
|
|
"print \"Content-Type: text/plain\\n\\nhi\";");
|
|
|
|
chmod(0755, $path);
|
|
|
|
o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
|
2010-11-30 01:27:25 +08:00
|
|
|
o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
|
|
|
|
|
2010-05-04 04:46:42 +08:00
|
|
|
my $mime_types = {
|
|
|
|
html => 'text/html',
|
|
|
|
htm => 'text/html',
|
|
|
|
txt => 'text/plain',
|
|
|
|
unknown_extension => 'text/plain',
|
|
|
|
js => 'application/x-javascript',
|
|
|
|
css => 'text/css',
|
|
|
|
jpg => 'image/jpeg',
|
|
|
|
c => 'text/plain',
|
|
|
|
'tar.gz' => 'blah',
|
|
|
|
bar => 'foo/bar',
|
|
|
|
baz => 'foo',
|
|
|
|
};
|
|
|
|
|
|
|
|
foreach my $key (keys %$mime_types) {
|
|
|
|
my $filename = "_mime_file_test.$key";
|
|
|
|
write_file("$root/$filename", '');
|
|
|
|
o("GET /$filename HTTP/1.0\n\n",
|
|
|
|
"Content-Type: $mime_types->{$key}", ".$key mime type");
|
|
|
|
unlink "$root/$filename";
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get binary file and check the integrity
|
|
|
|
my $binary_file = 'binary_file';
|
|
|
|
my $f2 = '';
|
|
|
|
foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
|
|
|
|
write_file("$root/$binary_file", $f2);
|
|
|
|
my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
|
|
|
|
while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
|
|
|
|
$f1 eq $f2 or fail("Integrity check for downloaded binary file");
|
|
|
|
|
|
|
|
my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
|
|
|
|
"Range: bytes=3-5\r\n\r\n";
|
|
|
|
o($range_request, '206 Partial Content', 'Range: 206 status code');
|
|
|
|
o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
|
|
|
|
o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
|
|
|
|
o($range_request, '\nple$', 'Range: body content');
|
|
|
|
|
|
|
|
# Test directory sorting. Sleep between file creation for 1.1 seconds,
|
|
|
|
# to make sure modification time are different.
|
|
|
|
mkdir "$test_dir/sort";
|
|
|
|
write_file("$test_dir/sort/11", 'xx');
|
|
|
|
select undef, undef, undef, 1.1;
|
|
|
|
write_file("$test_dir/sort/aa", 'xxxx');
|
|
|
|
select undef, undef, undef, 1.1;
|
|
|
|
write_file("$test_dir/sort/bb", 'xxx');
|
|
|
|
select undef, undef, undef, 1.1;
|
|
|
|
write_file("$test_dir/sort/22", 'x');
|
|
|
|
|
|
|
|
o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>11<.+>22<.+>aa<.+>bb<',
|
|
|
|
'Directory listing (name, ascending)');
|
|
|
|
o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>bb<.+>aa<.+>22<.+>11<',
|
|
|
|
'Directory listing (name, descending)');
|
|
|
|
o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>22<.+>11<.+>bb<.+>aa<',
|
|
|
|
'Directory listing (size, ascending)');
|
|
|
|
o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>aa<.+>bb<.+>11<.+>22<',
|
|
|
|
'Directory listing (size, descending)');
|
|
|
|
o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>11<.+>aa<.+>bb<.+>22<',
|
|
|
|
'Directory listing (modification time, ascending)');
|
|
|
|
o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
|
|
|
|
'200 OK.+>22<.+>bb<.+>aa<.+>11<',
|
|
|
|
'Directory listing (modification time, descending)');
|
|
|
|
|
|
|
|
unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
|
|
|
|
# Check that .htpasswd file existence trigger authorization
|
2010-12-03 19:48:56 +08:00
|
|
|
write_file("$root/.htpasswd", 'user with space, " and comma:mydomain.com:5deda12442309cbdcdffc6b2737a894f');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
|
|
|
|
'.htpasswd - triggering auth on file request');
|
|
|
|
o("GET / HTTP/1.1\n\n", '401 Unauthorized',
|
|
|
|
'.htpasswd - triggering auth on directory request');
|
2010-12-03 19:48:56 +08:00
|
|
|
|
|
|
|
# Test various funky things in an authentication header.
|
|
|
|
o("GET /hello.txt HTTP/1.0\nAuthorization: Digest eq== empty=\"\", empty2=, quoted=\"blah foo bar, baz\\\"\\\" more\\\"\", unterminatedquoted=\" doesn't stop\n\n",
|
|
|
|
'401 Unauthorized', 'weird auth values should not cause crashes');
|
|
|
|
my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
|
|
|
|
"realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
|
|
|
|
"response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
|
|
|
|
o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
|
2012-08-14 23:06:10 +08:00
|
|
|
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
|
|
|
|
'.htpasswd is hidden from the directory list');
|
|
|
|
o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(exploit.pl)))*$',
|
|
|
|
'hidden file is hidden from the directory list');
|
|
|
|
o("GET /.htpasswd HTTP/1.0\nAuthorization: $auth_header\n\n",
|
|
|
|
'^HTTP/1.1 404 ', '.htpasswd must not be shown');
|
|
|
|
o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
|
|
|
|
'^HTTP/1.1 404', 'hidden files must not be shown');
|
2010-05-04 04:46:42 +08:00
|
|
|
unlink "$root/.htpasswd";
|
|
|
|
|
2012-07-27 13:24:11 +08:00
|
|
|
|
2013-07-17 15:24:52 +08:00
|
|
|
o("GET /dir%20with%20spaces/hello.cgi HTTP/1.0\n\r\n",
|
|
|
|
'HTTP/1.1 200 OK.+hello', 'CGI script with spaces in path');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
|
2012-03-05 00:23:46 +08:00
|
|
|
o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 123 Please pass me to the client\r",
|
|
|
|
'CGI Status code text');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
|
|
|
|
'GET sh CGI file') unless on_windows();
|
|
|
|
o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
|
|
|
|
'QUERY_STRING wrong');
|
|
|
|
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
|
|
|
|
'var=HELLO', 'CGI POST wrong');
|
|
|
|
o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
|
|
|
|
'\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
|
|
|
|
o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
|
|
|
|
'HTTP_MY_HDR=abc', 'HTTP_* env');
|
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
|
|
|
|
'HTTP/1.1 200 OK', 'GET CGI with trailing data');
|
|
|
|
|
|
|
|
o("GET /env.cgi%20 HTTP/1.0\n\r\n",
|
|
|
|
'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
|
|
|
|
o("GET /env.cgi%ff HTTP/1.0\n\r\n",
|
|
|
|
'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
|
|
|
|
o("GET /env.cgi%2e HTTP/1.0\n\r\n",
|
|
|
|
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
|
|
|
|
o("GET /env.cgi%2b HTTP/1.0\n\r\n",
|
|
|
|
'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
|
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
|
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
|
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
|
|
|
|
o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
|
2012-03-24 03:37:32 +08:00
|
|
|
o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
|
|
|
|
o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
|
2013-09-08 01:49:24 +08:00
|
|
|
o("GET /env.cgi/foo/bar?a=b HTTP/1.0\n\n",
|
|
|
|
'SCRIPT_NAME=/env.cgi\s', 'SCRIPT_NAME for CGI with PATH_INFO');
|
2010-05-04 04:46:42 +08:00
|
|
|
|
|
|
|
# Check that CGI's current directory is set to script's directory
|
|
|
|
my $copy_cmd = on_windows() ? 'copy' : 'cp';
|
|
|
|
system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
|
|
|
|
$dir_separator . 'env.cgi');
|
|
|
|
o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
|
|
|
|
"CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
|
|
|
|
|
|
|
|
# SSI tests
|
|
|
|
o("GET /ssi1.shtml HTTP/1.0\n\n",
|
|
|
|
'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
|
|
|
|
o("GET /ssi2.shtml HTTP/1.0\n\n",
|
|
|
|
'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
|
|
|
|
my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
|
|
|
|
o("GET /$ssi_exec HTTP/1.0\n\n",
|
|
|
|
'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
|
|
|
|
my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
|
|
|
|
my $word = on_windows() ? 'boot loader' : 'root';
|
|
|
|
o("GET /$abs_path HTTP/1.0\n\n",
|
2013-07-19 14:57:47 +08:00
|
|
|
"ssi_begin.+$word.+ssi_end", 'SSI #include abspath');
|
2010-05-04 04:46:42 +08:00
|
|
|
o("GET /ssi7.shtml HTTP/1.0\n\n",
|
|
|
|
'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
|
|
|
|
o("GET /ssi8.shtml HTTP/1.0\n\n",
|
|
|
|
'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
|
|
|
|
|
|
|
|
# Manipulate the passwords file
|
|
|
|
my $path = 'test_htpasswd';
|
|
|
|
unlink $path;
|
2013-07-17 15:24:52 +08:00
|
|
|
system("$mongoose_exe -A $path a b c") == 0
|
2010-05-04 04:46:42 +08:00
|
|
|
or fail("Cannot add user in a passwd file");
|
2013-07-17 15:24:52 +08:00
|
|
|
system("$mongoose_exe -A $path a b c2") == 0
|
2010-05-04 04:46:42 +08:00
|
|
|
or fail("Cannot edit user in a passwd file");
|
|
|
|
my $content = read_file($path);
|
|
|
|
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
|
|
|
|
unlink $path;
|
|
|
|
|
|
|
|
do_PUT_test();
|
2010-08-30 05:30:49 +08:00
|
|
|
kill_spawned_child();
|
2012-01-24 17:30:01 +08:00
|
|
|
do_unit_test();
|
2010-05-04 04:46:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
sub do_PUT_test {
|
2010-12-03 19:48:56 +08:00
|
|
|
# This only works because mongoose currently doesn't look at the nonce.
|
|
|
|
# It should really be rejected...
|
2010-05-04 04:46:42 +08:00
|
|
|
my $auth_header = "Authorization: Digest username=guest, ".
|
|
|
|
"realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
|
|
|
|
"response=896327350763836180c61d87578037d9, qop=auth, ".
|
|
|
|
"nc=00000002, cnonce=53eddd3be4e26a98\n";
|
|
|
|
|
2011-08-03 17:39:55 +08:00
|
|
|
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
|
2010-05-04 04:46:42 +08:00
|
|
|
"HTTP/1.1 201 OK", 'PUT file, status 201');
|
|
|
|
fail("PUT content mismatch")
|
2011-08-03 17:39:55 +08:00
|
|
|
unless read_file("$root/a/put.txt") eq '1234567';
|
|
|
|
o("PUT /a/put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
|
2010-05-04 04:46:42 +08:00
|
|
|
"HTTP/1.1 200 OK", 'PUT file, status 200');
|
|
|
|
fail("PUT content mismatch")
|
2011-08-03 17:39:55 +08:00
|
|
|
unless read_file("$root/a/put.txt") eq 'abcd';
|
|
|
|
o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
|
2010-05-04 04:46:42 +08:00
|
|
|
"HTTP/1.1 411 Length Required", 'PUT 411 error');
|
2011-08-03 17:39:55 +08:00
|
|
|
o("PUT /a/put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
|
2010-05-04 04:46:42 +08:00
|
|
|
"$auth_header\nabcd",
|
|
|
|
"HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
|
2011-08-03 17:39:55 +08:00
|
|
|
o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
|
2010-05-04 04:46:42 +08:00
|
|
|
"$auth_header\nabcd",
|
|
|
|
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
|
|
|
|
}
|
|
|
|
|
2012-01-24 17:30:01 +08:00
|
|
|
sub do_unit_test {
|
2013-05-19 08:25:10 +08:00
|
|
|
my $target = on_windows() ? 'wi' : 'un';
|
2013-01-22 16:59:42 +08:00
|
|
|
system("make $target") == 0 or fail("Unit test failed!");
|
2012-01-24 17:30:01 +08:00
|
|
|
}
|
|
|
|
|
2010-05-04 04:46:42 +08:00
|
|
|
print "SUCCESS! All tests passed.\n";
|