#!/usr/bin/perl
#
# Process WPAD file to extract proxy settings
# http://docs.huihoo.com/gnu_linux/squid/html/x1187.html
#
# Author: Petter Reinholdtsen
# License: GNU General Public License v2 or later

use strict;
use warnings;

use Socket; # for inet_ntoa
use JavaScript; # From the libjavascript-perl debian package
use LWP::Simple; # From the libwww-perl debian package

my $debug = 0;

sub get_wpad_script_test {
    # Example wpad script for debugging and testing
    return <<EOF;
function FindProxyForURL(url, host)
    {
        if (!isResolvable(host) ||
            isPlainHostName(host) ||
            dnsDomainIs(host, ".intern"))
            return "DIRECT";
        else
            return "PROXY webcache:3128; DIRECT";
    }
EOF
}
sub get_wpad_script {
    my $wpadurl = shift;
    my $wpadbody;
    if ($wpadurl =~ m/^http[s]?:/) {
        print "Fetching URL $wpadurl\n" if $debug;
        $wpadbody = LWP::Simple::get($wpadurl);
        die "Could not get url $wpadurl!" unless defined $wpadbody;
    } elsif ($wpadurl =~ m/^file:\/\/(\/?.+)$/) {
        open(FILE, "<", $1) || die "Unable to read $1";
        $wpadbody = "";
        while (<FILE>) {
            $wpadbody .= $_;
        }
        close(FILE);
    } else {
        die "Unhandled URL type $wpadurl!";
    }
    return $wpadbody;
}

sub usage {
    my $retval = shift;
    print <<EOF;
Usage: $0 [URL-to-wpad-file]
EOF
    exit $retval;
}

my $wpadurl = $ARGV[0]; shift;
usage(1) unless $wpadurl;
my $wpadbody;
unless ("debug" eq $wpadurl) {
    $wpadbody = get_wpad_script($wpadurl);
} else {
    $wpadbody = get_wpad_script_test();
}

my @types = @ARGV;
@types = ("http", "ftp") unless @types;


for my $type (@types) {
    my $proxy = get_proxy($type, $wpadbody) || "";
    print "${type}_proxy=$proxy\n";
}

exit 0;

sub get_proxy {
    my ($type, $wpadbody) = @_;
    my $rt = new JavaScript::Runtime();
    my $cx = $rt->create_context();

    # See #564772 for info on why this is needed
    $cx->bind_function(name => "dnsResolve", func => sub {
        my $name = shift;
        my $packed = gethostbyname($name);
        # Make sure we can extract the WPAD setting even if DNS lookup fail.
        return $packed ? inet_ntoa($packed) : "dns-lookup-failed";
                       });

    # Load proxy autoconfig helper functions and WPAD script
    for my $script (get_pac_functions(),$wpadbody) {
        $cx->eval($script);
        die $@ if $@;
    }

    my ($url, $host);
    if ("http" eq $type) {
        $url =  "http://www.debian.org/";
        $host = "www.debian.org";
    } elsif ("ftp" eq $type) {
        $url =  "ftp://ftp.debian.org/";
        $host = "ftp.debian.org";
    } else {
        die "Unhandled proxy type requested";
    }
    my $setting = $cx->call("FindProxyForURL", $url, $host) || "";
    die $@ if $@;
    print "S[$type]: $setting\n" if $debug;
    if ($setting) {
        for my $entry (split(/; */, $setting)) {
            print "  E: $entry\n" if $debug;
            return undef if $entry eq "DIRECT";
            return "http://$1" if $entry =~ /^PROXY (.+)$/;
            # Not handling SOCKS proxy settings
        }
    } else {
        print STDERR "Unable to find proxy settings for $type\n";
    }
    return undef;
}

# Make common functions available.  Copied from
# http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsProxyAutoConfig.js
sub get_pac_functions {
    my $pacUtils = <<'EOF';
function dnsDomainIs(host, domain) {
    return (host.length >= domain.length &&
            host.substring(host.length - domain.length) == domain);
}

function dnsDomainLevels(host) {
    return host.split('.').length-1;
}

function convert_addr(ipchars) {
    var bytes = ipchars.split('.');
    var result = ((bytes[0] & 0xff) << 24) |
                 ((bytes[1] & 0xff) << 16) |
                 ((bytes[2] & 0xff) <<  8) |
                  (bytes[3] & 0xff);
    return result;
}

function isInNet(ipaddr, pattern, maskstr) {
    var test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/(ipaddr);
    if (test == null) {
        ipaddr = dnsResolve(ipaddr);
        if (ipaddr == null)
            return false;
    } else if (test[1] > 255 || test[2] > 255 ||
               test[3] > 255 || test[4] > 255) {
        return false;    // not an IP address
    }
    var host = convert_addr(ipaddr);
    var pat  = convert_addr(pattern);
    var mask = convert_addr(maskstr);
    return ((host & mask) == (pat & mask));

}

function isPlainHostName(host) {
    return (host.search('\\.') == -1);
}

function isResolvable(host) {
    var ip = dnsResolve(host);
    return (ip != null);
}

function localHostOrDomainIs(host, hostdom) {
    return (host == hostdom) ||
           (hostdom.lastIndexOf(host + '.', 0) == 0);
}

function shExpMatch(url, pattern) {
   pattern = pattern.replace(/\./g, '\\.');
   pattern = pattern.replace(/\*/g, '.*');
   pattern = pattern.replace(/\?/g, '.');
   var newRe = new RegExp('^'+pattern+'$');
   return newRe.test(url);
}

var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};
var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};

