use strict;
use warnings;

###############################################################################
# First we need to define the connection and request objects which are then
# used later.
# We use Net::Inspect HTTP handling and derive our connection object from the
# Net::Inspect::L7::HTTP connection object. This will create new HTTPRequest
# objects which are derived from Net::Inspect::L7::HTTP::Request::Simple. These
# give us access to the interna of the request and let us manipulate the
# respone header.
###############################################################################

package Net::IMP::HTTP_AddXFooHeader::HTTPConnection;
use base 'Net::Inspect::L7::HTTP';
use Net::IMP::Debug;
use fields 'imp';

sub new {
    my ($class) = @_;
    $Net::Inspect::Debug::DEBUG = $DEBUG;
    return $class->SUPER::new(
	# whenever we have a new request sub new_request
	# is called on this object
	Net::IMP::HTTP_AddXFooHeader::HTTPRequest->new
    );
}

sub new_connection {
    my Net::IMP::HTTP_AddXFooHeader::HTTPConnection $self = shift;
    my ($meta,$imp) = @_;

    my Net::IMP::HTTP_AddXFooHeader::HTTPConnection $obj =
	$self->SUPER::new_connection($meta);
    $obj->{imp} = $imp;
    return $obj;
}

package Net::IMP::HTTP_AddXFooHeader::HTTPRequest;
use base 'Net::Inspect::L7::HTTP::Request::Simple';
use Net::IMP;

# This is the only real action we do in this module:
# add an X-Foo: bar header at the end of the response header
sub in_response_header {
    my Net::IMP::HTTP_AddXFooHeader::HTTPRequest $self = shift;
    my ($hdr,$time) = @_;

    # add header
    $hdr =~s{(\r?\n)\1}{$1X-Foo: bar$1$1}
	or die "could not add header";

    # return the result with the replaced header
    my Net::IMP::HTTP_AddXFooHeader::HTTPConnection $conn = $self->{conn};
    $conn->{imp}->run_callback([
	IMP_REPLACE,
	1,
	$conn->offset(1),
	$hdr
    ]);
}

# in is called for all other data, e.g. only the response header got a special
# handling. We just accept the data unmodified here.
sub in {
    my Net::IMP::HTTP_AddXFooHeader::HTTPRequest $self = shift;
    my ($dir,$data,$eof,$time) = @_;
    my Net::IMP::HTTP_AddXFooHeader::HTTPConnection $conn = $self->{conn};

    $conn->{imp}->run_callback([
	IMP_PREPASS,
	$dir,
	$conn->offset($dir),
    ]);
}

sub fatal {
    my Net::IMP::HTTP_AddXFooHeader::HTTPRequest $self = shift;
    my ($reason,$dir,$time) = @_;
    $self->{conn}{imp}->run_callback([IMP_DENY,$dir||0,$reason]);
}


###############################################################################
# Net::IMP::HTTP_AddXFooHeader
# this is the analyzer class
###############################################################################

package Net::IMP::HTTP_AddXFooHeader;
use base 'Net::IMP::Base';
use fields (
    # everything is done inside a closures in new_analyzer
    # dataf is the pointer this closure to make it accessible for sub data
    'dataf'
);

use Net::IMP; # import IMP_ constants
use Carp 'croak';
use Scalar::Util 'weaken';

sub USED_RTYPES {
    return (
	# we use PREPASS to make sure, that we get all data to maintain
	# internal state, because we don't support gaps yet
	IMP_PREPASS,  # pass
	IMP_REPLACE,  # replace, insert header
	IMP_DENY,     # on error
    );
}


# Create a new object for the analysis context (e.g. HTTP connection).
# It consists mainly of a closure stored in $self->{dataf} which forwards
# incoming data to the HTTP connection object, which then creates HTTP request
# objects - from there the callback with the analysis results will call back
sub new_analyzer {
    my ($class,%args) = @_;

    my Net::IMP::HTTP_AddXFooHeader $self =
	$class->SUPER::new_analyzer( %args,
	dataf => undef, # set below
    );

    my @buf    = ('',''); # data which are not forwarded yet
    my @offset = (0,0);   # offset of buf[0] in stream
    my @eof    = (0,0);   # flag if eof on direction

    # the connection needs self to call back
    # to avoid circular references we need to give a weak reference
    weaken( my $weak_self = $self );
    my Net::IMP::HTTP_AddXFooHeader::HTTPConnection $analyzer =
	Net::IMP::HTTP_AddXFooHeader::HTTPConnection->new_connection(
	    {},         # we have no meta data
	    $weak_self
    );

    $self->{dataf} = sub {
	my ($dir,$data,$offset) = @_;
	defined $offset and die "gaps($offset) not yet supported";

	my $eof = 0;
	if ( $data eq '' ) {
	    $eof[$dir] = 1;
	    $eof = $eof[0] + $eof[1];
	} else {
	    $buf[$dir] .= $data;
	}

	# analyzer will give the results by throwing callbacks
	my $n = $analyzer->in($dir,$buf[$dir],$eof,0);
	croak("analyzer::in returned undef") if ! defined $n;

	# strip all data the analyzer accepted from buf
	substr( $buf[$dir],0,$n,'' ) if $n;
    };

    return $self;
}

# just call the closure in dataf
sub data {
    my Net::IMP::HTTP_AddXFooHeader $self = shift;
    return $self->{dataf}(@_)
}



1;
__END__

=head1 NAME

Net::IMP::HTTP_AddXFooHeader - adds X-Foo header to HTTP response

=head1 DESCRIPTION

This module analyses HTTP streams and adds an X-Foo header add the end of each
HTTP response header it finds in the stream.
This module is not very useful by its own.
It is only used to show, how these kind of manipulations can be done.

=head1 AUTHOR

Steffen Ullrich <sullr@cpan.org>

=head1 COPYRIGHT

Copyright by Steffen Ullrich.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
