# libwhisker v1.6
# libwhisker is a collection of routines used by whisker
#
# libwhisker copyright 2000,2001,2002 rfp.labs
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
#
# More information can be found at http://www.wiretrip.net/rfp/
# Libwhisker mailing list and resources are also available at
# http://sourceforge.net/projects/whisker/
#

package LW;
use 5.004;
$LW::VERSION="1.6";

####### external module tests ###################################

BEGIN {

## LW module manager stuff ##

	%LW::available		= ();
	$LW::LW_HAS_SOCKET	= 0;
	$LW::LW_HAS_SSL		= 0;
	$LW::LW_SSL_LIB		= 0;
	$LW::LW_NONBLOCK_CONNECT= 0;

## binary helper - may contain functions substituted further down ##
        eval "use LW::bin"; # do we have libwhisker binary helpers?
        if($@){ $LW::available{'LW::bin'}=$LW::bin::VERSION; }

## encode subpackage ##
	eval "require MIME::Base64";
	if($@){
        	*encode_base64 = \&encode_base64_perl; 
	        *decode_base64 = \&decode_base64_perl; 
	} else{ 
		# MIME::Base64 typically has faster C versions
		$LW::available{'mime::base64'}=$MIME::Base64::VERSION;
        	*encode_base64 = \&MIME::Base64::encode_base64;
        	*decode_base64 = \&MIME::Base64::decode_base64;}

## md5 subpackage ##
	eval "require MD5";
	if(!$@){ $LW::available{'md5'}=$MD5::VERSION;}

## http subpackage ##
        eval "use Socket"; # do we have socket support?
        if($@){ $LW::LW_HAS_SOCKET=0; }
        else { $LW::LW_HAS_SOCKET=1;
                $LW::available{'socket'}=$Socket::VERSION;}

    if($LW_HAS_SOCKET){
	eval "use Net::SSLeay"; # do we have SSL support?
        if($@){ $LW::LW_HAS_SSL=0; }
        else { $LW::LW_HAS_SSL=1;
                $LW::LW_SSL_LIB=1;
                $LW::available{'net::ssleay'}=$Net::SSLeay::VERSION;
                Net::SSLeay::load_error_strings();
                Net::SSLeay::SSLeay_add_ssl_algorithms();
                Net::SSLeay::randomize();}
        if(!$LW::LW_HAS_SSL){
                eval "use Net::SSL"; # different SSL lib
                if($@){ $LW::LW_HAS_SSL=0; }
                else { $LW::LW_HAS_SSL=1;
                        $LW::LW_SSL_LIB=2;
                        $LW::available{'net::ssl'}=$Net::SSL::VERSION;}
        }

## non-blocking IO ##

	if($^O!~/Win32/){
		eval "use POSIX qw(:errno_h :fcntl_h)"; # better
		if(!$@){
			$LW::LW_NONBLOCK_CONNECT=1;
		}
	}

    } # if($LW_HAS_SOCKET)

} # BEGIN

####### package variables #######################################

## crawl subpackage ##
	%LW::crawl_config=(	'save_cookies'	=> 0,
				'reuse_cookies'	=> 1,
				'save_offsites'	=> 0,
				'follow_moves'	=> 1,
				'url_limit'	=> 1000,
				'use_params'	=> 0,
				'params_double_record' => 0,
				'skip_ext'	=> '.gif .jpg .gz .mp3 .swf .zip ',
				'save_skipped'	=> 0,
				'save_referrers'=> 0,
				'do_head'	=> 0,
				'callback'	=> 0,
				'slashdot_bug'	=> 1,
				'normalize_uri'	=> 1,
				'source_callback' => 0
			);


	@LW::crawl_urls=();;
	%LW::crawl_server_tags=();
	%LW::crawl_referrers=();
	%LW::crawl_offsites=();
	%LW::crawl_cookies=();
	%LW::crawl_forms=();
	%LW::crawl_temp=();

	# this idea/structure was taken from HTML::LinkExtor.pm,
	# copyright 2000 Gisle Aas and Michael A. Chase
	%LW::crawl_linktags = (
		 'a'       => 'href',
		 'applet'  => [qw(codebase archive code)],
		 'area'    => 'href',
		 'base'    => 'href',
		 'bgsound' => 'src',
		 'blockquote' => 'cite',
		 'body'    => 'background',
		 'del'     => 'cite',
		 'embed'   => [qw(src pluginspage)],
		 'form'    => 'action',
		 'frame'   => [qw(src longdesc)],
		 'iframe'  => [qw(src longdesc)],
		 'ilayer'  => 'background',
		 'img'     => [qw(src lowsrc longdesc usemap)],
		 'input'   => [qw(src usemap)],
		 'ins'     => 'cite',
		 'isindex' => 'action',
		 'head'    => 'profile',
		 'layer'   => [qw(background src)],
		 'link'    => 'href',
		 'object'  => [qw(codebase data archive usemap)],
		 'q'       => 'cite',
		 'script'  => 'src',
		 'table'   => 'background',
		 'td'      => 'background',
		 'th'      => 'background',
		 'xmp'     => 'href',
	);


## forms subpackage ##
	@LW::forms_found=();
	%LW::forms_current=();


## http subpackage ##
	my $SOCKSTATE=0;
	my $TIMEOUT=10; # default
	my ($STATS_REQS,$STATS_SYNS)=(0,0);
	my ($LAST_HOST,$LAST_INET_ATON,$LAST_SSL)=('','',0);
	my ($OUTGOING_QUEUE,$INCOMING_QUEUE)=('','');
	my ($SSL_CTX, $SSL_THINGY);

	my %http_host_cache=();
	# order is following:
	# [0] - SOCKET
	# [1] - $SOCKSTATE
	# [2] - INET_ATON
	# [3] - $SSL_CTX
	# [4] - $SSL_THINGY
	# [5] - $OUTGOING_QUEUE
	# [6] - $INCOMING_QUEUE
	# [7] - $STATS_SYNS
	# [8] - $STATS_REQS

	my $Z; # array ref to current host specs





