#!/usr/local/bin/perl ############################################################################# # # search.cgi - Customizable WWW Interface to the Harvest Broker # # Usage: Called as a CGI proccess from httpd # ############################################################################# # # Harvest Indexer http://harvest.sourceforge.net/ # ----------------------------------------------- # # The Harvest Indexer is a continued development of code developed by # the Harvest Project. Development is carried out by numerous individuals # in the Internet community, and is not officially connected with the # original Harvest Project or its funding sources. # # Please mail lee@arco.de if you are interested in participating # in the development effort. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # Changes by Harald Weinreich for version 1.8 are # marked with "h.weinreich" $ENV{'HARVEST_HOME'} = "" unless defined($ENV{'HARVEST_HOME'}); # Uncomment this if you have problems with European 8-BIT characters on SunOS. #$ENV{'LANG'} = "C"; #$ENV{'LC_CTYPE'} = "iso_8859_1"; # Set this to a location for search.log, or to /dev/null #$BQLOG = "/logs/search.log"; $BQLOG = "/dev/null"; $rcsid = '$Id: search.in,v 2.30 2002/08/30 12:25:00 sxw Exp $'; # $expire # Temporary files are deleted after this many hours. Or, more precisely, when # this program is run, files older than $expire hours are deleted. # # $tmp_dir # This is the location where temporary files are kept. This directory needs a # permissive access mode, so that the 'nobody' user (or whatever # user httpd uses) can write and delete files there. It is a fatal error if # this directory does not exist, or cannot be written in. # ###### configure these locally! ####### $expire = 1; # expiration interval, in hours $tmp_dir = "$ENV{HARVEST_HOME}/tmp"; # dir for temp files ###### end configuration ####### $harvestIcon = "/Harvest/brokers/images/harvest-ic1.gif"; # logo $weightIcon = "/Harvest/brokers/images/harvest-weight.gif"; # weight image @X = split ('/', $0); $MYNAME = pop @X; $DIR = join ('/', @X); $ENV{'TMPDIR'} = "/tmp" unless defined($ENV{'TMPDIR'}); unshift(@INC, "$ENV{'HARVEST_HOME'}/lib"); not_configured() unless (-d $ENV{'HARVEST_HOME'}); require 'socket.ph'; # not sys/socket.ph, we use $HARVEST_HOME/lib/socket.ph $debug = 0; $hp_url = ''; $brokers = $ENV{'HARVEST_HOME'} . '/brokers/Brokers.cf'; # show weight of result and navigation bar by Javier Masa Marin $weight = 0; # weight of current object $maxWeight = 0; $totalPages = 0; # number of Result pages # ===== MAIN ================================================================= foreach $sig ('HUP', 'QUIT', 'TSTP', 'TERM', 'ABRT') { $SIG{$sig} = 'sigdie'; } foreach $sig ('ALRM') { $SIG{$sig} = 'sigharddie'; } # Parse the CGI request. # %RQ = &get_request; $debug = 1 if defined $RQ{'debug'}; foreach $key (keys %RQ) { $RQ{$key} =~ s/\n/ /g; } # use unbuffered output # $unbuffered = 1; # If name is nph-search.cgi, more headers have to be sent. # $nph = $0 =~ /nph-/; # Send the MIME header *now* if we're in debug mode this means it will always # be text/html, but means when can see debugging information from parse_config # send_header() if $debug; # Read in the 'master config' file. # &parse_config ("$DIR/lib/search.cf"); &parse_defaults; $CFG{'rcsid'} = $rcsid; # Parse a broker-specific config file (included as the following HTML - ) # # print "opening $DIR/lib/",&option('brokerqueryconfig'),"\n" if ($debug); &parse_config ("$DIR/lib/".&option('brokerqueryconfig')) if ( -f "$DIR/lib/".&option('brokerqueryconfig') ); &parse_defaults; # determine character set for the HTTP-header # # get the charset from search form or .cf-file $charset = &option('charset'); if ($charset eq "") {$charset = "ISO-8859-1";} # default # We can't send the MIME header *until* we've read the broker-specific file # (as they might change it in there) - so we have to wait until now to send # it. # send_header() unless $debug; # Now - once we're sure that a MIME header has been sent, we can validate # their input # &fatal ('NoQuery') if (%RQ == ()); &fatal ('rcsid') if ($RQ{'version'} ne ""); &dump_array (%RQ) if ($debug); &dump_array (%DEF) if ($debug); #&dump_array (%ENV) if ($debug); # EXTRACT QUERY OPTIONS # $lifetime = &option('lifetime'); $userquery = &option('query'); $category = &option('category'); $userclass = &option('class'); $caseflag = &option('caseflag') eq 'on' ? 1 : 0; # case sensitivity $wordflag = &option('wordflag') eq 'on' ? 1 : 0; # match on word boundary $csumflag = &option('csumflag') eq 'on' ? 1 : 0; # show links to indexing data $opaqueflag = &option('opaqueflag') eq 'on' ? 1 : 0; # return opaque data (matched lines) $descflag = &option('descflag') eq 'on' ? 1 : 0; # return object description $noregexflag = &option('noregexflag') eq 'on' ? 1 : 0; # $maxresult = &option('maxresultflag'); # max. num of result lines to be returned by broker $maxfiles = &option('maxobjflag'); # max. num of objects to be returned by broker $maxlines = &option('maxlineflag'); # max. num of matched lines per object $perpage = &option('perpageflag'); # show n objects per page (0 = don't split output) $page = &option('pageflag'); # show page nr x $errors = &option('errorflag'); # number of errors allowed $weightflag = &option('weightflag') eq 'on' ? 1 : 0; # show weight balls $broker = &option('broker') || &option('host'); # name of broker #$version = &option('version') eq "" ? 0 : 1; # ? $hp_url = &option('hp_url'); # URL of search page $sort = &option('sort'); # sort options @atts = split (/\s+/, &option('attribute')); # list of attributes to be displayed foreach $a (@atts) { $attributes .= " #attribute \"$a\""; } # Build filter strings. Filters are e.g. used to specify a host. # They are attached to the query but not displayed as query string # Filters consist of a query string and a user hint to be displayed # on the search results page, seperated by a pipe symbol. Example: # # See query-glimpse.html for more examples - by h.weinreich @filter = split (/\0/, &option('filter')); $filter = ""; # The filter is attached to $query $filterhint = ""; # Filterhint is the text shown in the output to the user... foreach (@filter) { @t = split(/\|/); $filter .= " AND $t[0]" if $t[0]; $filterhint .= "$t[1]\n" if $t[1]; } # SECURITY CHECKS AND TRANSLATION ON BROKER HOST,PORT # $errmsg = <<"EOF"; $broker not found
$MYNAME doesn't know the broker

    $broker

