#!/usr/bin/perl -ws
# Example usage:
# ./chunk-zip-test.pl -port=80 -size=40

use strict;
use vars qw( $server $port $url $file $algo $size $mreq );
use IO::Socket::INET;
use Compress::Zlib;

# these params can be specified at command line; defaults are here
	$server ||= "localhost";
	$port ||= "80";
	$url  ||= "/chunktest/index.jsp";
	$file ||= "chunk-test-file.txt";
	$algo ||= "default"; # algorithm for picking chunk sizes
	$size ||= 1000; # "size" parameter for chunk size algorithm
	$mreq ||= 20; #max requests to try

# choose an algorithm for sending chunk sizes
my $chunk_state = 0;
my $chunk_size_algo = {
	default => sub { $size },
	altdub => sub { ($chunk_state++ % 2 == 0) ? $size : $size * 2 },
	random => sub { 1 + int rand $size },
}->{$algo} or die "no such algo $algo\n";


my $CRLF = "\x0d\x0a"; # explicit line ending used w/ HTTP
# define the HTTP request header
my $header = join $CRLF, (split /\n/, <<"HEADER");
POST $url HTTP/1.1
Host: $server:$port
Content-Type: text/plain
Connection: close
Content-Encoding: gzip
Transfer-Encoding: chunked
HEADER
chomp $header;
$header .= $CRLF x 2;

# read in the test file
open my $fh, '<', $file or die "couldn't open '$file': $!\n";
binmode $fh or die "couldn't binmode file: $!\n";
$fh->read(my $content, 10_000_000) or die "couldn't read $file: $!\n";
my $file_size = length $content;

# deflate the test file for streaming
my($deflator, $status) = deflateInit(-WindowBits => 31);
$status == Z_OK or die "failed to create deflator: $status";
(my $zipped, $status) = $deflator->deflate($content);
$status == Z_OK or die "couldn't compress: $!";
$zipped .= $deflator->flush;

print "unzipped: $file_size\n";
print "zipped: ". length($zipped)."\n";

my $tries = 0; # number of tries to produce the bug
TRY: while($tries++ < $mreq) { # try until we fail or are killed

	# create a connection for sending the HTTP request
	my $socket = new IO::Socket::INET( 
			Proto    => 'tcp',
			PeerAddr => $server,
			PeerPort => $port,
			Timeout  => 10,
		) or die "couldn't open socket: $!\n";
	$socket->autoflush(1);
	$socket->send($header) or die "error sending header to socket: $!";

	# write the gzipped content in chunks
	my $sent = 0;
	my $last_chunk_size = -1;
	READ: while(1) {
		my $chunk_size = $chunk_size_algo->();

		# get some stuff (up to $chunk_size) from the zipped content...
		my $data = substr($zipped, $sent, $chunk_size);
		$chunk_size = length $data;

		# and send the chunk to the server
		$socket->send(sprintf '%x%s', $chunk_size, $CRLF . $data . $CRLF)
			or die "error sending header to socket: $!";
		$sent += $chunk_size;

		# if we are at the end, finish the transmission.
		last READ if $chunk_size == 0;
		$last_chunk_size = $chunk_size;
	}

	# check the response for the matching X-Size header which the JSP should set
	defined $socket->recv(my $response, 10_000, undef) or die "error while receiving response: $!";
	$socket->close;
	unless( $response =~ /^ X-Size: \s (\d+)/xsmi ) {
		print "Unexpected response for request $tries:\n$response";
		print "Last chunk size was $last_chunk_size.\n";
		last TRY;
	}
	if($1 != $file_size) {
		print "For request $tries; sizes DID NOT match.\n";
		print "File size is $file_size; request size was $1.\n";
		print "Last chunk size was $last_chunk_size.\n";
		last TRY;
	}
	print "Completed request $tries; sizes matched.\n";
}

exit 0;

