The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

# ----------------------------------------------------------------------------
# request object derived from HTTP::Request::InspectChain
# handles saving of request data into files or writing of request
# information to stdout
# ----------------------------------------------------------------------------

use strict;
use warnings;

package privHTTPRequest;
use base 'Net::Inspect::L7::HTTP::Request::InspectChain';
use fields qw(writer outdir fcache infosub flowid flowreqid chunked stat);
use Net::Inspect::Debug;

sub new {
    my ($class,%args) = @_;
    my $self = $class->SUPER::new;
    $self->{infosub} = $args{info}   || ref($class) && $class->{infosub};
    $self->{writer}  = $args{writer} || ref($class) && $class->{writer};
    $self->{outdir}  = $args{dir}    || ref($class) && $class->{outdir};
    $self->{fcache}  = $args{fcache} || ref($class) && $class->{fcache};
    die "no fcache given" if $self->{outdir} and ! $self->{fcache};
    return $self;
}
sub new_request {
    my ($self,$meta,$conn) = @_;
    my $obj = $self->SUPER::new_request($meta,$conn);
    $obj->{flowid} = $conn->{connid};
    $obj->{flowreqid}  = $meta->{reqid},
    $obj->{stat} = {};
    return $obj;
}

sub in_request_header {
    my ($self,$hdr,$time) = @_;
    $self->{stat}{rqhdr} += length($hdr);

    my $write;
    if ( $self->{outdir} ) {
	my @fh;
	for my $dir (0,1) {
	    my $fname = sprintf("%s/%05d.%04d.%02d-%s.%s-%s.%s-%d",
		$self->{outdir},
		$self->{flowid},
		$self->{flowreqid},
		$self->{meta}{time},
		$self->{meta}{saddr}, $self->{meta}{sport},
		$self->{meta}{daddr}, $self->{meta}{dport},
		$dir
	    );
	    $fh[$dir] = $self->{fcache}->create($fname) 
		or die "cannot create $fname: $!";
	}
	$write = sub {
	    my ($dir,$data) = @_;
	    $fh[$dir]->print($data);
	};
    } elsif ( $self->{writer}) {
	my $obj = $self->{writer}->new_connection($self->{meta});
	$write = sub {
	    my ($dir,$data) = @_;
	    $obj->{writer}->write($dir,$data);
	}
    }

    if ($write) {
	my $wfh = sub {
	    my ($dir,$hdr) = @_;
	    $write->($dir,$hdr);
	    return 0;
	};
	my $wfb = sub {
	    my ($dir,$dr) = @_;
	    $write->($dir,$$dr);
	    my $rv = $$dr;
	    $$dr = '';
	    return $rv;
	};

	$self->add_hooks({
	    request_header  => sub { $wfh->(0,${$_[1]}) },
	    response_header => sub { $wfh->(1,${$_[1]}) },
	    request_body    => sub { $wfb->(0,$_[1]) },
	    response_body   => sub { $wfb->(1,$_[1]) },
	    chunk_header    => sub { $wfh->($_[1],${$_[2]}) },
	    chunk_trailer   => sub { $wfh->($_[1],${$_[2]}) },
	});
    }

    return $self->SUPER::in_request_header($hdr,$time);
}

sub in_request_body {
    my ($self,$data,$eof,$time) = @_;
    $self->{stat}{rqbody} += length($data);
    return $self->SUPER::in_request_body($data,$eof,$time);
}

sub in_response_header {
    my ($self,$hdr,$time) = @_;
    $self->{stat}{rphdr} += length($hdr);
    return $self->SUPER::in_response_header($hdr,$time);
}

sub in_response_body {
    my ($self,$data,$eof,$time) = @_;
    $self->{stat}{rpbody} += length($data);
    my $rv = $self->SUPER::in_response_body($data,$eof,$time);
    if ($eof) {
	$self->{stat}{duration} = $time - $self->{meta}{time};
	$self->_info;
    }
    return $rv;
}

sub in_chunk_header {
    my ($self,$data,$time) = @_;
    $self->{stat}{rpbody} += length($data);
    $self->{stat}{chunks} ++;
    return $self->SUPER::in_chunk_header($data,$time);
}

sub in_chunk_trailer {
    my ($self,$data,$time) = @_;
    $self->{stat}{rpbody} += length($data);
    return $self->SUPER::in_chunk_trailer($data,$time);
}

sub in_data {
    my ($self,$dir,$data,$eof,$time) = @_;
    $self->{stat}{ $dir ? 'rpbody':'rqbody' } += length($data);
    my $rv = $self->SUPER::in_data($dir,$data,$eof,$time);
    if ($eof>1) {
	# both sides closed
	$self->{stat}{duration} = $time - $self->{meta}{time};
	$self->_info;
    }
    return $rv;
}

sub _info {
    my $self = shift;
    my $infosub = $self->{infosub} or return;

    # end of data
    my $req = $self->request_header;
    my $uri = $req->uri;
    if ( $uri !~m{^\w+://} ) {
	my $host = $req->header('Host') || $self->{meta}{daddr};
	$uri = "http://$host$uri";
    }
    my $resp = $self->response_header;
    my @info;
    push @info, "ct:$_"     for grep { $_ } $resp->header('content-type');
    push @info, "refer:$_"  for grep { $_ } $req->header('referer');
    push @info, "origin:$_" for grep { $_ } $req->header('origin');
    push @info, $_ for sort keys %{$self->{info}};
    push @info, "ttl:$self->{meta}{ttl}";
    $infosub->( 
	sprintf("%7.2f %05d.%04d %s %s -> %d %s",
	    $self->{meta}{time},
	    $self->{flowid},
	    $self->{flowreqid},
	    $req->method, $uri,
	    $resp->code,
	    join(' ', @info),
	),
	{
	    meta   => $self->{meta},
	    flowid => $self->{flowid},
	    reqid  => $self->{flowreqid},
	    method => $req->method,
	    uri    => $uri,
	    req    => $req,
	    resp   => $resp,
	    info   => \@info,
	    stat   => $self->{stat},
	}
    );
}


sub fatal {
    my ($self,$reason) = @_;
    trace( sprintf("%05d.%04d %s",$self->{flowid},$self->{flowreqid},$reason));
}


1;