Either it is not in the allowed list, or perhaps
$brokers is not readable.
EOF &fatal($errmsg) unless (@hostport = &get_host_port ($broker)); # HACKS FOR BROKEN LYNX BROWSER # $errors = 0 if ($errors eq 'None'); $errors = 1 if ($errors eq '1 Error'); $errors = 2 if ($errors eq '2 Errors'); # SET THE LIFETIME # $BQlife = $CFG{'Timeout'}; $BQlife = $lifetime + 300 if ($lifetime ne ""); alarm ($BQlife); ### # SANITY CHECKS ### # ### &fatal ('NoReplica') if ($host eq "No Replicas"); ### &fatal ('Misconfig') if ($host eq "" || $port == 0); # CHECK QUERY STRING FOR COMMON MISTAKES # $userquery =~ s/^\s+//; # remove leading whitespace $userquery =~ s/\s+$//; # remove trailing whitespace $userquery = &entities($userquery); # translate SGML entities unless ( $userquery =~ /\s+and\s+/i || $userquery =~ /\s+or\s+/i || $userquery =~ /:\s+/ || $userquery =~ /\"/) { if ($category ne "anytext") { @X = split (/\s+/, $userquery); for ($i=0; $i<=$#X; $i++) { # Support for + and - in front of search words - h.weinreich $X[$i] =~ s/^\+(.*)/$1/; # remove leading + $X[$i] =~ s/^\-(.*)/NOT $1/; # replace leading - by NOT $X[$i] =~ s/\,//; # remove commata: not supported by broker # put quotes around unknown characters $X[$i] = "\"$X[$i]\"" if ($X[$i] =~ /[^ \w\d-]/); $X[$i] = "$category:$X[$i]" if (($category ne "any") && ($category ne "")); } $userquery = join (' AND ', @X); } else { @X = split (/\s+/, $userquery); for ($i=0; $i<=$#X; $i++) { $X[$i] = "\"$X[$i]\"" if ($X[$i] =~ /\W/); $X[$i] = "(keywords:$X[$i] OR title:$X[$i] OR body:$X[$i] OR headings:$X[$i] OR address:$X[$i])" } $userquery = join (' AND ', @X); } } # BUILD QUERY STRING # $query = ""; $query .= $userclass . " AND " if ($userclass ne ""); $query .= $userquery; # BUILD BROKER QUERY # $bquery = "#USER"; $bquery .= " #opaque" if ($opaqueflag); $bquery .= " #desc" if ($descflag); $bquery .= " #index timeout $lifetime" if ($lifetime ne ""); $bquery .= " #index error $errors" if ($errors ne ""); $bquery .= " #index maxresult $maxresult" if ($maxresult ne ""); $bquery .= " #index maxfiles $maxfiles" if ($maxfiles ne ""); $bquery .= " #index maxlines $maxlines" if ($maxlines ne ""); $bquery .= " #index case"; $bquery .= $caseflag ? " insensitive" : " sensitive"; $bquery .= " #index matchword" if ($wordflag); $bquery .= " #index noregex" if ($noregexflag); $bquery .= $attributes; $bquery .= " #END "; $bquery .= $query; $bquery .= $filter; $simple_query = $1 if ($query =~ /^.*partial-text\s*:\s+"(.*)".*$/io); # Call Init Function # eval $CFG{'InitFunction'} if (defined ($CFG{'InitFunction'})); # DO THE QUERY # $html_query = &html_escape ($query); print &expand ($CFG{'ResultHeader'}); $connected = 0; # Split query into words for highlighting... $searchwords = &option('query'); $searchwords =~ s/-|\.|\,|;/ /g; $searchwords =~ s/ AND | OR | NOT |\(|\)|:|\"|\+/ /g; @searchwords = split(/ +/,$searchwords); &cleanup(); # remove outdated files... &do_query ($bquery, @hostport); exit 0; # END OF PROGRAM # ===== SUBROUTINES ========================================================== # Send the appropriate HTTP headers sub send_header { my $content = (defined $CFG{'ContentType'}) ? &expand($CFG{'ContentType'}) : "text/html"; $content .= "; charset=".$charset if ($charset ne ""); $|=1 if ($unbuffered); # use unbuffered output. if ($nph) { # $|=1; print "HTTP/1.0 200 OK\n"; print "Server: $ENV{SERVER_SOFTWARE}\n"; } print "Content-Type: $content\n\n"; } # broker_host_port: # # If given a name, return the corresponding (host,port) pair. # If given a host:port string, make sure it is a valid broker. sub get_host_port { local ($broker) = @_; local ($name,$host,$port); local ($pattern); local (@hostport) = (); if ($broker =~ /([^:]+):(\d+)/) { # given host:port $host = $1; $port = $2; return ($host,$port) unless ( -r $brokers ); $pattern = '\S+\s+' . $host . '\s+' . $port; } else { # given a name return () unless ( -r $brokers ); # cant translate name $broker_re = $broker; $broker_re =~ s/\W/\\$&/g; # escape specials $pattern = '^' . $broker_re . '\s+\S+\s+\d+'; } open (brokers) || &fatal ("$brokers: $!\n"); while () { chop; s/#.*//; # strip comments s/^\s+//; # leading whitespace s/\s+$//; # trailing whitespace next unless (/$pattern/io); ($name,$host,$port) = split; # found match push (@hostport, $host); # add host to array push (@hostport, $port); # add port to array } close brokers; return (@hostport); # not found } #### cleanup: remove old temporary files - h.weinreich sub cleanup { # (if $expire=12, then 12/24 sets a 12 hour expiration cycle) if (chdir "$tmp_dir/$broker") { foreach $file (<[a-z]*\.search>) { unlink "$file" if -M $file > $expire/24; } } } #### do_query: send query to broker and write it to a temporary file #### or read from temporary file if it already exists. sub do_query { local ($bquery, @hostport) = @_; local ($nobjects) = 0; local ($nopaquelines) = 0; # The temporary files search results are used as buffers to reduce requests # to the broker, e.g. necessary for splitted result pages - h.weinreich local ($filename) = &get_hashcode($bquery); # create filename for the buffer file $filename = "$tmp_dir/$broker/$filename.search"; local $tempfile_exists = 0; if (-e $filename) { # does temp file exist? open($INPUT, "<$filename") || &fatal ("Could not open file for read: $filename\n"); my $oldquery = (<$INPUT>); chop $oldquery; if ($oldquery eq $bquery) { # hash code OK $tempfile_exists = 1; # use tempfile as } else { close $INPUT; } } print "$tempfile_exists
" if ($debug); print "$filename
" if ($debug); if (!$tempfile_exists) { # tempfile does not exist: use broker as # read from broker and write to tempfile - h.weinreich while ($#hostport > $[) { $host = shift (@hostport); $port = shift (@hostport); if ($INPUT = &client_socket ($host, $port)) { $connected = 1; last; } } &broker_down ($host, $port) unless ($connected); print "Sending
 $bquery 
