#! /usr/bin/perl
#
# spider.pl   Set tabstops to 3.
#

$| = 1;
# 0=no debug, 1=display progress, 2=complete dump
$DEBUG = 0;  		
# Check hyperlinks to other hosts?
$SPANHOSTS = "off";	

if(scalar(@ARGV) < 2){
print "Usage: $0 <fully-qualified-URL> <search-phrase>\n";
exit 1;
}

# Initialize.
%URLqueue = ();
chop($client_host=`hostname`);
$been = 0;
$search_phrase = $ARGV[1];

# Load the queue with the first URL to hit.
$URLqueue{$ARGV[0]} = 0;
$thisURL = &find_new(%URLqueue);

# While there's a URL in our queue which we haven't looked at ...
while($thisURL ne ""){

# Progress report.
$count = 0;
while(($key,$value) = each(%URLqueue)){
	$count ++;
}
print "-----------------------------------------\n" if($DEBUG>=1);
printf("Been: %d  To Go: %d\n", $been, $count-$been)
if($DEBUG>=1);
print "Current URL: $thisURL\n" if($DEBUG>=1);
&dump_stack() if($DEBUG>=2);

# Split the protocol from the URL.
($protocol, $rest) = $thisURL =~ m|^([^:/]*):(.*)$|;

# If the protocol is http, fetch the page and process it.
if($protocol eq "http"){

	# Split out the hostname, port and document.
	($server_host, $port, $document) =
		$rest =~ m|^//([^:/]*):*([0-9]*)/*([^:]*)$|;

	# Get the page of text and remove CR/LF characters and HTML
	# comments from it.
	$page_text = &get_http($client_host, $server_host, $port, 
		$document);
	$page_text =~ tr/\r\n//d;
	$page_text =~ s|<!--[^>]*-->||g;

	# Report if our search string is found here.
	if($page_text =~ m|$search_phrase|i){
		print "$thisURL\n"
	}	

	# Find anchors in the HTML and update our list of URLs..
	(@anchors) = $page_text =~ m|<A[^>]*HREF\s*=\s*"([^
">]*)"|gi;
	foreach $anchor (@anchors){
		$newURL = &fqURL($thisURL, $anchor);
		if($URLqueue{$newURL} > 0){

			# Increment the count for URLs we've already 
			# checked out.
			$URLqueue{$newURL}++;

		}else{

			# Add a zero record for URLs we haven't 
			# encountered.
			# Optionally, ignore URL's which point to other
			# hosts.
			($new_host) =
			   $newURL =~ m|^[^:/]*:/*([^/:]*):*[0-9]*/*[^:]*$|;
			if($SPANHOSTS eq "on" || $new_host eq 
				$server_host){
				$URLqueue{$newURL}=0;
			}
		}
	}
}else{
	print "Protocol '$protocol' ignored.\n" if($DEBUG>=1);
}

# Record the fact that we've been here, and get a new URL to process.
$URLqueue{$thisURL} ++;
$been ++;
$thisURL = &find_new(%URLqueue);

}
exit;

#--------------------------------------------------------------
# Build a fully specified URL.
#--------------------------------------------------------------
sub fqURL
{
local($thisURL, $anchor) = @_;
local($has_proto, $has_lead_slash, $currprot, $currhost, $newURL);

# Strip anything following a number sign '#', because its
# just a reference to a position within a page.
$anchor =~ s|^.*#[^#]*$|$1|;

# Examine anchor to see what parts of the URL are specified.
$has_proto = 0;
$has_lead_slash=0;
$has_proto = 1 if($anchor =~ m|^[^/:]+:|);
$has_lead_slash = 1 if ($anchor =~ m|^/|);

if($has_proto == 1){

	# If protocol specified, assume anchor is fully qualified.
	$newURL = $anchor;

}
elsif($has_lead_slash == 1){

	# If document has a leading slash, it just needs protocol and host.
	($currprot, $currhost) = $thisURL =~ m|^([^:/]*):/+([^:/]*)|;
	$newURL = $currprot . "://" . $currhost . $anchor;

}	
else{

	# Anchor must be just relative pathname, so append it to current URL.
	($newURL) = $thisURL =~ m|^(.*)/[^/]*$|;
	$newURL .= "/" if (! ($newURL =~ m|/$|));
	$newURL .= $anchor;

}
if($DEBUG >=2){
	print "Link Found\n   In:$thisURL\n   Anchor:$anchor\n   Result: $newURL\n"
}
return $newURL;
}

