#!/usr/bin/perl
#
# Use the getopts library
#
require "getopts.pl";

$hostname = "";

$configdir = "/usr/local/etc/httpd/conf";
#$configdir = "";
$docroot = "";
$indexname = "index.html";
$followlinks = 0;
$checkexternal = 0;
$showexternal = 0;
$followsymlinks = 0;
$cgiextension = ".cgi";
$imagemap = "imagemap";
$imagemapconf = "imagemap.conf";
$defaultport = 80;
$defaulttimeout = 60;
$showmailto = 0;

$verbose = 0;
$debug = 0;

# Pour le parsing
$/ = "";
$* = 1;

# Constantes
$AF_INET = 2;
$SOCK_STREAM = 1;

sub usage
{
    print "Usage: $0 [-hefs] [-Cpath] [-Dpath] [-Hname] [-Iname]\n";
    print "       filename1 [filename2 ...]\n";
    print "\nOptions:\n";
    print "\t-h\tHelp (this message)\n";
    print "\t-e\tCheck external links (references to files not on this ",
          "server).\n";
    print "\t-f\tFollow hyperlinks on this server.\n";
    print "\t-s\tShow external links (but don't check them).\n";
    print "\t-Bpath\tSet the actual path for /cgi-bin/ accesses.\n";
    print "\t-Cpath\tSet the server configuration file directory path.\n";
    print "\t-Dpath\tSet the document root directory path.\n";
    print "\t-Hname\tSet the hostname.\n";
    print "\t-Iname\tSet the default index file name.\n";
    print "\n";
}

#
# Add a trailing slash to a path if needed
#

sub addslash
{
    local($x);
    $x = substr($_[0], -1, 1);
    if ($x ne "/")
    {
        substr($_[0], -1, 1) = $x . "/";
    }
}

#
# Remove the trailing slash from a path if needed
#

sub remslash
{
    if (substr($_[0], -1, 1) eq "/")
    {
        chop($_[0]);
    }
}

#
# Combine two (potentially relative) directory paths
#