sub anti_ids {
	my ($rhin,$modes)=(shift,shift);
	my (@T,$x,$c,$s,$y);
	my $ENCODED=0;
	my $W = $$rhin{'whisker'};

	return if(!(defined $rhin && ref($rhin)));

	# in case they didn't do it already
	$$rhin{'whisker'}->{'uri_orig'}=$$rhin{'whisker'}->{'uri'};

	# note: order is important!

	# mode 9 - session splicing
	if($modes=~/9/){
		$$rhin{'whisker'}->{'ids_session_splice'}=1;
	}

	# mode 4 - prepend long random string
	if($modes=~/4/){$s='';
		if($$W{'uri'}=~m#^/#){
			$y=&utils_randstr;
			$s.=$y while(length($s)<512);
			$$W{'uri'}="/$s/..".$$W{'uri'};
		}
	}

	# mode 7  - (windows) random case sensitivity
	if($modes=~/7/){ 
		@T=split(//,$$W{'uri'});
		for($x=0;$x<(scalar @T);$x++){
			if((rand()*2)%2 == 1){
				$T[$x]=uc($T[$x]);}}
		$$W{'uri'}=join('',@T);
	}

	# mode 2 - directory self-reference (/./)
	if($modes=~/2/){
		$$W{'uri'}=~s#/#/./#g;
	}


	# mode 8 - windows directory separator (\)
	if($modes=~/8/){
		$$W{'uri'}=~s#/#\\#g;
		$$W{'uri'}=~s#^\\#/#;
		$$W{'uri'}=~s#^(http|file|ftp|nntp|news|telnet):\\#$1://#;
		$$W{'uri'}=~s#\\$#/#;
	}

	# mode 1 - random URI (non-UTF8) encoding
	if($modes=~/1/){
		if($ENCODED==0){
			$$W{'uri'}=encode_str2ruri($$W{'uri'});
		$ENCODED=1;}
	}	


	# mode 5 - fake parameter
	if($modes=~/5/){ 
		($s,$y)=(&utils_randstr,&utils_randstr); 
		$$W{'uri'}="/$s.html%3f$y=/../$$W{'uri'}";
	}

	# mode 3 - premature URL ending
	if($modes=~/3/){ 
		$s=&utils_randstr;
		$$W{'uri'}="/%20HTTP/1.1%0D%0A%Accept%3A%20$s/../..$$W{'uri'}";
	}
	
	# mode 6 - TAB as request spacer
	if($modes=~/6/){
		$$W{'req_spacer'}="\t";
	}	

} # end anti_ids







sub auth_brute_force {
 my ($auth_method, $hrin, $user, $pwordref, $dom)=@_;
 my ($P,%hout);

 return undef if(!defined $auth_method || length($auth_method)==0);
 return undef if(!defined $user        || length($user)       ==0);
 return undef if(!(defined $hrin     && ref($hrin)    ));
 return undef if(!(defined $pwordref && ref($pwordref)));

 map {
    ($P=$_)=~tr/\r\n//d;
    auth_set_header($auth_method,$hrin,$user,$P,$dom);
    return undef if(http_do_request($hrin,\%hout));
    return $P if($hout{'whisker'}->{'http_resp'} ne 401);
 } @$pwordref;

 return undef;}




sub auth_set_header {
 my ($method, $href, $user, $pass, $domain)=(lc(shift),@_);

 return if(!(defined $href && ref($href)));
 return if(!defined $user || !defined $pass);

 if($method eq 'basic'){
	$$href{'Authorization'}='Basic '.encode_base64($user.':'.$pass,'');
 }

 if($method eq 'proxy-basic'){
	$$href{'Proxy-Authorization'}='Basic '.encode_base64($user.':'.$pass,'');
 }

 if($method eq 'ntlm'){
	my $o=ntlm_new($user,$pass,$domain);
	$$href{'whisker'}->{'ntlm_obj'}=$o;
	$$href{'whisker'}->{'ntlm_step'}=0;
	$$href{'Authorization'}='NTLM '.ntlm_client($o);
 }

}




sub do_auth {
	goto &auth_set_header;
}



sub bruteurl {
 my ($hin, $upre, $upost, $arin, $arout)=@_;
 my ($U,%hout);

 return if(!(defined $hin   && ref($hin)  ));
 return if(!(defined $arin  && ref($arin) ));
 return if(!(defined $arout && ref($arout)));
 return if(!defined $upre  || length($upre) ==0);
 return if(!defined $upost || length($upost)==0);

 http_fixup_request($hin);

 map {
  ($U=$_)=~tr/\r\n//d; next if($U eq '');
  if(!http_do_request($hin,\%hout,{'uri'=>$upre.$U.$upost})){
    if(	$hout{'whisker'}->{'http_resp'}==200 ||
	$hout{'whisker'}->{'http_resp'}==403){
	push(@{$arout},$U);
    }
  }
 } @$arin;
}





sub cookie_read {
 my ($count,$jarref,$href)=(0,@_);

 return 0 if(!(defined $jarref && ref($jarref)));
 return 0 if(!(defined $href   && ref($href)  ));

 my $lc = $$href{'whisker'}->{'lowercase_incoming_headers'}||0;
 my $target = $lc ? 'set-cookie' : 'Set-Cookie';

 if(!defined $$href{$target}){
	return 0;}

 if(ref($$href{$target})){ # multiple headers
	foreach ($$href{$target}){
		cookie_parse($jarref,$_);
		$count++; }
 } else { # single header
	cookie_parse($jarref,$$href{$target});
	$count=1; }

 return $count;
}




sub cookie_parse {
 my ($jarref, $header)=@_;
 my ($del,$part,@parts,@construct,$cookie_name)=(0);

 return if(!(defined $jarref && ref($jarref)));
 return if(!(defined $header && length($header)>0));

 @parts=split(/;/,$header);

 foreach $part (@parts){
	if($part=~/^[ \t]*(.+?)=(.*)$/){
		my ($name,$val)=($1,$2);
		if($name=~/^domain$/i){		
			$val=~s#^http://##;
			$val=~s#/.*$##;
			$construct[1]=$val;
		} elsif($name=~/^path$/i){
			$val=~s#/$## if($val ne '/');
			$construct[2]=$val;
		} elsif($name=~/^expires$/i){
			$construct[3]=$val;
		} else {
			$cookie_name=$name;
			if($val eq ''){		$del=1;
			} else {		$construct[0]=$val;}
		}	
	} else {
		if($part=~/secure/){
			$construct[4]=1;}
 }	}

 if($del){
  	delete $$jarref{$cookie_name} if defined $$jarref{$cookie_name};
 } else {
	$$jarref{$cookie_name}=\@construct;
 }
}




sub cookie_write {
 my ($jarref, $hin, $override)=@_;
 my ($name,$out)=('','');

 return if(!(defined $jarref && ref($jarref)));
 return if(!(defined $hin    && ref($hin)   ));

 $override=$override||0;
 $$hin{'whisker'}->{'ssl'}=$$hin{'whisker'}->{'ssl'}||0;

 foreach $name (keys %$jarref){
	next if($name eq '');
	next if($$hin{'whisker'}->{'ssl'}==0 && $$jarref{$name}->[4]>0);
	if($override || 
          ($$hin{'whisker'}->{'host'}=~/$$jarref{$name}->[1]$/i &&
	   $$hin{'whisker'}->{'uri'}=~/$$jarref{$name}->[2]/i)){
		$out.="$name=$$jarref{$name}->[0];";
 }	}

 if($out ne ''){ $$hin{'Cookie'}=$out; }

}




sub cookie_get {
 my ($jarref,$name)=@_;

 return undef if(!(defined $jarref && ref($jarref)));

 if(defined $$jarref{$name}){
	return @{$$jarref{$name}};}

 return undef;
}




sub cookie_set {
 my ($jarref,$name,$value,$domain,$path,$expire,$secure)=@_;
 my @construct;

 return if(!(defined $jarref && ref($jarref)));

 return if($name eq '');
 if($value eq ''){
	delete $$jarref{$name};
	return;}
 $path=$path||'/';
 $secure=$secure||0;

 @construct=($value,$domain,$path,$expire,$secure);
 $$jarref{$name}=\@construct; 
}







sub crawl {
 my ($START, $MAX_DEPTH, $hrtrack, $hrin)=@_;
 my (%hout, %jar);
 my ($T, @ST, @links, @tlinks, @vals, @ERRORS)=('');

 return if(!(defined $hrtrack && ref($hrtrack)));
 return if(!(defined $hrin    && ref($hrin)   )); 
 return if(!defined $START || length($START)==0);

 $MAX_DEPTH||=2;

 # $ST[0]=HOST  $ST[1]=URL  $ST[2]=CWD  $ST[3]=HTTPS  $ST[4]=SERVER
 # $ST[5]=PORT  $ST[6]=DEPTH

 @vals=utils_split_uri($START);
 $ST[1]=$vals[0]; 	# uri
 $ST[0]=$vals[2]; 	# host
 $ST[5]=$vals[3]; 	# port
 $ST[4]=undef;		# server tag

 return if($ST[0] eq '');

 # some various informationz...
 $LW::crawl_config{'host'}=$ST[0];
 $LW::crawl_config{'port'}=$ST[5];
 $LW::crawl_config{'start'}=$ST[1];

 $$hrin{'whisker'}->{'host'}=$ST[0];
 $$hrin{'whisker'}->{'port'}=$ST[5];
 $$hrin{'whisker'}->{'lowercase_incoming_headers'}=1; # makes life easier

 http_fixup_request($hrin);

 # this is so callbacks can access internals via references
 $LW::crawl_config{'ref_links'}=\@links;
 $LW::crawl_config{'ref_jar'}=\%jar;
 $LW::crawl_config{'ref_hin'}=$hrin;
 $LW::crawl_config{'ref_hout'}=\%hout;

 %LW::crawl_referrers=(); # empty out existing referrers
 %LW::crawl_server_tags=();
 %LW::crawl_offsites=();
 %LW::crawl_cookies=();
 %LW::crawl_forms=();

 push @links, \@{[$ST[1],1,($vals[1] eq 'https')?1:0]};

 while(@links){
  my $C=shift @links;
  $ST[1]=$C->[0]; # url
  $ST[6]=$C->[1]; # depth
  $ST[3]=$C->[2]; # https

  next if(defined $$hrtrack{$ST[1]} && $$hrtrack{$ST[1]} ne '?');

  if($ST[6] > $MAX_DEPTH){
	$$hrtrack{$ST[1]}='?' if($LW::crawl_config{'save_skipped'}>0);
	next;
  }

  $ST[2]=utils_get_dir($ST[1]);

  $$hrin{'whisker'}->{'uri'}=$ST[1];
  $$hrin{'whisker'}->{'ssl'}=$ST[3];
  my $result = crawl_do_request($hrin,\%hout);
  if($result==1 || $result==2){
	push @ERRORS, "Error on making request for '$ST[1]': $hout{'whisker'}->{'error'}";
	next;
  }

  if($result==0 || $result==4){
	$$hrtrack{$ST[1]}=$hout{'whisker'}->{'http_resp'}; }
  
  if($result==3 || $result==5){
	$$hrtrack{$ST[1]}='?' if($LW::crawl_config{'save_skipped'}>0); }

  if(defined $hout{'server'}){ 
   if(!defined $ST[4]){ # server tag
	$ST[4]=$hout{'server'}; }
   $LW::crawl_server_tags{$hout{'server'}}++;
  }

  if(defined $hout{'set-cookie'}){
		if($LW::crawl_config{'save_cookies'}>0){
			if(ref($hout{'set-cookie'})){
				foreach (@{$hout{'set-cookie'}}){
					$LW::crawl_cookies{$_}++; }
			} else {
				$LW::crawl_cookies{$hout{'set-cookie'}}++; 
		}	}

		if($LW::crawl_config{'reuse_cookies'}>0){
			cookie_read(\%jar,\%hout); }
  }


  next if($result==4 || $result==5);  
  next if(scalar @links > $LW::crawl_config{'url_limit'});

  if($result==0){ # page should be parsed
	if($LW::crawl_config{'source_callback'} != 0  &&
		ref($LW::crawl_config{'source_callback'})){
		&{$LW::crawl_config{'source_callback'}}($hrin,\%hout); }

	LW::html_find_tags(\$hout{'whisker'}->{'data'},
		\&crawl_extract_links_test);
	$LW::crawl_config{'stats_html'}++; # count how many pages we've parsed
  }

  if($result==3){ # follow the move via location header
	push @LW::crawl_urls, $hout{'location'}; }

  foreach $T (@LW::crawl_urls){
	 $T=~tr/\0\r\n//d; # the NULL character is a bug that's somewhere
	 next if (length($T)==0);
	 next if ($T=~/^javascript:/i); # stupid javascript
	 next if ($T=~/^#/i); # fragment

	 if($LW::crawl_config{'callback'} != 0){
		next if &{$LW::crawl_config{'callback'}}($T,@ST); }

	 push(@{$LW::crawl_referrers{$T}}, $ST[1]) 
		if( $LW::crawl_config{'save_referrers'}>0 );

	 $T=utils_absolute_uri($T,$ST[1],1) if($LW::crawl_config{'normalize_uri'}>0);
	 @vals=utils_split_uri($T);

	 # slashdot bug: workaround for the following fsck'd html code:
	 # <FORM ACTION="//slashdot.org/users.pl" METHOD="GET">
	 if($LW::crawl_config{'slashdot_bug'} > 0 && 
			substr($vals[0],0,2) eq '//'){
		if($ST[3]==1){	$T='https:'.$T;
		} else {	$T='http:' .$T; }
		@vals=utils_split_uri($T);
	 }

	 # make sure URL is on same host, port, and protocol
	 if( (defined $vals[2] && $vals[2] ne $ST[0]) || 
			(defined $vals[3] && $vals[3] != $ST[5]) ||
			(defined $vals[1] && ($vals[1] ne 'http' 
				&& $vals[1] ne 'https'))){
		if($LW::crawl_config{'save_offsites'}>0){
			$LW::crawl_offsites{utils_join_uri(@vals)}++; }
		next; }

	 if(substr($vals[0],0,1) ne '/'){
		$vals[0]=$ST[2].$vals[0]; }

	 my $where=rindex($vals[0],'.');
	 my $EXT='';
	 if($where >= 0){
	   $EXT = substr($vals[0], $where+1, length($vals[0])-$where); }

	 $EXT=~tr/0-9a-zA-Z//cd; # yucky chars will puke regex below

	 if($EXT ne '' && $LW::crawl_config{'skip_ext'}=~/\.$EXT /i){
		if($LW::crawl_config{'save_skipped'}>0){
			$$hrtrack{$vals[0]}='?'; }
	 	next; }

	 if(defined $vals[4] && $LW::crawl_config{'use_params'}>0){
		if($LW::crawl_config{'params_double_record'}>0 &&
				!defined $$hrtrack{$vals[0]}){
			$$hrtrack{$vals[0]}='?'; }
		$vals[0]=$vals[0].'?'.$vals[4];	
	 }

	 next if(defined $$hrtrack{$vals[0]});

	 push @links, \@{[$vals[0],$ST[6]+1, ($vals[1] eq 'https')?1:0]};

  } # foreach

  @LW::crawl_urls=(); # reset for next round
 } # while

 my $key;
 foreach $key (keys %LW::crawl_config){
 	delete $LW::crawl_config{$key} if (substr($key,0,4) eq 'ref_');}

 $LW::crawl_config{'stats_reqs'}=$hout{'whisker'}->{'stats_reqs'};
 $LW::crawl_config{'stats_syns'}=$hout{'whisker'}->{'stats_syns'};

} # end sub crawl



sub crawl_get_config {
	my $key=shift;
	return $LW::crawl_config{$key};
}



sub crawl_set_config {
	return if(!defined $_[0]);
	my %opts=@_;
	while( my($k,$v)=each %opts){
		$LW::crawl_config{lc($k)}=$v; }
}



sub crawl_extract_links_test {
	my ($TAG, $hr, $dr, $start, $len)=(lc(shift),@_);
	my $t;

	# this should be most of the time...
	return undef if(!defined ($t=$LW::crawl_linktags{$TAG}));
	return undef if(!scalar %$hr); # fastpath quickie

	while( my ($key,$val)= each %$hr){ # normalize element values
		$$hr{lc($key)} = $val;
	}

	if(ref($t)){
		foreach (@$t){
			push(@LW::crawl_urls,$$hr{$_}) if(defined $$hr{$_});
		}
	} else {
		push(@LW::crawl_urls,$$hr{$t}) if(defined $$hr{$t});
	}

	if($TAG eq 'form' && defined $$hr{action}){
		my $u=$LW::crawl_config{'ref_hout'}->{'whisker'}->{'uri'};
		$LW::crawl_forms{utils_absolute_uri($$hr{action},$u,1)}++;
	}

	return undef;
}



sub crawl_do_request {
 my ($hrin,$hrout) = @_;
 my $ret;

 if($LW::crawl_config{'do_head'}){  
	my $save=$$hrin{'whisker'}->{'method'};
	$$hrin{'whisker'}->{'method'}='HEAD';
	$ret=http_do_request($hrin,$hrout);
	$$hrin{'whisker'}->{'method'}=$save;

	return 2 if($ret==2); # if there was connection error, do not continue
	if($ret==0){ # successful request
	    	if($$hrout{'whisker'}->{'http_resp'}==501){ # HEAD not allowed
    			$LW::crawl_config{'do_head'}=0; # no more HEAD requests
	    	}

		if($$hrout{'whisker'}->{'http_resp'} <308 &&
				$$hrout{'whisker'}->{'http_resp'} >300){
			if($LW::crawl_config{'follow_moves'} >0){
				return 3 if(defined $$hrout{'location'}); }
			return 5; # not avail
		}

		if($$hrout{'whisker'}->{'http_resp'}==200){
			# no content-type is treated as text/htm
			if(defined $$hrout{'content-type'} &&
					$$hrout{'content-type'}!~/^text\/htm/i){
				return 4;
			}		
			# fall through to GET request below			
		}
    	}
	# request errors are essentially redone via GET, below
  }

 return http_do_request($hrin,$hrout);
}





sub dumper {
	my %what=@_;
	my ($final,$k,$v)=('');
	while( ($k,$v)=each %what){
		return 'ERROR' if(ref($k) || !ref($v));
		$final.="\$$k = "._dump(1,$v);
		$final=~s#,\n$##;
		$final.=";\n"; }
	return $final;
}



sub dumper_writefile {
	my $file=shift;
	my $output=dumper(@_);
	return 1 if(!open(OUT,">$file") || $output eq 'ERROR');
	print OUT $output;
	close(OUT);
}



sub _dump { # dereference and dump an element
	my ($t, $ref)=@_;
	my ($out,$k,$v)=('');
	if(ref($ref) eq 'HASH'){
		$out.="{\n";
		while( ($k,$v)=each %$ref){
			$out.= "\t"x$t;
			$out.=_dumpd($k).' => ';
			if(ref($v)){ $out.=_dump($t+1,$v); }
			else { $out.=_dumpd($v).",\n"; }}
		$out=~s#,\n$#\n#;
		$out.="\t"x$t;
		$out.="},\n";
	} elsif(ref($ref) eq 'ARRAY'){
		$out.="[\n";
		foreach $v (@$ref) {
			$out.= "\t"x$t;
			if(ref($v)){ $out.=_dump($t+1,$v); }
			else { $out.=_dumpd($v).",\n"; }}
		$out=~s#,\n$#\n#;
		$out.="\t"x$t;
		$out.="],\n";
	} elsif(ref($ref) eq 'SCALAR'){
		$out.=_dumpd($$ref);
	} elsif(ref($ref) eq 'REF'){
		$out.=_dump($t,$$ref);
	} elsif(ref($ref)){
		$out.='"" # unsupported reference type: ';
		$out.=ref($ref);	$out.="\n";
	} else { # normal scalar
		$out.=_dumpd($ref);
	}
	return $out;
}



sub _dumpd { # escape a scalar string
	my $v=shift;
	return "'$v'" if($v!~tr/!-~//c);
	$v=~s#\\#\\\\#g;	$v=~s#"#\\"#g;
	$v=~s#\r#\\r#g;		$v=~s#\n#\\n#g;
	$v=~s#\0#\\0#g;		$v=~s#\t#\\t#g;
	$v=~s#([^!-~ ])#sprintf('\\x%02x',ord($1))#eg;
	return "\"$v\"";
}




sub get_page {
	my ($URL,$hr)=(shift,shift);
	return (undef,"No URL supplied") if(length($URL)==0);

	my (%req,%resp);
	my $rptr;

	if(defined $hr && ref($hr)){
		$rptr=$hr;
	} else {
		$rptr=\%req;
		LW::http_init_request(\%req);
	}

	LW::utils_split_uri($URL,$rptr); # this is newer >=1.1 syntax
	LW::http_fixup_request($rptr);

	if(http_do_request($rptr,\%resp)){
		return (undef,$resp{'whisker'}->{'error'});
	}

	return ($resp{'whisker'}->{'code'}, $resp{'whisker'}->{'data'});
}



sub get_page_hash {
	my ($URL,$hr)=(shift,shift);
	return undef if(length($URL)==0);

	my (%req,%resp);
	my $rptr;

	if(defined $hr && ref($hr)){
		$rptr=$hr;
	} else {
		$rptr=\%req;
		LW::http_init_request(\%req);
	}

	LW::utils_split_uri($URL,$rptr); # this is newer >=1.1 syntax
	LW::http_fixup_request($rptr);

	my $r=http_do_request($rptr,\%resp);
	$resp{whisker}->{get_page_hash}=$r;

	return \%resp;
}



sub get_page_to_file {
	my ($URL, $filepath, $hr)=@_;

	return undef if(length($URL)==0);
	return undef if(length($filepath)==0);

	my (%req,%resp);
	my $rptr;

	if(defined $hr && ref($hr)){
		$rptr=$hr;
	} else {
		$rptr=\%req;
		LW::http_init_request(\%req);
	}

	LW::utils_split_uri($URL,$rptr); # this is newer >=1.1 syntax
	LW::http_fixup_request($rptr);

	if(http_do_request($rptr,\%resp)){
		return undef;
	}
	open(OUT,">$filepath") || return undef;
	binmode(OUT); # stupid Windows
	print OUT $resp{'whisker'}->{'data'};
	close(OUT);

	return $resp{'whisker'}->{'code'};
}



sub upload_file {
	my ($URL, $filepath, $paramname, $hr)=@_;

	return undef if(length($URL)      ==0);
	return undef if(length($filepath) ==0);
	return undef if(length($paramname)==0);
	return undef if(!(-e $filepath && -f $filepath));

	my (%req,%resp,%multi);
	my $rptr;

	if(defined $hr && ref($hr)){
		$rptr=$hr;
	} else {
		$rptr=\%req;
		LW::http_init_request(\%req);
	}

	LW::utils_split_uri($URL,$rptr); # this is newer >=1.1 syntax
	$rptr{'whisker'}->{'method'}='POST';
	LW::http_fixup_request($rptr);

	LW::multipart_setfile(\%multi,$filepath,$paramname);
	LW::multipart_write(\%multi,$rptr);

	if(http_do_request($rptr,\%resp)){
		return undef;
	}

	return $resp{'whisker'}->{'code'};
}



sub download_file {
	goto &LW::get_page_to_file;
}






#sub encode_base64;




#sub decode_base64;




sub encode_base64_perl { # ripped from MIME::Base64
    my $res = "";
    my $eol = $_[1];
    $eol = "\n" unless defined $eol;
    pos($_[0]) = 0;
    while ($_[0] =~ /(.{1,45})/gs) {
        $res .= substr(pack('u', $1), 1);
        chop($res);}
    $res =~ tr|` -_|AA-Za-z0-9+/|;
    my $padding = (3 - length($_[0]) % 3) % 3;
    $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
    if (length $eol) {
        $res =~ s/(.{1,76})/$1$eol/g;
    } $res; }




sub decode_base64_perl { # ripped from MIME::Base64
    my $str = shift;
    my $res = "";
    $str =~ tr|A-Za-z0-9+=/||cd;
    $str =~ s/=+$//;                        # remove padding
    $str =~ tr|A-Za-z0-9+/| -_|;            # convert to uuencoded format
    while ($str =~ /(.{1,60})/gs) {
        my $len = chr(32 + length($1)*3/4); # compute length byte
        $res .= unpack("u", $len . $1 );    # uudecode
    }$res;}




sub encode_str2uri { # normal hex encoding
	my $str=shift;
	$str=~s/([^\/])/sprintf("%%%02x",ord($1))/ge;
	return $str;}




sub encode_str2ruri { # random normal hex encoding
    my @T=split(//,shift);
    my $s;
    foreach (@T) {
     if(m#;=:&@\?#){
        $s.=$_;
        next;
      }
      if((rand()*2)%2 == 1){	$s.=sprintf("%%%02x",ord($_)) ;
      }else{			$s.=$_; }
    }
    return $s;
}



sub encode_unicode
{
	my $r='';
 	foreach $c (split(//,shift)){
		$r.=pack("v",ord($c));
	}
	return $r;
}




sub forms_read {
	my $dr=shift;
	return undef if(!ref($dr) || length($$dr)==0);

	@LW::forms_found=();
	LW::html_find_tags($dr,\&forms_parse_callback);

	if(scalar %LW::forms_current){
		my %DUP=%LW::forms_current;
		push(@LW::forms_found,\%DUP);
	}
	return @LW::forms_found;
}



sub forms_write {
	my $hr=shift;
	return undef if(!ref($hr) || !(scalar %$hr));
	return undef if(!defined $$hr{"\0"});
	
	my $t='<form name="'.$$hr{"\0"}->[0].'" method="';
	$t.=$$hr{"\0"}->[1].'" action="'.$$hr{"\0"}->[2].'"';
	if(defined $$hr{"\0"}->[3]){
		$t.=' '.join(' ',@{$$hr{"\0"}->[3]}); }
	$t.=">\n";

	while( my($name,$ar)=each(%$hr) ){
	  next if($name eq "\0");
	  foreach $a (@$ar){
		my $P='';
		$P=' '.join(' ', @{$$a[2]}) if(defined $$a[2]);
		$t.="\t";

		if($$a[0] eq 'textarea'){
			$t.="<textarea name=\"$name\"$P>$$a[1]";
			$t.="</textarea>\n";

		} elsif($$a[0]=~m/^input-(.+)$/){
			$t.="<input type=\"$1\" name=\"$name\" ";
			$t.="value=\"$$a[1]\"$P>\n";

		} elsif($$a[0] eq 'option'){
			$t.="\t<option value=\"$$a[1]\"$P>$$a[1]\n";

		} elsif($$a[0] eq 'select'){
			$t.="<select name=\"$name\"$P>\n";

		} elsif($$a[0] eq '/select'){
			$t.="</select$P>\n";

		} else { # button
			$t.="<button name=\"$name\" value=\"$$a[1]\">\n";
		}
	  }
	}

	$t.="</form>\n";
	return $t;
}




{ # these are private static variables for &forms_parse_html
%FORMS_ELEMENTS=(	'form'=>1,	'input'=>1,
			'textarea'=>1,	'button'=>1,
			'select'=>1,	'option'=>1,
			'/select'=>1	);
$CURRENT_SELECT=undef;
$UNKNOWNS=0;

sub forms_parse_callback {
	my ($TAG, $hr, $dr, $start, $len)=(lc(shift),@_);
	my ($saveparam, $parr, $key)=(0,undef,'');

	# fastpath shortcut
	return undef if(!defined $FORMS_ELEMENTS{$TAG});
	LW::utils_lowercase_hashkeys($hr) if(scalar %$hr);

	if($TAG eq 'form'){

		if(scalar %LW::forms_current){ # save last form
			my %DUP=%LW::forms_current;
			push (@LW::forms_found, \%DUP);
			%LW::forms_current=();
		}

		$LW::forms_current{"\0"}=[$$hr{name},$$hr{method},
			$$hr{action},undef];
		delete $$hr{'name'}; delete $$hr{'method'}; delete $$hr{'action'};
		$key="\0"; $parr=\@{$LW::forms_current{"\0"}};
		$UNKNOWNS=0;

	} elsif($TAG eq 'input'){
		$$hr{type}='text' if(!defined $$hr{type});
		$$hr{name}='unknown'.$UNKNOWNS++ if(!defined $$hr{name});
		$key=$$hr{name};
	
		push( @{$LW::forms_current{$key}}, 
			(['input-'.$$hr{type},$$hr{value},undef]) );
		delete $$hr{'name'}; delete $$hr{'type'}; delete $$hr{'value'};
		$parr=\@{$LW::forms_current{$key}->[-1]};

	} elsif($TAG eq 'select'){
		$$hr{name}='unknown'.$UNKNOWNS++ if(!defined $$hr{name});
		$key=$$hr{name};
		push( @{$LW::forms_current{$key}}, (['select',undef,undef]) );
		$parr=\@{$LW::forms_current{$key}->[-1]};
		$CURRENT_SELECT=$key;
		delete $$hr{name};

	} elsif($TAG eq '/select'){
		push( @{$LW::forms_current{$CURRENT_SELECT}}, 
			(['/select',undef,undef]) );
		$CURRENT_SELECT=undef;
		return undef;

	} elsif($TAG eq 'option'){
		return undef if(!defined $CURRENT_SELECT);
		if(!defined $$hr{value}){
			my $stop=index($$dr,'<',$start+$len);
			return undef if($stop==-1); # MAJOR PUKE
			$$hr{value}=substr($$dr,$start+$len,
				($stop-$start-$len));
			$$hr{value}=~tr/\r\n//d;
		}
		push( @{$LW::forms_current{$CURRENT_SELECT}}, 
			(['option',$$hr{value},undef]) );
		delete $$hr{value};
		$parr=\@{$LW::forms_current{$CURRENT_SELECT}->[-1]};

	} elsif($TAG eq 'textarea'){
		my $stop=$start+$len;
		# find closing </textarea> tag
		do {	$stop=index($$dr,'</',$stop+2); 
			return undef if($stop==-1); # MAJOR PUKE
		} while( lc(substr($$dr,$stop+2,8)) ne 'textarea');
		$$hr{value}=substr($$dr,$start+$len,($stop-$start-$len));

		$$hr{name}='unknown'.$UNKNOWNS++ if(!defined $$hr{name});
		$key=$$hr{name};
		push( @{$LW::forms_current{$key}}, 
			(['textarea',$$hr{value},undef]) );
		$parr=\@{$LW::forms_current{$key}->[-1]};
		delete $$hr{'name'}; delete $$hr{'value'};

	} else { # button
		$$hr{name}='unknown'.$UNKNOWNS++ if(!defined $$hr{name});
		$key=$$hr{name};
		push( @{$LW::forms_current{$key}}, 
			(['button',$$hr{value},undef]) );
	}

	if(scalar %$hr){
		my @params=();
		foreach $k (keys %$hr){
			if(defined $$hr{$k}){
					push @params, "$k=\"$$hr{$k}\"";
			} else {	push @params, $k; }
		}
		$$parr[2]=\@params;
	}

	return undef;
}}




sub html_find_tags {
 # use faster binary helper
 goto &LW::bin::html_find_tags 
 	if(defined $LW::available{'lw::bin'});
	
 my ($dataref, $callbackfunc)=@_;

 return if(!(defined $dataref      && ref($dataref)     ));
 return if(!(defined $callbackfunc && ref($callbackfunc)));

 my ($CURTAG, $ELEMENT, $VALUE, $c, $cc);
 my ($INCOMMENT,$INTAG,$INSCRIPT)=(0,0,0);
 my (%TAG, $ret, $start, $tagstart, $commstart, $scriptstart, $x);

 # YES, this looks like C.  In fact, it's my C version ported to
 # perl.  But it's faster and more dependable than any regex mess
 # someone could come up with.
 my $LEN = length($$dataref);
 for ($c=0; $c<$LEN; $c++){

	$cc=substr($$dataref,$c,1);

	if(!$INCOMMENT && !$INTAG && !$INSCRIPT && $cc ne '>' && $cc ne '<'){
		next; }

        if($cc eq '<'){
		if($INSCRIPT){
			if(lc(substr($$dataref,$c+1,7)) eq '/script'){
				$INSCRIPT=0;
				$TAG{'='}=substr($$dataref, $scriptstart,
					$c - $scriptstart - 1);
			} else { next; }
		}

                if(substr($$dataref,$c+1,3) eq '!--'){
                        $INCOMMENT=1; $commstart=$c; $c+=3;

		} else {
    	                $INTAG=1; $c++;
			$c++ while(substr($$dataref,$c,1)=~/[< \t\r\n]/);
			$tagstart=$c-1; 

			$CURTAG='';
			while(($x=substr($$dataref,$c,1))!~/[ \t\r\n>=]/ &&
					$c < $LEN){
				$CURTAG.=$x; $c++;}

			$c++ if($x ne '>');

			$INSCRIPT=1 if($CURTAG eq 'script');
		}	
		$cc=substr($$dataref,$c,1); # refresh current char (cc)
	}

        if($cc eq '>'){
		if($INSCRIPT){
			if($CURTAG eq 'script'){
				$scriptstart = $c + 1; 
			} else { next; }
		}
		if(!$INCOMMENT && $INTAG){ 
			$INTAG=0; 
			$ret=&$callbackfunc($CURTAG,\%TAG, $dataref,
				$tagstart, $c-$tagstart+1);
			if(defined $ret && $ret != 0){
				$c+=$ret;}
			$CURTAG='';
			%TAG=();
		}
                if($INCOMMENT && substr($$dataref,$c-2,2) eq '--'){
                        $INCOMMENT=0; 
			$TAG{'='}=substr($$dataref,$commstart+4,
				$c-$commstart-3);
			$ret=&$callbackfunc('!--',\%TAG, $dataref,
				$commstart, $c-$commstart+1);
			if(defined $ret && $ret != 0){
				$c+=$ret;}
			delete $TAG{'='};
			next;
		}
	}

        next if($INCOMMENT);

        if($INTAG){

                $ELEMENT=''; $VALUE='';

		# eat whitespace
		while(substr($$dataref,$c,1)=~/[ \t\r\n]/i){ $c++; }

		$start=$c;
		while(substr($$dataref,$c,1)!~/[ \t\r\n=\>]/i &&
			$c < $LEN) { $c++; }

		$ELEMENT=substr($$dataref,$start,$c-$start);

		$VALUE='';
		if(substr($$dataref,$c,1) ne '>'){
		 # eat whitespace
		 while(substr($$dataref,$c,1)=~/[ \t\r\n]/i) { $c++; }

                 if(substr($$dataref,$c,1) eq '='){ 
                	$c++;
			$start=$c;
			my $p = substr($$dataref,$c,1);
                        if($p eq '"' || $p eq '\''){ 
                        	$c++; $start++;
	                        $c++ while(substr($$dataref,$c,1) ne $p &&
	                        	$c < $LEN);
				$VALUE=substr($$dataref,$start,$c-$start);
                                $c++; 
			} else {
                                $c++ while(substr($$dataref,$c,1)!~/[ \t\r\n\>]/ &&
                                	$c < $LEN);
				$VALUE=substr($$dataref,$start,$c-$start);
			}

			# eat whitespace
                	while(substr($$dataref,$c,1)=~/[ \t\r\n]/) { $c++; }
                 } 
		} # if $c ne '>'
		$c--;
		$TAG{$ELEMENT}=$VALUE; # save element in the hash
	}
}}





sub http_init_request { # doesn't return anything
 my ($hin)=shift;

 return if(!(defined $hin && ref($hin)));
 %$hin=(); # clear control hash

# control values
 $$hin{'whisker'}={
	req_spacer		=>	' ',
	req_spacer2		=>	' ',
	http_ver		=>	'1.1',
	method			=>	'GET',
	method_postfix		=>	'',
	port			=>	80,
	uri			=>	'/',
	uri_prefix		=>	'',
	uri_postfix		=>	'',
	uri_param_sep		=>	'?',
	host			=>	'localhost',
	http_req_trailer    	=>	'',
	timeout			=>	10,
	include_host_in_uri 	=>	0,
	ignore_duplicate_headers=> 	1,
	normalize_incoming_headers =>	1,
	lowercase_incoming_headers =>	0,
	ssl			=>	0,
	http_eol		=>	"\x0d\x0a",
	force_close		=>	0,
	force_open		=>	0,
	retry			=>	1,
	trailing_slurp		=>	0,
	force_bodysnatch	=>	0,
	INITIAL_MAGIC		=>	31337
};

 
# default header values
 $$hin{'Connection'}='Keep-Alive'; # notice it is now default!
 $$hin{'User-Agent'}="libwhisker/$LW::VERSION"; # heh
}




sub http_do_request {
 my @params = @_;
 my $retry_count = ${$params[0]}{'whisker'}->{'retry'} || 0;
 my ($ret, @retry_errors, $auth);

 return 1 if(!(defined $params[0] && ref($params[0])));
 return 1 if(!(defined $params[1] && ref($params[1])));

 if(defined $params[2]){
	foreach (keys %{$params[2]}){
		${$params[0]}{'whisker'}->{$_}=${$params[2]}{$_};}}

 $auth=$params[0]->{'Authorization'} if(defined $params[0]->{'Authorization'});
 do {
    if(defined $auth && $auth=~/^NTLM/){
	$ret=0;
	if($params[0]->{'whisker'}->{'ntlm_step'}==0){
		$ret=LW::http_do_request_ex($params[0],$params[1]);
		return 2 if($ret==2);
		if($ret==0){
			return 0 if($params[1]->{'whisker'}->{'code'} == 200);
			return 1 if($params[1]->{'whisker'}->{'code'} != 401);
			$params[0]->{'whisker'}->{'ntlm_step'}=1;
			my $thead=utils_find_lowercase_key($params[1],'www-authenticate');
			return 1 if(!defined $thead);
			return 1 if($thead!~m/^NTLM (.+)$/);  
			$params[0]->{'Authorization'}='NTLM '.ntlm_client(
				$params[0]->{'whisker'}->{'ntlm_obj'},$1);
		}
	}
	if($ret==0){
		delete $params[0]->{'Authorization'}
			if($params[0]->{'whisker'}->{'ntlm_step'}>1);
		$ret=LW::http_do_request_ex($params[0],$params[1]);
		$params[0]->{'Authorization'}=$auth; 
		if($ret>0){ 	$params[0]->{'whisker'}->{'ntlm_step'}=0;
		} else {	$params[0]->{'whisker'}->{'ntlm_step'}=2; }
		return $ret if($ret==2||$ret==0);
	}
    } else {
    	$ret=LW::http_do_request_ex($params[0],$params[1]);
	push @{${$params[1]}{'whisker'}->{'retry_errors'}},
		@retry_errors if scalar(@retry_errors);
	return $ret if($ret==0 || $ret==2);
    }
    push @retry_errors, ${$params[1]}{'whisker'}->{'error'};
    $retry_count--;
  } while( $retry_count >= 0);

 # if we get here, we still had errors, but no more retries
 return 1;
}



sub http_do_request_ex {
 my ($hin, $hout, $hashref)=@_;
 my ($temp,$vin,$resp,$S,$a,$b,$vout,@c,$c,$res)=(1,'');
 my $W; # shorthand alias for the {'whisker'} hash

 return 1 if(!(defined $hin  && ref($hin) ));
 return 1 if(!(defined $hout && ref($hout)));

 %$hout=(); # clear output hash
 $$hout{whisker}->{uri}=$$hin{whisker}->{uri}; # for tracking purposes
 $$hout{whisker}->{'INITIAL_MAGIC'}=31338; # we can tell requests from responses

 if($LW::LW_HAS_SOCKET==0){
	$$hout{'whisker'}->{'error'}='Socket support not available';
	return 2;}

 if(!defined $$hin{'whisker'} || 
    !defined $$hin{'whisker'}->{'INITIAL_MAGIC'} ||
    $$hin{'whisker'}->{'INITIAL_MAGIC'}!=31337 ){
	$$hout{'whisker'}->{'error'}='Input hash not initialized';
	return 2;
 }

 if(defined $hashref){
	foreach (keys %$hashref){
		$$hin{'whisker'}->{$_}=$$hashref{$_};}}

 # if we want anti-IDS, make a copy and setup new values
 if(defined $$hin{'whisker'}->{'anti_ids'}){
	my %copy=%{$hin};
	anti_ids(\%copy,$$hin{'whisker'}->{'anti_ids'});
	$W = $copy{'whisker'};
 } else {
	$W = $$hin{'whisker'};
 }

 if($$W{'ssl'}>0 && $LW::LW_HAS_SSL!=1){
	$$hout{'whisker'}->{'error'}='SSL not available';
	return 2;}

 $TIMEOUT=$$W{'timeout'}||10;

 my $cache_key = defined $$W{'proxy_host'} ?
	join(':',$$W{'proxy_host'},$$W{'proxy_port'}) :
	join(':',$$W{'host'},$$W{'port'});

 if(!defined $http_host_cache{$cache_key}){
	# make new entry
	push(@{$http_host_cache{$cache_key}},
		undef, 	# SOCKET		$$Z[0]
		0,	# $SOCKSTATE		$$Z[1]
		undef,	# INET_ATON		$$Z[2]
		undef,	# $SSL_CTX		$$Z[3]
		undef,	# $SSL_THINGY		$$Z[4]
		'',	# $OUTGOING_QUEUE	$$Z[5]
		'',	# $INCOMING_QUEUE	$$Z[6]
		0,	# $STATS_SYNS		$$Z[7]
		0, 	# $STATS_REQS		$$Z[8]
		undef ) # SSL session ID	$$Z[9]
 }

 # NOTE: the 'Z' reference will be going away in future versions...
 $Z = $http_host_cache{$cache_key};

 # use $chost/$cport for actual server we are connecting to
 my ($chost,$cport,$cwhat,$PROXY)=('',80,'',0);

 if(defined $$W{'proxy_host'}){
    $chost=$$W{'proxy_host'};
    $cport=$$W{'proxy_port'}||80;
    $cwhat='proxy';
    $PROXY=1;

    if($$W{'ssl'}>0 && $LW::LW_SSL_LIB==2){
	$ENV{HTTPS_PROXY} ="$$W{'proxy_host'}:";
	$ENV{HTTPS_PROXY}.=$$W{'proxy_port'}||80; }

 } else {
    $chost=$$W{'host'};
    $cport=$$W{'port'};
    $cwhat='host';
 }

 if($$Z[1]>0){ # check to see if socket is still alive
	if(! sock_valid($Z,$hin,$hout) ){
		$$Z[1]=0;
		sock_close($$Z[0],$$Z[4]);
 }	}
 # technically we have a race condition: socket can go
 # bad before we send request, below.  But that's ok,
 # we handle the errors down there.

 if($$Z[1]==0){

	if(defined $$W{'UDP'} && $$W{'UDP'}>0){
		if(!socket(SOCK,PF_INET,SOCK_DGRAM,getprotobyname('udp')||0)){
			$$hout{'whisker'}->{'error'}='Socket() problems (UDP)'; 
			return 2;}
	} else {
		if(!socket(SOCK,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0)){
			$$hout{'whisker'}->{'error'}='Socket() problems'; 
			return 2;}
	}

	$$Z[0]=SOCK; # lame hack to get perl to take variable for socket

	$$Z[5]=$$Z[6]=''; # flush in/out queues

	if($$W{'ssl'}>0){ # ssl setup stuff

	    if($LW::LW_SSL_LIB==1){
		if(!defined($$Z[3])){
		    if(! ($$Z[3] = Net::SSLeay::CTX_new()) ){
			$$hout{'whisker'}->{'error'}="SSL_CTX error: $!";
			return 2;}
		    if(defined $$W{'ssl_rsacertfile'}){
			if(! (Net::SSLeay::CTX_use_RSAPrivateKey_file($$Z[3], 
					$$W{'ssl_rsacertfile'},
					&Net::SSLeay::FILETYPE_PEM))){
				$$hout{'whisker'}->{'error'}="SSL_CTX_use_rsacert error: $!";
				return 2;}
		    }
		    if(defined $$W{'ssl_certfile'}){
			if(! (Net::SSLeay::CTX_use_certificate_file($$Z[3], 
					$$W{'ssl_certfile'},
					&Net::SSLeay::FILETYPE_PEM))){
				$$hout{'whisker'}->{'error'}="SSL_CTX_use_cert error: $!";
				return 2;}
		    }
		}
		if(! ($$Z[4] = Net::SSLeay::new($$Z[3])) ){
			$$hout{'whisker'}->{'error'}="SSL_new error: $!";
			return 2;}
		if(defined $$W{'ssl_ciphers'}){
			if(!(Net::SSLeay::set_cipher_list($$Z[4], 
					$$W{'ssl_ciphers'}))){
				$$hout{'whisker'}->{'error'}="SSL_set_ciphers error: $!";
				return 2;}
		}
	    }
	}

	$$Z[2]=inet_aton($chost) if(!defined $$Z[2]);
	if(!defined $$Z[2]){ # can't find hostname
		$$hout{'whisker'}->{'error'}="Can't resolve hostname";
		return 2;
	}

	if($$W{'ssl'}>0 && $LW::LW_SSL_LIB==2){
		# proxy set in ENV; we always connect to host
		$$Z[4]= Net::SSL->new(
			PeerAddr => $$hin{'whisker'}->{'host'},
			PeerPort => $$hin{'whisker'}->{'port'},
			Timeout => $TIMEOUT );
		if($@){ $$hout{'whisker'}->{'error'}="Can't connect via SSL: $@[0]";
			return 2;}
		$$Z[4]->autoflush(1);
	} else {
		if($LW::LW_NONBLOCK_CONNECT){
			my $flags=fcntl($$Z[0],F_GETFL,0);
			$flags |= O_NONBLOCK; # set nonblock flag
			if(!(fcntl($$Z[0],F_SETFL,$flags))){ # error setting flag
				$LW::LW_NONBLOCK_CONNECT=0; # revert to normal
			} else {
				my $R=connect($$Z[0],sockaddr_in($cport,$$Z[2]));
				if(!$R){ # we didn't connect...
					if($! != EINPROGRESS){
						close($$Z[0]);
						$$Z[0]=undef; # this is a bad socket
						$$hout{'whisker'}->{'error'}="Can't connect to $cwhat";
						return 2;}
					vec($vin,fileno($$Z[0]),1)=1;
					if(!select(undef,$vin,undef,$TIMEOUT) || !getpeername($$Z[0])){
						close($$Z[0]);
						$$Z[0]=undef; # this is a bad socket
						$$hout{'whisker'}->{'error'}="Can't connect to $cwhat";
						return 2;
				}	}
				$flags &= ~O_NONBLOCK; # clear nonblock flag
				if(!(fcntl($$Z[0],F_SETFL,$flags))){ # not good!
					close($$Z[0]);
					$LW::LW_NONBLOCK_CONNECT=0;
					$$Z[0]=undef;
					$$hout{'whisker'}->{'error'}="Error setting socket to block";
					return 2;
			}	}	
		}	

		if(!defined $$Z[0]){ # this is a safety catch
			$$hout{'whisker'}->{'error'}="Error creating valid socket connection";
			return 2; }

		if($LW::LW_NONBLOCK_CONNECT==0){ # attempt to do a timeout alarm...
			eval {
				local $SIG{ALRM} = sub { die "timeout\n" };
				eval {alarm($TIMEOUT)};
				if(!connect($$Z[0],sockaddr_in($cport,$$Z[2]))){
					alarm(0);
					die("no_connect\n"); }
				eval {alarm(0)};
			};
			if($@ || !(defined $$Z[0])){
				$$hout{'whisker'}->{'error'}="Can't connect to $cwhat";
				return 2;
		}	}

		binmode($$Z[0]); # stupid Windows
		# same as IO::Handle->autoflush(1), without importing 1000+ lines
		my $S=select($$Z[0]); 
		$|++; select($S);
	}

	$$Z[1]=1; $$Z[7]++;

	if($$W{'ssl'}>0){

	    if($LW::LW_SSL_LIB==1){

	        if($PROXY){ # handle the proxy CONNECT stuff...
		    my $SSL_CONNECT = "CONNECT $$W{'host'}".
			":$$W{'port'}/ HTTP/1.0\n\n";
		    syswrite($$Z[0],$SSL_CONNECT, length($SSL_CONNECT)); }

		Net::SSLeay::set_fd($$Z[4], fileno($$Z[0]));
		Net::SSLeay::set_session($$Z[4],$$Z[9]) if(defined $$Z[9]);
		if(! (Net::SSLeay::connect($$Z[4])) ){
			$$hout{'whisker'}->{'error'}="SSL_connect error: $!";
			sock_close($$Z[0],$$Z[4]); return 2;}

		if(defined $$W{'save_ssl_info'} && 
				$$W{'save_ssl_info'}>0){
			ssl_save_info($hout,$$Z[4]); }
		my $x=Net::SSLeay::ctrl($$Z[4],6,0,'');
		$$Z[9]=Net::SSLeay::get_session($$Z[4]) unless(defined $$W{'ssl_resume'} &&
			$$W{'ssl_resume'}==0);
	    }

	} else {
		$$Z[4]=undef;
	}
 }

 if(defined $$W{'ids_session_splice'} &&
            $$W{'ids_session_splice'}>0 &&
		$$W{'ssl'}==0){ # no session_spice over ssl
	setsockopt($$Z[0],SOL_SOCKET,SO_SNDLOWAT,1);
	@c=split(//, &http_req2line($hin));
	# notice we bypass queueing here, in order to trickle the packets
	my $ss;
	foreach $c (@c){ 
		$ss=syswrite($$Z[0],$c,1); # char size assumed to be 1
		if(!defined $ss || $ss==0){
			$$hout{'whisker'}->{'error'}="Error sending session splice request to server";
			sock_close($$Z[0],$$Z[4]); return 1;
		}
		select(undef,undef,undef,.1);
	}
 } else {
	 http_queue(http_req2line($hin)); }

 $$Z[8]++;

 if($$W{'http_ver'} ne '0.9'){
    my %SENT;
    if(defined $$W{'header_order'} && ref($$W{'header_order'})){
	foreach (@{$$W{'header_order'}}){
		next if($_ eq '' || $_ eq 'whisker');
		if(ref($$hin{$_})){
			$SENT{$_}||=0;
			my $v=$$hin{$_}->[$SENT{$_}];
			http_queue("$_: $v$$W{'http_eol'}");
		} else {
			http_queue("$_: $$hin{$_}$$W{'http_eol'}");
		}
		$SENT{$_}++;
	}
    }

    foreach (keys %$hin){
	next if($_ eq '' || $_ eq 'whisker');
	next if(defined $SENT{$_});
	if(ref($$hin{$_})){ # header with multiple values
		my $key=$_;
		foreach (@{$$hin{$key}}){
		  http_queue("$key: $_$$W{'http_eol'}");}
	} else { # normal header
		http_queue("$_: $$hin{$_}$$W{'http_eol'}");
	}
    }

    if(defined $$W{'raw_header_data'}){
	http_queue($$W{'raw_header_data'});}

    http_queue($$W{'http_eol'});

    if(defined $$W{'data'}){ 
	http_queue($$W{'data'});}

 } # http 0.9 support

 # take a MD5 of queue, if wanted
 if(defined $$W{'queue_md5'}){
	$$hout{'whisker'}->{'queue_md5'}= LW::md5($$Z[5]);
 }


 # all data is wrangled...actually send it now
 if($res=http_queue_send($$Z[0],$$Z[4])){
	$$hout{'whisker'}->{'error'}="Error sending request to server: $res";
	sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}

 if(defined $$Z[4]){
	if($LW::LW_SSL_LIB==1){ # Net::SSLeay
 		shutdown $$Z[0], 1; 
	} else { # Net::SSL
		shutdown $$Z[4], 1;
	}
 }

 vec($vin,fileno($$Z[0]),1)=1; # wait only so long to read...
 if(!select($vin,undef,undef,$TIMEOUT)){
	$$hout{'whisker'}->{'error'}="Server read timed out";
	sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}

my ($LC,$CL,$TE,$CO)=('',-1,'',''); # extra header stuff

$$hout{'whisker'}->{'lowercase_incoming_headers'} = 
	$$W{'lowercase_incoming_headers'};

if($$W{'http_ver'} ne '0.9'){

 do { # catch '100 Continue' responses
  $resp=sock_getline($$Z[0],$$Z[4]);
  #$resp=~tr/\r\n//d if(defined $resp);

  if(!defined $resp){
	$$hout{'whisker'}->{'error'}='Error reading HTTP response';
	if($!){ # this should be left over from sysread via sock_getline
		$$hout{'whisker'}->{'error'}.=": $!"; }
	$$hout{'whisker'}->{'data'}=$$Z[6];
	sock_close($$Z[0],$$Z[4]); $$Z[1]=0; # otherwise bad crap lingers
	return 1;}

  if(defined $$W{'save_raw_headers'}){
	$$hout{'whisker'}->{'raw_header_data'}.=$resp;}

  if($resp!~/^HTTP\/([0-9.]{3})[ \t]+(\d+)[ \t]{0,1}(.*?)[\r\n]+/){
	$$hout{'whisker'}->{'error'}="Invalid HTTP response: $resp";
	# let's save the incoming data...we might want it
	$$hout{'whisker'}->{'data'}=$resp;
	while(defined ($_=sock_getline($$Z[0],$$Z[4]))){ 
		$$hout{'whisker'}->{'data'}.=$_;}
	# normally we'd check the results to see if socket is closed, but
	# we close it anyway, so it doesn't matter
	sock_close($$Z[0],$$Z[4]); $$Z[1]=0; # otherwise bad crap lingers
	return 1;}

  $$hout{'whisker'}->{'http_ver'}	= $1;
  $$hout{'whisker'}->{'http_resp'}	= $2;
  $$hout{'whisker'}->{'http_resp_message'}= $3;
  $$hout{'whisker'}->{'code'}		= $2;

  $$hout{'whisker'}->{'100_continue'}++ if($2 == 100);

  while(defined ($_=sock_getline($$Z[0],$$Z[4]))){ # check pertinent headers

	if(defined $$W{'save_raw_headers'}){
		$$hout{'whisker'}->{'raw_header_data'}.=$_;}

	$_=~s/[\r]{0,1}\n$//; # anchored regex, so it's fast
	last if ($_ eq ''); # acceptable assumption case?

	my $l2=index($_,':'); # this is faster than regex
	$a=substr($_,0,$l2); 
	$b=substr($_,$l2+1);
	$b=~s/^([ \t]*)//; # anchored regex, so it's fast

	$hout{'whisker'}->{'abnormal_header_spacing'}++ if($1 ne ' ');

	$LC = lc($a);
	next         if($LC eq 'whisker');
	$TE = lc($b) if($LC eq 'transfer-encoding');
	$CL = $b     if($LC eq 'content-length');
	$CO = lc($b) if($LC eq 'connection');

	if($$W{'lowercase_incoming_headers'}>0){
		$a=$LC;
	} elsif($$W{'normalize_incoming_headers'}>0){ 
                $a=~s/(-[a-z])/uc($1)/eg;
 	}

	# save the received header order, in case we're curious
	push(@{$$hout{'whisker'}->{'recv_header_order'}},$a);

	if(defined $$hout{$a} && $$W{'ignore_duplicate_headers'}!=1){
	  if(!ref($$hout{$a})){
	    my $temp=$$hout{$a};
	    delete $$hout{$a};
	    push(@{$$hout{$a}},$temp);
	  }
	  push(@{$$hout{$a}},$b);
	} else {
	  $$hout{$a}=$b;
  }	}

  # did we have a socket error?
  if($!){
	$hout{'whisker'}->{'error'}='Error in reading response/headers';
	sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1; }

  if( $CO eq '' ){ # do whatever the client wanted
	$CO = (defined $$hin{'Connection'}) ? lc($$hin{'Connection'}) : 
		'close'; }

 } while($$hout{'whisker'}->{'http_resp'}==100);

} else { # http ver 0.9, we need to fake it
 # Keep in mind lame broken servers, like IIS, still send headers for 
 # 0.9 requests; the headers are treated as data.  Also keep in mind
 # that if the server doesn't support HTTP 0.9 requests, it will spit
 # back an HTTP 1.0 response header.  User is responsible for figuring
 # this out himself.
 $$hout{'whisker'}->{'http_ver'}='0.9';
 $$hout{'whisker'}->{'http_resp'}='200';
 $$hout{'whisker'}->{'http_resp_message'}='';
}

 if($$W{'force_bodysnatch'} || ( $$W{'method'} ne 'HEAD' && 
	$$hout{'whisker'}->{'http_resp'}!=206 &&
	$$hout{'whisker'}->{'http_resp'}!=102)){
  if ($TE eq 'chunked') { 
	if(!defined ($a=sock_getline($$Z[0],$$Z[4]))){
		$$hout{'whisker'}->{'error'}='Error reading chunked data length';
		sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}
	$a=~tr/a-fA-F0-9//cd; $CL=hex($a); 
	$$hout{'whisker'}->{'data'}='';
	while($CL!=0) { # chunked sucks
		if(!defined ($temp=sock_get($$Z[0],$$Z[4],$CL))){
			$$hout{'whisker'}->{'error'}="Error reading chunked data: $!";
			sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}
		$$hout{'whisker'}->{'data'}=$$hout{'whisker'}->{'data'} . $temp;
		$temp=sock_getline($$Z[0], $$Z[4]);
		($temp=sock_getline($$Z[0], $$Z[4])) if(defined $temp &&
			$temp=~/^[\r\n]*$/);
		if(!defined $temp){ # this will catch errors in either sock_getline
			$$hout{'whisker'}->{'error'}="Error reading chunked data: $!";
			sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}
		$temp=~tr/a-fA-F0-9//cd; $CL=hex($temp);}
	# read in trailer headers
	while(defined ($_=sock_getline($$Z[0],$$Z[4]))){ tr/\r\n//d; last if($_ eq ''); }
	# Hmmmm...error, but we should have full body.  Don't return error
	if($!){ $$Z[1]=0; sock_close($$Z[0],$$Z[4]); }
  } else {
 	if ($CL != -1) {
		if(!defined ($temp=sock_get($$Z[0],$$Z[4],$CL))){
			$$hout{'whisker'}->{'error'}="Error reading data: $!";
			sock_close($$Z[0],$$Z[4]); $$Z[1]=0; return 1;}
	} else {  # Yuck...read until server stops sending....
		$temp=sock_getall($$Z[0],$$Z[4]);
		# we go until we puke, so close socket and don't return error
		sock_close($$Z[0],$$Z[4]); $$Z[1]=0;
	}
	$$hout{'whisker'}->{'data'}=$temp; 
  }
 } # /method ne HEAD && http_resp ne 206 or 102/

 if(($CO ne 'keep-alive' || ( defined $$hin{'Connection'} &&
		lc($$hin{'Connection'}) eq 'close')) && $$W{'force_open'}!=1){
	$$Z[1]=0; sock_close($$Z[0],$$Z[4]); 
 }	 

 # this way we know what the state *would* have been...
 $$hout{'whisker'}->{'sockstate'}=$$Z[1];
 if($$W{'force_close'}>0) {
	$$Z[1]=0; sock_close($$Z[0],$$Z[4]); } 

 if($$W{'ssl'}>0){ # we don't reuse SSL sockets
	$$Z[1]=0; sock_close($$Z[0],$$Z[4]); }

 $$hout{'whisker'}->{'stats_reqs'}=$$Z[8];
 $$hout{'whisker'}->{'stats_syns'}=$$Z[7];
 $$hout{'whisker'}->{'error'}=''; # no errors
 return 0;
}




sub http_req2line {
 my ($S,$hin,$UO)=('',@_);
 $UO||=0; # shut up -w warning

 # notice: full_request_override can play havoc with proxy settings
 if(defined $$hin{'whisker'}->{'full_request_override'}){
	return $$hin{'whisker'}->{'full_request_override'};

 } else { # notice the components of a request--this is for flexibility

	if($UO!=1){$S.= 	$$hin{'whisker'}->{'method'}.
				$$hin{'whisker'}->{'method_postfix'}.
				$$hin{'whisker'}->{'req_spacer'};
	
		if($$hin{'whisker'}->{'include_host_in_uri'}>0){
			$S.=	'http://';

			if(defined $$hin{'whisker'}->{'uri_user'}){
			$S.=	$$hin{'whisker'}->{'uri_user'};
			if(defined $$hin{'whisker'}->{'uri_password'}){
				$S.=	':'.$$hin{'whisker'}->{'uri_user'};
			}
			$S.=	'@';
			}

			$S.=	$$hin{'whisker'}->{'host'}.
				':'.$$hin{'whisker'}->{'port'};}}

	$S.=	$$hin{'whisker'}->{'uri_prefix'}.
		$$hin{'whisker'}->{'uri'}.
		$$hin{'whisker'}->{'uri_postfix'};

	if(defined $$hin{'whisker'}->{'uri_param'}){
		$S.= 	$$hin{'whisker'}->{'uri_param_sep'}.
			$$hin{'whisker'}->{'uri_param'};}

	if($UO!=1){  if($$hin{'whisker'}->{'http_ver'} ne '0.9'){
			$S.= 	$$hin{'whisker'}->{'req_spacer2'}.'HTTP/'.
				$$hin{'whisker'}->{'http_ver'}.
				$$hin{'whisker'}->{'http_req_trailer'};}
			$S.=	$$hin{'whisker'}->{'http_eol'};}}
 return $S;}





sub sock_close {
	my ($fd,$ssl)=@_;

	eval { close($fd); };
	if(defined $ssl){
	    if($LW::LW_SSL_LIB==1){ # Net::SSLeay
		eval "&Net::SSLeay::free($ssl)";
#		eval "&Net::SSLeay::CTX_free($$Z[3])";
	    } else { # Net::SSL
		eval { close($ssl) }; # is that right for Net::SSL?
	    }
	}

	$$Z[4]=undef;
}



sub sock_valid {
	my ($z,$Hin,$Hout)=@_;

	my $slurp=$$Hin{'whisker'}->{'trailing_slurp'};
	my ($o,$vin)=(undef,'');

	return 0 if(defined $$z[3]); # we don't do SSL yet

	# closed socket sets read flag (and so does waiting data)
 	vec($vin,fileno($$z[0]),1)=1;
 	if(select(($o=$vin),undef,undef,.01)){ # we have data to read
		my ($hold, $res);

		do {
			$res = sysread($$z[0], $hold, 4096);
			$$z[6].=$hold if($slurp==0); # save to queue
			$$Hout{'whisker'}->{'slurped'}.="$hold\0"
				if($slurp==1); # save to hout hash
			# fall through value of 2 doesn't do anything
		} while ($res && select(($o=$vin),undef,undef,.01));

		if(!defined $res || $res==0){ # error or EOF
			return 0;
		}
	}
    
	return 1;
}



sub sock_getline { # read from socket w/ timeouts
        my ($fd,$ssl) = @_;
        my ($str,$t)=('','');

        $t = index($$Z[6],"\n",0);

        while($t < 0){
                return undef if &http_queue_read($fd,$ssl);
                $t=index($$Z[6],"\n",0);
        }

	# MEMLEAK: use following lines; comment out SPEEDUP and LEGACY lines
	# my $r;
	# ($r,$$Z[6])=unpack('A'.($t+1).'A*',$$Z[6]);
	# return $r;

	# SPEEDUP: use following line; comment out MEMLEAK and LEGACY lines
	# return substr($$Z[6],0,$t+1,'');

	# LEGACY: use following lines; comment out MEMLEAK and SPEEDUP lines
	my $r = substr($$Z[6],0,$t+1);
	substr($$Z[6],0,$t+1)='';
	return $r;
}



sub sock_get { # read from socket w/ timeouts
        my ($fd,$ssl,$amount) = @_;
        my ($str,$t)=('','');

	while($amount > length($$Z[6])){
                return undef if &http_queue_read($fd,$ssl);
	}

	# MEMLEAK: use following lines; comment out SPEEDUP and LEGACY lines
	# my $r;
	# ($r,$$Z[6])=unpack('A'.$amount.'A*',$$Z[6]);
	# return $r;

	# SPEEDUP: use following line; comment out MEMLEAK and LEGACY lines
	# return substr($$Z[6],0,$amount,'');

	# LEGACY: use following lines; comment out MEMLEAK and SPEEDUP lines
	my $r = substr($$Z[6],0,$amount);
	substr($$Z[6],0,$amount)='';
	return $r;
}



sub sock_getall {
        my ($fd,$ssl) = @_;
        1 while( !(&http_queue_read($fd,$ssl)) );
        return $$Z[6];
}



sub http_queue_read {
	my ($fd,$ssl)=@_;
	my ($vin, $t)=('','');

	if(defined $ssl){
	    if($LW::LW_SSL_LIB==1){ # Net::SSLeay
		local $SIG{ALRM} = sub { die "timeout\n" };
		local $SIG{PIPE} = sub { die "pipe_error\n" };
		eval {
			eval { alarm($TIMEOUT); };
			$t=Net::SSLeay::read($ssl);
			eval { alarm(0); };
		};
        	if($@ || !defined $t || $t eq ''){
			return 1;}
		$$Z[6].=$t;
	    } else { # Net::SSL
		if(!$ssl->read($t,1024)){ return 1;
		} else { $$Z[6].=$t;}
	    }
	} else {
		vec($vin,fileno($fd),1)=1; # wait only so long to read...
		if(!select($vin,undef,undef,$TIMEOUT)){
			return 1;}
               	if(!sysread($fd,$t,4096)){	return 1; # EOF or error
		} else {			$$Z[6].=$t;}
	}

	return 0;
}



sub http_queue_send { # write to socket
	my ($fd,$ssl)=@_;
	my ($v,$wrote,$err)=('');

	my $len = length($$Z[5]);
	if(defined $ssl){
	    if($LW::LW_SSL_LIB==1){ # Net::SSLeay
		($wrote,$err)=Net::SSLeay::ssl_write_all($ssl,$$Z[5]);
		return 'Could not send entire data queue' if ($wrote!=$len);
		return "SSL_write error: $err" unless $wrote;
	    } else { # Net::SSL
		$ssl->print($$Z[5]);
	    }
	} else {
        	vec($v,fileno($fd),1)=1;
 		if(!select(undef,$v,undef,.01)){ 
			return 'Socket write test failed'; }
		$wrote=syswrite($fd,$$Z[5],length($$Z[5]));
		return "Error sending data queue: $!" if(!defined $wrote);
		return 'Could not send entire data queue' if ($wrote != $len);
	}
	$$Z[5]=''; return undef;
}




sub http_queue {
	$$Z[5].= shift;
}




sub http_fixup_request {
 my $hin=shift;

 return if(!(defined $hin && ref($hin)));

 if($$hin{'whisker'}->{'http_ver'} eq '1.1'){
 	$$hin{'Host'}=$$hin{'whisker'}->{'host'} if(!defined $$hin{'Host'});
	$$hin{'Connection'}='Keep-Alive' if(!defined $$hin{'Connection'});
 }

 if(defined $$hin{'whisker'}->{'data'}){ 
 	if(!defined $$hin{'Content-Length'}){
		$$hin{'Content-Length'}=length($$hin{'whisker'}->{'data'});}
#	if(!defined $$hin{'Content-Encoding'}){
#		$$hin{'Content-Encoding'}='application/x-www-form-urlencoded';}
 }

 if(defined $$hin{'whisker'}->{'proxy_host'}){
	$$hin{'whisker'}->{'include_host_in_uri'}=1;}

}



sub http_reset {
 my $key;

 foreach $key (keys %http_host_cache){
 	# *Z=$http_host_cache{$key};
	sock_close($http_host_cache{$key}->[0],
			$http_host_cache{$key}->[4]);
	my $x=$http_host_cache{$key}->[3];
	if(defined $x && $LW::LW_SSL_LIB==1){
		eval "Net::SSLeay::CTX_free($x)"; }
	delete $http_host_cache{$key};
 }
}



sub ssl_save_info {
	my ($hr,$SSL)=@_;
	my $cert;

	return if($LW::LW_SSL_LIB!=1); # only Net::SSLeay used
	$$hr{'whisker'}->{'ssl_cipher'}=Net::SSLeay::get_cipher($SSL);		

	if( $cert = Net::SSLeay::get_peer_certificate($SSL)){
		$$hr{'whisker'}->{'ssl_cert_subject'} = 
			Net::SSLeay::X509_NAME_oneline(
                    	Net::SSLeay::X509_get_subject_name($cert) );

		$$hr{'whisker'}->{'ssl_cert_issuer'} = 
			Net::SSLeay::X509_NAME_oneline(
                    	Net::SSLeay::X509_get_issuer_name($cert) );
	}
}




{ # start md5 packaged varbs
my (@S,@T,@M);
my $code='';


sub md5 {
	return undef if(!defined $_[0]); # oops, forgot the data
	return MD5->hexhash($_[0]) if(defined $LW::available{'md5'});
	return md5_perl($_[0]);
}



sub md5_perl {
        my $DATA=shift;
        $DATA=md5_pad($DATA);
        &md5_init() if(!defined $M[0]);
        return md5_perl_generated(\$DATA);
}



sub md5_init {
        return if(defined $S[0]);
        for(my $i=1; $i<=64; $i++){ $T[$i-1]=int((2**32)*abs(sin($i))); }
        my @t=(7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21);
        for($i=0; $i<64; $i++){  $S[$i]=$t[(int($i/16)*4)+($i%4)]; }
        @M=(    0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
                1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,
                5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,
                0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9 );
        &md5_generate();

	# check to see if it works correctly
	my $TEST=md5_pad('foobar');
	if( md5_perl_generated(\$TEST) ne 
		'3858f62230ac3c915f300c664312c63f'){
		die('Error: MD5 self-test not successful.');
	}
}



sub md5_pad {
	my $l = length(my $msg=shift() . chr(128));
	$ msg .= "\0" x (($l%64<=56?56:120)-$l%64);
	$l=($l-1)*8;
	$msg .= pack 'VV',$l & 0xffffffff, ($l >> 16 >> 16);
	return $msg;
}



sub md5_generate {
 my $N='abcddabccdabbcda';
 my $M='';
 $M='&0xffffffff' if((1 << 16) << 16); # mask for 64bit systems

 $code=<<EOT;
        sub md5_perl_generated {
	BEGIN { \$^H |= 1; }; # use integer
        my (\$A,\$B,\$C,\$D)=(0x67452301,0xefcdab89,0x98badcfe,0x10325476);
        my (\$a,\$b,\$c,\$d,\$t,\$i);
        my \$dr=shift;
        my \$l=length(\$\$dr);
        for my \$L (0 .. ((\$l/64)-1) ) {
                my \@D = unpack('V16', substr(\$\$dr, \$L*64,64));
                (\$a,\$b,\$c,\$d)=(\$A,\$B,\$C,\$D);
EOT

 for($i=0; $i<16; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
        $code.="\$t=((\$$d^(\$$b\&(\$$c^\$$d)))+\$$a+\$D[$M[$i]]+$T[$i])$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1)))+\$$b)$M;\n";
 }
 for(; $i<32; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
        $code.="\$t=((\$$c^(\$$d\&(\$$b^\$$c)))+\$$a+\$D[$M[$i]]+$T[$i])$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1)))+\$$b)$M;\n";
 }
 for(; $i<48; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
        $code.="\$t=((\$$b^\$$c^\$$d)+\$$a+\$D[$M[$i]]+$T[$i])$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1)))+\$$b)$M;\n";
 }
 for(; $i<64; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
        $code.="\$t=((\$$c^(\$$b|(~\$$d)))+\$$a+\$D[$M[$i]]+$T[$i])$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1)))+\$$b)$M;\n";
 }

 $code.=<<EOT;
                \$A=\$A+\$a\&0xffffffff; \$B=\$B+\$b\&0xffffffff;
                \$C=\$C+\$c\&0xffffffff; \$D=\$D+\$d\&0xffffffff;
        } # for
	return unpack('H*', pack('V4',\$A,\$B,\$C,\$D)); }
EOT
 eval "$code";
}

} # md5 package container


{ # start md4 packaged varbs
my (@S,@T,@M);
my $code='';


sub md4 {
	return undef if(!defined $_[0]); # oops, forgot the data
	md4_perl(@_);
}



sub md4_perl {
        my $DATA=shift;
        $DATA=md5_pad($DATA);
        &md4_init() if(!defined $M[0]);
        return md4_perl_generated(\$DATA);
}



sub md4_init {
 return if(defined $S[0]);
 my @t=(3,7,11,19,3,5,9,13,3,9,11,15);
 for($i=0; $i<48; $i++){  $S[$i]=$t[(int($i/16)*4)+($i%4)]; }
 @M=(	0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
	0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15,
	0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15 );

 my $N='abcddabccdabbcda';
 my $M='';
 $M='&0xffffffff' if((1 << 16) << 16); # mask for 64bit systems

 $code=<<EOT;
        sub md4_perl_generated {
	BEGIN { \$^H |= 1; }; # use integer
        my (\$A,\$B,\$C,\$D)=(0x67452301,0xefcdab89,0x98badcfe,0x10325476);
        my (\$a,\$b,\$c,\$d,\$t,\$i);
        my \$dr=shift;
        my \$l=length(\$\$dr);
        for my \$L (0 .. ((\$l/64)-1) ) {
                my \@D = unpack('V16', substr(\$\$dr, \$L*64,64));
                (\$a,\$b,\$c,\$d)=(\$A,\$B,\$C,\$D);
EOT
 
 for($i=0; $i<16; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
	$code.="\$t=((\$$d^(\$$b\&(\$$c^\$$d)))+\$$a+\$D[$M[$i]])$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1))))$M;\n";
 }
 for(; $i<32; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
 	$code.="\$t=(( (\$$b&\$$c)|(\$$b&\$$d)|(\$$c&\$$d) )+\$$a+\$D[$M[$i]]+0x5a827999)$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1))))$M;\n";
 }
 for(; $i<48; $i++){
        my ($a,$b,$c,$d)=split('',substr($N,($i%4)*4,4));
 	$code.="\$t=(( \$$b^\$$c^\$$d )+\$$a+\$D[$M[$i]]+0x6ed9eba1)$M;\n";
        $code.="\$$a=(((\$t<<$S[$i])|((\$t>>(32-$S[$i]))&((1<<$S[$i])-1))))$M;\n";
 }
 
 $code.=<<EOT;
                \$A=\$A+\$a\&0xffffffff; \$B=\$B+\$b\&0xffffffff;
                \$C=\$C+\$c\&0xffffffff; \$D=\$D+\$d\&0xffffffff;
        } # for
	return unpack('H*', pack('V4',\$A,\$B,\$C,\$D)); }
EOT
 eval "$code";

 my $TEST=md5_pad('foobar');
 if( md4_perl_generated(\$TEST) ne 
	'547aefd231dcbaac398625718336f143'){
	die('Error: MD4 self-test not successful.');
 }
}

} # md4 package container





sub multipart_set {
	my ($hr,$n,$v)=@_;
	return if(!ref($hr)); # error check
	return undef if(!defined $n || $n eq '');
	$$hr{$n}=$v;	
}



sub multipart_get {
	my ($hr,$n)=@_;
	return undef if(!ref($hr)); # error check
	return undef if(!defined $n || $n eq '');
	return $$hr{$n};
}



sub multipart_setfile {
	my ($hr,$n,$path)=(shift,shift,shift);
	my ($fname)=shift;

	return undef if(!ref($hr)); # error check
	return undef if(!defined $n || $n eq '');
	return undef if(!defined $path);
	return undef if(! (-e $path && -f $path) );

	if(!defined $fname){
		$path=~m/[\\\/]([^\\\/]+)$/;
		$fname=$1||"whisker-file";
	}

	$$hr{$n}="\0FILE";
	$$hr{"\0$n"}=[$path,$fname];
	return 1;
}



sub multipart_getfile {
	my ($hr,$n)=@_;

	return undef if(!ref($hr)); # error check
	return undef if(!defined $n || $n eq '');
	return undef if(!defined $$hr{$n} || $$hr{$n} ne "\0FILE");

	return @{$$hr{"\0$n"}};
}



sub multipart_boundary {
	my ($hr,$new)=@_;
	my $ret;

	return undef if(!ref($hr)); # error check

	if(!defined $$hr{"\0BOUNDARY"}){
		# create boundary on the fly
		my $b = uc(LW::utils_randstr(20));
		my $b2 = '-' x 32;
		$$hr{"\0BOUNDARY"}="$b2$b";
	}

	$ret=$$hr{"\0BOUNDARY"};
	if(defined $new){
		$$hr{"\0BOUNDARY"}=$new;
	}

	return $ret;
}



sub multipart_write {
	my ($mp,$hr)=@_;

	return undef if(!ref($mp)); # error check
	return undef if(!ref($hr)); # error check

	if(!defined $$mp{"\0BOUNDARY"}){
		# create boundary on the fly
		my $b = uc(LW::utils_randstr(20));
		my $b2 = '-' x 32;
		$$mp{"\0BOUNDARY"}="$b2$b";
	}

	my $B = $$mp{"\0BOUNDARY"};
	my $EOL = $$hr{whisker}->{http_eol}||"\x0d\x0a";

	my $keycount=0;
	foreach (keys %$mp){
		next if(substr($_,0,1) eq "\0");
		$keycount++;
		if($$mp{$_} eq "\0FILE"){
			my ($path,$name)=LW::multipart_getfile($mp,$_);
			next if(!defined $path);
			$$hr{whisker}->{data}.="$B$EOL";
			$$hr{whisker}->{data}.="Content-Disposition: ".
				"form-data; name=\"$_\"; ";
			$$hr{whisker}->{data}.="filename=\"$name\"$EOL";
			$$hr{whisker}->{data}.="Content-Type: ".
				"application/octet-stream$EOL";
			$$hr{whisker}->{data}.=$EOL;
			next if(!open(IN,"<$path"));
			binmode(IN); # stupid Windows
			while(<IN>){
				$$hr{whisker}->{data}.=$_; }
			close(IN);
			$$hr{whisker}->{data}.=$EOL;  # WARNING: is this right? 
		} else {
			$$hr{whisker}->{data}.="$B$EOL";
			$$hr{whisker}->{data}.="Content-Disposition: ".
				"form-data; name=\"$_\"$EOL";
			$$hr{whisker}->{data}.="$EOL$$mp{$_}$EOL";
		}
	}

	if($keycount){
		$$hr{whisker}->{data}.="$B--$EOL"; # closing boundary
		$$hr{"Content-Length"}=length($$hr{whisker}->{data});
		$$hr{"Content-Type"}="multipart/form-data; boundary=$B";
		return 1;
	} else {
		# multipart hash didn't contain params to upload
		return undef;
	}
}




sub multipart_read {
	my ($mp, $hr, $fp)=@_;

	return undef if(!(defined $mp && ref($mp)));
	return undef if(!(defined $hr && ref($hr)));

	my $ctype = LW::utils_find_lowercase_key($hr,'content-type');
	return undef if(!defined $ctype);
	return undef if($ctype!~m#^multipart/form-data#i);

	return LW::multipart_read_data($mp,
		\${$hr{'whisker'}->{'data'}},undef,$fp);

}



sub multipart_read_data {
	my ($mp, $dr, $bound, $fp)=@_;

	return undef if(!(defined $mp && ref($mp)));
	return undef if(!(defined $dr && ref($dr)));

	# if $bound is undef, then we'll snag what looks to be
	# the first boundry from the data.
	if(!defined $bound){
		if($$dr=~/([-]{5,}[A-Z0-9]+)[\r\n]/i){
			$bound=$1;
		} else {
			# we didn't spot a typical boundary; error
			return undef;
		}
	}

	if(defined $fp && !(-d $fp && -w $fp)){
		$fp=undef; }

	my $line = LW::utils_getline_crlf($dr,0);
	return undef if(!defined $line);
	return undef if( index($line,$bound) != 0);

	my $done=0;
	while(!$done){
		$done=multipart_read_data_part($mp, $dr, $bound, $fp);
	}

	return 1;
}



sub multipart_read_data_part {
	my ($mp, $dr, $bound, $fp)=@_;

	my $dispinfo = LW::utils_getline_crlf($dr);
	return 1 if(!defined $dispinfo);
	return 1 if(length($dispinfo)==0);
	my $lcdisp = lc($dispinfo);

	if(index($lcdisp,'content-disposition: form-data;') != 0){
		return 1; } # bad disposition

	my ($s,$e,$l);

	$s=index($lcdisp,'name="',30);
	$e=index($lcdisp, '"', $s+6);
	return 1 if($s == -1 || $e == -1);	
	my $NAME=substr($dispinfo,$s+6,$e-$s-6);

	$s=index($lcdisp,'filename="',$e);
	my $FILENAME=undef;
	if($s != -1){
		$e=index($lcdisp, '"', $s+10);
		return 1 if($e == -1); # puke; malformed filename
		$FILENAME=substr($dispinfo,$s+10,$e-$s-10);
		$s=rindex($FILENAME,'\\');
		$e=rindex($FILENAME,'/');
		$s=$e if($e>$s);
		$FILENAME=substr($FILENAME,$s+1,length($FILENAME)-$s);
	}

	my $CTYPE = LW::utils_getline_crlf($dr);

	return 1 if(!defined $CTYPE);
	$CTYPE = lc($CTYPE);

	if(length($CTYPE)>0){
		$s=index($CTYPE,'content-type:');
		return 1 if($s!=0); # bad ctype line
		$CTYPE=substr($CTYPE,13,length($CTYPE)-13);
		$CTYPE=~tr/ \t//d;
		my $xx=LW::utils_getline_crlf($dr);
		return 1 if(!defined $xx);
		return 1 if(length($xx)>0);
	} else {
		$CTYPE='application/octet-stream';
	}


	my $VALUE='';
	while( defined ($l=LW::utils_getline_crlf($dr)) ){
		last if(index($l,$bound)==0);	
		$VALUE.=$l;
		$VALUE.="\r\n";
	}

	substr($VALUE,-2,2)='';

	if(!defined $FILENAME){ # read in param
		$$mp{$NAME}=$VALUE;
		return 0;

	} else {  # read in file
		$$mp{$NAME}="\0FILE";
		return 0 if(!defined $fp);

		# TODO: funky content types, like application/x-macbinary
		if($CTYPE ne 'application/octet-stream'){
			return 0; }

		my $rfn = lc(LW::utils_randstr(12));
		my $fullpath = "$fp$rfn";

		$$mp{"\0$NAME"}=[undef,$FILENAME];
		return 0 if(!open(OUT,">$fullpath")); # error opening file
		binmode(OUT); # stupid Windows
		$$mp{"\0$NAME"}=[$fullpath,$FILENAME];
		print OUT $VALUE;
		close(OUT);

		return 0;

	} # if !defined $FILENAME

	return 0; # um, this should never be reached...
}




sub multipart_files_list {
	my ($mp)=shift;
	my @ret;

	return () if(!(defined $mp && ref($mp)));
	while( my ($K, $V)=each(%$mp)){
		push(@ret,$K) if($V eq "\0FILE"); }
	return @ret;
}




sub multipart_params_list {
	my ($mp)=shift;
	my @ret;

	return () if(!(defined $mp && ref($mp)));
	while( my ($K, $V)=each(%$mp)){
		push(@ret,$K) if($V ne "\0FILE" &&
			substr($K,0,1) ne "\0" ); 
	}
	return @ret;
}






sub ntlm_new {
	my ($user,$pass,$domain,$flag)=@_; 
	$flag||=0;
	return undef if(!defined $user);
	$pass||=''; $domain||='';
	my @a=("$user","$pass","$domain",undef,undef);
	my $t;

	if($flag==0){
		$t=substr($pass,0,14);
		$t=~tr/a-z/A-Z/;
		$t.= "\0"x(14-length($t));
		$a[3]=des_E_P16($t); # LanMan password hash
		$a[3].= "\0"x(21-length($a[3]));
	}

	$t=md4(encode_unicode($pass));
	$t=~s/([a-z0-9]{2})/sprintf("%c",hex($1))/ieg;
	$t.="\0"x(21-length($t));
	$a[4]=$t; # NTLM password hash

	&des_cache_reset(); # reset the keys hash
	return \@a;
}



sub ntlm_generate_responses {
	my ($obj,$chal)=@_;
	return (undef,undef) if(!defined $obj || !defined $chal);
	return (undef,undef) if(!ref($obj));
	my $x='';
	$x=des_E_P24($obj->[3], $chal) if(defined $obj->[3]);
	return ($x, des_E_P24($obj->[4], $chal));
}



sub ntlm_decode_challenge {
  return undef if(!defined $_[0]);
  my $chal=shift;
  my @res;

  @res=unpack('Z8VvvVVa8a8a8',substr($chal,0,48));
  push(@res,substr($chal,48));
  unshift(@res,substr($chal,$res[4],$res[2]));
  return @res;
}



sub ntlm_header {
	my ($s,$h,$o)=@_;
	my $l=length($s);
	return pack('vvV',0,0,$o-$h) if($l==0);
	return pack('vvV',$l,$l,$o);
}



sub ntlm_client {
	my ($obj,$p)=@_;
	my $resp="NTLMSSP\0";

	return undef if(!defined $obj || !ref($obj));

	if(defined $p && $p ne ''){ # answer challenge
		$p=~tr/ \t\r\n//d;
		$p=LW::decode_base64($p);
		my @c=ntlm_decode_challenge($p);
		$uu=encode_unicode($obj->[0]); # username
		$resp.=pack('V',3);
		my($hl,$hn)=ntlm_generate_responses($obj,$c[7]); # token
		return undef if(!defined $hl || !defined $hn);
		my $o=64;
		$resp.=ntlm_header($hl,64,$o);			# LM hash
		$resp.=ntlm_header($hn,64,($o+=length($hl)));	# NTLM hash
		$resp.=ntlm_header($c[0],64,($o+=length($hn)));	# domain
		$resp.=ntlm_header($uu,64,($o+=length($c[0])));	# username
		$resp.=ntlm_header($uu,64,($o+=length($uu))); 	# workstation
		$resp.=ntlm_header('',64,($o+=length($uu)));	# session
		$resp.=pack('V',$c[6]);
		$resp.=$hl.$hn.$c[0].$uu.$uu;

	} else { # initiate challenge
		$resp.=pack('VV',1,0x0000b207);
		$resp.=ntlm_header($obj->[0],32,32);
		$resp.=ntlm_header($obj->[2],32,32+length($obj->[0]));
		$resp .= $obj->[0].$obj->[2];
	}

	return encode_base64($resp,'');
}



{ # start of DES local container #######################################
my $generated=0;
my $perm1 = [57, 49, 41, 33, 25, 17, 9,	1, 58, 50, 42, 34, 26, 18,
	     10, 2, 59, 51, 43, 35, 27,	19, 11, 3, 60, 52, 44, 36,
	     63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22,
	     14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4];
my $perm2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10,
	     23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
	     41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48,
	     44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32];
my $perm3 = [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4,
	     62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8,
	     57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
	     61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7];
my $perm4 = [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9,
	     8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
	     16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25,
	     24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1];
my $perm5 = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
	     2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25];
my $perm6 = [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31,
	     38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29,
	     36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27,
	     34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41,  9, 49, 17, 57, 25];
my $sc = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];

sub des_E_P16 {
  my ($p14) = @_;
  my $sp8 = [0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25];
  my $p7 = substr($p14, 0, 7);
  my $p16 = des_smbhash($sp8, $p7);
  $p7 = substr($p14, 7, 7);
  $p16 .= des_smbhash($sp8, $p7);
  return $p16;
}

sub des_E_P24 {
  my ($p21, $c8_str) = @_;
  my @c8 = map {ord($_)} split(//, $c8_str);
  my $p24 = des_smbhash(\@c8, substr($p21, 0, 7));
  $p24 .= des_smbhash(\@c8, substr($p21, 7, 7));
  $p24 .= des_smbhash(\@c8, substr($p21, 14, 7));
}

sub des_permute {
  my ($i,$out, $in, $p, $n) = (0,@_);
  foreach $i (0..($n-1)){
    $out->[$i] = $in->[$p->[$i]-1]; }
}

sub des_lshift {
	my ($c, $d, $count)=@_;
	my (@outc, @outd, $i, $x);
	while($count--){
		push @$c, shift @$c;
		push @$d, shift @$d;
	}
}

my %dohash_cache; # cache for key data; saves some cycles
my %key_cache;	  # another cache for key data
sub des_cache_reset {
	%dohash_cache=();
	%key_cache=();
}

sub des_dohash
{
  my ($out, $in, $key) = @_;
  my ($i, $j, $k, @pk1, @c, @d, @cd,
      @ki, @pd1, @l, @r, @rl);

# if(!defined $dohash_cache{$skey}){
  &des_permute(\@pk1, $key, $perm1, 56);

  for($i=0;$i<28;$i++) {
    $c[$i] = $pk1[$i];
    $d[$i] = $pk1[$i+28];
  }
  for($i=0;$i<16;$i++){
    my @array;
    &des_lshift(\@c,\@d,$sc->[$i]);
    @cd = (@c, @d);
    &des_permute(\@array, \@cd, $perm2, 48);
    $ki[$i] = \@array;
#    $dohash_cache{$skey}->[$i]=\@array; 
  }
# } else {
#	for($i=0;$i<16;$i++){
#		$ki[$i]=$dohash_cache{$skey}->[$i];}
# }

  des_dohash2($in,\@l,\@r,\@ki);

  @rl = (@r, @l);
  &des_permute($out, \@rl, $perm6, 64);
}

sub des_str_to_key{
  my ($str) = @_;
  my ($i,@key,$out);
  unshift(@str,ord($_))while($_=chop($str));
  $key[0] = $str[0]>>1;
  $key[1] = (($str[0]&0x01)<<6) | ($str[1]>>2);
  $key[2] = (($str[1]&0x03)<<5) | ($str[2]>>3);
  $key[3] = (($str[2]&0x07)<<4) | ($str[3]>>4);
  $key[4] = (($str[3]&0x0f)<<3) | ($str[4]>>5);
  $key[5] = (($str[4]&0x1f)<<2) | ($str[5]>>6);
  $key[6] = (($str[5]&0x3f)<<1) | ($str[6]>>7);
  $key[7] = $str[6]&0x7f;
  foreach $i (0..7) {
    $key[$i] = 0xff&($key[$i]<<1); }
  @{$key_cache{$str}}=@key;
  return \@key;
}

sub des_smbhash
{
  # use faster binary helper
  goto &LW::bin::des_smbhash if(defined $LW::available{'lw::bin'});

  my ($in, $key) = @_;
  my $key2;

  &des_generate if(!$generated);
  if(defined $key_cache{$key}){	$key2=$key_cache{$key};
  } else {			$key2=&des_str_to_key($key); }

 my ($i, $div, $mod, @in, @outb, @inb, @keyb, @out);
  foreach $i (0..63){
    $div = int($i/8); $mod = $i%8;
    $inb[$i] = ($in->[$div] & (1<<(7-($mod))))? 1: 0;
    $keyb[$i] = ($key2->[$div] & (1<<(7-($mod))))? 1: 0;
    $outb[$i] = 0;
  }
  &des_dohash(\@outb, \@inb, \@keyb);
  foreach $i (0..7){ $out[$i] = 0; }
  foreach $i (0..63){
    $out[int($i/8)] |= (1<<(7-($i%8))) if ($outb[$i]); }
  my $out = pack("C8", @out);

  return $out;
}


sub des_generate { # really scary dragons here....this code is optimized
		   # for speed, and not readability
 my ($i,$j);
 my $code=<<EOT;
{ my \$sbox = [[
[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7],[0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8],
[4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0],[15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13]
],[
[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10],[3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5],
[0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15],[13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9]
],[
[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8],[13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1],
[13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7],[1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12]
],[
[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15],[13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9],
[10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4],[3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14]
],[
[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9],[14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6],
[4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14],[11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3]
],[
[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11],[10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8],
[9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6],[4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13]
],[
[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1],[13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6],
[1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2],[6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12]
],[
[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7],[1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2],
[7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8],[2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]
]];
EOT

 $code.='sub des_dohash2 { my ($in,$l,$r,$ki)=@_; my (@p,$i,$j,$k,$m,$n);';
 for($i=0;$i<64;$i++){
	$code.="\$p[$i] = \$in->[".($perm3->[$i]-1)."];\n"; }
 for($i=0;$i<32;$i++){
	$code.="\$l->[$i]=\$p[$i]; \$r->[$i]=\$p[".($i+32)."];\n"; }
 $code.='for($i=0;$i<16;$i++){ local (@er,@erk,@b,@cb,@pcb,@r2);';
 for($i=0;$i<48;$i++){
	$code.="\$erk[$i]=\$r->[".($perm4->[$i]-1)."]^(\$ki->[\$i]->[$i]);\n"; }
 for($i=0;$i<8;$i++){
	for($j=0;$j<6;$j++){
		$code.="\$b[$i][$j]=\$erk[".($i*6+$j)."];\n"; }}
 for($i=0;$i<8;$i++){
	$code.="\$m=(\$b[$i][0]<<1)|\$b[$i][5];";
	$code.="\$n=(\$b[$i][1]<<3)|(\$b[$i][2]<<2)|(\$b[$i][3]<<1)|\$b[$i][4];";
	for($j=0;$j<4;$j++){
		$code.="\$b[$i][$j]=(\$sbox->[$i][\$m][\$n]&".(1<<(3-$j)).")?1:0;"; }}
 for($i=0;$i<8;$i++){
	for($j=0;$j<4;$j++){
		$code.="\$cb[".($i*4+$j)."]=\$b[$i][$j];\n"; }}
 for($i=0;$i<32;$i++){
	$code.="\$pcb[$i]=\$cb[".($perm5->[$i]-1)."];\n"; }
 for($i=0;$i<32;$i++){
	$code.="\$r2[$i]=(\$l->[$i])^\$pcb[$i];\n"; }
 for($i=0;$i<32;$i++){
	$code.="\$l->[$i]=\$r->[$i]; \$r->[$i]=\$r2[$i];\n"; }
 $code.='}}}';
 
 eval "$code";
 $generated++;
}

} ##### end of DES container ################################################





# '/', 0, \@dir.split, \@valid, \&func, \%track, \%arrays, \&cfunc
sub utils_recperm {
 my ($p, $pp, $pn, $r, $fr, $dr, $ar, $cr)=(shift,shift,@_);
 $p=~s#/+#/#g; if($pp >= @$pn) { push @$r, $p if &$cr($$dr{$p});
 } else { my $c=$$pn[$pp];
  if($c!~/^\@/){ utils_recperm($p.$c.'/',$pp+1,@_) if(&$fr($p.$c.'/'));
  } else {	$c=~tr/\@//d; if(defined $$ar{$c}){
		foreach $d (@{$$ar{$c}}){
			if(&$fr($p.$d.'/')){
                  utils_recperm($p.$d.'/',$pp+1,@_);}}}}}}




sub utils_array_shuffle { # fisher yates shuffle....w00p!
        my $array=shift; my $i;
        for ($i = @$array; --$i;){
                my $j = int rand ($i+1);
                next if $i==$j;
                @$array[$i,$j]=@$array[$j,$i];
}} # end array_shuffle, from Perl Cookbook (rock!)




sub utils_randstr {
        my $str;
        my $drift=shift||((rand() * 10) % 10)+10; 

	# 'a'..'z' doesn't seem to work on string assignment :(
	my $CHARS = shift || 'abcdefghijklmnopqrstuvwxyz' .
			'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .
			'0123456789';

	my $L = length($CHARS);
        for(1..$drift){
	        $str .= substr($CHARS,((rand() * $L) % $L),1);
	}
        return $str;}



sub utils_get_dir {
        my ($w,$URL)=(0,shift);

	return undef if(!defined $URL);

	substr($URL,$w,length($URL)-$w)='' if( ($w=index($URL,'?')) >= 0);
	substr($URL,$w,length($URL)-$w)='' if( ($w=index($URL,'#')) >= 0);

	if( ($w=rindex($URL,'/')) >= 0){
		$URL = substr($URL,0,$w+1);
	} else {
		if(substr($URL,-1,1) ne '/'){
			$URL.='/';}
	}
        return $URL; 
}




sub utils_port_open {  # this should be platform-safe
        my ($target,$port)=@_;

	return 0 if(!defined $target || !defined $port);

        if(!(socket(S,PF_INET,SOCK_STREAM,0))){ return 0;}
        if(connect(S,sockaddr_in($port,inet_aton($target)))){
                close(S); return 1;
        } else { return 0;}}




sub utils_split_uri {
	my ($uri,$work,$w)=(shift,'',0);
	my ($hr)=shift;
	my @res=(undef,'http',undef,80,undef,undef,undef,undef);

	return undef if(!defined $uri);

	# handle mailto's (people miswrite them as mailto:email@host)
	if(index($uri,'mailto:',0) == 0){
		$res[1]='mailto';
		($res[0]=$uri)=~s/^mailto:[\/]{0,2}//;
		return @res; }

	# handle absolute urls
	if(index($uri,'://',0) > 0 ){ # fastpath check
	 if($uri=~m#^([a-zA-Z]+)://([^/]*)(.*)$#){
		$res[1]=lc($1); 	# protocol
		$res[2]=$2;	    	# host
		$res[0]=$3;		# uri

		if($res[1] eq 'https') 	{ $res[3]=443; }

		while(($w=rindex($res[2],'@')) >=0){
			# SPEEDUP
			# $work=substr($res[2],0,$w,'');
			$work=substr($res[2],0,$w);
			substr($res[2],0,$w)='';
			$res[2]=~tr/@//d;
			if(($w=index($work,':',0)) >=0){
				$res[6]=substr($work,0,$w);
				$res[7]=substr($work,$w+1,length($work)-$2);
			} else {
				$res[6]=$work; }
		}
		
		# check for port in host
		if(($w=index($res[2],':',0)) >=0){
			# SPEEDUP
			# $res[3]=substr($res[2],$w,length($res[2])-$w,'');
			($res[2],$res[3])=split(':',$res[2],2);
			$res[3]=~tr/0-9//cd;
		}

		$res[3]||=80;
		
		$res[0]||='/'; # in case they left off URI or end slash

	 } else { $res[0]=$uri; }  # note that if the URL isn't formed
	} else { $res[0]=$uri; }   # perfectly, we make it all a URI  :/

	# remove fragments
	if(($w=index($res[0],'#',0)) >=0){
		# SPEEDUP
		# $res[5]=substr($res[0],$w+1,length($res[0])-$w,'');
		# $res[0]=~tr/#//d;
		($res[0],$res[5])=split('#',$res[0],2);
	}

	# remove parameters
	if(($w=index($res[0],'?',0)) >=0){
		# SPEEDUP
		# $res[4]=substr($res[0],$w+1,length($res[0])-$w,'');
		# $res[0]=~tr/?//d; 
		($res[0],$res[4])=split(/\?/,$res[0],2);
	}

	if(defined $hr && ref($hr) && ( $res[1] eq 'http' ||
			$res[1] eq 'https') ){
		if(defined $res[0]){
			$$hr{whisker}->{uri}=$res[0]; }
		if($res[1] eq 'https'){
			$$hr{whisker}->{ssl}=1; }
		if(defined $res[2]){
			$$hr{whisker}->{host}=$res[2]; }
		$$hr{whisker}->{port}=$res[3];
		if(defined $res[4]){
			$$hr{whisker}->{uri_param}=$res[4]; }
		if(defined $res[6]){
			$$hr{whisker}->{uri_user}=$res[6]; }
		if(defined $res[7]){
			$$hr{whisker}->{uri_password}=$res[7]; }
	}
		
	return @res;
}


sub utils_lowercase_headers {
	my $href=shift;

	return if(!(defined $href && ref($href)));

	while( my ($key,$val)=each %$href ){
		delete $$href{$key};
		$$href{lc($key)}=$val;
	}
}


sub utils_lowercase_hashkeys {
	goto &LW::utils_lowercase_headers;
}



sub utils_find_lowercase_key {
	my ($href,$key)=(shift,lc(shift));

	return undef if(!(defined $href && ref($href)));
	return undef if(!defined $key);	

	while( my ($k,$v)=each %$href ){
		return $v if(lc($k) eq $key);
	}
	return undef;
}



sub utils_join_uri {
	my @V=@_;
	my $URL="$V[1]://$V[2]";
	$URL .= ":$V[3]" if defined $V[3];
	$URL.=$V[0];
	$URL .= "?$V[4]" if defined $V[4];
	$URL .= "#$V[5]" if defined $V[5];
	return $URL;
}



{ $POS=0;
sub utils_getline {
	my ($dr, $rp)=@_;

	return undef if(!(defined $dr && ref($dr)));
	$POS=$rp if(defined $rp);

	my $where=index($$dr,"\n",$POS);
	return undef if($where==-1);

	my $str=substr($$dr,$POS,$where-$POS);
	$POS=$where+1;

	return $str;
}}



{ $POS=0;
sub utils_getline_crlf {
	my ($dr, $rp)=@_;

	return undef if(!(defined $dr && ref($dr)));
	$POS=$rp if(defined $rp);

	my $tpos=$POS;
	while(1){
		my $where=index($$dr,"\n",$tpos);
		return undef if($where==-1);

		if(substr($$dr,$where-1,1) eq "\r"){
			my $str=substr($$dr,$POS,$where-$POS-1);
			$POS=$where+1;
			return $str;
		} else {
			$tpos=$where+1;
		}
	}
}}



sub utils_absolute_uri {
        my ($uri, $buri, $norm)=@_;
        return undef if(!defined $uri || !defined $buri);
	return $uri if($uri=~m#^[a-zA-Z]+://#);

	if(substr($uri,0,1) eq '/'){
		if($buri=~m#^[a-zA-Z]+://#){
			my @p=utils_split_uri($buri);
			$buri="$p[1]://$p[2]";
			$buri.=":$p[3]" if($p[3]!=80);
			$buri.='/';
		} else { # ah suck, base URI isn't absolute...
			return $uri;
		}
	} else {
		$buri.='/' if($buri=~m#^[a-z]+://[^/]+$#i);
		$buri=~s#/[^/]*$#/#;
	}
	return utils_normalize_uri("$buri$uri") 
		if(defined $norm && $norm > 0);
        return $buri.$uri;
}



sub utils_normalize_uri {
	my ($host,$uri, $win)=('',@_);

	$uri=~tr#\\#/# if(defined $win && $win>0);
	if($uri=~s#^([a-z]+://[^/]+)##i){
		$host=$1; }
	return "$host/" if($uri eq '' || $uri eq '/');

	# fast path check
	$uri=~s#/\.{0,1}/#/#g; # quickie
	return "$host$uri" if(index($uri,'/.')==-1);

	my $f='';
	$f='/' if($uri=~m#/\.{1,2}$#);

	my @final=();
	my @dirs=split('/',$uri);

	foreach (@dirs){
		next if($_ eq '.');
		next if($_ eq '');
		if($_ eq '..'){
			pop(@final);
		} else {
			push(@final,$_);
	}	} 
	$f='' if(scalar @final == 0);
	return "$host/".join('/',@final).$f;
}



sub utils_save_page {
	my ($file, $hr)=@_;
	return 1 if(!ref($hr) || ref($file));
	return 0 if(!defined $$hr{'whisker'} || 
		!defined $$hr{'whisker'}->{'data'});
	open(OUT,">$file") || return 1;
	print OUT $$hr{'whisker'}->{'data'};
	close(OUT);
	return 0;
}



sub utils_getopts {
        my ($str,$ref)=@_;
        my (%O,$l);
        my @left;

        return 1 if($str=~tr/-:a-zA-Z0-9//c);

        while($str=~m/([a-z0-9]:{0,1})/ig){
                $l=$1;
                if($l=~tr/://d){        $O{$l}=1;
                } else {                $O{$l}=0; }
        }

        while($l=shift(@ARGV)){
                push(@left,$l)&&next if(substr($l,0,1) ne '-');
                push(@left,$l)&&next if($l eq '-');
                substr($l,0,1)='';
                if(length($l)!=1){
                        %$ref=();
                        return 1; }
                if($O{$l}==1){
                        my $x=shift(@ARGV);
                        $$ref{$l}=$x;
                } else { $$ref{$l}=1; }
        }

        @ARGV=@left;
        return 0;
}



sub utils_unidecode_uri {
        my $str = $_[0];
        return $str if($str!~tr/!-~//c); # fastpath
        my ($lead,$count,$idx);
        my $out='';
        my $len = length($str);
        my ($ptr,$no,$nu)=(0,0,0);

        while($ptr < $len){
                my $c=substr($str,$ptr,1);
                if( ord($c) >= 0xc0 && ord($c) <= 0xfd){
                        $count=0;
                        $c=ord($c)<<1;
                        while( ($c & 0x80) == 0x80){
                                $c<<=1;
                                last if($count++ ==4);
                        }
                        $c = ($c & 0xff);
                        for( $idx=1; $idx<$count; $idx++){
                                my $o=ord(substr($str,$ptr+$idx,1));
                                $no=1 if($o != 0x80);
                                $nu=1 if($o <0x80 || $o >0xbf);
                        }
                        my $o=ord(substr($str,$ptr+$idx,1));
                        $nu=1 if( $o < 0x80 || $o > 0xbf);
                        if($nu){
                                $out.=substr($str,$ptr++,1);
                        } else {
                                if($no){
                                        $out.="\xff"; # generic replacement char
                                } else {
                                        my $prior=ord(substr($str,$ptr+$count-1,1))<<6;
                                        $out.= pack("C", (ord(substr($str,$ptr+$count,1) )&0x7f)+$prior);
                                }
                                $ptr += $count+1;
                        }
                        $no=$nu=0;
                } else {
                        $out.=$c;
                        $ptr++;
                }
        }
        return $out;
}



sub utils_text_wrapper {
        my ($out,$w,$str,$crlf,$width)=('',0,@_);
	$crlf||="\n";	$width||=76;
        $str.=$crlf if($str!~/$crlf$/);
        return $str if(length($str)<=$width);
        while(length($str)>$width){
                my $w1=rindex($str,' ',$width);
                my $w2=rindex($str,"\t",$width);
                if($w1>$w2){ $w=$w1; } else { $w=$w2; }
                if($w==-1){	$w=$width;
	        } else {	substr($str,$w,1)=''; }
                $out.=substr($str,0,$w,'');
                $out.=$crlf;
        }
        return $out.$str;
}


1;
