xref: /openbsd/regress/usr.sbin/relayd/funcs.pl (revision 508def2a)
1*508def2aSbluhm#	$OpenBSD: funcs.pl,v 1.26 2024/06/14 15:12:57 bluhm Exp $
2c2d4e910Sbluhm
3b65dd5eaSbluhm# Copyright (c) 2010-2021 Alexander Bluhm <bluhm@openbsd.org>
4c2d4e910Sbluhm#
5c2d4e910Sbluhm# Permission to use, copy, modify, and distribute this software for any
6c2d4e910Sbluhm# purpose with or without fee is hereby granted, provided that the above
7c2d4e910Sbluhm# copyright notice and this permission notice appear in all copies.
8c2d4e910Sbluhm#
9c2d4e910Sbluhm# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10c2d4e910Sbluhm# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11c2d4e910Sbluhm# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12c2d4e910Sbluhm# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13c2d4e910Sbluhm# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14c2d4e910Sbluhm# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15c2d4e910Sbluhm# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16c2d4e910Sbluhm
17c2d4e910Sbluhmuse strict;
18c2d4e910Sbluhmuse warnings;
19c2d4e910Sbluhmuse Errno;
20c2d4e910Sbluhmuse Digest::MD5;
21c2d4e910Sbluhmuse Socket;
22c2d4e910Sbluhmuse Socket6;
23c2d4e910Sbluhmuse IO::Socket;
24b65dd5eaSbluhmuse IO::Socket::IP;
25c2d4e910Sbluhm
26c2d4e910Sbluhmsub find_ports {
27c2d4e910Sbluhm	my %args = @_;
28c2d4e910Sbluhm	my $num    = delete $args{num}    // 1;
29c2d4e910Sbluhm	my $domain = delete $args{domain} // AF_INET;
30c2d4e910Sbluhm	my $addr   = delete $args{addr}   // "127.0.0.1";
31c2d4e910Sbluhm
32c2d4e910Sbluhm	my @sockets = (1..$num);
33c2d4e910Sbluhm	foreach my $s (@sockets) {
34b65dd5eaSbluhm		$s = IO::Socket::IP->new(
35c2d4e910Sbluhm		    Proto  => "tcp",
36b65dd5eaSbluhm		    Family => $domain,
37c2d4e910Sbluhm		    $addr ? (LocalAddr => $addr) : (),
38c2d4e910Sbluhm		) or die "find_ports: create and bind socket failed: $!";
39c2d4e910Sbluhm	}
40c2d4e910Sbluhm	my @ports = map { $_->sockport() } @sockets;
41c2d4e910Sbluhm
42c2d4e910Sbluhm	return @ports;
43c2d4e910Sbluhm}
44c2d4e910Sbluhm
45c2d4e910Sbluhm########################################################################
46c2d4e910Sbluhm# Client funcs
47c2d4e910Sbluhm########################################################################
48c2d4e910Sbluhm
493daec375Sbennosub write_syswrite {
503daec375Sbenno	my $self = shift;
513daec375Sbenno	my $buf = shift;
523daec375Sbenno
53c6fb90fcSbluhm	IO::Handle::flush(\*STDOUT);
54c6fb90fcSbluhm	my $size = length($buf);
55c6fb90fcSbluhm	my $len = 0;
563daec375Sbenno	while ($len < $size) {
57c6fb90fcSbluhm		my $n = syswrite(STDOUT, $buf, $size, $len);
58c6fb90fcSbluhm		if (!defined($n)) {
59c6fb90fcSbluhm			$!{EWOULDBLOCK}
60c6fb90fcSbluhm			    or die ref($self), " syswrite failed: $!";
61c6fb90fcSbluhm			print STDERR "blocked write at $len of $size: $!\n";
62c6fb90fcSbluhm			next;
633daec375Sbenno		}
64c6fb90fcSbluhm		if ($len + $n != $size) {
65c6fb90fcSbluhm			print STDERR "short write $n at $len of $size\n";
663daec375Sbenno		}
67c6fb90fcSbluhm		$len += $n;
683daec375Sbenno	}
693daec375Sbenno	return $len;
703daec375Sbenno}
713daec375Sbenno
72c6fb90fcSbluhmsub write_block {
733daec375Sbenno	my $self = shift;
743daec375Sbenno	my $len = shift;
753daec375Sbenno
763daec375Sbenno	my $data;
773daec375Sbenno	my $outb = 0;
783daec375Sbenno	my $blocks = int($len / 1000);
793daec375Sbenno	my $rest = $len % 1000;
803daec375Sbenno
813daec375Sbenno	for (my $i = 1; $i <= 100 ; $i++) {
82c6fb90fcSbluhm		$data .= "012345678\n";
833daec375Sbenno	}
843daec375Sbenno
85c6fb90fcSbluhm	my $opct = 0;
863daec375Sbenno	for (my $i = 1; $i <= $blocks; $i++) {
873daec375Sbenno		$outb += write_syswrite($self, $data);
88c6fb90fcSbluhm		my $pct = ($outb / $len) * 100.0;
89c6fb90fcSbluhm		if ($pct >= $opct + 1) {
90c6fb90fcSbluhm			printf(STDERR "%.2f%% $outb/$len\n", $pct);
91c6fb90fcSbluhm			$opct = $pct;
92c6fb90fcSbluhm		}
933daec375Sbenno	}
943daec375Sbenno
953daec375Sbenno	if ($rest>0) {
963daec375Sbenno		for (my $i = 1; $i < $rest-1 ; $i++) {
973daec375Sbenno		    $outb += write_syswrite($self, 'r');
98c6fb90fcSbluhm		    my $pct = ($outb / $len) * 100.0;
99c6fb90fcSbluhm		    if ($pct >= $opct + 1) {
100c6fb90fcSbluhm			    printf(STDERR "%.2f%% $outb/$len\n", $pct);
101c6fb90fcSbluhm			    $opct = $pct;
1023daec375Sbenno		    }
1033daec375Sbenno		}
104c6fb90fcSbluhm	}
1053daec375Sbenno	$outb += write_syswrite($self, "\n\n");
1063daec375Sbenno	IO::Handle::flush(\*STDOUT);
1073daec375Sbenno	print STDERR "LEN: ", $outb, "\n";
1083daec375Sbenno}
1093daec375Sbenno
110c2d4e910Sbluhmsub write_char {
111c2d4e910Sbluhm	my $self = shift;
112c2d4e910Sbluhm	my $len = shift // $self->{len} // 251;
113c2d4e910Sbluhm	my $sleep = $self->{sleep};
114c2d4e910Sbluhm
1153daec375Sbenno	if ($self->{fast}) {
116c6fb90fcSbluhm		write_block($self, $len);
1173daec375Sbenno		return;
1183daec375Sbenno	}
1193daec375Sbenno
120c2d4e910Sbluhm	my $ctx = Digest::MD5->new();
121c2d4e910Sbluhm	my $char = '0';
122c2d4e910Sbluhm	for (my $i = 1; $i < $len; $i++) {
123c2d4e910Sbluhm		$ctx->add($char);
124c2d4e910Sbluhm		print $char
125c2d4e910Sbluhm		    or die ref($self), " print failed: $!";
126*508def2aSbluhm		if    ($char =~ /9/)  { $char = 'A' }
127*508def2aSbluhm		elsif ($char =~ /Z/)  { $char = 'a' }
128*508def2aSbluhm		elsif ($char =~ /z/)  { $char = "\n" }
129*508def2aSbluhm		elsif ($char =~ /\n/) { print STDERR "."; $char = '0' }
130*508def2aSbluhm		else                  { $char++ }
131c2d4e910Sbluhm		if ($self->{sleep}) {
132c2d4e910Sbluhm			IO::Handle::flush(\*STDOUT);
133c2d4e910Sbluhm			sleep $self->{sleep};
134c2d4e910Sbluhm		}
135c2d4e910Sbluhm	}
136c2d4e910Sbluhm	if ($len) {
137c2d4e910Sbluhm		$char = "\n";
138c2d4e910Sbluhm		$ctx->add($char);
139c2d4e910Sbluhm		print $char
140c2d4e910Sbluhm		    or die ref($self), " print failed: $!";
141c2d4e910Sbluhm		print STDERR ".\n";
142c2d4e910Sbluhm	}
143c2d4e910Sbluhm	IO::Handle::flush(\*STDOUT);
144c2d4e910Sbluhm
145c2d4e910Sbluhm	print STDERR "LEN: ", $len, "\n";
146c2d4e910Sbluhm	print STDERR "MD5: ", $ctx->hexdigest, "\n";
147c2d4e910Sbluhm}
148c2d4e910Sbluhm
1499d1e6fe8Sbluhmsub http_client {
1509d1e6fe8Sbluhm	my $self = shift;
151cab60319Sbluhm
152cab60319Sbluhm	unless ($self->{lengths}) {
153cab60319Sbluhm		# only a single http request
154cab60319Sbluhm		my $len = shift // $self->{len} // 251;
155cab60319Sbluhm		my $cookie = $self->{cookie};
156cab60319Sbluhm		http_request($self, $len, "1.0", $cookie);
1571b3c3ba0Sbluhm		http_response($self, $len);
158cab60319Sbluhm		return;
159cab60319Sbluhm	}
160cab60319Sbluhm
161cab60319Sbluhm	$self->{http_vers} ||= ["1.1", "1.0"];
162cab60319Sbluhm	my $vers = $self->{http_vers}[0];
163cab60319Sbluhm	my @lengths = @{$self->{redo}{lengths} || $self->{lengths}};
164cab60319Sbluhm	my @cookies = @{$self->{redo}{cookies} || $self->{cookies} || []};
165cab60319Sbluhm	while (defined (my $len = shift @lengths)) {
166cab60319Sbluhm		my $cookie = shift @cookies || $self->{cookie};
1671b3c3ba0Sbluhm		eval {
1681b3c3ba0Sbluhm			http_request($self, $len, $vers, $cookie);
1691b3c3ba0Sbluhm			http_response($self, $len);
1701b3c3ba0Sbluhm		};
171cab60319Sbluhm		warn $@ if $@;
172cab60319Sbluhm		if (@lengths && ($@ || $vers eq "1.0")) {
173cab60319Sbluhm			# reconnect and redo the outstanding requests
174cab60319Sbluhm			$self->{redo} = {
175cab60319Sbluhm			    lengths => \@lengths,
176cab60319Sbluhm			    cookies => \@cookies,
177cab60319Sbluhm			};
178cab60319Sbluhm			return;
179cab60319Sbluhm		}
180cab60319Sbluhm	}
181cab60319Sbluhm	delete $self->{redo};
182cab60319Sbluhm	shift @{$self->{http_vers}};
183cab60319Sbluhm	if (@{$self->{http_vers}}) {
184cab60319Sbluhm		# run the tests again with other persistence
185cab60319Sbluhm		$self->{redo} = {
186cab60319Sbluhm		    lengths => [@{$self->{lengths}}],
187cab60319Sbluhm		    cookies => [@{$self->{cookies} || []}],
188cab60319Sbluhm		};
189cab60319Sbluhm	}
190cab60319Sbluhm}
191cab60319Sbluhm
192cab60319Sbluhmsub http_request {
193cab60319Sbluhm	my ($self, $len, $vers, $cookie) = @_;
1943a4f532cSbluhm	my $method = $self->{method} || "GET";
195a3c410c1Sbluhm	my %header = %{$self->{header} || {}};
1969d1e6fe8Sbluhm
197c64e9fedSbluhm	# encode the requested length or chunks into the url
1987582d965Sbluhm	my $path = ref($len) eq 'ARRAY' ? join("/", @$len) : $len;
19985781182Sandre	# overwrite path with custom path
20085781182Sandre	if (defined($self->{path})) {
20185781182Sandre		$path = $self->{path};
20285781182Sandre	}
203a3c410c1Sbluhm	my @request = ("$method /$path HTTP/$vers");
204a3c410c1Sbluhm	push @request, "Host: foo.bar" unless defined $header{Host};
2051b3c3ba0Sbluhm	if ($vers eq "1.1" && $method eq "PUT") {
2061b3c3ba0Sbluhm		if (ref($len) eq 'ARRAY') {
2071b3c3ba0Sbluhm			push @request, "Transfer-Encoding: chunked"
2081b3c3ba0Sbluhm			    if !defined $header{'Transfer-Encoding'};
2091b3c3ba0Sbluhm		} else {
210c64e9fedSbluhm			push @request, "Content-Length: $len"
2111b3c3ba0Sbluhm			    if !defined $header{'Content-Length'};
2121b3c3ba0Sbluhm		}
2131b3c3ba0Sbluhm	}
2147e1bbd8eSreyk	foreach my $key (sort keys %header) {
2157e1bbd8eSreyk		my $val = $header{$key};
2167e1bbd8eSreyk		if (ref($val) eq 'ARRAY') {
2177e1bbd8eSreyk			push @request, "$key: $_"
2187e1bbd8eSreyk			    foreach @{$val};
2197e1bbd8eSreyk		} else {
2207e1bbd8eSreyk			push @request, "$key: $val";
2217e1bbd8eSreyk		}
2227e1bbd8eSreyk	}
223cab60319Sbluhm	push @request, "Cookie: $cookie" if $cookie;
224c64e9fedSbluhm	push @request, "";
225c64e9fedSbluhm	print STDERR map { ">>> $_\n" } @request;
226c64e9fedSbluhm	print map { "$_\r\n" } @request;
2271b3c3ba0Sbluhm	if ($method eq "PUT") {
2281b3c3ba0Sbluhm		if (ref($len) eq 'ARRAY') {
2291b3c3ba0Sbluhm			if ($vers eq "1.1") {
2301b3c3ba0Sbluhm				write_chunked($self, @$len);
2311b3c3ba0Sbluhm			} else {
2321b3c3ba0Sbluhm				write_char($self, $_) foreach (@$len);
2331b3c3ba0Sbluhm			}
2341b3c3ba0Sbluhm		} else {
2351b3c3ba0Sbluhm			write_char($self, $len);
2361b3c3ba0Sbluhm		}
2371b3c3ba0Sbluhm	}
2389d1e6fe8Sbluhm	IO::Handle::flush(\*STDOUT);
239a3c410c1Sbluhm	# XXX client shutdown seems to be broken in relayd
240a3c410c1Sbluhm	#shutdown(\*STDOUT, SHUT_WR)
241a3c410c1Sbluhm	#    or die ref($self), " shutdown write failed: $!"
242a3c410c1Sbluhm	#    if $vers ne "1.1";
2431b3c3ba0Sbluhm}
2449d1e6fe8Sbluhm
2451b3c3ba0Sbluhmsub http_response {
2461b3c3ba0Sbluhm	my ($self, $len) = @_;
2471b3c3ba0Sbluhm	my $method = $self->{method} || "GET";
2481b3c3ba0Sbluhm
2491b3c3ba0Sbluhm	my $vers;
2507582d965Sbluhm	my $chunked = 0;
2519d1e6fe8Sbluhm	{
2529d1e6fe8Sbluhm		local $/ = "\r\n";
2539d1e6fe8Sbluhm		local $_ = <STDIN>;
254a3c410c1Sbluhm		defined
255cab60319Sbluhm		    or die ref($self), " missing http $len response";
256cab60319Sbluhm		chomp;
257cab60319Sbluhm		print STDERR "<<< $_\n";
2581b3c3ba0Sbluhm		m{^HTTP/(\d\.\d) 200 OK$}
259cab60319Sbluhm		    or die ref($self), " http response not ok"
260cab60319Sbluhm		    unless $self->{httpnok};
2611b3c3ba0Sbluhm		$vers = $1;
2629d1e6fe8Sbluhm		while (<STDIN>) {
2639d1e6fe8Sbluhm			chomp;
264c64e9fedSbluhm			print STDERR "<<< $_\n";
2659d1e6fe8Sbluhm			last if /^$/;
2669d1e6fe8Sbluhm			if (/^Content-Length: (.*)/) {
26732fc6e3dSbluhm				if ($self->{httpnok}) {
26832fc6e3dSbluhm					$len = $1;
26932fc6e3dSbluhm				} else {
2709d1e6fe8Sbluhm					$1 == $len or die ref($self),
2719d1e6fe8Sbluhm					    " bad content length $1";
2729d1e6fe8Sbluhm				}
27332fc6e3dSbluhm			}
2747582d965Sbluhm			if (/^Transfer-Encoding: chunked$/) {
2757582d965Sbluhm				$chunked = 1;
2769d1e6fe8Sbluhm			}
2779d1e6fe8Sbluhm		}
2787582d965Sbluhm	}
279bfbf8851Sbenno	if ($method ne 'HEAD') {
2807582d965Sbluhm		if ($chunked) {
2817582d965Sbluhm			read_chunked($self);
2827582d965Sbluhm		} else {
283c6fb90fcSbluhm			undef $len unless defined($vers) && $vers eq "1.1";
284c6fb90fcSbluhm			read_char($self, $len)
2853a4f532cSbluhm			    if $method eq "GET";
2869d1e6fe8Sbluhm		}
2877582d965Sbluhm	}
288bfbf8851Sbenno}
2897582d965Sbluhm
2907582d965Sbluhmsub read_chunked {
2917582d965Sbluhm	my $self = shift;
2927582d965Sbluhm
2937582d965Sbluhm	for (;;) {
2947582d965Sbluhm		my $len;
2957582d965Sbluhm		{
2967582d965Sbluhm			local $/ = "\r\n";
2977582d965Sbluhm			local $_ = <STDIN>;
2987582d965Sbluhm			defined or die ref($self), " missing chunk size";
2997582d965Sbluhm			chomp;
300c64e9fedSbluhm			print STDERR "<<< $_\n";
3017582d965Sbluhm			/^[[:xdigit:]]+$/
3027582d965Sbluhm			    or die ref($self), " chunk size not hex: $_";
3037582d965Sbluhm			$len = hex;
3047582d965Sbluhm		}
3057582d965Sbluhm		last unless $len > 0;
3067582d965Sbluhm		read_char($self, $len);
3077582d965Sbluhm		{
3087582d965Sbluhm			local $/ = "\r\n";
3097582d965Sbluhm			local $_ = <STDIN>;
3107582d965Sbluhm			defined or die ref($self), " missing chunk data end";
3117582d965Sbluhm			chomp;
312c64e9fedSbluhm			print STDERR "<<< $_\n";
3137582d965Sbluhm			/^$/ or die ref($self), " no chunk data end: $_";
3147582d965Sbluhm		}
3157582d965Sbluhm	}
3167582d965Sbluhm	{
3177582d965Sbluhm		local $/ = "\r\n";
3187582d965Sbluhm		while (<STDIN>) {
3197582d965Sbluhm			chomp;
320c64e9fedSbluhm			print STDERR "<<< $_\n";
3217582d965Sbluhm			last if /^$/;
3227582d965Sbluhm		}
3237582d965Sbluhm		defined or die ref($self), " missing chunk trailer";
3247582d965Sbluhm	}
3257582d965Sbluhm}
326c2d4e910Sbluhm
327e1c6cc06Sbluhmsub errignore {
328e1c6cc06Sbluhm	$SIG{PIPE} = 'IGNORE';
329e1c6cc06Sbluhm	$SIG{__DIE__} = sub {
330e1c6cc06Sbluhm		die @_ if $^S;
331a7ce14b0Sbluhm		warn "Error ignored";
332e1c6cc06Sbluhm		warn @_;
333e1c6cc06Sbluhm		IO::Handle::flush(\*STDERR);
334e1c6cc06Sbluhm		POSIX::_exit(0);
335e1c6cc06Sbluhm	};
336e1c6cc06Sbluhm}
337e1c6cc06Sbluhm
338c2d4e910Sbluhm########################################################################
339c6fb90fcSbluhm# Common funcs
340c2d4e910Sbluhm########################################################################
341c2d4e910Sbluhm
342c2d4e910Sbluhmsub read_char {
343c2d4e910Sbluhm	my $self = shift;
3449d1e6fe8Sbluhm	my $max = shift // $self->{max};
345c2d4e910Sbluhm
3463daec375Sbenno	if ($self->{fast}) {
347c6fb90fcSbluhm		read_block($self, $max);
3483daec375Sbenno		return;
3493daec375Sbenno	}
3503daec375Sbenno
351c2d4e910Sbluhm	my $ctx = Digest::MD5->new();
352c2d4e910Sbluhm	my $len = 0;
3539d1e6fe8Sbluhm	if (defined($max) && $max == 0) {
3549d1e6fe8Sbluhm		print STDERR "Max\n";
3559d1e6fe8Sbluhm	} else {
356c2d4e910Sbluhm		while (<STDIN>) {
357c2d4e910Sbluhm			$len += length($_);
358c2d4e910Sbluhm			$ctx->add($_);
359c2d4e910Sbluhm			print STDERR ".";
3609d1e6fe8Sbluhm			if (defined($max) && $len >= $max) {
361c2d4e910Sbluhm				print STDERR "\nMax";
362c2d4e910Sbluhm				last;
363c2d4e910Sbluhm			}
364c2d4e910Sbluhm		}
365c2d4e910Sbluhm		print STDERR "\n";
3669d1e6fe8Sbluhm	}
367c2d4e910Sbluhm
368c2d4e910Sbluhm	print STDERR "LEN: ", $len, "\n";
369c2d4e910Sbluhm	print STDERR "MD5: ", $ctx->hexdigest, "\n";
370c2d4e910Sbluhm}
371c2d4e910Sbluhm
372c6fb90fcSbluhmsub read_block {
373c6fb90fcSbluhm	my $self = shift;
374c6fb90fcSbluhm	my $max = shift // $self->{max};
375c6fb90fcSbluhm
376c6fb90fcSbluhm	my $opct = 0;
377c6fb90fcSbluhm	my $ctx = Digest::MD5->new();
378c6fb90fcSbluhm	my $len = 0;
379c6fb90fcSbluhm	for (;;) {
380c6fb90fcSbluhm		if (defined($max) && $len >= $max) {
381c6fb90fcSbluhm			print STDERR "Max\n";
382c6fb90fcSbluhm			last;
383c6fb90fcSbluhm		}
384c6fb90fcSbluhm		my $rlen = POSIX::BUFSIZ;
385c6fb90fcSbluhm		if (defined($max) && $rlen > $max - $len) {
386c6fb90fcSbluhm			$rlen = $max - $len;
387c6fb90fcSbluhm		}
388c6fb90fcSbluhm		defined(my $n = read(STDIN, my $buf, $rlen))
389c6fb90fcSbluhm		    or die ref($self), " read failed: $!";
390c6fb90fcSbluhm		$n or last;
391c6fb90fcSbluhm		$len += $n;
392c6fb90fcSbluhm		$ctx->add($buf);
393c6fb90fcSbluhm		my $pct = ($len / $max) * 100.0;
394c6fb90fcSbluhm		if ($pct >= $opct + 1) {
395c6fb90fcSbluhm			printf(STDERR "%.2f%% $len/$max\n", $pct);
396c6fb90fcSbluhm			$opct = $pct;
397c6fb90fcSbluhm		}
398c6fb90fcSbluhm	}
399c6fb90fcSbluhm
400c6fb90fcSbluhm	print STDERR "LEN: ", $len, "\n";
401c6fb90fcSbluhm	print STDERR "MD5: ", $ctx->hexdigest, "\n";
402c6fb90fcSbluhm}
403c6fb90fcSbluhm
404c6fb90fcSbluhm########################################################################
405c6fb90fcSbluhm# Server funcs
406c6fb90fcSbluhm########################################################################
407c6fb90fcSbluhm
4089d1e6fe8Sbluhmsub http_server {
4099d1e6fe8Sbluhm	my $self = shift;
410e49fb922Sreyk	my %header = %{$self->{header} || { Server => "Perl/".$^V }};
41185781182Sandre	my $cookie = $self->{cookie} || "";
4129d1e6fe8Sbluhm
4133a4f532cSbluhm	my($method, $url, $vers);
4149d1e6fe8Sbluhm	do {
4153a4f532cSbluhm		my $len;
4169d1e6fe8Sbluhm		{
4179d1e6fe8Sbluhm			local $/ = "\r\n";
4189d1e6fe8Sbluhm			local $_ = <STDIN>;
4199d1e6fe8Sbluhm			return unless defined $_;
4209d1e6fe8Sbluhm			chomp;
421c64e9fedSbluhm			print STDERR "<<< $_\n";
4223a4f532cSbluhm			($method, $url, $vers) = m{^(\w+) (.*) HTTP/(1\.[01])$}
4239d1e6fe8Sbluhm			    or die ref($self), " http request not ok";
424bfbf8851Sbenno			$method =~ /^(GET|HEAD|PUT)$/
4253a4f532cSbluhm			    or die ref($self), " unknown method: $method";
4267582d965Sbluhm			($len, my @chunks) = $url =~ /(\d+)/g;
4277582d965Sbluhm			$len = [ $len, @chunks ] if @chunks;
4289d1e6fe8Sbluhm			while (<STDIN>) {
4299d1e6fe8Sbluhm				chomp;
430c64e9fedSbluhm				print STDERR "<<< $_\n";
4319d1e6fe8Sbluhm				last if /^$/;
432a3c410c1Sbluhm				if ($method eq "PUT" &&
433a3c410c1Sbluhm				    /^Content-Length: (.*)/) {
4343a4f532cSbluhm					$1 == $len or die ref($self),
4353a4f532cSbluhm					    " bad content length $1";
4369d1e6fe8Sbluhm				}
437cab60319Sbluhm				$cookie ||= $1 if /^Cookie: (.*)/;
4389d1e6fe8Sbluhm			}
4393a4f532cSbluhm		}
4401b3c3ba0Sbluhm		if ($method eq "PUT" ) {
4411b3c3ba0Sbluhm			if (ref($len) eq 'ARRAY') {
4421b3c3ba0Sbluhm				read_chunked($self);
4431b3c3ba0Sbluhm			} else {
4441b3c3ba0Sbluhm				read_char($self, $len);
4451b3c3ba0Sbluhm			}
4461b3c3ba0Sbluhm		}
4479d1e6fe8Sbluhm
448a3c410c1Sbluhm		my @response = ("HTTP/$vers 200 OK");
44985781182Sandre		$len = defined($len) ? $len : scalar(split /|/,$url);
450bfbf8851Sbenno		if ($vers eq "1.1" && $method =~ /^(GET|HEAD)$/) {
4517582d965Sbluhm			if (ref($len) eq 'ARRAY') {
4521b3c3ba0Sbluhm				push @response, "Transfer-Encoding: chunked";
4537582d965Sbluhm			} else {
4541b3c3ba0Sbluhm				push @response, "Content-Length: $len";
4551b3c3ba0Sbluhm			}
4567582d965Sbluhm		}
4577e1bbd8eSreyk		foreach my $key (sort keys %header) {
4587e1bbd8eSreyk			my $val = $header{$key};
4597e1bbd8eSreyk			if (ref($val) eq 'ARRAY') {
4607e1bbd8eSreyk				push @response, "$key: $_"
4617e1bbd8eSreyk				    foreach @{$val};
4627e1bbd8eSreyk			} else {
4637e1bbd8eSreyk				push @response, "$key: $val";
4647e1bbd8eSreyk			}
4657e1bbd8eSreyk		}
466cab60319Sbluhm		push @response, "Set-Cookie: $cookie" if $cookie;
467a3c410c1Sbluhm		push @response, "";
46885781182Sandre
469a3c410c1Sbluhm		print STDERR map { ">>> $_\n" } @response;
470a3c410c1Sbluhm		print map { "$_\r\n" } @response;
471c64e9fedSbluhm
4721b3c3ba0Sbluhm		if ($method eq "GET") {
4737582d965Sbluhm			if (ref($len) eq 'ARRAY') {
474cab60319Sbluhm				if ($vers eq "1.1") {
4757582d965Sbluhm					write_chunked($self, @$len);
4767582d965Sbluhm				} else {
477cab60319Sbluhm					write_char($self, $_) foreach (@$len);
478cab60319Sbluhm				}
479cab60319Sbluhm			} else {
4801b3c3ba0Sbluhm				write_char($self, $len);
4811b3c3ba0Sbluhm			}
4827582d965Sbluhm		}
4839d1e6fe8Sbluhm		IO::Handle::flush(\*STDOUT);
4849d1e6fe8Sbluhm	} while ($vers eq "1.1");
485cab60319Sbluhm	$self->{redo}-- if $self->{redo};
4869d1e6fe8Sbluhm}
4879d1e6fe8Sbluhm
4887582d965Sbluhmsub write_chunked {
4897582d965Sbluhm	my $self = shift;
4907582d965Sbluhm	my @chunks = @_;
4917582d965Sbluhm
4927582d965Sbluhm	foreach my $len (@chunks) {
493c64e9fedSbluhm		printf STDERR ">>> %x\n", $len;
4947582d965Sbluhm		printf "%x\r\n", $len;
4957582d965Sbluhm		write_char($self, $len);
496c64e9fedSbluhm		printf STDERR ">>> \n";
4977582d965Sbluhm		print "\r\n";
4987582d965Sbluhm	}
499c64e9fedSbluhm	my @trailer = ("0", "X-Chunk-Trailer: @chunks", "");
500c64e9fedSbluhm	print STDERR map { ">>> $_\n" } @trailer;
501c64e9fedSbluhm	print map { "$_\r\n" } @trailer;
5027582d965Sbluhm}
5037582d965Sbluhm
504a7ce14b0Sbluhm########################################################################
505a7ce14b0Sbluhm# Script funcs
506a7ce14b0Sbluhm########################################################################
507a7ce14b0Sbluhm
508a7ce14b0Sbluhmsub check_logs {
509a7ce14b0Sbluhm	my ($c, $r, $s, %args) = @_;
510a7ce14b0Sbluhm
511a7ce14b0Sbluhm	return if $args{nocheck};
512a7ce14b0Sbluhm
513a7ce14b0Sbluhm	check_len($c, $r, $s, %args);
514a7ce14b0Sbluhm	check_md5($c, $r, $s, %args);
515a7ce14b0Sbluhm	check_loggrep($c, $r, $s, %args);
5161b3c3ba0Sbluhm	$r->loggrep("lost child")
5171b3c3ba0Sbluhm	    and die "relayd lost child";
518a7ce14b0Sbluhm}
519a7ce14b0Sbluhm
520*508def2aSbluhmsub array_eq {
521*508def2aSbluhm	my ($a, $b) = @_;
522*508def2aSbluhm	return if @$a != @$b;
523*508def2aSbluhm	for (my $i = 0; $i < @$a; $i++) {
524*508def2aSbluhm		return if $$a[$i] ne $$b[$i];
525*508def2aSbluhm	}
526*508def2aSbluhm	return 1;
527*508def2aSbluhm}
528*508def2aSbluhm
529a7ce14b0Sbluhmsub check_len {
530a7ce14b0Sbluhm	my ($c, $r, $s, %args) = @_;
531a7ce14b0Sbluhm
5326d509cd0Sbluhm	$args{len} ||= 251 unless $args{lengths};
5336d509cd0Sbluhm
534f8f8530cSbluhm	my (@clen, @slen);
535f8f8530cSbluhm	@clen = $c->loggrep(qr/^LEN: /) or die "no client len"
536a7ce14b0Sbluhm	    unless $args{client}{nocheck};
537f8f8530cSbluhm	@slen = $s->loggrep(qr/^LEN: /) or die "no server len"
538a7ce14b0Sbluhm	    unless $args{server}{nocheck};
539*508def2aSbluhm	!@clen || !@slen || array_eq \@clen, \@slen
540a7ce14b0Sbluhm	    or die "client: @clen", "server: @slen", "len mismatch";
541a7ce14b0Sbluhm	!defined($args{len}) || !$clen[0] || $clen[0] eq "LEN: $args{len}\n"
542a7ce14b0Sbluhm	    or die "client: $clen[0]", "len $args{len} expected";
543a7ce14b0Sbluhm	!defined($args{len}) || !$slen[0] || $slen[0] eq "LEN: $args{len}\n"
544a7ce14b0Sbluhm	    or die "server: $slen[0]", "len $args{len} expected";
545a7ce14b0Sbluhm	my @lengths = map { ref eq 'ARRAY' ? @$_ : $_ }
546a7ce14b0Sbluhm	    @{$args{lengths} || []};
547a7ce14b0Sbluhm	foreach my $len (@lengths) {
548a3c410c1Sbluhm		unless ($args{client}{nocheck}) {
549a7ce14b0Sbluhm			my $clen = shift @clen;
550a7ce14b0Sbluhm			$clen eq "LEN: $len\n"
551a7ce14b0Sbluhm			    or die "client: $clen", "len $len expected";
552a3c410c1Sbluhm		}
553a3c410c1Sbluhm		unless ($args{server}{nocheck}) {
554a7ce14b0Sbluhm			my $slen = shift @slen;
555a7ce14b0Sbluhm			$slen eq "LEN: $len\n"
556a7ce14b0Sbluhm			    or die "server: $slen", "len $len expected";
557a7ce14b0Sbluhm		}
558a7ce14b0Sbluhm	}
559a3c410c1Sbluhm}
560a7ce14b0Sbluhm
561a7ce14b0Sbluhmsub check_md5 {
562a7ce14b0Sbluhm	my ($c, $r, $s, %args) = @_;
563a7ce14b0Sbluhm
564f8f8530cSbluhm	my (@cmd5, @smd5);
565f8f8530cSbluhm	@cmd5 = $c->loggrep(qr/^MD5: /) unless $args{client}{nocheck};
566f8f8530cSbluhm	@smd5 = $s->loggrep(qr/^MD5: /) unless $args{server}{nocheck};
5674812e4f6Sbluhm	!@cmd5 || !@smd5 || $cmd5[0] eq $smd5[0]
5684812e4f6Sbluhm	    or die "client: $cmd5[0]", "server: $smd5[0]", "md5 mismatch";
5694812e4f6Sbluhm
5704812e4f6Sbluhm	my @md5 = ref($args{md5}) eq 'ARRAY' ? @{$args{md5}} : $args{md5} || ()
5714812e4f6Sbluhm	    or return;
5724812e4f6Sbluhm	foreach my $md5 (@md5) {
5734812e4f6Sbluhm		unless ($args{client}{nocheck}) {
5744812e4f6Sbluhm			my $cmd5 = shift @cmd5
5754812e4f6Sbluhm			    or die "too few md5 in client log";
5764812e4f6Sbluhm			$cmd5 =~ /^MD5: ($md5)$/
577a7ce14b0Sbluhm			    or die "client: $cmd5", "md5 $md5 expected";
5784812e4f6Sbluhm		}
5794812e4f6Sbluhm		unless ($args{server}{nocheck}) {
5804812e4f6Sbluhm			my $smd5 = shift @smd5
5814812e4f6Sbluhm			    or die "too few md5 in server log";
5824812e4f6Sbluhm			$smd5 =~ /^MD5: ($md5)$/
583a7ce14b0Sbluhm			    or die "server: $smd5", "md5 $md5 expected";
584a7ce14b0Sbluhm		}
5854812e4f6Sbluhm	}
5864812e4f6Sbluhm	@cmd5 && ref($args{md5}) eq 'ARRAY'
5874812e4f6Sbluhm	    and die "too many md5 in client log";
5884812e4f6Sbluhm	@smd5 && ref($args{md5}) eq 'ARRAY'
5894812e4f6Sbluhm	    and die "too many md5 in server log";
5904812e4f6Sbluhm}
591a7ce14b0Sbluhm
592a7ce14b0Sbluhmsub check_loggrep {
593a7ce14b0Sbluhm	my ($c, $r, $s, %args) = @_;
594a7ce14b0Sbluhm
595a7ce14b0Sbluhm	my %name2proc = (client => $c, relayd => $r, server => $s);
596a7ce14b0Sbluhm	foreach my $name (qw(client relayd server)) {
597aa8f1300Sbluhm		my $p = $name2proc{$name} or next;
598a7ce14b0Sbluhm		my $pattern = $args{$name}{loggrep} or next;
599a7ce14b0Sbluhm		$pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY';
600a7ce14b0Sbluhm		foreach my $pat (@$pattern) {
601a7ce14b0Sbluhm			if (ref($pat) eq 'HASH') {
602a7ce14b0Sbluhm				while (my($re, $num) = each %$pat) {
603a7ce14b0Sbluhm					my @matches = $p->loggrep($re);
604a7ce14b0Sbluhm					@matches == $num
605aa8f1300Sbluhm					    or die "$name matches '@matches': ",
606aa8f1300Sbluhm					    "'$re' => $num";
607a7ce14b0Sbluhm				}
608a7ce14b0Sbluhm			} else {
609a7ce14b0Sbluhm				$p->loggrep($pat)
610aa8f1300Sbluhm				    or die "$name log missing pattern: '$pat'";
611a7ce14b0Sbluhm			}
612a7ce14b0Sbluhm		}
613a7ce14b0Sbluhm	}
614a7ce14b0Sbluhm}
615a7ce14b0Sbluhm
616c2d4e910Sbluhm1;
617