sub combinepaths
{
    local($path1, $path2) = @_;

    #
    # If path2 is an absolute path, it takes precedence
    #
    if (substr($path2, $[, 1) eq "/")
    {
        $path1 = "";
    }

    &remslash($path1);
    &remslash($path2);

    local($element);
    local(@path1) = split(/\//, $path1);
    local(@path2) = split(/\//, $path2);

    foreach $element (@path2)
    {
        if ($element eq '.')
        {
            # Ignore me!
        }
        elsif ($element eq '..')
        {
            # Back one dir
            pop(@path1);
        }
        else
        {
            # Add a dir
            push(@path1, $element);
        }
    }

    # Reassemble and return
    join("/", @path1);
}

#
# Process aliases
#

sub process_aliases
{
    local(*filename, *iscgi) = @_;
    local($substituted) = 0;

    # First try the normal aliases

    while (($key, $val) = each %alias)
    {
        if ($filename =~ m!^${key}!)
        {
            $filename =~ s!^${key}!${val}!;
            print "Substituted $key with $val.\n" if $debug;
            $substituted = 1;
        }
    }

    # Next, try the script (CGI) aliases

    while (($key, $val) = each %scr_alias)
    {
        if ($filename =~ m!^${key}!)
        {
            $filename =~ s!^${key}!${val}!;
            $iscgi = 1;
            print "Substituted $key with $val (CGI).\n" if $debug;
            $substituted = 1;
        }
    }

    return $substituted;
}

#
# Parse the NCSA httpd files
#

sub parseconf
{
    local ($line, $servroot, $resconfig, $srmpath);

    if ($configdir)
    {
        &addslash($configdir);

        # Process the httpd.conf file

        print "HTTPD config path: ", $configdir . "httpd.conf\n" if $debug;

        if (open(HTTPDCONF, $configdir . "httpd.conf"))
        {
            while (<HTTPDCONF>)
            {
                $line = $_;

                if ($line =~ /^\s*ServerRoot\s+(\S+)/i)
                {
                    # Server root for other config files
                    $servroot = &combinepaths($1, "conf");
                }

                if ($line =~ /^\s*ServerName\s+(\S+)/i)
                {
                    # Server name (replaces hostname)
                    $hostname = $1;
                }

                if ($line =~ /^\s*ResourceConfig\s+(\S+)/i)
                {
                    # Resource config file name
                    $resconfig = $1;
                }
            }

            close (HTTPDCONF);
        }
        else
        {
#            print "Error: Can't find httpd.conf file, skipping.\n";
            return;
        }

        # Create the path for the srm.conf file

        if ($resconfig)
        {
            if ($servroot)
            {
                &addslash($servroot);
                $srmpath = &combinepaths($servroot, $resconfig);
            }
            else
            {
                $srmpath = &combinepaths($configdir, $resconfig);
            }
        }
        else
        {
            if ($servroot)
            {
                $srmpath = $servroot;
            }
            else
            {
                $srmpath = $configdir;
            }

            &addslash($srmpath);

            $srmpath .=  "srm.conf";
        }

        # Process the srm.conf file

        print "SRM file path: $srmpath\n" if $debug;

        if (open(SRMCONF, $srmpath))
        {
            while (<SRMCONF>)
            {
                $line = $_;

                if ($line =~ /^\s*DocumentRoot\s+(\S+)/i)
                {
                    # Document root
                    $docroot = $1;
                }

                if ($line =~ /^\s*DirectoryIndex\s+(\S+)/i)
                {
                    # Default index filename
                    $indexname = $1;
                }

                if ($line =~ /^\s*Alias\s+(\S+)\s+(\S+)/i)
                {
                    # Add a path alias
                    $alias{$1} = $2;
                }

                if ($line =~ /^\s*ScriptAlias\s+(\S+)\s+(\S+)/i)
                {
                    # Add a script alias
                    $scr_alias{$1} = $2;
                }
            }

            close (SRMCONF);
        }
        else
        {
            print "Error: Can't find srm.conf file, skipping.\n";
            return;
        }


    }
    else
    {
        print "No config directory specified.\n";
    }
}
        
#
# Figure out the correct directory and file to go to
#

sub nextfile # old, new
{
    local($olddir, $newdir, *iscgi) = @_;
    local($tmpdir);
    local($retcode);

    $retcode = "";
    $iscgi = 0;

    if($newdir =~ m;http://([^\s/]*)(.*);i)
    {
        # Full specification
        $tmpdir = $2;

        # Check for default filename case
        if (substr($tmpdir, -1, 1) eq "/")
        {
            $tmpdir .= $indexname;
        }

        # Build filename
        if ($1 eq $hostname)
        {
            # this is on our server
            if (&process_aliases(*tmpdir, *iscgi))
            {
                # Alias was substituted - do not process further
                $retcode = $tmpdir;
            }
            else
            {
                $retcode = $docroot . $tmpdir;
            }
        }
        else
        {
            # Someone else's server
            $retcode = "";
        }
    }
    else
    {
        $tmpdir = $newdir;
        # Partial specification
        # Check for default filename case
        if (substr($tmpdir, -1, 1) eq "/")
        {
            $tmpdir .= $indexname;
        }

        # Process aliases - if an alias is substituted, do not do any
        # further processing of the path

        if (&process_aliases(*tmpdir, *iscgi))
        {
            # Alias was substituted - do not process further
            $retcode = $tmpdir;
        }
        else
        {
            # Build full path
            if (substr($tmpdir, $[, 1) eq "/")
            {
                # Same server, absolute path
                $retcode = $docroot . $tmpdir;
            }
            else
            {
                # Relative path
                # Combine the paths
                $retcode = &combinepaths($olddir, $tmpdir);
            }
        }
    }

    # Now try to interpret it as a symbolic link if requested
    if ($followsymlinks && ($retcode ne ""))
    {

        printf "Link check: $retcode\n" if $debug;
        $linkname = readlink($retcode);
        if ($linkname)
        {
            &remslash($retcode);
            @oldpath = split(/\//, $retcode);
            pop(@oldpath);      # Remove the filename part
            local($newretcode) = join('/', @oldpath);
            $linkname = &combinepaths($newretcode, $linkname);

            print "$retcode is a link to $linkname\n" if $debug;
            $retcode = $linkname;
        }
    }

    $retcode;
}

#
# Check the validity of an imagemap
#

sub check_imagemap
{
    local($imapname, $currpath) = @_;
    local($imapfile);
    local($newfile, $iscgi);
    local(@links);
    local($found) = 0;

    if (!$seen_imap{$imapname})
    {
        print "Checking imagemap: $imapname\n" if $debug;

        # Open the imagemap.conf file
        $/ = "\n";
        $imagemapconf = &combinepaths($configdir, $imagemapconf);
        if (open(IMCONF, $imagemapconf))
        {
            while (<IMCONF>)
            {
                if (/${imapname}\s*:\s*(\S+)/)
                {
                    # Found the filename: Open it, too
                    $imapfile = &combinepaths($currpath, $1);
                    if (open(IMFILE, $imapfile))
                    {
                        while (<IMFILE>)
                        {
                            if (/^\s*[^#\s]+\s+([^#\s]+)[\s#]+/)
                            {
                                # Add each link to the list of links to follow
                                push(@links, $1);
                            }
                        }
                        close (IMFILE);
                    }
                    else
                    {
                        print ("** Imagemap file $imapfile not found for ");
                        print ("imagemap: $imapname\n");
                    }

                    $found = 1;
                }
            }

            close (IMCONF);

            $/ = "";

            if ($found == 0)
            {
                print ("** Imagemap name $imapname not found in imagemap ");
                print ("config file.\n");
            }
            else
            {
                # Validate all links
                $seen_imap{$imapname} = 1;
                print ("Checking imagemaps in file: $imapfile\n") if $verbose;
                &check_links($currpath, $imapfile, @links);
                print ("Done checking imagemaps in file: $imapfile\n") if $verbose;
            }
        }
        else
        {
            print ("** Imagemap config file not found: $imagemapconf\n");
        }
    }
    else
    {
        print ("-- Ignored imagemap: $imapname\n") if $verbose;
        $found = 1;
    }

    return ($found);
}

# Does this file exist? Also checks for an anchor name within the file
# if provided.
#

sub exists
{
    local(*fname, $aname, $iscgi) = @_;
    local(@filepart, $tmpfile, $tmppart, $tmppath);

    # Attempt to find the file

    # Check the whole path.
    # This has to be done directory level at a time to allow for any
    # additional path info provided after the actual file name.
    # This extra path info is only allowed in CGI scripts.

    &remslash($fname);
    @filepart = split(/\//, $fname);
    # Remove any bogus initial path element
    if ((scalar(@filepart) > 1) && (@filepart[0] eq ""))
    {
        shift(@filepart);
    }

    if (substr($fname, $[, 1) eq "/")
    {
        $tmpfile = "/";
    }

    while (scalar(@filepart) > 0)
    {
        $tmppart = shift(@filepart);
        $tmppath = $tmpfile;
        $tmpfile .= $tmppart;
        if (-f $tmpfile)
        {
            # Found the actual file
            if ($iscgi ||
                ((substr($tmppart, -length($cgiextension)) eq
                  $cgiextension) && (-f $tmpfile)))
            {
                # This is a CGI script. Truncate the rest of the path.
                $fname = $tmpfile;

                # Check if this is the imagemap program
                if ($tmppart eq $imagemap)
                {
                    # Check the validity of the imagemap configuration
                    $tmpfile = join('/', @filepart);
                    if (!&check_imagemap($tmpfile, $tmppath))
                    {
                        # Bad imagemap - fail!
                        return (0);
                    }
                }

                @filepart = ();
            }
            else
            {
                # This is a plain file. Any extra path is illegal!
                if (scalar(@filepath) > 0)
                {
                    return (0);
                }
            }
        }
        elsif (!(-e $tmpfile))
        {
            # File does not exist!
            return (0);
        }
        else
        {
            $tmpfile .= "/";
        }
    }

    # Now open that file, and examine it if needed

    &remslash($fname);
        
    if (open(TMPFILE, $fname))
    {
        # Attempt to find anchor if specified
        if ($aname)
        {
            print "-- Searching for $fname, $aname.\n" if $debug;
            while (<TMPFILE>)
            {
                if (/<A\s+NAME\s*=[\s\"]*${aname}[\s\">]/i)
                {
                    close(TMPFILE);
                    return(1);
                }
            }

            close(TMPFILE);
            return(0);
        }
        else
        {
            close(TMPFILE);
            return(1);
        }
    }
    else
    {
        return(0);
    }
}

#
# Check the existence of an external file using HTTP
#
sub exists_external
{
    local($url) = @_;
    local($hostname, $otherhost, $port, $proto, $thisaddr, $otheraddr);
    local($this, $other, $sockaddr);
    local($name, $aliases, $type, $len);
    local($statuscode, $statustext, $filename);
    local($rin, $win, $ein, $nfound, $timeleft, $timeout);

    # Get the hostname and port number from the URL

    if ($url =~ m!^http://([^\s/]+)([^\r\n\s]*)!i)
    {
        $otherhost = $1;
        $filename = $2;
        if ($filename eq "")
        {
            $filename = "/";
        }

        if ($otherhost =~ m!^([^\s/:]+):([\d]+)$!)
        {
            $otherhost = $1;
            $port = $2;
        }
        else
        {
            $port = $defaultport;
        }

        # Prepare for opening the socket

        chop($hostname = `hostname`);
        ($name, $aliases, $proto) = getprotobyname('tcp');

        $len = 0;
        ($name, $aliases, $type, $len, $thisaddr) =
            gethostbyname($hostname);
        if ($len < 1)
        {
            print "** Error looking up local IP address: $hostname\n";
            return 0;
        }

        # Look up by name if needed, or we might have the IP address
        # already
        if ($otherhost =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
        {
            # This is an IP address
            $otheraddr = pack('c4', $1, $2, $3, $4);
            $len = 1;
        }
        else
        {
            # This is a host name
            $len = 0;
            ($name, $aliases, $type, $len, $otheraddr) =
                gethostbyname($otherhost);
        }

        if ($len < 1)
        {
            print "** Error looking up host: $otherhost\n";
            return 0;
        }

        $sockaddr = 'S n a4 x8';
        $this = pack($sockaddr, $AF_INET, 0, $thisaddr);
        $other = pack($sockaddr, $AF_INET, $port, $otheraddr);
    
        #
        # Now open the socket connection
        #
        if (!socket(S, $AF_INET, $SOCK_STREAM, $proto))
        {
            print "** Error opening socket for $url\n";
            return 0;
        }

        if (!bind(S, $this))
        {
            print "** Error binding socket for $url\n";
            close S;
            return 0;
        }

        if (!connect(S, $other))
        {
            print "** Error connecting socket for $url\n";
            close S;
            return 0;
        }

        #
        # Now send the HTTP request
        #

        select(S); $| = 1; select(STDOUT);

        print S "HEAD $filename HTTP/1.0\r\nUser-Agent: missinglink/3.0\r\n\r\n";

        #
        # Anxiously await a response - eventually time out
        #

        $timeout = $defaulttimeout;
        $win = $ein = '';
        vec($rin, fileno(S), 1) = 1;
        ($nfound, $timeout) = select($rin, $win, $ein, $timeout);

        if ($nfound < 1)
        {
            print "** Timeout occurred while accessing $url\n";
            close S;
            return 0;
        }

        while(<S>)
        {
            #
            # This assumes if we don't get response header, but rather
            # get unrecognizeable data, it was a success. This is
            # according to the HTTP/0.9 protocol.
            #

            if (m!^HTTP/[\d\.]+ (\d+) ([^\r\n]+)!)
            {
                $statuscode = $1;
                $statustext = $2;

                if ($statuscode == 200)
                {
                    print "-- Server returned $statuscode $statustext for $url\n" if $verbose;
                }
                else
                {
                    print "** Server returned $statuscode $statustext for $url\n";
                    close S;
                    return 0;
                }
                last;
            }
        }

        close S;
    }
    else
    {
        print "** Invalid URL: $url\n";
        return 0;
    }

    return 1;
}

#
# This subroutine validates a list of links
#

sub check_links
{
    local($pathname, $filename, @links) = @_;
    local($link, $tmplink, $tmpname);
    local($newfile, $iscgi);

    foreach $link (@links)
    {
        # Check for special cases
        if ($link =~ /^mailto:.*/i)
        {
            # Special MAILTO case
            print "-- MAILTO: $link in $filename\n" if $showmailto;
        }
        else
        {
            # Normal (and default case)
            #

            # Remove any query string
            if ($link =~ /(^[^\?]*)\?\S*/)
            {
                $link = $1;
            }

            # Now check for a link of the form xxxxx#yyyyy
            if ($link =~ /(^[^#]*)#(\S*$)/)
            {
                $tmplink = $1;
                $tmpname = $2;
                if ($tmplink eq "")
                {
                    # No filename before the # means use this file.
                    if ($filename =~ m!([^/]+$)!)
                    {
                        $tmplink = $1;
                    }
                }
            }
            else
            {
                $tmplink = $link;
                $tmpname = "";
            }

            print "-- A HREF: $tmplink ($filename)\n" if $verbose;
            $newfile = &nextfile($pathname, $tmplink, *iscgi);
            if ($newfile ne "")
            {
		$tmplink2 = $tmplink;
		# Verifie que les liens sont en minuscules	    
		if ($tmplink2 =~ /[A-Z]/)
		{		# 
                    print "** $filename : $link doit etre en minuscules\n";
		}

		# Verifie que les chemins sont en relatifs 
		if (substr($tmplink, $[, 1) eq "/")
		{
                    print "** $filename : $link doit etre exprime en relatif\n";
		}
                # Local file
                if (&exists(*newfile, $tmpname, $iscgi))
                {
                    print "-- Found at: $newfile\n" if $verbose;

                    # Recurse if requested (but only search HTML files!)
                    if ($followlinks && (!$iscgi) && ($newfile =~ /\.html?$/))
                    {
                        &checkfile($newfile);
                    }
                }
                else
                {
                    print "** $filename : $link lien errone (fichier n'existe pas)\n" unless $debug;
                }
            }
            else
            {
                # Remote file
                if ($checkexternal)
                {
                    if (&exists_external($link))
                    {
                        print "-- Found at: $link\n" if $verbose;
                    }
                    else
                    {
                        print "** $filename : $link lien errone (fichier n'existe pas)\n";
                    }
                }
                else
                {
                    print "-- HREF: $link lien externe dans $filename\n" if $showexternal;
                }
                    
            }
        }
    }
}

#
# This subroutine does the work of parsing the links out of an HTML file.
#
# The first and only argument must be a real pathname to the file to
# check.
#

sub checkfile
{
    local($filename) = @_;
    local($pathname, $tmppath);
    local($tmpfile);
    local($key, $val);
    local(@links);
    local($iscgi);

    # Make sure we're not processing this file a second time

    $seen{$filename}++;

    if ($seen{$filename} <= 1)
    {
        # Attempt to open the file

        if (open(INFILE, $filename) == 0)
        {
            die "Error $! opening file $filename";
        }

        # Find pathname of this file (minus filename)
        if (substr($filename, -1, 1) eq "/")
        {
            $pathname = $filename;
        }
        else
        {
            $filename =~ m!(.*)/[^/]*$!;
            if ($1)
            {
                $pathname = $1 . "/";
            }
        }

        # Search the file for hyperlinks

	$titre = "";
        while (<INFILE>)
        {
	    # Cherche le titre
            if (/<TITLE>(.*)<\/TITLE>/i)
            {
		$titre = $1;
            }

            # Search for <BASE HREF="http://hostname/something">
            while (m!<\s*BASE\s+HREF\s*=[^/]+://([^/]+)/([^\">\s]+)!ig)
            {
                # Check for a server name error
                if ($1 eq $hostname)
                {
                    # Extract the pathname from the URL
                    $tmppath = substr($2, $[, rindex($2, "/") - $[ + 1);
                    $pathname = &combinepaths($docroot, $tmppath);
                    print "-- BASE HREF: $hostname , $pathname\n" if $debug;
                }
                else
                {
                    print "** BASE HREF: hote incorrect $1 dans $filename.\n";
                }
            }

            # Search for <IMG SRC="something">
            while (/<\s*IMG\s+SRC\s*=[\s\"]*([^\">\s]*)[^>]*>/ig)
            {
                $tmpfile = $1;
                print "-- IMG SRC: $tmpfile ($filename)\n" if $verbose;
                $newfile = &nextfile($pathname, $tmpfile, *iscgi);
                if ($newfile ne "")
                {
		    # Check relative path
		    if (substr($tmpfile, $[, 1) eq "/")
		    {
			print "** $filename : $tmpfile devrait etre exprime en relatif\n";
		    }
                    # Local file
                    if (&exists(*newfile, "", 0))
                    {
                        print "-- Found at: $newfile\n" if $verbose;
                    }
                    else
                    {
                        print "** $filename : $tmpfile appel image errone (fichier n'existe pas)\n" unless $debug;
                    }
                }
                else
                {
                    # Remote file
                    if ($checkexternal)
                    {
                        if (&exists_external($tmpfile))
                        {
                            print "-- Found at: $tmpfile\n" if $verbose;
                        }
                        else
                        {
                        print "** $filename : $tmpfile appel image errone (fichier n'existe pas)" unless $debug;
                        }
                    }
                    else
                    {
                        print "-- lien vers une image externe IMG: $tmpfile dans $filename\n" if $showexternal;
                    }
                }
            }

            # Search for <A HREF="something">
            while (/<\s*A\s+HREF\s*=[\s\"]*([^\">\s]*)[^>]*>/ig)
            {
                # Add to the list of links to follow
                push(@links, $1);
            }
        }
	if ($titre eq "")
	{
	    print "** $filename : pas de titre specifie\n";
	}		
	
        close(INFILE);

        # Now follow all of those links
        &check_links($pathname, $filename, @links);
    }
    else
    {
        print "-- Rejected: $filename\n" if $verbose;
    }

    $depth--;

    return (1);
}

sub DoOpts
{
    # Set defaults
    if ($hostname eq "")
    {
        $hostname = $ENV{'HOSTNAME'};
    }

    &Getopts('hefsB:C:D:H:I:');

    if ($opt_h)
    {
        # Help

        &usage;
        exit 0;
    }

    if ($opt_e)
    {
        # Check external links

        $checkexternal = 1;
    }

    if ($opt_f)
    {
        # Follow links

        $followlinks = 1;
    }

    if ($opt_s)
    {
        # Show external links

        $showexternal = 1;
    }

    if ($opt_B)
    {
        # Set cgi-bin alias
        &add_slash($opt_B);
        $scr_alias{'/cgi-bin/'} = $opt_B;
    }

    if ($opt_C)
    {
        # Set server config directory

        $configdir = $opt_C;
    }

    # Process the server config files before continuing so that
    # the command line may override some settings.
    &parseconf;

    if ($opt_D)
    {
        # Set document root

        $docroot = $opt_D;
    }

    if ($opt_H)
    {
        # Set hostname

        $hostname = $opt_H;
    }

    if ($opt_I)
    {
        # Set default index file name

        $indexname = $opt_I;
    }
}

#
# Main routine
#

# Check the command line options

&DoOpts;

if (scalar(@ARGV) == 0)
{
    &usage;
    exit 0;
}

# Begin processing

#print "Configuration directory is: $configdir\n";
#print "Document root is: $docroot\n";
#print "Default index file name is: $indexname\n";
#print "Server name is: $hostname\n";
#print "Validating external links\n" if $checkexternal;
#print "Following links on server\n" if $followlinks;
if ($debug)
{
    while (($key, $val) = each %alias)
    {
        print "Alias: $key -> $val\n";
    }

    while (($key, $val) = each %scr_alias)
    {
        print "Script Alias: $key -> $val\n";
    }
}

print "\n";

#
# Begin reading files - process all files specified and all files within
#                       specified directories.
#
@dirs = grep(-d, @ARGV);
@files = grep(/.*\.html?$/, @ARGV);

while (@dirs || @files)
{
    if (@files)
    {
        while ($fname = shift(@files))
        {
            print "Processing $fname.\n" if $verbose;
            &checkfile($fname);
        }
    }

    if ($dname = shift(@dirs))
    {
        &remslash($dname);
        $currpath = ($dname . "/");
        print "Directory: $dname.\n" if $verbose;
        if (!opendir(NEWDIR, $dname))
        {
            die "Error $! opening directory $dname";
        }

        @newdir = readdir(NEWDIR);
        closedir(NEWDIR);

        grep(($_ = $currpath . $_) && 0, @newdir);
        @files = grep(/.*\.html?$/, @newdir);
        if ($filepattern)
        {
            @files = grep(/$filepattern/o, @files);
        }
        push(@dirs, grep(!m%.*/\.{1,2}$%, grep(-d, @newdir)));

        print "Files: ", join(" ", @files), "\n" if $debug;
        print "Dirs: ", join(" ", @dirs), "\n" if $debug;
    }
}
print STDERR "Fini\n";


##
##
## Change history
##
##
## Version 3.0 - 1/3/96:
## Major enhancements and cleanup in preparation for release to the
## unsuspecting general public.
## Added command line options.
## Combined 1.0 and 2.0 features to allow both directory recursion and
## link following.
## Added NCSA HTTPD config file parsing
## Added anchor name support (<A NAME="..."> and <A HREF="something#name")
## Added query string suport (<A HREF="blah?blah">)
## Added path alias support
## Added external link checking
## Added <BASE HREF=...> support
## Added imagemap support
##
##
## Version 2.0 - 5/11/95
## Removed link following and replaced with directory recursion, which turns
## out to be more useful.
## Various bug fixes.
##
##
## Version 1.0 - January 1995
## Initial version, supporting link following, inline images, http links,
## mailto and external links (the latter two are reported, not validated).
##
##

