##############################################################################
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
# Jabber
# Copyright (C) 1998-1999 The Jabber Team http://jabber.org/
#
##############################################################################
package HTTP::ProxyAutoConfig;
=head1 NAME
HTTP::ProxyAutoConfig - provides a unifed way to get the proxy information
=head1 SYNOPSIS
HTTP::ProxyAutoConfig is a module that allows perl scripts that need
access to proxy servers to utilize the standard proxy settings provided
by an IT department.
=head1 DESCRIPTION
This module provides a consistent method for finding the proxy server
needed to talk to for a given URL. It can handle parsing the http_proxy,
https_proxy, ftp_proxy, and http_auto_proxy variables to determine
what it is you want it to do. If you set the http_auto_proxy variable
it overrides the others and fetches the PAC file from there and uses
those settings.
Access to the proxy information is provided in a single function call
to FindProxyForURL(url,host). A string is returned that tells you what
to do, either "DIRECT", "PROXY host:port", or "SOCKS host:port".
The Proxy Auto Config format and rules are defined at Netscape:
http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/proxy-live.html
The file basically works by defining a JavaScript function called
FindProxyForURL. This module fetches that file and converts the
JavaScript function into a Perl function and then defines the Perl
function with that converted data.
=head1 METHODS
new(url) - creates the FindProxyForURL function and the object.
The url argument is optional, and points to the auto-proxy
file provided on your network. If you do not specify a
url, then it will check the http_auto_proxy variable,
followed by the http_proxy, https_proxy, and ftp_proxy
variables.
my $pac = new HTTP::ProxyAutoConfig("http://foo.bar/auto-proxy.pac");
my $pac = new HTTP::ProxyAutoConfig();
FindProxyForURL(url,host) - takes the url, and the host (minus
port) from the URL, and determines the
action you should take to contact that
host. It returns one of three things:
DIRECT - connect directly to them
PROXY host:port - connect via the proxy
SOCKS host:port - connect via SOCKS
FindProxy(url) - calls the FindProxyForURL function and passes it the
correct options. This is just a wrapper.
Reload() - allows you to fetch the PAC again and regenerate the
FindProxyForURL function based on anything you might
have changed in the environment.
=head1 AUTHOR
By Ryan Eatmon in May of 2001
=head1 COPYRIGHT
This module is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
use strict;
use Carp;
use Sys::Hostname;
use IO::Socket;
use POSIX;
use vars qw($VERSION );
$VERSION = "0.1";
sub new {
my $proto = shift;
my $self = { };
bless($self,$proto);
$self->{URL} = shift if ($#_ > -1);
$self->Reload();
return $self;
}
##############################################################################
#
# FindProxy - wrapper for FindProxyForURL function so that you don't have to
# figure out the host.
#
##############################################################################
sub FindProxy {
my $self = shift;
my ($url) = @_;
my ($host) = ($url =~ /^(\S*\:?\/?\/?[^\/:]+)/);
$host =~ s/^[^\:]+\:\/\///;
foreach my $proxy (split(/\s*\;\s*/,$self->FindProxyForURL($url,$host))) {
return $proxy if ($proxy eq "DIRECT");
my ($host,$port) = ($proxy =~ /^PROXY\s*(\S+):(\d+)$/);
return $proxy if (new IO::Socket::INET(PeerAddr=>$host,
PeerPort=>$port,
Proto=>"tcp"));
}
return undef;
}
##############################################################################
#
# Reload - grok the environment variables and define the FindProxyForURL
# function.
#
##############################################################################
sub Reload {
my $self = shift;
my $url = (exists($self->{URL}) ? $self->{URL} : $ENV{"http_auto_proxy"});
if (defined($url) && ($url ne "")) {
my ($host,$port,$path) = ($url =~ /^http:\/\/([^\/:]+):?(\d*)\/?(.*)$/);
$port = 80 if ($port eq "");
my $sock = new IO::Socket::INET(PeerAddr=>$host,
PeerPort=>$port,
Proto=>"tcp");
die("Cannot create normal socket: $!") unless defined($sock);
my $send = "GET /$path HTTP/1.1\r\nCache-Control: no-cache\r\nHost: $host:$port\r\n\r\n";
$sock->syswrite($send,length($send),0);
my $buff;
my $status = 1;
my $function = "";
while($status > 0) {
$status = $sock->sysread($buff,POSIX::BUFSIZ);
$function .= $buff;
}
my $chunked = ($function =~ /chunked/);
$function =~ s/^.+?\r?\n\r?\n//s;
if ($chunked == 1) {
$function =~ s/\n\r\n\S+\s*\r\n/\n/g;
$function =~ s/^\S+\s*\r\n//;
}
$function = $self->JavaScript2Perl($function);
eval($function);
} else {
my $http_host;
my $http_port;
my $function = "sub FindProxyForURL { my (\$self,\$url,\$host) = \@_; ";
$function .= "if (isResolvable(\$host)) { return \"DIRECT\"; } ";
if (exists($ENV{http_proxy})) {
($http_host,$http_port) = ($ENV{"http_proxy"} =~ /^(\S+)\:(\d+)$/);
$http_host =~ s/^http\:\/\///;
$function .= "if (shExpMatch(\$url,\"http://*\")) { return \"PROXY $http_host\:$http_port\"; } ";
}
if (exists($ENV{https_proxy})) {
my($host,$port) = ($ENV{"https_proxy"} =~ /^(\S+)\:(\d+)$/);
$host =~ s/^https?\:\/\///;
$function .= "if (shExpMatch(\$url,\"https://*\")) { return \"PROXY $host\:$port\"; } ";
}
if (exists($ENV{ftp_proxy})) {
my($host,$port) = ($ENV{"ftp_proxy"} =~ /^(\S+)\:(\d+)$/);
$host =~ s/^ftp\:\/\///;
$function .= "if (shExpMatch(\$url,\"ftp://*\")) { return \"PROXY $host\:$port\"; } ";
}
if (defined($http_host) && defined($http_port)) {
$function .= " return \"PROXY $http_host\:$http_port\"; }";
} else {
$function .= " return \"DIRECT\"; }";
}
eval($function);
}
}
##############################################################################
#
# JavaScript2Perl - function to convert JavaScript code into Perl code.
#
##############################################################################
sub JavaScript2Perl {
my $self = shift;
my ($function) = @_;
my $quoted = 0;
my $blockComment = 0;
my $lineComment = 0;
my $newFunction = "";
my %vars;
my $variable;
foreach my $piece (split(/(\s)/,$function)) {
foreach my $subpiece (split(/([\"\'\=])/,$piece)) {
next if ($subpiece eq "");
if ($subpiece eq "=") {
$vars{$variable} = 1;
}
$variable = $subpiece unless ($subpiece eq " ");
$subpiece = "." if (($quoted == 0) && ($subpiece eq "+"));
$lineComment = 0 if ($subpiece eq "\n");
$quoted ^= 1 if (($blockComment == 0) &&
($lineComment == 0) &&
($subpiece =~ /(\"|\')/));
if (($quoted == 0) && ($subpiece =~ /\/\*/)) {
$blockComment = 1;
} elsif (($quoted == 0) && ($subpiece =~ /\/\//)) {
$lineComment = 1;
} elsif (($blockComment == 1) && ($subpiece =~ /\*\//)) {
$blockComment = 0;
} else {
$newFunction .= $subpiece
unless (($blockComment == 1) || ($lineComment == 1));
}
}
}
$newFunction =~ s/^\s*function\s*(\S+)\s*\(\s*([^\,]+)\s*\,\s*([^\)]+)\s*\)\s*\{/sub $1 \{\n my \(\$self,$2,$3\) = \@_\;\n my(\$stub);\n/;
$vars{$2} = 2;
$vars{$3} = 2;
$quoted = 0;
my $finalFunction = "";
foreach my $piece (split(/(\s)/,$newFunction)) {
if ($piece eq "my(\$stub);") {
$piece = "my(\$stub";
foreach my $var (keys(%vars)) {
next if ($vars{$var} == 2);
$piece .= ",\$".$var;
}
$piece .= ");";
}
foreach my $subpiece (split(/([\"\'\=\,\+\)\(])/,$piece)) {
next if ($subpiece eq "");
$quoted ^= 1 if (($blockComment == 0) &&
($lineComment == 0) &&
($subpiece =~ /(\"|\')/));
$subpiece = "\$".$subpiece
if (($quoted == 0) && exists($vars{$subpiece}));
$finalFunction .= $subpiece;
}
}
return $finalFunction;
}
##############################################################################
#
# isPlainHostName - PAC command that tells if this is a plain host name
# (no dots)
#
##############################################################################
sub isPlainHostName {
my ($host) = @_;
return (($host =~ /\./) ? 0 : 1);
}
##############################################################################
#
# dnsDomainIs - PAC command to tell if the host is in the domain.
#
##############################################################################
sub dnsDomainIs {
my ($host,$domain) = @_;
$domain =~ s/\./\\\./;
return (($host =~ /$domain$/) ? 1 : 0);
}
##############################################################################
#
# localHostOrDomainIs - PAC command to tell if the host matches, or if it is
# unqaulifed and in the domain.
#
##############################################################################
sub localHostOrDomainIs {
my ($host,$hostdom) = @_;
return 1 if ($host eq $hostdom);
return 0 if ($host =~ /\./);
return 1 if ($hostdom =~ /^$host/);
}
##############################################################################
#
# isResolvable - PAC command to see if the host can be resolved via DNS.
#
##############################################################################
sub isResolvable {
my ($host) = @_;
return (defined(gethostbyname($host)) ? 1 : 0);
}
##############################################################################
#
# isInNet - PAC command to see if the IP address is in this network based on
# the mask and pattern.
#
##############################################################################
sub isInNet {
my ($host,$pattern,$mask) = @_;
my $addr = dnsResolve($host);
return unless defined($addr);
my @addr = split(/\./,$addr);
my @mask = split(/\./,$mask);
my @pattern;
foreach my $count (0..3) {
my $bitAddr = dec2bin($addr[$count]);
my $bitMask = dec2bin($mask[$count]);
$pattern[$count] = bin2dec($bitAddr & $bitMask),"\n";
}
my $hostPattern = join(".",@pattern);
return (($pattern eq $hostPattern) ? 1 : 0);
}
##############################################################################
#
# dec2bin - decimal to binary conversion
#
##############################################################################
sub dec2bin {
my $str = unpack("B32", pack("N", shift));
return $str;
}
##############################################################################
#
# bin2dec - binary to decimal conversion
#
##############################################################################
sub bin2dec {
return unpack("N", pack("B32", substr("0" x 32 . shift, -32)));
}
##############################################################################
#
# dnsResolve - PAC command to get the IP from the host name.
#
##############################################################################
sub dnsResolve {
my ($host) = @_;
return unless isResolvable($host);
return inet_ntoa(inet_aton($host));
}
##############################################################################
#
# myIpAddress - PAC command to get your IP.
#
##############################################################################
sub myIpAddress {
return inet_ntoa(inet_aton(hostname()));
}
##############################################################################
#
# dnsDomainLevels - PAC command to tell how many domain levels there are in
# the host name (number of dots).
#
##############################################################################
sub dnsDomainLevels {
my ($host) = @_;
my $count = 0;
foreach my $piece (split(/(\.)/,$host)) {
$count++ if ($piece eq ".");
}
return $count;
}
##############################################################################
#
# shExpMatch - PAC command to see if a URL/path matches the shell expression.
# Shell expressions are like */foo/* or http://*.
#
##############################################################################
sub shExpMatch {
my ($str,$shellExp) = @_;
$shellExp =~ s/\//\\\//g;
$shellExp =~ s/\*/\.\*/g;
return (($str =~ /$shellExp/) ? 1 : 0);
}
##############################################################################
#
# weekDayRange - PAC command to see if the current weekday falls within a
# range.
#
##############################################################################
sub weekDayRange {
my $wd1 = shift;
my $wd2 = "";
$wd2 = shift if ($_[0] ne "GMT");
my $gmt = "";
$gmt = shift if ($_[0] eq "GMT");
my %wd = ( SUN=>0,MON=>1,TUE=>2,WED=>3,THU=>4,FRI=>5,SAT=>6);
my $dow = (($gmt eq "GMT") ? (gmtime)[6] : (localtime)[6]);
if ($wd2 eq "") {
return (($dow eq $wd{$wd1}) ? 1 : 0);
} else {
my @range;
if ($wd{$wd1} < $wd{$wd2}) {
@range = ($wd{$wd1}..$wd{$wd2});
} else {
@range = ($wd{$wd1}..6,0..$wd{$wd2});
}
foreach my $tdow (@range) {
return 1 if ($dow eq $tdow);
}
return 0;
}
return 0;
}
##############################################################################
#
# dateRange - PAC command to see if the current date falls within a range.
#
##############################################################################
sub dateRange {
my %mon = ( JAN=>0,FEB=>1,MAR=>2,APR=>3,MAY=>4,JUN=>5,JUL=>6,AUG=>7,SEP=>8,OCT=>9,NOV=>10,DEC=>11);
my %args;
my $dayCount = 1;
my $monCount = 1;
my $yearCount = 1;
while ($#_ > -1) {
if ($_[0] eq "GMT") {
$args{gmt} = shift;
} elsif (exists($mon{$_[0]})) {
my $month = shift;
$args{"mon$monCount"} = $mon{$month};
$monCount++;
} elsif ($_[0] > 31) {
$args{"year$yearCount"} = shift;
$yearCount++;
} else {
$args{"day$dayCount"} = shift;
$dayCount++;
}
}
my $mday = (exists($args{gmt}) ? (gmtime)[3] : (localtime)[3]);
my $mon = (exists($args{gmt}) ? (gmtime)[4] : (localtime)[4]);
my $year = 1900+(exists($args{gmt}) ? (gmtime)[5] : (localtime)[5]);
if (exists($args{day1}) && exists($args{mon1}) && exists($args{year1}) &&
exists($args{day2}) && exists($args{mon2}) && exists($args{year2})) {
if (($args{year1} < $year) && ($args{year2} > $year)) {
return 1;
} elsif (($args{year1} == $year) && ($args{mon1} <= $mon)) {
return 1;
} elsif (($args{year2} == $year) && ($args{mon2} >= $mon)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{mon1}) && exists($args{year1}) &&
exists($args{mon2}) && exists($args{year2})) {
if (($args{year1} < $year) && ($args{year2} > $year)) {
return 1;
} elsif (($args{year1} == $year) && ($args{mon1} < $mon)) {
return 1;
} elsif (($args{year2} == $year) && ($args{mon2} > $mon)) {
return 1;
} elsif (($args{year1} == $year) && ($args{mon1} == $mon) &&
($args{day1} <= $mday)) {
return 1;
} elsif (($args{year2} == $year) && ($args{mon2} == $mon) &&
($args{day2} >= $mday)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{day1}) && exists($args{mon1}) &&
exists($args{day2}) && exists($args{mon2})) {
if (($args{mon1} < $mon) && ($args{mon2} > $mon)) {
return 1;
} elsif (($args{mon1} == $mon) && ($args{day1} <= $mday)) {
return 1;
} elsif (($args{mon2} == $mon) && ($args{day2} >= $mday)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{year1}) && exists($args{year2})) {
foreach my $tyear ($args{year1}..$args{year2}) {
return 1 if ($tyear == $year);
}
return 0;
} elsif (exists($args{mon1}) && exists($args{mon2})) {
foreach my $tmon ($args{mon1}..$args{mon2}) {
return 1 if ($tmon == $mon);
}
return 0;
} elsif (exists($args{day1}) && exists($args{day2})) {
foreach my $tmday ($args{day1}..$args{day2}) {
return 1 if ($tmday == $mday);
}
return 0;
} elsif (exists($args{year1})) {
return (($args{year1} == $year) ? 1 : 0);
} elsif (exists($args{mon1})) {
return (($args{mon1} == $mon) ? 1 : 0);
} elsif (exists($args{day1})) {
return (($args{day1} == $mday) ? 1 : 0);
} else {
return 0;
}
return 0;
}
##############################################################################
#
# timeRange - PAC command to see if the current time falls within a range.
#
##############################################################################
sub timeRange {
my %args;
my $dayCount = 1;
my $monCount = 1;
my $yearCount = 1;
$args{gmt} = pop(@_) if ($_[$#_] eq "GMT");
if ($#_ == 0) {
$args{hour1} = shift;
} elsif ($#_ == 1) {
$args{hour1} = shift;
$args{hour2} = shift;
} elsif ($#_ == 3) {
$args{hour1} = shift;
$args{min1} = shift;
$args{hour2} = shift;
$args{min2} = shift;
} elsif ($#_ == 5) {
$args{hour1} = shift;
$args{min1} = shift;
$args{sec1} = shift;
$args{hour2} = shift;
$args{min2} = shift;
$args{sec2} = shift;
}
my $sec = (exists($args{gmt}) ? (gmtime)[0] : (localtime)[0]);
my $min = (exists($args{gmt}) ? (gmtime)[1] : (localtime)[1]);
my $hour = (exists($args{gmt}) ? (gmtime)[2] : (localtime)[2]);
if (exists($args{sec1}) && exists($args{min1}) && exists($args{hour1}) &&
exists($args{sec2}) && exists($args{min2}) && exists($args{hour2})) {
if (($args{hour1} < $hour) && ($args{hour2} > $hour)) {
return 1;
} elsif (($args{hour1} == $hour) && ($args{min1} <= $min)) {
return 1;
} elsif (($args{hour2} == $hour) && ($args{min2} >= $min)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{min1}) && exists($args{hour1}) &&
exists($args{min2}) && exists($args{hour2})) {
if (($args{hour1} < $hour) && ($args{hour2} > $hour)) {
return 1;
} elsif (($args{hour1} == $hour) && ($args{min1} < $min)) {
return 1;
} elsif (($args{hour2} == $hour) && ($args{min2} > $min)) {
return 1;
} elsif (($args{hour1} == $hour) && ($args{min1} == $min) &&
($args{sec1} <= $sec)) {
return 1;
} elsif (($args{hour2} == $hour) && ($args{min2} == $min) &&
($args{sec2} >= $sec)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{sec1}) && exists($args{min1}) &&
exists($args{sec2}) && exists($args{min2})) {
if (($args{min1} < $min) && ($args{min2} > $min)) {
return 1;
} elsif (($args{min1} == $min) && ($args{sec1} <= $sec)) {
return 1;
} elsif (($args{min2} == $min) && ($args{sec2} >= $sec)) {
return 1;
} else {
return 0;
}
return 0;
} elsif (exists($args{hour1}) && exists($args{hour2})) {
foreach my $thour ($args{hour1}..$args{hour2}) {
return 1 if ($thour == $hour);
}
return 0;
} elsif (exists($args{min1}) && exists($args{min2})) {
foreach my $tmin ($args{min1}..$args{min2}) {
return 1 if ($tmin == $min);
}
return 0;
} elsif (exists($args{sec1}) && exists($args{sec2})) {
foreach my $tsec ($args{sec1}..$args{sec2}) {
return 1 if ($tsec == $sec);
}
return 0;
} elsif (exists($args{hour1})) {
return (($args{hour1} == $hour) ? 1 : 0);
} elsif (exists($args{min1})) {
return (($args{min1} == $min) ? 1 : 0);
} elsif (exists($args{sec1})) {
return (($args{sec1} == $sec) ? 1 : 0);
} else {
return 0;
}
return 0;
}
1;