to $host:$port
\n" if ($debug); alarm(300); # after 5 minutes just kill it, CERN httpd won't do it # Send query to broker print $INPUT $bquery; # Open temporary file for output open(TEMPFILE, ">$filename") || \ &fatal ("Could not open temporary file for write: $filename\n"); # Write query string first print TEMPFILE "$bquery\n"; } # Read input and pre-format for output. while (<$INPUT>) { # print to tempfile if $INPUT is broker print TEMPFILE; if ($debug) { chop; print "|$_|\n"; } # Ignore status messages next if (/^200 -/o); # Read Broker homepage URL (see file admin/broker.conf) if (/^126 - (.*)$/o) { $hp_url = $1 if ($hp_url eq ''); next; } # Read relevant data into @OBJ if (/^120 -/o || /^103 -/o || /^111 -/o) { $OBJ[++$#OBJ] = $_; last if (/^103 -/o || /^111 -/o); } else { $OBJ[$#OBJ] .= $_; } } close ($INPUT) || &fatal ("socket: $!\n"); close TEMPFILE; $totnumber = (scalar (@OBJ)) - 1; # obtain the navigator bar $totalPages = $perpage == 0 ? 1 : ceil ($totnumber/$perpage); # set default page $page = 1 if ($page eq ''); eval $CFG{'CreateNavBars'}; print &expand ($CFG{'ResultSetBegin'}); # @OBJ = sort bynml @OBJ if ($sort eq 'by-NML'); &rank_objects(@OBJ,@RANK) if ($sort eq 'by-rank'); while (($curobj || (undef %SOIF,$curobj = shift(@OBJ))) && (($_, $curobj) = split(/\n/, $curobj, 2))) { # skip all lines before first match to be displayed if ($nobjects<=(($page-1)*$perpage) && $nobjects>0) { # New objects start with the URL of match. Here $nobjects is increased if (!(/^120 - (.*)$/o )) { $nopaquelines++ if (/^122 - (.*)$/o); next; } } # skip all lines after last match to be displayed next if ($nobjects>(($page)*$perpage) && !(/^103 - (.*)$/o )); # 120 - The URL of the match if (/^120 - (.*)$/o) { $url = $1; $url =~ /^([^:]+):\/*([^\/]+)(.*)$/o; $A = $1; $H = $2; $P = $3; @X = split ('/', $P); $F = pop (@X); $D = join ("/", @X, ""); $F = $url if ($url =~ /\/$/o); if ($A eq 'news') { $F = $H; $H = $P = $D = undef; } $objectnum = sprintf ($CFG{'ObjectNumPrintf'}, ++$nobjects); #print &expand ($CFG{'PrintUrl'}); next; } # 124 - The description line of the match if (/^124 - (\d+)/o) { $n = $1; next if ($n == 0); $desc = &html_escape (substr($curobj, 0, $n)); $curobj = substr($curobj, $n); # delete the data next; } # 127 - Attribute request if (/^127 - ([^\s]+) (\d+)/o) { $att = $1; $n = $2; next if ($n == 0); $SOIF{$att} = &html_escape (substr($curobj, 0, $n)); $curobj = substr($curobj, $n); # delete the data next; } # 122 - Opaque data if (/^122 - (.*)$/o) { $_ = &html_escape ($1); eval $CFG{'MatchedLineSub'}; $opaque .= $_ . "\n" if (/\S/); $nopaquelines++; next; } # 101 - message to the user if (/^101 - (.*)$/o) { $usermsg = $1; #print &expand ($CFG{'UserMessage'}); next; } # 103 - end of broker results # 111 - Error Message that ends Broker Results if (/^103 - (.*)$/o || /^111 - (.*)$/o) { $msg = $1; if ($msg =~ /PARSE ERROR/o) { if ($query eq '') { print &expand ($CFG{'NoQuery'}); } else { print &expand ($CFG{'ParseError'}); } } elsif ($msg =~ /heavily loaded/o) { print &expand ($CFG{'BrokerLoad'}); } elsif ($msg =~ /(\d+) Returned Object/o) { $nreturned = $1; print &expand ($CFG{'EndBrokerResults'}); print &expand ($CFG{'EmptySetWarning'}) if ($nreturned == 0); print &expand ($CFG{'TruncateWarning'}) if ($nopaquelines >= $maxresult || $nreturned == $maxfiles); } else { print &expand ($CFG{'FailBrokerResults'}); } print &expand ($CFG{'ResultSetEnd'}); print &expand ($CFG{'ResultTrailer'}); last; } # content summary URL if (/^125 - (.*)$/o) { $cs_url = $1; $cs_url =~ /^([^:]+):\/*([^\/]+)(.*)$/o; $cs_a = $1; $cs_h = $2; $cs_p = $3; @X = split ('/', $cs_p); $cs_f = pop (@X); $cs_d = join ("/", @X, ""); $cs_f = $cs_url if ($cs_url =~ /\/$/o); next; } # 130 - Object Ends, start a new one if (/^130/o) { $weight = $aWeight[$nobjects-1]; eval $CFG{'PerObjectFunction'} if (defined ($CFG{'PerObjectFunction'})); $attributes = ''; foreach $k (sort keys %SOIF) { $att = $k; $val = $SOIF{$k}; eval ($CFG{'PerAttributeFunction'}); $attributes .= &expand($CFG{'FormatAttribute'}) if ($att ne ''); # $att may be deleted in search.cf } print &expand ($CFG{'PrintObject'}); $weight = 0; $opaque = ""; $url = ""; $cs_url = ""; $desc = ""; %SOIF = (); next; } } } sub fatal { local ($key) = shift; local ($msg) = defined ($CFG{$key}) ? &expand ($CFG{$key}) : $key; print STDOUT "$msg"; if (open(BQLOG, ">>$BQLOG")) { my $now = localtime; printf BQLOG ("[%s] %s: %s", $now, $0, $msg); close BQLOG; } exit(1); } # Perform simple variable substitution. See Wall & Schwartz # 'Programming Perl' book, p 217. # sub expand2 { $_ = shift; s/"/\\"/g; eval qq/"$_"/; } # Gross regexp's to do some ifdef-type substitutions # Jonathan Rochkind, jrochkin@cs.oberlin.edu, 21 March 1996. # # Use: # abcxyz becomes abcxyz # abcxyz is deleted # # so the user can conditionally include a section of text by # enclosing it in .... The section will be # included if $var is not the null string. If a newline # follows the newline is removed. # sub expand { $_ = &expand2 (shift); # First get rid of everything of the form "Some Stuff" local($begin) = 0; local($end) = 0; while ($begin != -1) { # While there is still a $begin=index($_,''); # Find it's location. unless ($begin == -1) { # As long as we did find it.... # Find it's paired $end=index($_,'',$begin)+4; # Include the following \n too, if there... if (substr($_,$end,1) eq "\n") { ++$end; } # Now delete the whole " ... " substr($_,$begin,$end-$begin) = ""; } } # Now just delete _all_ tags of the form , # Don't even bother matching them up in pairs, # just delete them where you see em. s/<\?[^>]+>//g; # delete all s/<\/\?[^>]+>//g; # delete all s/\n\n*/\n/g; $_; } # escape special HTML characters sub html_escape { $_ = shift; s/\&/\&/g; # do ampersand first! s//\>/g; s/\&(\w+;)/\&$1/g; # try putting some HTML back &Circ; s/\&(#\d+;)/\&$1/g; # try putting some HTML back & s/"/\"/g; $_; } # create search URIs used for the navigation bars - by h.weinreich sub create_link { my $pageno = shift; my $searchurl = "/Harvest/cgi-bin/search.cgi"; $searchurl .= "?query=".url_encode($RQ{'query'}); $searchurl .= "&broker=".$RQ{'broker'} if ($RQ{'broker'} ne ''); $searchurl .= "&hp_url=".$RQ{'hp_url'} if ($RQ{'hp_url'} ne ''); $searchurl .= "&caseflag=".$RQ{'caseflag'} if ($RQ{'caseflag'} ne ''); $searchurl .= "&wordflag=".$RQ{'wordflag'} if ($RQ{'wordflag'} ne ''); $searchurl .= "&opaqueflag=".$RQ{'opaqueflag'} if ($RQ{'opaqueflag'} ne ''); $searchurl .= "&descflag=".$RQ{'descflag'} if ($RQ{'descflag'} ne ''); $searchurl .= "&maxresultflag=".$RQ{'maxresultflag'} if ($RQ{'maxresultflag'} ne ''); $searchurl .= "&maxobjflag=".$RQ{'maxobjflag'} if ($RQ{'maxobjflag'} ne ''); $searchurl .= "&maxlineflag=".$RQ{'maxlineflag'} if ($RQ{'maxlineflag'} ne ''); $searchurl .= "&weightflag=".$RQ{'weightflag'} if ($RQ{'weightflag'} ne ''); $searchurl .= "&perpageflag=".$RQ{'perpageflag'} if ($RQ{'perpageflag'} ne ''); $searchurl .= "&sort=".$RQ{'sort'} if ($RQ{'sort'} ne ''); $searchurl .= "&brokerqueryconfig=".$RQ{'brokerqueryconfig'} if ($RQ{'brokerqueryconfig'} ne ''); $searchurl .= "&attribute=".url_encode("@atts") if ("@atts" ne ''); foreach (@filter) { $searchurl .= "&filter=".url_encode($_); } $searchurl .= "&pageflag=$pageno" if ($pageno > 0); return $searchurl; } sub parse_config { local ($CF) = shift; local ($out) = 1; undef (local ($key)); undef (local ($val)); open (CF) || &fatal ("$CF: $!\n"); while () { next if (/^#/o); if ($out && /<(\w+)>/o) { $key = $1; $val = ""; $out = 0; next; } if (!$out && /<\/$key>/) { #eval ("\$$key=$val"); print "setting $key...\n" if ($debug); #print "to $val\n"; chop ($CFG{$key} = $val); undef ($val); undef ($key); $out = 1; next; } chop, chop if (/[^\\]\\$/o); # chop newline if esc'd $val .= $_ if (!$out && defined ($val)); } close CF; 1; } sub client_socket { local ($host, $port) = @_; local ($sockaddr) = 'S n a4 x8'; local ($name, $aliases, $proto) = getprotobyname('tcp'); local ($connected) = 0; # Lookup addresses for remote hostname # local($w,$x,$y,$z,@thataddrs) = gethostbyname($host); &fatal("Unknown Host: $host\n") unless (@thataddrs); # bind local socket to INADDR_ANY # local ($thissock) = pack($sockaddr, &AF_INET, 0, "\0\0\0\0"); &fatal("socket: $!\n") unless socket (SOCK, &AF_INET, &SOCK_STREAM, $proto); &fatal("bind: $!\n") unless bind (SOCK, $thissock); # Try all addresses # foreach $thataddr (@thataddrs) { local ($that) = pack($sockaddr, &AF_INET, $port, $thataddr); @IP = unpack('C4', $thataddr); printf ("Trying connection to %d.%d.%d.%d
\n", @IP) if ($debug); if (connect (SOCK, $that)) { $connected = 1; printf ("Connected to %d.%d.%d.%d!
\n", @IP) if ($debug); last; } } return () unless ($connected); # Set socket to flush-after-write and return it # select (SOCK); $| = 1; select (STDOUT); return (SOCK); } sub sigharddie { kill ('KILL', $$); } sub sigdie { local ($sig) = @_; &fatal ('sigdie'); } sub dump_array { local (%A) = @_; local ($key); print "
\n";
	foreach $key ( sort keys %A) {
	    print "$key=$A{$key}\n";
	}
	print "