function weekdayRange() {
    function getDay(weekday) {
        if (weekday in wdays) {
            return wdays[weekday];
        }
        return -1;
    }
    var date = new Date();
    var argc = arguments.length;
    var wday;
    if (argc < 1)
        return false;
    if (arguments[argc - 1] == 'GMT') {
        argc--;
        wday = date.getUTCDay();
    } else {
        wday = date.getDay();
    }
    var wd1 = getDay(arguments[0]);
    var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;
    return (wd1 == -1 || wd2 == -1) ? false
                                    : (wd1 <= wday && wday <= wd2);
}

function dateRange() {
    function getMonth(name) {
        if (name in months) {
            return months[name];
        }
        return -1;
    }
    var date = new Date();
    var argc = arguments.length;
    if (argc < 1) {
        return false;
    }
    var isGMT = (arguments[argc - 1] == 'GMT');

    if (isGMT) {
        argc--;
    }
    // function will work even without explict handling of this case
    if (argc == 1) {
        var tmp = parseInt(arguments[0]);
        if (isNaN(tmp)) {
            return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==
getMonth(arguments[0]));
        } else if (tmp < 32) {
            return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);
        } else {
            return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==
tmp);
        }
    }
    var year = date.getFullYear();
    var date1, date2;
    date1 = new Date(year,  0,  1,  0,  0,  0);
    date2 = new Date(year, 11, 31, 23, 59, 59);
    var adjustMonth = false;
    for (var i = 0; i < (argc >> 1); i++) {
        var tmp = parseInt(arguments[i]);
        if (isNaN(tmp)) {
            var mon = getMonth(arguments[i]);
            date1.setMonth(mon);
        } else if (tmp < 32) {
            adjustMonth = (argc <= 2);
            date1.setDate(tmp);
        } else {
            date1.setFullYear(tmp);
        }
    }
    for (var i = (argc >> 1); i < argc; i++) {
        var tmp = parseInt(arguments[i]);
        if (isNaN(tmp)) {
            var mon = getMonth(arguments[i]);
            date2.setMonth(mon);
        } else if (tmp < 32) {
            date2.setDate(tmp);
        } else {
            date2.setFullYear(tmp);
        }
    }
    if (adjustMonth) {
        date1.setMonth(date.getMonth());
        date2.setMonth(date.getMonth());
    }
    if (isGMT) {
    var tmp = date;
        tmp.setFullYear(date.getUTCFullYear());
        tmp.setMonth(date.getUTCMonth());
        tmp.setDate(date.getUTCDate());
        tmp.setHours(date.getUTCHours());
        tmp.setMinutes(date.getUTCMinutes());
        tmp.setSeconds(date.getUTCSeconds());
        date = tmp;
    }
    return ((date1 <= date) && (date <= date2));
}

function timeRange() {
    var argc = arguments.length;
    var date = new Date();
    var isGMT= false;

    if (argc < 1) {
        return false;
    }
    if (arguments[argc - 1] == 'GMT') {
        isGMT = true;
        argc--;
    }

    var hour = isGMT ? date.getUTCHours() : date.getHours();
    var date1, date2;
    date1 = new Date();
    date2 = new Date();

    if (argc == 1) {
        return (hour == arguments[0]);
    } else if (argc == 2) {
        return ((arguments[0] <= hour) && (hour <= arguments[1]));
    } else {
        switch (argc) {
        case 6:
            date1.setSeconds(arguments[2]);
            date2.setSeconds(arguments[5]);
        case 4:
            var middle = argc >> 1;
            date1.setHours(arguments[0]);
            date1.setMinutes(arguments[1]);
            date2.setHours(arguments[middle]);
            date2.setMinutes(arguments[middle + 1]);
            if (middle == 2) {
                date2.setSeconds(59);
            }
            break;
        default:
          throw 'timeRange: bad number of arguments'
        }
    }

    if (isGMT) {
        date.setFullYear(date.getUTCFullYear());
        date.setMonth(date.getUTCMonth());
        date.setDate(date.getUTCDate());
        date.setHours(date.getUTCHours());
        date.setMinutes(date.getUTCMinutes());
        date.setSeconds(date.getUTCSeconds());
    }
    return ((date1 <= date) && (date <= date2));
}
EOF
    return $pacUtils;
}
