test.pl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. #!/usr/bin/env perl
  2. # This script is used to test Mongoose web server
  3. # $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
  4. use IO::Socket;
  5. use File::Path;
  6. use strict;
  7. use warnings;
  8. #use diagnostics;
  9. sub on_windows { $^O =~ /win32/i; }
  10. my $port = 23456;
  11. my $pid = undef;
  12. my $num_requests;
  13. my $dir_separator = on_windows() ? '\\' : '/';
  14. my $copy_cmd = on_windows() ? 'copy' : 'cp';
  15. my $test_dir_uri = "test_dir";
  16. my $root = 'test';
  17. my $test_dir = $root . $dir_separator. $test_dir_uri;
  18. my $config = 'mongoose.conf';
  19. my $exe = '.' . $dir_separator . 'mongoose';
  20. my $embed_exe = '.' . $dir_separator . 'embed';
  21. my $unit_test_exe = '.' . $dir_separator . 'unit_test';
  22. my $exit_code = 0;
  23. my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
  24. "$root/a+.txt", "$root/.htpasswd", "$root/binary_file", "$root/a",
  25. "$root/myperl", $embed_exe, $unit_test_exe);
  26. END {
  27. unlink @files_to_delete;
  28. kill_spawned_child();
  29. File::Path::rmtree($test_dir);
  30. exit $exit_code;
  31. }
  32. sub fail {
  33. print "FAILED: @_\n";
  34. $exit_code = 1;
  35. exit 1;
  36. }
  37. sub get_num_of_log_entries {
  38. open FD, "access.log" or return 0;
  39. my @lines = (<FD>);
  40. close FD;
  41. return scalar @lines;
  42. }
  43. # Send the request to the 127.0.0.1:$port and return the reply
  44. sub req {
  45. my ($request, $inc, $timeout) = @_;
  46. my $sock = IO::Socket::INET->new(Proto=>"tcp",
  47. PeerAddr=>'127.0.0.1', PeerPort=>$port);
  48. fail("Cannot connect: $!") unless $sock;
  49. $sock->autoflush(1);
  50. foreach my $byte (split //, $request) {
  51. last unless print $sock $byte;
  52. select undef, undef, undef, .001 if length($request) < 256;
  53. }
  54. my ($out, $buf) = ('', '');
  55. eval {
  56. alarm $timeout if $timeout;
  57. $out .= $buf while (sysread($sock, $buf, 1024) > 0);
  58. alarm 0 if $timeout;
  59. };
  60. close $sock;
  61. $num_requests += defined($inc) ? $inc : 1;
  62. my $num_logs = get_num_of_log_entries();
  63. unless ($num_requests == $num_logs) {
  64. fail("Request has not been logged: [$request], output: [$out]");
  65. }
  66. return $out;
  67. }
  68. # Send the request. Compare with the expected reply. Fail if no match
  69. sub o {
  70. my ($request, $expected_reply, $message, $num_logs) = @_;
  71. print "==> $message ... ";
  72. my $reply = req($request, $num_logs);
  73. if ($reply =~ /$expected_reply/s) {
  74. print "OK\n";
  75. } else {
  76. #fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
  77. fail("Expected: [$expected_reply], got: [$reply]");
  78. }
  79. }
  80. # Spawn a server listening on specified port
  81. sub spawn {
  82. my ($cmdline) = @_;
  83. print 'Executing: ', @_, "\n";
  84. if (on_windows()) {
  85. my @args = split /\s+/, $cmdline;
  86. my $executable = $args[0];
  87. $executable .= '.exe';
  88. Win32::Spawn($executable, $cmdline, $pid);
  89. die "Cannot spawn @_: $!" unless $pid;
  90. } else {
  91. unless ($pid = fork()) {
  92. exec $cmdline;
  93. die "cannot exec [$cmdline]: $!\n";
  94. }
  95. }
  96. sleep 1;
  97. }
  98. sub write_file {
  99. open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
  100. binmode FD;
  101. print FD $_[1];
  102. close FD;
  103. }
  104. sub read_file {
  105. open FD, $_[0] or fail "Cannot open $_[0]: $!";
  106. my @lines = <FD>;
  107. close FD;
  108. return join '', @lines;
  109. }
  110. sub kill_spawned_child {
  111. if (defined($pid)) {
  112. kill(9, $pid);
  113. waitpid($pid, 0);
  114. }
  115. }
  116. ####################################################### ENTRY POINT
  117. unlink @files_to_delete;
  118. $SIG{PIPE} = 'IGNORE';
  119. $SIG{ALRM} = sub { die "timeout\n" };
  120. #local $| =1;
  121. # Make sure we export only symbols that start with "mg_", and keep local
  122. # symbols static.
  123. if ($^O =~ /darwin|bsd|linux/) {
  124. my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
  125. foreach (split /\n/, $out) {
  126. /T\s+_?mg_.+/ or fail("Exported symbol $_")
  127. }
  128. }
  129. if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
  130. do_unit_test();
  131. exit 0;
  132. }
  133. # Make sure we load config file if no options are given.
  134. # Command line options override config files settings
  135. write_file($config, "access_log_file access.log\n" .
  136. "listening_ports 127.0.0.1:12345\n");
  137. spawn("$exe -p 127.0.0.1:$port");
  138. o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
  139. unlink $config;
  140. kill_spawned_child();
  141. # Spawn the server on port $port
  142. my $cmd = "$exe ".
  143. "-listening_ports 127.0.0.1:$port ".
  144. "-access_log_file access.log ".
  145. "-error_log_file debug.log ".
  146. "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
  147. "-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
  148. '-put_delete_auth_file test/passfile ' .
  149. '-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
  150. "-document_root $root ".
  151. "-hide_files_patterns **exploit.pl ".
  152. "-enable_keep_alive yes ".
  153. "-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
  154. $cmd .= ' -cgi_interpreter perl' if on_windows();
  155. spawn($cmd);
  156. o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
  157. 'Content-Length: 15\s', 'Range past the file end');
  158. o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
  159. 'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
  160. 'Request pipelining', 2);
  161. my $x = 'x=' . 'A' x (200 * 1024);
  162. my $len = length($x);
  163. o("POST /env.cgi HTTP/1.0\r\nContent-Length: $len\r\n\r\n$x",
  164. '^HTTP/1.1 200 OK', 'Long POST');
  165. # Try to overflow: Send very long request
  166. req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
  167. o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
  168. o("GET /hello.txt HTTP/1.0\nContent-Length: -2147483648\n\n",
  169. 'HTTP/1.1 200 OK', 'Negative content length');
  170. o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
  171. 'GET regular file Content-Length');
  172. o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
  173. 'HTTP/1.1 200 OK', 'URL-decoding');
  174. # Break CGI reading after 1 second. We must get full output.
  175. # Since CGI script does sleep, we sleep as well and increase request count
  176. # manually.
  177. my $slow_cgi_reply;
  178. print "==> Slow CGI output ... ";
  179. fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
  180. ($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
  181. print "OK\n";
  182. sleep 3;
  183. $num_requests++;
  184. # '+' in URI must not be URL-decoded to space
  185. write_file("$root/a+.txt", '');
  186. o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
  187. # Test HTTP version parsing
  188. o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 500', 'Bad HTTP Version', 0);
  189. o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 0);
  190. o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 0);
  191. o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
  192. # File with leading single dot
  193. o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
  194. o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
  195. o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3')
  196. if on_windows();
  197. o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
  198. mkdir $test_dir unless -d $test_dir;
  199. o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
  200. 'HTTP/1.1 404', 'PATH_INFO loop problem');
  201. o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
  202. o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
  203. write_file("$test_dir/index.html", "tralala");
  204. o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
  205. o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
  206. o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
  207. o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
  208. mkdir $test_dir . $dir_separator . 'x';
  209. my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
  210. write_file($path, read_file($root . $dir_separator . 'env.cgi'));
  211. chmod(0755, $path);
  212. o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
  213. 'index.cgi execution');
  214. o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
  215. "SCRIPT_FILENAME=test/test_dir/x/index.cgi", 'SCRIPT_FILENAME');
  216. o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
  217. 'Aliases SCRIPT_NAME');
  218. o("GET /hello.txt HTTP/1.1\nConnection: close\n\n", 'Connection: close',
  219. 'No keep-alive');
  220. $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
  221. system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
  222. write_file($path, "#!../../myperl\n" .
  223. "print \"Content-Type: text/plain\\n\\nhi\";");
  224. chmod(0755, $path);
  225. o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
  226. o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
  227. my $mime_types = {
  228. html => 'text/html',
  229. htm => 'text/html',
  230. txt => 'text/plain',
  231. unknown_extension => 'text/plain',
  232. js => 'application/x-javascript',
  233. css => 'text/css',
  234. jpg => 'image/jpeg',
  235. c => 'text/plain',
  236. 'tar.gz' => 'blah',
  237. bar => 'foo/bar',
  238. baz => 'foo',
  239. };
  240. foreach my $key (keys %$mime_types) {
  241. my $filename = "_mime_file_test.$key";
  242. write_file("$root/$filename", '');
  243. o("GET /$filename HTTP/1.0\n\n",
  244. "Content-Type: $mime_types->{$key}", ".$key mime type");
  245. unlink "$root/$filename";
  246. }
  247. # Get binary file and check the integrity
  248. my $binary_file = 'binary_file';
  249. my $f2 = '';
  250. foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
  251. write_file("$root/$binary_file", $f2);
  252. my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
  253. while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
  254. $f1 eq $f2 or fail("Integrity check for downloaded binary file");
  255. my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
  256. "Range: bytes=3-5\r\n\r\n";
  257. o($range_request, '206 Partial Content', 'Range: 206 status code');
  258. o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
  259. o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
  260. o($range_request, '\nple$', 'Range: body content');
  261. # Test directory sorting. Sleep between file creation for 1.1 seconds,
  262. # to make sure modification time are different.
  263. mkdir "$test_dir/sort";
  264. write_file("$test_dir/sort/11", 'xx');
  265. select undef, undef, undef, 1.1;
  266. write_file("$test_dir/sort/aa", 'xxxx');
  267. select undef, undef, undef, 1.1;
  268. write_file("$test_dir/sort/bb", 'xxx');
  269. select undef, undef, undef, 1.1;
  270. write_file("$test_dir/sort/22", 'x');
  271. o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
  272. '200 OK.+>11<.+>22<.+>aa<.+>bb<',
  273. 'Directory listing (name, ascending)');
  274. o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
  275. '200 OK.+>bb<.+>aa<.+>22<.+>11<',
  276. 'Directory listing (name, descending)');
  277. o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
  278. '200 OK.+>22<.+>11<.+>bb<.+>aa<',
  279. 'Directory listing (size, ascending)');
  280. o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
  281. '200 OK.+>aa<.+>bb<.+>11<.+>22<',
  282. 'Directory listing (size, descending)');
  283. o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
  284. '200 OK.+>11<.+>aa<.+>bb<.+>22<',
  285. 'Directory listing (modification time, ascending)');
  286. o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
  287. '200 OK.+>22<.+>bb<.+>aa<.+>11<',
  288. 'Directory listing (modification time, descending)');
  289. unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
  290. # Check that .htpasswd file existence trigger authorization
  291. write_file("$root/.htpasswd", 'user with space, " and comma:mydomain.com:5deda12442309cbdcdffc6b2737a894f');
  292. o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
  293. '.htpasswd - triggering auth on file request');
  294. o("GET / HTTP/1.1\n\n", '401 Unauthorized',
  295. '.htpasswd - triggering auth on directory request');
  296. # Test various funky things in an authentication header.
  297. o("GET /hello.txt HTTP/1.0\nAuthorization: Digest eq== empty=\"\", empty2=, quoted=\"blah foo bar, baz\\\"\\\" more\\\"\", unterminatedquoted=\" doesn't stop\n\n",
  298. '401 Unauthorized', 'weird auth values should not cause crashes');
  299. my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
  300. "realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
  301. "response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
  302. o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
  303. o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(.htpasswd)))*$',
  304. '.htpasswd is hidden from the directory list');
  305. o("GET / HTTP/1.0\nAuthorization: $auth_header\n\n", '^(.(?!(exploit.pl)))*$',
  306. 'hidden file is hidden from the directory list');
  307. o("GET /.htpasswd HTTP/1.0\nAuthorization: $auth_header\n\n",
  308. '^HTTP/1.1 404 ', '.htpasswd must not be shown');
  309. o("GET /exploit.pl HTTP/1.0\nAuthorization: $auth_header\n\n",
  310. '^HTTP/1.1 404', 'hidden files must not be shown');
  311. unlink "$root/.htpasswd";
  312. o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
  313. o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 123 Please pass me to the client\r",
  314. 'CGI Status code text');
  315. o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
  316. 'GET sh CGI file') unless on_windows();
  317. o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
  318. 'QUERY_STRING wrong');
  319. o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
  320. 'var=HELLO', 'CGI POST wrong');
  321. o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
  322. '\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
  323. o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
  324. 'HTTP_MY_HDR=abc', 'HTTP_* env');
  325. o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
  326. 'HTTP/1.1 200 OK', 'GET CGI with trailing data');
  327. o("GET /env.cgi%20 HTTP/1.0\n\r\n",
  328. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
  329. o("GET /env.cgi%ff HTTP/1.0\n\r\n",
  330. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
  331. o("GET /env.cgi%2e HTTP/1.0\n\r\n",
  332. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
  333. o("GET /env.cgi%2b HTTP/1.0\n\r\n",
  334. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
  335. o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
  336. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
  337. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
  338. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
  339. o("GET /env.cgi/a/b/98 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/98\n', 'PATH_INFO');
  340. o("GET /env.cgi/a/b/9 HTTP/1.0\n\r\n", 'PATH_INFO=/a/b/9\n', 'PATH_INFO');
  341. # Check that CGI's current directory is set to script's directory
  342. my $copy_cmd = on_windows() ? 'copy' : 'cp';
  343. system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
  344. $dir_separator . 'env.cgi');
  345. o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
  346. "CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
  347. # SSI tests
  348. o("GET /ssi1.shtml HTTP/1.0\n\n",
  349. 'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
  350. o("GET /ssi2.shtml HTTP/1.0\n\n",
  351. 'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
  352. my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
  353. o("GET /$ssi_exec HTTP/1.0\n\n",
  354. 'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
  355. my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
  356. my $word = on_windows() ? 'boot loader' : 'root';
  357. o("GET /$abs_path HTTP/1.0\n\n",
  358. "ssi_begin.+$word.+ssi_end", 'SSI #include file= (absolute)');
  359. o("GET /ssi7.shtml HTTP/1.0\n\n",
  360. 'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
  361. o("GET /ssi8.shtml HTTP/1.0\n\n",
  362. 'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
  363. # Manipulate the passwords file
  364. my $path = 'test_htpasswd';
  365. unlink $path;
  366. system("$exe -A $path a b c") == 0
  367. or fail("Cannot add user in a passwd file");
  368. system("$exe -A $path a b c2") == 0
  369. or fail("Cannot edit user in a passwd file");
  370. my $content = read_file($path);
  371. $content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
  372. unlink $path;
  373. do_PUT_test();
  374. kill_spawned_child();
  375. do_unit_test();
  376. }
  377. sub do_PUT_test {
  378. # This only works because mongoose currently doesn't look at the nonce.
  379. # It should really be rejected...
  380. my $auth_header = "Authorization: Digest username=guest, ".
  381. "realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
  382. "response=896327350763836180c61d87578037d9, qop=auth, ".
  383. "nc=00000002, cnonce=53eddd3be4e26a98\n";
  384. o("PUT /a/put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
  385. "HTTP/1.1 201 OK", 'PUT file, status 201');
  386. fail("PUT content mismatch")
  387. unless read_file("$root/a/put.txt") eq '1234567';
  388. o("PUT /a/put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
  389. "HTTP/1.1 200 OK", 'PUT file, status 200');
  390. fail("PUT content mismatch")
  391. unless read_file("$root/a/put.txt") eq 'abcd';
  392. o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
  393. "HTTP/1.1 411 Length Required", 'PUT 411 error');
  394. o("PUT /a/put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
  395. "$auth_header\nabcd",
  396. "HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
  397. o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
  398. "$auth_header\nabcd",
  399. "HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
  400. }
  401. sub do_unit_test {
  402. my $target = on_windows() ? 'w' : 'u';
  403. system("make $target") == 0 or fail("Unit test failed!");
  404. }
  405. print "SUCCESS! All tests passed.\n";