\n"; 1; } sub broker_down { local($host, $port) = @_; print &expand ($CFG{'BrokerDown'}); &fatal("$host:$port: $!\n"); } sub not_configured { print <<"EOM"; Content-type: text/html $0 not properly installed WARNING: You have not installed the WWW interface to the Harvest Broker correctly. The \$HARVEST_HOME directory
        $ENV{'HARVEST_HOME'}
does not exist.

Please refer to the current Harvest User's Manual. EOM exit 0; } # Read defaults from .cf sub parse_defaults { local($attr,$value); local(@vars)=split(/\n/,$CFG{'Default'}); while (@vars) { ($attr,$value)=split(/:/,shift(@vars)); $attr=~s/\s//g; $value=~s/^\s*//; $DEF{$attr}=$value; } } sub option { local($attr)=@_[0]; return (defined $RQ{$attr} && $RQ{$attr} ne '') ? $RQ{$attr} : $DEF{$attr}; } # calculate from the query-string a (hopefully) unique string that can be used # as temporary filename... sub get_hashcode { my $querystring = shift; my $h = 0; my $t = 0; my $hashcode = ""; my $len = length($querystring); # calculate hash number from querystring for ($i = 0; $i < $len; $i++) { $h = 137*$h + ord(substr($querystring, $i, 1)); $h = sprintf ("%016.0f",$h); $h = substr($h,-15); } # convert hash-number to hash-string. while ($h > 1) { $t = ($h % 25) + 97; $hashcode .= pack("c",$t); $h = $h / 25; } return $hashcode; } ###################################################################### # Below is cgi.pl... # # The CGI_HANDLERS deal with basic CGI POST or GET method request # elements such as those delivered by an HTTPD form, i.e. a url # encoded line of "=" separated key=value pairs separated by &'s # Routines: # get_request: reads the request and returns both the raw and # processed version. # url_decode: URL decodes a string or array of strings # # Author: # James Tappin: sjt@xun8.sr.bham.ac.uk # School of Physics & Space Research University of Birmingham # Feb 1993. # # Copyright & Disclaimer. # This set of routines may be freely distributed, modified and # used, provided this copyright & disclaimer remains intact. # This package is used at your own risk, if it does what you # want, good; if it doesn't, modify it or use something else--but # don't blame me. Support level = negligable (i.e. mail bugs but # not requests for extensions) # Usage: # &get_request; will get the request and decode it into an # indexed array %rqpairs, the raw request is in # $request # # ... = &url_decode(LIST); will return a URL decoded version of # the contents of LIST # sub get_request { # Subroutine get_request reads the POST or GET form request from STDIN # into the variable $request, and then splits it into its # name=value pairs in the associative array %rqpairs. # The number of bytes is given in the environment variable # CONTENT_LENGTH which is automatically set by the request generator. # Encoded HEX values and spaces are decoded in the values at this # stage. # $request will contain the RAW request. N.B. spaces and other # special characters are not handler in the name field. if ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN, $request, $ENV{'CONTENT_LENGTH'}); } elsif ($ENV{'REQUEST_METHOD'} eq "GET" ) { $request = $ENV{'QUERY_STRING'}; } @F = split(/[&=]/, $request); &url_decode(@F); } sub url_decode { # Decode a URL encoded string or array of strings # + -> space # %xx -> character xx foreach (@_) { tr/+/ /; s/%(..)/pack("c",hex($1))/ge; } # This gross stuff handles multiply defined attributes. For example: # foo=abc&foo=xyz # comes back as # $RQ{'foo'} eq 'abc\0xyz' # Otherwise, the previous method just gave us one or the other. -DW&HW # local ($k, $v); local (%Y); while (($k=shift @_) ne '' && ($#_ >= 0)) { $v=shift @_; $Y{$k} = defined $Y{$k} ? join ("\0", $Y{$k}, $v) : $v; } %Y; } ###################################################################### # encode special characters for urls - h.weinreich sub url_encode { $_ = shift; s/([^a-zA-Z0-9\%\ \-\+])/uc('%'.unpack("H*", $1))/eg; s/ /+/g; return $_; } # sort the objects by number of matched lines # #sub bynml { # split(/\n/, $b) <=> split(/\n/, $a); # number of lines in object #} # sort the objects by "rank". This is based on code from Wesley Alan Wright # modified and optimized by h.weinreich sub rank_objects { local (@objects) = @_; local ($objnum) = 0; local (%ratings); local (@object_index); foreach $object (@objects) { $rank = 1; $lastline = ""; # don't count repeated lines split(/\n/,$object); foreach $line (@_) { # $rawline=$line; $line =~ s/\s{2,}/ /g; # Remove multiple whitespaces $line =~ s/(Matched line: )(.*)$/$2/; $line =~ s/^(.*)\{(\d+)\}(.*)$/$1$3/; # Remove "{12}:" etc. # if (open(BQLOG, ">>$BQLOG")) { # printf BQLOG ($rawline."\n"); # printf BQLOG ($line."\n"); # printf BQLOG ($rank."\n"); # close BQLOG; # } if ($line ne $lastline) { #the domain name is often highly significant... if (index($line,"url#") > 0) { $temphost = $line; $temphost =~ s/^(.*)\/\/(([a-z]|\.|-)*)\/(.*)$/$2/; $rank += 700 if (index($temphost,$query) > 0 ); } #title is most important $rank += 600 - length($line)*3 if (index($line,"title#") > 0 && length($line) < 200); #url is also important $rank += 150 - length($line)/1.5 if (index($line,"url#") > 0 && length($line) < 200); #there are some very long headings... $rank += 110 - length($line)/2 if (index($line,"headings#") > 0 && length($line)<200); # used for PDF, PS etc. $rank += 80 - length($line)/3 if (index($line,"description#") > 0 && length($line)<200); $rank += 60 if (index($line,"subject#") > 0); $rank += 30 if (index($line,"images#") > 0); # these are sometimes too long. $rank += 25 - length($line)/8 if (index($line,"url-references#") > 0 ); # somehow redundant, not as important as it may seem... $rank += 20 if (index($line,"keywords#") > 0); $rank += 10 if (index($line,"body#") > 0); # Consider length of URL of every Page $rank += 400 - length($line)*2 if (index($line,"120 - ") == 0 && length($line) < 200); } $lastline = $line; } $ratings{$objnum} = $rank; $object_index[$objnum] = $objnum; $objnum++; } # pop off the "126" and "103" results (they're at the end) pop(@object_index); pop(@object_index); @sorted_index= sort byrate @object_index; $objnum=0; # the first element has the highest weight $maxWeight = $ratings{$sorted_index[0]}; foreach $index (@sorted_index) { $OBJ[$objnum] = $objects[$index]; $aWeight[$objnum] = $ratings{$index}; # save weight of each object $objnum++; } } sub byrate { $ratings{$b} <=> $ratings{$a}; } # translate SGML entities # produced from # perl -ne 'if (/^ *<.ENTITY *([\S]*) *CDATA *"&#([\d]+);".*>/) # { if ($2 <0x100) {printf "s/&$1;/\\x%x/g;\n", $2;} # else {printf "s/&$1;/\\\\#$2/g;\n";}}' sub entities { $_ = $_[0]; # ISO Latin 1 s/ /\xa0/g; s/¡/\xa1/g; s/¢/\xa2/g; s/£/\xa3/g; s/¤/\xa4/g; s/¥/\xa5/g; s/¦/\xa6/g; s/§/\xa7/g; s/¨/\xa8/g; s/©/\xa9/g; s/ª/\xaa/g; s/«/\xab/g; s/¬/\xac/g; s/­/\xad/g; s/®/\xae/g; s/¯/\xaf/g; s/°/\xb0/g; s/±/\xb1/g; s/²/\xb2/g; s/³/\xb3/g; s/´/\xb4/g; s/µ/\xb5/g; s/¶/\xb6/g; s/·/\xb7/g; s/¸/\xb8/g; s/¹/\xb9/g; s/º/\xba/g; s/»/\xbb/g; s/¼/\xbc/g; s/½/\xbd/g; s/¾/\xbe/g; s/¿/\xbf/g; s/À/\xc0/g; s/Á/\xc1/g; s/Â/\xc2/g; s/Ã/\xc3/g; s/Ä/\xc4/g; s/Å/\xc5/g; s/Æ/\xc6/g; s/Ç/\xc7/g; s/È/\xc8/g; s/É/\xc9/g; s/Ê/\xca/g; s/Ë/\xcb/g; s/Ì/\xcc/g; s/Í/\xcd/g; s/Î/\xce/g; s/Ï/\xcf/g; s/Ð/\xd0/g; s/Ñ/\xd1/g; s/Ò/\xd2/g; s/Ó/\xd3/g; s/Ô/\xd4/g; s/Õ/\xd5/g; s/Ö/\xd6/g; s/×/\xd7/g; s/Ø/\xd8/g; s/Ù/\xd9/g; s/Ú/\xda/g; s/Û/\xdb/g; s/Ü/\xdc/g; s/Ý/\xdd/g; s/Þ/\xde/g; s/ß/\xdf/g; s/à/\xe0/g; s/á/\xe1/g; s/â/\xe2/g; s/ã/\xe3/g; s/ä/\xe4/g; s/å/\xe5/g; s/æ/\xe6/g; s/ç/\xe7/g; s/è/\xe8/g; s/é/\xe9/g; s/ê/\xea/g; s/ë/\xeb/g; s/ì/\xec/g; s/í/\xed/g; s/î/\xee/g; s/ï/\xef/g; s/ð/\xf0/g; s/ñ/\xf1/g; s/ò/\xf2/g; s/ó/\xf3/g; s/ô/\xf4/g; s/õ/\xf5/g; s/ö/\xf6/g; s/÷/\xf7/g; s/ø/\xf8/g; s/ù/\xf9/g; s/ú/\xfa/g; s/û/\xfb/g; s/ü/\xfc/g; s/ý/\xfd/g; s/þ/\xfe/g; s/ÿ/\xff/g; # Cougar HTMLsym s/ƒ/\\#402/g; s/Α/\\#913/g; s/Β/\\#914/g; s/Γ/\\#915/g; s/Δ/\\#916/g; s/Ε/\\#917/g; s/Ζ/\\#918/g; s/Η/\\#919/g; s/Θ/\\#920/g; s/Ι/\\#921/g; s/Κ/\\#922/g; s/Λ/\\#923/g; s/Μ/\\#924/g; s/Ν/\\#925/g; s/Ξ/\\#926/g; s/Ο/\\#927/g; s/Π/\\#928/g; s/Ρ/\\#929/g; s/Σ/\\#931/g; s/Τ/\\#932/g; s/Υ/\\#933/g; s/Φ/\\#934/g; s/Χ/\\#935/g; s/Ψ/\\#936/g; s/Ω/\\#937/g; s/α/\\#945/g; s/β/\\#946/g; s/γ/\\#947/g; s/δ/\\#948/g; s/ε/\\#949/g; s/ζ/\\#950/g; s/η/\\#951/g; s/θ/\\#952/g; s/ι/\\#953/g; s/κ/\\#954/g; s/λ/\\#955/g; s/μ/\\#956/g; s/ν/\\#957/g; s/ξ/\\#958/g; s/ο/\\#959/g; s/π/\\#960/g; s/ρ/\\#961/g; s/ς/\\#962/g; s/σ/\\#963/g; s/τ/\\#964/g; s/υ/\\#965/g; s/φ/\\#966/g; s/χ/\\#967/g; s/ψ/\\#968/g; s/ω/\\#969/g; s/ϑ/\\#977/g; s/ϒ/\\#978/g; s/ϖ/\\#982/g; s/•/\\#8226/g; s/…/\\#8230/g; s/′/\\#8242/g; s/″/\\#8243/g; s/‾/\\#8254/g; s/⁄/\\#8260/g; s/℘/\\#8472/g; s/ℑ/\\#8465/g; s/ℜ/\\#8476/g; s/™/\\#8482/g; s/ℵ/\\#8501/g; s/←/\\#8592/g; s/↑/\\#8593/g; s/→/\\#8594/g; s/↓/\\#8595/g; s/↔/\\#8596/g; s/↵/\\#8629/g; s/⇐/\\#8656/g; s/⇑/\\#8657/g; s/⇒/\\#8658/g; s/⇓/\\#8659/g; s/⇔/\\#8660/g; s/∀/\\#8704/g; s/∂/\\#8706/g; s/∃/\\#8707/g; s/∅/\\#8709/g; s/∇/\\#8711/g; s/∈/\\#8712/g; s/∉/\\#8713/g; s/∋/\\#8715/g; s/∏/\\#8719/g; s/∑/\\#8721/g; s/−/\\#8722/g; s/∗/\\#8727/g; s/√/\\#8730/g; s/∝/\\#8733/g; s/∞/\\#8734/g; s/∠/\\#8736/g; s/∧/\\#8869/g; s/∨/\\#8870/g; s/∩/\\#8745/g; s/∪/\\#8746/g; s/∫/\\#8747/g; s/∴/\\#8756/g; s/∼/\\#8764/g; s/≅/\\#8773/g; s/≈/\\#8776/g; s/≠/\\#8800/g; s/≡/\\#8801/g; s/≤/\\#8804/g; s/≥/\\#8805/g; s/⊂/\\#8834/g; s/⊃/\\#8835/g; s/⊄/\\#8836/g; s/⊆/\\#8838/g; s/⊇/\\#8839/g; s/⊕/\\#8853/g; s/⊗/\\#8855/g; s/⊥/\\#8869/g; s/⋅/\\#8901/g; s/⌈/\\#8968/g; s/⌉/\\#8969/g; s/⌊/\\#8970/g; s/⌋/\\#8971/g; s/⟨/\\#9001/g; s/⟩/\\#9002/g; s/◊/\\#9674/g; s/♠/\\#9824/g; s/♣/\\#9827/g; s/♥/\\#9829/g; s/♦/\\#9830/g; # Cougar HTMLmisc s/"/\x22/g; s/&/\x26/g; s/</\x3c/g; s/>/\x3e/g; s/Œ/\\#338/g; s/œ/\\#339/g; s/Š/\\#352/g; s/š/\\#353/g; s/Ÿ/\\#376/g; s/ˆ/\\#710/g; s/˜/\\#732/g; s/ /\\#8194/g; s/ /\\#8195/g; s/ /\\#8201/g; s/‌/\\#8204/g; s/‍/\\#8205/g; s/‎/\\#8206/g; s/‏/\\#8207/g; s/–/\\#8211/g; s/—/\\#8212/g; s/‘/\\#8216/g; s/’/\\#8217/g; s/‚/\\#8218/g; s/“/\\#8220/g; s/”/\\#8221/g; s/„/\\#8222/g; s/†/\\#8224/g; s/‡/\\#8225/g; s/‰/\\#8240/g; s/‹/\\#8249/g; s/›/\\#8250/g; return($_); } sub ceil { my $x = shift; $x += 1.0 unless ($x == int($x)); return int($x); }