package UAcoder;

use strict;
use vars qw($VERSION @ISA @EXPORT $LINE $FUNC $Q $A);
use CGI;
$Q=new CGI(); $A=new CGI(); my $q=new CGI;
$Q->delete_all(); $A->delete_all();
use autouse 'Digest::MD5' => qw(md5_hex);
use autouse 'Digest::SHA1' => qw(sha1_hex);

require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(&genPASS $Q $A);
$VERSION = '1.01';

##########################

sub new {
	my $class = shift;

	$FUNC = defined $_[0] ? $_[0] : '';
	if( $FUNC=~/SHA1/i ){ $FUNC = sub{ sha1_hex(@_) } }
	else { $FUNC = sub{ md5_hex(@_) } }

	my $self = [];
	bless $self, $class;
}

sub field {
	my $self = shift;
	my $i = @$self>0 ? @$self : 1;
	$self->[$i] = $_[0] if @_;
	$self;
}

sub pass {
	my $self = shift;
	$self->[0] = $_[0];
}

sub line {
	my $self = shift;
	$self->[1] = $_[0];
}

##########################

sub coding {
	my $self = shift;
	my( $pass,@field ) = @{$self};
	my( @fN,$p )=();
	die 'Password Error!' if !defined $pass || length $pass<32;

	unless( @field ){
		for $p ( $Q->param ){ push( @field, map{"$p=$_"} $Q->param($p) ) }
	}
	$Q->delete_all(); my $fN = @field;

	for( 0..$#field ){ $fN[$_] = length $field[$_] }
	$LINE = join('a', $fN,@fN).'a'.unpack('H*', join('', @field));

	my $kADD = &genKEY;
	sumKey( substr( $pass, 0,16 ), $kADD );
	Combi( substr( $pass, 0,16 ) );
	addKey( substr( $pass, 0,16 ), substr( $kADD, 0,15 ) );

	$kADD = &genKEY;
	sumKey( substr( $pass, 16,16 ), $kADD );
	Combi( substr( $pass, 16,16 ) );
	addKey( substr( $pass, 16,16 ), substr( $kADD, 0,15 ) );

	$LINE;
}


sub decoding {

	my $self = shift;
	$LINE = $self->[1];
	my $pQ = defined $q->param('Q') ? $q->param('Q') : '';
	my $pk = defined $q->param('keywords') ? $q->param('keywords') : '';

	if( !$LINE && $pQ ){
		$LINE = $pQ;
		$q->delete('Q')
	} 
	elsif( !$LINE && $pk ){
		$LINE = $pk;
		$q->delete('keywords');
	}
	die 'No string for decoding!' if !defined $LINE || length $LINE<32;

	my( $pass,$fN,@fN,@field )=( $self->[0],0 );
	die 'Password Error!' if !defined $pass || length $pass<32;

	my $kADD = unaddKey( substr( $pass, 16,16 ) );
	unCombi( substr( $pass, 16,16 ) );
	unsumKey( substr( $pass, 16,16 ), $kADD );

	$kADD = unaddKey( substr( $pass, 0,16 ) );
	unCombi( substr( $pass, 0,16 ) );
	unsumKey( substr( $pass, 0,16 ), $kADD );

	$LINE =~ s/^(\d+)a//;
	for( 0..$1-1 ){ $LINE=~s/^(\d+)a//; $fN[$_]=$1 }
	$LINE = pack('H*', $LINE);
	for( 0..$#fN ){
		$field[$_] = substr( $LINE, $fN,$fN[$_] );
		$fN += $fN[$_];
	}

	$A->delete_all();
	for( @field ){
		0=~/(.)/; s/^([^=]+)\=//;
		my $n= defined $1 ? $1 : 'keywords';
		my $v= defined $_ ? $_ : '';
		$A->append(-name=>$n, -values=>$v);
	}

	\@field;
}

##########################

sub Combi {

	my( $key )=@_;
	my( $i,$len )=( 0,length $LINE );

	my $str = "-" x ($len+15);
	for( 0..$len-1 ) {
		my $n = hex substr( $key, $_-$i,1 );
		substr( $str, $_+$n,1 ) = substr( $LINE, $_,1 );
		$i=$_+1 if $_-$i+1>=16;
	}
	
	$LINE = $str;
}

sub unCombi {

	my( $key )=@_;
	my( $i,$len )=( 0,length $LINE );

	my $str = "\x00" x ($len-15);
	for( 0..$len-16 ) {
		my $n = hex substr( $key, $_-$i,1 );
		substr( $str, $_,1 ) = substr( $LINE, $_+$n,1 );
		$i=$_+1 if $_-$i+1>=16;
	}
	
	$LINE = $str;
}


sub addKey {

	my( $pas, $key )=@_; my $i=0;

	for( @{holeSearch( $pas, length($LINE)-15 )} ){
		substr( $LINE, $_,1 ) = substr( $key, $i,1 );
		$i++;
	}
}

sub unaddKey {

	my( $pas )=@_;
	my( $i, $key )=( 0, "\x00" x 15 );

	for( @{holeSearch( $pas, length($LINE)-15 )} ){
		substr( $key, $i,1 ) = substr( $LINE, $_,1 );
		$i++;
	}

	return $key;
}


sub sumKey {

	my( $pas, $kADD )=@_;
	my $kSUM = &{$FUNC}( $pas,$kADD,0 );
	my( $i,$j,$sum,$len )=( 0,0,0,length $kSUM );

	for( 0..length($LINE)-1 ) {

		substr($LINE, $_,1)=Plus( substr($LINE, $_,1), substr($kSUM, $_-$i,1) );

		if( $_-$i+1==$len ){
			$i=$_+1; $j++;
			$kSUM = &{$FUNC}( $pas,$kADD,$j );
		}
	}
}

sub unsumKey {

	my( $pas, $kADD )=@_;
	my $kSUM = &{$FUNC}( $pas,$kADD,0 );
	my( $i,$j,$sum,$len )=( 0,0,0,length $kSUM );

	for( 0..length($LINE)-1 ) {

		substr($LINE, $_,1)=Minus( substr($LINE, $_,1), substr($kSUM, $_-$i,1) );

		if( $_-$i+1==$len ){
			$i=$_+1; $j++;
			$kSUM = &{$FUNC}( $pas,$kADD,$j );
		}
	}
}

##########################

sub genPASS {

	my( $i,$r,$pas,@pas )=();

	for $i ( 0..15 ){
		do{ $r = int rand(16) } until( $pas[$i+$r]!=1 );
		$pas[$i+$r]=1;
		$pas[$i+$r+16]=1;
		$pas = $pas.sprintf('%x', $r);
	}

	@pas=();
	for $i ( 0..15 ){
		do{ $r = int rand(16) } until( $pas[$i+$r]!=1 );
		$pas[$i+$r]=1;
		$pas[$i+$r+16]=1;
		$pas = $pas.sprintf('%x', $r);
	}
	$pas;
}


sub genKEY {

	my( $KEY )=();
	for( 0..14 ){ $KEY = $KEY.sprintf('%x', int rand 16) }
	$KEY;
}

##########################

sub Plus {

	my( $A,$B )=( hex $_[0],hex $_[1] );
	substr( sprintf('%x', $A+$B), -1,1 );
}


sub Minus{

	my( $A,$B,$sum )=( hex $_[0],hex $_[1] );
	$sum = $A>=$B	?	$A-$B	:	16-$B+$A;
	sprintf('%x', $sum);
}


sub holeSearch {	#holes search

	my( $pas,$len )=@_;
	my( @hole,%open,%close )=();

	my $min = ( $len<31 ? 16 : $len-16 );
	my $max = ( $len<16 ? $len-1 : 15 );

	for( 0..$max, $min..$len-1 ){

		my $i = sprintf('%.0f', ($_/16 - (int($_/16)))*16); 
		my $n = hex(substr( $pas, $i,1 )) + $_;
		$close{$n} = 1;
	}
	for ( 0..15, $len..$len+14 ){ $open{$_}=1 unless $close{$_} }
	for( sort {$a<=>$b} keys %open ){ push( @hole,$_ ) }

	return \@hole;
}

1;


=head1 NAME

UAcoder - Coding and decoding of the information for transfer and reception under the protocol HTTP

=head1 SYNOPSIS

## Password generation
use UAcoder;
my $pass = genPASS; #Password for coding and decoding of the information

## Coding
my $line = 'Test line for check:)';	#The information for coding and transfer on HTTP

use UAcoder;
my $ctx = new UAcoder();
$ctx->field($line);
$ctx->pass($pass); # $pass - Password
my $L = $ctx->coding; 	#Code for transfer on HTTP

## Decoding
use UAcoder;
my $ctx = new UAcoder();
$ctx->line($L); # $L - Code for transfer on HTTP
$ctx->pass($pass); # $pass - Password
my $D = $ctx->decoding;
my $field = $D->[0];

=head1 DESCRIPTION

The module codes and decodes given for transfer under the protocol HTTP.
With the help of the Module UAcoder it is possible to transfer and to accept the symbolical data of the protocol ANSI.

A Simple Example. It is necessary to code and to transfer a line:
$line = 'Test line for check:)';

Task of a Password and Coding of the data.
$pass = genPASS;

We connect the module:
use UAcoder;
my $ctx = new UAcoder();
$ctx->field($line);
$ctx->pass($pass);

We code the data:
my $L = $ctx->coding;

We receive a line of such kind:
d571a8e236c4f9b06663925767e0c4542626370666820f65106b923316f95a65 ($L)

We transfer a line on HTTP.
We receive a line:
use UAcoder;
my $ctx = new UAcoder();
$ctx->line($L); # $L - Code for transfer on HTTP
$ctx->pass($pass);
my $D = $ctx->decoding;
my $field = $D->[0];

The lines should be equal: $field eq $line

=head1 AUTHOR

V. V. Gorhilin, arhitrade@mail.ru
http://uniq-ip.biz/gorchilin/UAcoder/uacoder.htm
2004

=cut