test.pl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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 $root = 'test';
  14. my $dir_separator = on_windows() ? '\\' : '/';
  15. my $copy_cmd = on_windows() ? 'copy' : 'cp';
  16. my $test_dir_uri = "test_dir";
  17. my $test_dir = $root . $dir_separator. $test_dir_uri;
  18. my $alias = "/aliased=/etc/,/ta=$test_dir";
  19. my $config = 'mongoose.conf';
  20. my $exe = '.' . $dir_separator . 'mongoose';
  21. my $embed_exe = '.' . $dir_separator . 'embed';
  22. my $unit_test_exe = '.' . $dir_separator . 'unit_test';
  23. my $exit_code = 0;
  24. my @files_to_delete = ('debug.log', 'access.log', $config, "$root/put.txt",
  25. "$root/a+.txt", "$root/.htpasswd", "$root/binary_file",
  26. $embed_exe, $unit_test_exe);
  27. END {
  28. unlink @files_to_delete;
  29. kill_spawned_child();
  30. File::Path::rmtree($test_dir);
  31. exit $exit_code;
  32. }
  33. sub fail {
  34. print "FAILED: @_\n";
  35. $exit_code = 1;
  36. exit 1;
  37. }
  38. sub get_num_of_log_entries {
  39. open FD, "access.log" or return 0;
  40. my @lines = (<FD>);
  41. close FD;
  42. return scalar @lines;
  43. }
  44. # Send the request to the 127.0.0.1:$port and return the reply
  45. sub req {
  46. my ($request, $inc) = @_;
  47. my $sock = IO::Socket::INET->new(Proto=>"tcp",
  48. PeerAddr=>'127.0.0.1', PeerPort=>$port);
  49. fail("Cannot connect: $!") unless $sock;
  50. $sock->autoflush(1);
  51. foreach my $byte (split //, $request) {
  52. last unless print $sock $byte;
  53. select undef, undef, undef, .001 if length($request) < 256;
  54. }
  55. my @lines = <$sock>;
  56. my $out = join '', @lines;
  57. close $sock;
  58. $num_requests += defined($inc) ? $inc : 1;
  59. my $num_logs = get_num_of_log_entries();
  60. unless ($num_requests == $num_logs) {
  61. fail("Request has not been logged: [$request], output: [$out]");
  62. }
  63. return $out;
  64. }
  65. # Send the request. Compare with the expected reply. Fail if no match
  66. sub o {
  67. my ($request, $expected_reply, $message, $num_logs) = @_;
  68. print "==> Testing $message ... ";
  69. my $reply = req($request, $num_logs);
  70. if ($reply =~ /$expected_reply/s) {
  71. print "OK\n";
  72. } else {
  73. fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
  74. }
  75. }
  76. # Spawn a server listening on specified port
  77. sub spawn {
  78. my ($cmdline) = @_;
  79. if (on_windows()) {
  80. my @args = split /\s+/, $cmdline;
  81. my $executable = $args[0];
  82. $executable .= '.exe';
  83. Win32::Spawn($executable, $cmdline, $pid);
  84. die "Cannot spawn @_: $!" unless $pid;
  85. } else {
  86. unless ($pid = fork()) {
  87. exec $cmdline;
  88. die "cannot exec [$cmdline]: $!\n";
  89. }
  90. }
  91. sleep 1;
  92. }
  93. sub write_file {
  94. open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
  95. binmode FD;
  96. print FD $_[1];
  97. close FD;
  98. }
  99. sub read_file {
  100. open FD, $_[0] or fail "Cannot open $_[0]: $!";
  101. my @lines = <FD>;
  102. close FD;
  103. return join '', @lines;
  104. }
  105. sub kill_spawned_child {
  106. if (defined($pid)) {
  107. kill(9, $pid);
  108. waitpid($pid, 0);
  109. }
  110. }
  111. ####################################################### ENTRY POINT
  112. unlink @files_to_delete;
  113. $SIG{PIPE} = 'IGNORE';
  114. #local $| =1;
  115. # Make sure we export only symbols that start with "mg_", and keep local
  116. # symbols static.
  117. if ($^O =~ /darwin|bsd|linux/) {
  118. my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
  119. foreach (split /\n/, $out) {
  120. /T\s+_?mg_.+/ or fail("Exported symbol $_")
  121. }
  122. }
  123. if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
  124. do_embedded_test();
  125. exit 0;
  126. }
  127. # Make sure we load config file if no options are given
  128. write_file($config, "ports 12345\naccess_log access.log\n");
  129. spawn($exe);
  130. my $saved_port = $port;
  131. $port = 12345;
  132. o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
  133. $port = $saved_port;
  134. unlink $config;
  135. kill_spawned_child();
  136. # Spawn the server on port $port
  137. my $cmd = "$exe -ports $port -access_log access.log -error_log debug.log ".
  138. "-cgi_env CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
  139. "-mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
  140. "-root test -aliases $alias -admin_uri /hh";
  141. $cmd .= ' -cgi_interp perl' if on_windows();
  142. spawn($cmd);
  143. # Try to overflow: Send very long request
  144. req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
  145. o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
  146. o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
  147. 'GET regular file Content-Length');
  148. o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
  149. 'HTTP/1.1 200 OK', 'URL-decoding');
  150. # '+' in URI must not be URL-decoded to space
  151. write_file("$root/a+.txt", '');
  152. o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
  153. #o("GET /hh HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET admin URI');
  154. # Test HTTP version parsing
  155. o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0);
  156. o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version');
  157. o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version');
  158. o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported',
  159. 'HTTP Version >1.1');
  160. # File with leading single dot
  161. o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
  162. o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
  163. o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3');
  164. o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
  165. mkdir $test_dir unless -d $test_dir;
  166. o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
  167. 'HTTP/1.1 404', 'PATH_INFO loop problem');
  168. o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
  169. o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
  170. write_file("$test_dir/index.html", "tralala");
  171. o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
  172. o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
  173. o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
  174. o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
  175. mkdir $test_dir . $dir_separator . 'x';
  176. my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
  177. write_file($path, read_file($root . $dir_separator . 'env.cgi'));
  178. chmod 0755, $path;
  179. o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
  180. 'index.cgi execution');
  181. o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
  182. 'Aliases SCRIPT_NAME');
  183. my $mime_types = {
  184. html => 'text/html',
  185. htm => 'text/html',
  186. txt => 'text/plain',
  187. unknown_extension => 'text/plain',
  188. js => 'application/x-javascript',
  189. css => 'text/css',
  190. jpg => 'image/jpeg',
  191. c => 'text/plain',
  192. 'tar.gz' => 'blah',
  193. bar => 'foo/bar',
  194. baz => 'foo',
  195. };
  196. foreach my $key (keys %$mime_types) {
  197. my $filename = "_mime_file_test.$key";
  198. write_file("$root/$filename", '');
  199. o("GET /$filename HTTP/1.0\n\n",
  200. "Content-Type: $mime_types->{$key}", ".$key mime type");
  201. unlink "$root/$filename";
  202. }
  203. # Get binary file and check the integrity
  204. my $binary_file = 'binary_file';
  205. my $f2 = '';
  206. foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
  207. write_file("$root/$binary_file", $f2);
  208. my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
  209. while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
  210. $f1 eq $f2 or fail("Integrity check for downloaded binary file");
  211. my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
  212. "Range: bytes=3-5\r\n\r\n";
  213. o($range_request, '206 Partial Content', 'Range: 206 status code');
  214. o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
  215. o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
  216. o($range_request, '\nple$', 'Range: body content');
  217. # Test directory sorting. Sleep between file creation for 1.1 seconds,
  218. # to make sure modification time are different.
  219. mkdir "$test_dir/sort";
  220. write_file("$test_dir/sort/11", 'xx');
  221. select undef, undef, undef, 1.1;
  222. write_file("$test_dir/sort/aa", 'xxxx');
  223. select undef, undef, undef, 1.1;
  224. write_file("$test_dir/sort/bb", 'xxx');
  225. select undef, undef, undef, 1.1;
  226. write_file("$test_dir/sort/22", 'x');
  227. o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
  228. '200 OK.+>11<.+>22<.+>aa<.+>bb<',
  229. 'Directory listing (name, ascending)');
  230. o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
  231. '200 OK.+>bb<.+>aa<.+>22<.+>11<',
  232. 'Directory listing (name, descending)');
  233. o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
  234. '200 OK.+>22<.+>11<.+>bb<.+>aa<',
  235. 'Directory listing (size, ascending)');
  236. o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
  237. '200 OK.+>aa<.+>bb<.+>11<.+>22<',
  238. 'Directory listing (size, descending)');
  239. o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
  240. '200 OK.+>11<.+>aa<.+>bb<.+>22<',
  241. 'Directory listing (modification time, ascending)');
  242. o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
  243. '200 OK.+>22<.+>bb<.+>aa<.+>11<',
  244. 'Directory listing (modification time, descending)');
  245. unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
  246. # Check that .htpasswd file existence trigger authorization
  247. write_file("$root/.htpasswd", '');
  248. o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
  249. '.htpasswd - triggering auth on file request');
  250. o("GET / HTTP/1.1\n\n", '401 Unauthorized',
  251. '.htpasswd - triggering auth on directory request');
  252. unlink "$root/.htpasswd";
  253. o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
  254. o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
  255. 'GET sh CGI file') unless on_windows();
  256. o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
  257. 'QUERY_STRING wrong');
  258. o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
  259. 'var=HELLO', 'CGI POST wrong');
  260. o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
  261. '\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
  262. o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
  263. 'HTTP_MY_HDR=abc', 'HTTP_* env');
  264. o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
  265. 'HTTP/1.1 200 OK', 'GET CGI with trailing data');
  266. o("GET /env.cgi%20 HTTP/1.0\n\r\n",
  267. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
  268. o("GET /env.cgi%ff HTTP/1.0\n\r\n",
  269. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
  270. o("GET /env.cgi%2e HTTP/1.0\n\r\n",
  271. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
  272. o("GET /env.cgi%2b HTTP/1.0\n\r\n",
  273. 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
  274. o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
  275. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
  276. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
  277. o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
  278. # Check that CGI's current directory is set to script's directory
  279. my $copy_cmd = on_windows() ? 'copy' : 'cp';
  280. system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
  281. $dir_separator . 'env.cgi');
  282. o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
  283. "CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
  284. # SSI tests
  285. o("GET /ssi1.shtml HTTP/1.0\n\n",
  286. 'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
  287. o("GET /ssi2.shtml HTTP/1.0\n\n",
  288. 'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
  289. my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
  290. o("GET /$ssi_exec HTTP/1.0\n\n",
  291. 'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
  292. my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
  293. my $word = on_windows() ? 'boot loader' : 'root';
  294. o("GET /$abs_path HTTP/1.0\n\n",
  295. "ssi_begin.+$word.+ssi_end", 'SSI #include file= (absolute)');
  296. o("GET /ssi7.shtml HTTP/1.0\n\n",
  297. 'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
  298. o("GET /ssi8.shtml HTTP/1.0\n\n",
  299. 'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
  300. # Manipulate the passwords file
  301. my $path = 'test_htpasswd';
  302. unlink $path;
  303. system("$exe -A $path a b c") == 0
  304. or fail("Cannot add user in a passwd file");
  305. system("$exe -A $path a b c2") == 0
  306. or fail("Cannot edit user in a passwd file");
  307. my $content = read_file($path);
  308. $content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
  309. unlink $path;
  310. kill_spawned_child();
  311. do_PUT_test();
  312. #do_embedded_test();
  313. }
  314. sub do_PUT_test {
  315. $cmd .= ' -auth_PUT test/passfile';
  316. spawn($cmd);
  317. my $auth_header = "Authorization: Digest username=guest, ".
  318. "realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
  319. "response=896327350763836180c61d87578037d9, qop=auth, ".
  320. "nc=00000002, cnonce=53eddd3be4e26a98\n";
  321. o("PUT /put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
  322. "HTTP/1.1 201 OK", 'PUT file, status 201');
  323. fail("PUT content mismatch")
  324. unless read_file("$root/put.txt") eq '1234567';
  325. o("PUT /put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
  326. "HTTP/1.1 200 OK", 'PUT file, status 200');
  327. fail("PUT content mismatch")
  328. unless read_file("$root/put.txt") eq 'abcd';
  329. o("PUT /put.txt HTTP/1.0\n$auth_header\nabcd",
  330. "HTTP/1.1 411 Length Required", 'PUT 411 error');
  331. o("PUT /put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
  332. "$auth_header\nabcd",
  333. "HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
  334. o("PUT /put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
  335. "$auth_header\nabcd",
  336. "HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
  337. kill_spawned_child();
  338. }
  339. sub do_embedded_test {
  340. my $cmd = "cc -o $embed_exe $root/embed.c mongoose.c -I. ".
  341. "-DNO_SSL -lpthread -DLISTENING_PORT=\\\"$port\\\"";
  342. if (on_windows()) {
  343. $cmd = "cl $root/embed.c mongoose.c /I. /nologo ".
  344. "/DNO_SSL /DLISTENING_PORT=\\\"$port\\\" ".
  345. "/link /out:$embed_exe.exe ws2_32.lib ";
  346. }
  347. print $cmd, "\n";
  348. system($cmd) == 0 or fail("Cannot compile embedded unit test");
  349. spawn("./$embed_exe");
  350. o("GET /test_get_header HTTP/1.0\nHost: blah\n\n",
  351. 'Value: \[blah\]', 'mg_get_header', 0);
  352. o("GET /test_get_var?a=b&my_var=foo&c=d HTTP/1.0\n\n",
  353. 'Value: \[foo\]', 'mg_get_var 1', 0);
  354. o("GET /test_get_var?my_var=foo&c=d HTTP/1.0\n\n",
  355. 'Value: \[foo\]', 'mg_get_var 2', 0);
  356. o("GET /test_get_var?a=b&my_var=foo HTTP/1.0\n\n",
  357. 'Value: \[foo\]', 'mg_get_var 3', 0);
  358. o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
  359. "my_var=foo", 'Value: \[foo\]', 'mg_get_var 4', 0);
  360. o("POST /test_get_var HTTP/1.0\nContent-Length: 18\n\n".
  361. "a=b&my_var=foo&c=d", 'Value: \[foo\]', 'mg_get_var 5', 0);
  362. o("POST /test_get_var HTTP/1.0\nContent-Length: 14\n\n".
  363. "a=b&my_var=foo", 'Value: \[foo\]', 'mg_get_var 6', 0);
  364. o("GET /test_get_var?a=one%2btwo&my_var=foo& HTTP/1.0\n\n",
  365. 'Value: \[foo\]', 'mg_get_var 7', 0);
  366. o("GET /test_get_var?my_var=one%2btwo&b=two%2b HTTP/1.0\n\n",
  367. 'Value: \[one\+two\]', 'mg_get_var 8', 0);
  368. # + in form data MUST be decoded to space
  369. o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
  370. "my_var=b+c", 'Value: \[b c\]', 'mg_get_var 7', 0);
  371. # Test that big POSTed vars are not truncated
  372. my $my_var = 'x' x 64000;
  373. o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n".
  374. "my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 8', 0);
  375. # Test PUT
  376. o("PUT /put HTTP/1.0\nContent-Length: 3\n\nabc",
  377. '\nabc$', 'put callback', 0);
  378. o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n".
  379. "Content-Length: 3\n\na=b",
  380. 'Method: \[POST\].URI: \[/test_get_request_info\].'.
  381. 'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'.
  382. 'HTTP header \[Content-Length\]: \[3\].'.
  383. 'Query string: \[xx=yy\].POST data: \[a=b\].'.
  384. 'Remote IP: \[\d+\].Remote port: \[\d+\].'.
  385. 'Remote user: \[\]'
  386. , 'request_info', 0);
  387. o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0);
  388. o("bad request\n\n", 'Error: \[400\]', '* error handler', 0);
  389. o("GET /test_user_data HTTP/1.0\n\n",
  390. 'User data: \[1234\]', 'user data in callback', 0);
  391. # o("GET /foo/secret HTTP/1.0\n\n",
  392. # '401 Unauthorized', 'mg_protect_uri', 0);
  393. # o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
  394. # '401 Unauthorized', 'mg_protect_uri (bill)', 0);
  395. # o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n",
  396. # '200 OK', 'mg_protect_uri (joe)', 0);
  397. # Test un-binding the URI
  398. o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 200 OK', '/foo bound', 0);
  399. o("GET /test_remove_callback HTTP/1.0\n\n",
  400. 'Removing callbacks', 'Callback removal', 0);
  401. o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 404', '/foo unbound', 0);
  402. kill_spawned_child();
  403. }
  404. print "SUCCESS! All tests passed.\n";