#---------------------------------------------------------------
# Do a linear search of the URL stack to find a URL with a data
# value of 0 (i.e. one we haven't checked out yet).
#---------------------------------------------------------------
sub find_new
{
local(%URLqueue) = @_;
local($key, $value);

while(($key, $value) = each(%URLqueue)){
	return $key if($value == 0);
}
return "";
}

#-------------------------------------------------------------------
# Debugging utility.
#-------------------------------------------------------------------
sub dump_stack
{
local($key, $x);
local($done, $togo) = ("", "");

foreach $key (keys(%URLqueue)){
	if($URLqueue{$key} == 0){
		$togo .= "  " . $key . "\n";
	}else{
		$done .= "  " . $key . " (hitcount = "
		    . $URLqueue{$key} . ")\n";
	}
}

print "Been There:\n" . $done;
print "To Go:\n" . $togo;
print "------- Hit Q to Quit, Enter to Continue -------\n";
read(STDIN, $key, 1);
exit(1) if($key eq 'Q' || $key eq 'q');
}

#-------------------------------------------------------------------------
# Get the page indicated by the $server_host and $document parameters.
#-------------------------------------------------------------------------
sub get_http
{
local($client_host, $server_host, $port, $document) = @_;
local($name,$aliases,$type,$len);
local($this,$thisaddr,$that,$thataddr);
local($client_host, $sockaddr, $a,$b,$c,$d);
local($page, $header, $header_text, $content);

# Some constants used to access the TCP network.
$AF_INET=2;
$SOCK_STREAM=1;

# Use default http port if none specified.
$port = 80 if($port == 0);

# Get the protocol number for TCP.
($name,$aliases,$proto)=getprotobyname("tcp");

# Get the IP addresses for the two hosts.
($name,$aliases,$type,$len,$thisaddr) = gethostbyname($client_host);
($name,$aliases,$type,$len,$thataddr) = gethostbyname($server_host);

# Check we could resolve the server host name.
($a,$b,$c,$d) = unpack('C4', $thataddr);
if($a eq "" && $b eq "" && $c eq  "" && $d eq ""){
	print "ERROR: Unknown host $server_host.\n";
	return "";
}
print "Server: $server_host ($a.$b.$c.$d)\n" if($DEBUG>=2);

# Pack the AF_INET magic number, the port, and the (already packed) IP
# addresses into the same format as the C structure would use. Note
# this is architecture dependent: this pack format works for 32 bit
# architectures.
$sockaddr="S n a4 x8";
$this=pack($sockaddr, $AF_INET, 0, $thisaddr);
$that=pack($sockaddr, $AF_INET, $port, $thataddr);

# Create the socket and connect.
if(socket(S, $AF_INET, $SOCK_STREAM, $proto) == false){
	print "ERROR: Cannot create socket.\n";
	return "";
}
print "Socket OK\n" if($DEBUG>=2);
if(connect(S, $that) == false){
	print "ERROR: Cannot connect to server $server_host,
		 port $port.\n";
	return "";
}
print "Connect OK\n" if($DEBUG>>>>>>>>=2);

# Turn buffering in the socket off, and send request to the server.
select(S); $| = 1; select(STDOUT);
print S "GET /$document HTTP/1.0\n\n";

# Receive the response. Check to ensure the response is of MIME
# type text/html or text/plain.
$page = "";
$header = 1;
$header_text = "";
while(<S>){
	
	# Check if we've hit the end of the HTTP header (an empty
line).
	# If we have, check for a content-type header line, and
ensure
	# it is valid.
	if( m|^[\n\r]*$| ){
		$header = 0;
		($content) = $header_text =~ m|Content-type: (\S+)|i;
		if($content ne "text/html" && $content ne "text/plain"){
			print "Content type '$content' ignored.\n" 
				if($DEBUG>=1);
			last;
		}
	}
	# Save to a header string if we're still working on the HTTP
	# header.
	elsif($header == 1){
		$header_text .= "   " . $_;
	}
	# Otherwise, save to the html page string.
	else{
		$page .= $_;
	}

print "HTTP header: \n $header_text" if($DEBUG>=2);
return $page;
}

Magazine Table of Contents