René Nyffenegger's collection of things on the web
René Nyffenegger on Oracle - Most wanted - Feedback -
 

A Proxy in Perl

This is a proxy that I have written to demonstrate the http protocol. If you use it on Windows, make sure you disable the annoying beep
I wanted this proxy to be small and understandable so as to be able to tweak it. So, there is no logging functionality for example or other bells and whistels.
http_proxy.pl
use strict;
use URI;
use IO::Socket;

my $showOpenedSockets=1;

my $server = IO::Socket::INET->new (
   LocalPort => 8080,
   Type      => SOCK_STREAM,
   Reuse     => 1,
   Listen    => 10);


binmode $server;

while (my $browser = $server->accept()) {
  print "\n\n--------------------------------------------\n";

  binmode $browser;

  my $method              ="";
  my $content_length      = 0;
  my $content             = 0;
  my $accu_content_length = 0;
  my $host;
  my $hostAddr;
  my $httpVer;

  while (my $browser_line = <$browser>) {
    unless ($method) {
      ($method, $hostAddr, $httpVer) = $browser_line =~ /^(\w+) +(\S+) +(\S+)/;

      my $uri = URI->new($hostAddr);

      $host = IO::Socket::INET->new (
        PeerAddr=> $uri->host,
        PeerPort=> $uri->port );

        die "couldn't open $hostAddr" unless $host;

      if ($showOpenedSockets) {
        print "Opened ".$uri->host." , port ".$uri->port."\n";
      }

      binmode $host;

      print $host "$method ".$uri->path_query." $httpVer\n";
      print "$method ".$uri->path_query." $httpVer\n";
      next;
    }

    $content_length = $1 if      $browser_line=~/Content-length: +(\d+)/i;
    $accu_content_length+=length $browser_line;

    print $browser_line;

    print $host $browser_line;

    last if $browser_line =~ /^\s*$/ and $method ne 'POST';
    if ($browser_line =~ /^\s*$/ and $method eq "POST") {
      $content = 1;
      last unless $content_length;
      next;
    }
    if ($content) {
      $accu_content_length+=length $browser_line;
      last if $accu_content_length >= $content_length;
    }
  }
  print "\n\n....................................\n";
  
  $content_length      = 0;
  $content             = 0;
  $accu_content_length = 0;
  while (my $host_line = <$host>) {
    print $host_line;
    print $browser $host_line;
    $content_length = $1 if $host_line=~/Content-length: +(\d+)/i;
    if ($host_line =~ m/^\s*$/ and not $content) {
      $content = 1;
      #last unless $content_length;
      next;
    }
    if ($content) {
      if ($content_length) {
        $accu_content_length+=length $host_line;
        #print "\nContent Length: $content_length, accu: $accu_content_length\n";
        last if $accu_content_length >= $content_length;
      }
    }
  }
  $browser-> close;
  $host   -> close;
}

The proxy as a package

The following package can be used for a generic proxy, that is, it just forwards what it receives without interpreting it. It must be noted, that it is not multithreaded, and only forwards one connection.
proxy.pm
package proxy;

use strict;
use warnings;

use IO::Socket;
use IO::Select;
use IO::Handle;

sub new {
  my $obj  = shift;
  my $self = {};

  my $proxy_port           = shift;

  $self->{server_host}     = shift;
  $self->{server_port}     = shift;

  $self->{client_callback} = shift;
  $self->{server_callback} = shift;

  $self->{proxy} = IO::Socket::INET->new (
   LocalPort => $proxy_port,
   Type      => SOCK_STREAM,
   Reuse     => 1,
   Listen    => 10);

  binmode $self->{proxy};

  return bless $self, $obj;
}

sub accept {
  my $self = shift;

  my $client = $self-> {proxy} -> accept();
  binmode $client;

  my $server = IO::Socket::INET->new (
           PeerAddr => $self->{server_host},
           PeerPort => $self->{server_port});

  binmode $server;

  $client->blocking(0);
  $server->blocking(0);

  my $select = new IO::Select;
  $select->add($server);
  $select->add($client);

  autoflush $server;
  autoflush $client;

  while (my @ready = $select->can_read()) {
    foreach my $fd (@ready) {

      my $buf="";

      if ($fd == $client) {
        sysread($client, $buf, 1024);
        &{$self->{client_callback}}($buf);
        print $server $buf;
      }

      if ($fd == $server) {
        sysread($server, $buf, 1024);
        &{$self->{server_callback}}($buf);
        print $client $buf;
      }
    }
  }
}

1;
This package is then used like so:
use proxy;

use strict;
use warnings;

sub from_client {
  print "\n\nFrom client:\n";
  print shift;
}

sub from_server {
  print "\n\nFrom server:\n";
  print shift;
}

my $proxy = new proxy(
   7777, 
  'www.adp-gmbh.ch', 
   80,
 \&from_client,
 \&from_server
  );

$proxy->accept();

Links

This package is used in On a breakable Oracle to demonstrate a critical security bug.

Thanks

Thanks to Paul Harman who notified me of a bug in proxy.pl.