#!/usr/local/bin/perl -Tw # # Acknowledgements # # Thanks to # Paul Clark's aglimpse program # paul@cs.arizona.edu # which was the starting point for this program. # # Written by: # Michael Smith # msmith@cs.arizona.edu # # Modifications # # 4/13/96 Version 1.0, original # # Modifications # We translate the local file name to URL and grab the title in # this script, instead of start 2 "glimpse -k" sessions. # 06/28/96 ZDC # # 9/96 # In-lined a lot of "require"d code for speed # Tried to optimize output of information # # 9/97 # Security fixes and added "no local copy link" option --GB # # 5/98 # Added check for optional modules. Customized output. --GB # # 7/98 # Added module that will perform cookie-based caching of result set # in order to return "Next n hits" --GB # ####################################################################### # **** **** **** **** CONFIGURABLE VARIABLES **** **** **** **** # We need some of these to find our libraries, so wrap them in a BEGIN block BEGIN{ $WEBGLIMPSE_HOME = "/home/bugs/bugstopper-www"; $GLIMPSE_LOC = "/home/bugs/bugstopper-www/glimpse-4.1-bin-Linux-2.0.30-i486/bin/glimpse"; # $GREP_LOC = $GLIMPSE_LOC; # lib directory $WEBGLIMPSE_LIB = "$WEBGLIMPSE_HOME/lib"; # Path to your scripts $CGIBIN = "cgi-bin"; # Maximum characters to print from META NAME="DESCRIPTION" tag $MAX_METADESC_LEN = 200; # One of these output modules must exist $CUSTOM_OUTPUT = "$WEBGLIMPSE_LIB/CustomOutputTool.pm"; $DEFAULT_OUTPUT = "$WEBGLIMPSE_LIB/OutputTool.pm"; # Optional result-caching module $RESULT_CACHE = "$WEBGLIMPSE_LIB/ResultCache.pm"; } # **** **** **** **** bugstopper.com header and footer **** **** **** **** $header = " Search Results - Advanced Services - Pest Control - Termite Control - Serving the Central Savannah River Area
\"Advanced
Search
\"Serving
\"BugStopper.com\"
Search Results
HERE ARE THE RESULTS OF YOUR SEARCH"; $footer = "
 
© Advanced Services for Pest Control, Inc.* All Rights Reserved
Powered by Brasch Consulting Services, LLC
"; # **** **** **** **** NO CONFIGURATION NEEDED BELOW **** **** **** **** # glimpse occasionally needs to invoke some system programs, like cat, sort, # and mv. Set up a path so it can find them. If you don't like this, # edit the file index/glimpse.h in the glimpse source hierarchy to hard-code # paths for SYSTEM_CAT and its companions, then set the PATH here to a # benign location. (Beware: using an empty path '' with GNU libc, as on # Linux, is equivalent to using '.'). $ENV{'PATH'} = '/bin:/usr/bin'; # lock file $LOCKFILE = "indexing-in-progress"; # Use cache flag $USE_CACHE = 0; # If you want per-line access $FSSERV = "/$CGIBIN/mfs" ; # Set file name pattern where to suppress HTML tags # Comment out to cancel suppression $SUPPRESS_HTML_TAGS = "\\.s?html?\$"; # $MAPFILE = ".wgmapfile"; $nh_pre = ".nh."; # $CONFIGFILE, $REMOTEDIR not used. commented out 11/5/97 --GB # name of config file #$CONFIGFILE = "archive.cfg"; #$REMOTEDIR = ".remote"; # Default values for user inputs $QS_age = ''; # Restrict matches to updates in the last $QS_age days $QS_case = ''; # Case-sensitive if set to 'on' $QS_debug = ''; # Debug on/off $QS_errors = ''; # Number of errors allowed in a match, or 'Best match' $QS_file = ''; # File to search neighborhood of $QS_lines = ''; # Print line numbers & enable jump to line option # Local copy links eliminated 1/7/98 in version 1.6 --GB #$QS_localcopy = ''; # Print "local copy" links in output (Added 9/97 --GB) $QS_maxfiles = ''; # Maximum number of files to print matches from $QS_maxlines = ''; # Maximum number of lines per file to print matches from $QS_maxchars = ''; # Maximum number of characters to print (in case file has no line breaks) $QS_pathinfo = ''; # Path to index dir; not in wgindex.html by default $QS_query = ''; # WHAT YOU ARE SEARCHING FOR $QS_scope = ''; # Full archive search or neighborhood only $QS_whole = ''; # Whole or partial word search $QS_filter = ''; # Restrict the search to files matching QS_filter (Added 11/5/97 --GB) # Added optional module to support result caching $QS_cache = ''; # **** **** **** **** Done settings **** **** **** **** BEGIN { # make the output as we can $| = 1; # might as well start the message now print "Content-type: text/html\n\n"; print "\n"; } $errstring = ''; # Include libraries right at the start, before the rest of the code compiles. BEGIN { #--------------------------------- # make my libraries more important unshift(@INC, "$WEBGLIMPSE_LIB"); require "config.pl"; # Check for custom modules below, after we get user variables } ### DEBUG # $other, $starthour are unused #($startsec, $startmin, $starthour, $other) = localtime(time); # Get inputs now so we can use a user-set path # $prefix appears to be unused. Commented out 11/5/97 --GB # To support an ISINDEX type search, set query string if given # an argument on the command line #$prefix="whole=on&case=off&query=" if ( $#ARGV >= 0 ); # Check that a query has been made ($query = $ENV{'QUERY_STRING'}) || &err_noquery ; # Strip the variables out from the query string, # and assign them into variables, prefixed by 'QS_' foreach $pspec (split (/\&/, $query)) { $pname = ''; $pvalue = ''; ($pname, $pvalue) = (split (/=/, $pspec)); # Decode form results (hex characters, spaces etc) $pvalue = www_form_urldecode($pvalue); $pname = www_form_urldecode($pname); if ($pname =~ /^[a-zA-Z0-9_]*$/ ) { # We should do this quote removal only for variables that will be placed on a command line # $pvalue =~ s/\'//g; $varname = "QS_$pname"; $$varname = $pvalue; } } $QS_pathinfo =~ s/%2f/\//ig; $path_info = ($QS_pathinfo ne "") ? $QS_pathinfo : $ENV{'PATH_INFO'}; $_ = $path_info; $indexdir = $path_info; # Check that indexdir has no single quote characters; it will be used on a command line $indexdir =~ s/[\']//g; # Added check for ".." as per CERT 11/7/97 --GB if ($indexdir =~ /\.\./) { &err_insecurepath; } if(-e "$indexdir/$LOCKFILE"){ &err_locked; } if(&TestConfig($indexdir)!=2){ &err_conf; } # Unused variables: $explicit_only, $remote_limit, $local_limit, $addboxes, $numhops, $nhhops, $traverse_type, @urllist = () - 11/5/97 --GB # Initialize just to make perl -Tw happy $explicit_only = 0; $remote_limit = 0; $local_limit = 0; $addboxes = 0; $numhops = 0; $nhhops = 0; $traverse_type = 0; $urlpath = ''; @urllist = (); ($title, $urlpath, $traverse_type, $explicit_only, $numhops, $nhhops, $local_limit, $remote_limit, $addboxes, @urllist) = ReadConfig($indexdir); # Ensure that Glimpse is available on this machine -x $GLIMPSE_LOC || &err_noglimpse($GLIMPSE_LOC) ; # Ensure that index is available -r "$indexdir/.glimpse_index" || &err_noindex($indexdir) ; # resubstitute / for %2F in the file paths $QS_file =~ s/%2f/\//ig; $QS_query =~ s|\+| |g; $QS_query =~ s|%(\w\w)|sprintf("%c", hex($1))|ge; $pquery = $QS_query; $QS_query =~ s|\'|\'\"\'\"\'|g; $OPT_errors=''; $OPT_errors="-$QS_errors" if $QS_errors =~ /^[0-8]$/; $OPT_errors="-B" if $QS_errors =~ /^Best\+match$/; # remove the '-i' from case if the switch is on $OPT_case="-i"; $OPT_case="" if $QS_case =~ /^on$/; $OPT_whole = ''; $OPT_whole="-w" unless $QS_whole =~ /^on$/; $OPT_age = ''; $OPT_age = "-Y $QS_age" if $QS_age =~ /^[0-9]+$/; # print "OPT_age = $OPT_age
\n"; $QS_filter =~ s/\./\\./g; $QS_filter =~ s/\'//g; $OPT_filter = ''; $OPT_filter="-F '$QS_filter'" if $QS_filter; # Added optional caching option # cache="[cachefile]" for next n hits or cache="yes" for first time $USE_CACHE = 0; $cachefile = ''; if ($QS_cache ne '') { if ($QS_cache eq 'yes') { $USE_CACHE = 2; } else { $USE_CACHE = 1; $cachefile = $QS_cache; } } if ($QS_maxlines =~ /\d+/) { $maxlines = $&; } else { $maxlines = 1; } if ($QS_maxchars =~ /\d+/) { $maxchars = $&; } else { $maxchars = 500; } if ($QS_maxfiles =~ /\d+/) { $maxfiles = $&; } else { $maxfiles = 25; } $highlight = $QS_query; $highlight =~ s/^\W+//; $highlight = join("|",split(/\W+/,$highlight)); # check if the query contains any words &err_badquery if !$highlight; $highlight = '\b('.$highlight.')\b' if $OPT_whole; # if the scope is full, delete any file options if($QS_scope =~ /^full$/i){ $QS_file=""; } $title = ''; $metadesc = ''; if($QS_file){ ($title, $metadesc) = &lookup_titledesc($QS_file); if ($title eq "No Title") { $title=$QS_file; } else { if($title eq ""){ $title=$QS_file; } } # $fullfile = "$indexdir/$QS_file"; $fullfile = $QS_file; # it might not be in a subdir of the archivepwd # modify the file name to include the .nh. # prepend the file name with nh_pre $fullfile =~ s/([^\/]+)$/$nh_pre$1/; #$OPT_file = "-f $fullfile"; Changed to -p --> bgopal oct/6/96 $OPT_file = "-p $fullfile:0:0:2"; if(!(-e $fullfile)){ &err_noneighborhood($fullfile); } }else{ $OPT_file = ""; } # Try using -H switch instead of chdir, as per Peter Bigot's suggestion. GB 10/17/97 #chdir $indexdir; # the default is *no* jump to lines. If line=on, tell glimpse to get lines $OPT_linenums = ''; if($QS_lines){ $OPT_linenums="-n"; } ##### At this point we have all the options chosen by the user ######## # **** **** **** **** CHECK FOR CUSTOM MODULES **** **** **** **** # # Currently recognized: CustomOutputTool, OutputTool, CacheResults # # Do not import symbols; all module functions are called explicitly # $HAVE_CUSTOM_OUTPUT = 0; if ( -e $CUSTOM_OUTPUT) { require $CUSTOM_OUTPUT; $mOutput = new CustomOutputTool($indexdir,$QS_query,$title); $HAVE_CUSTOM_OUTPUT = 1; } elsif (-e $DEFAULT_OUTPUT) { require $DEFAULT_OUTPUT; $mOutput = new OutputTool; } else { print "Sorry, no output modules seem to be installed.\n"; print "More information has been printed to the web server's error log.\n"; $errstring = "You need at least one of \n$CUSTOM_OUTPUT, \n$DEFAULT_OUTPUT\n"; $errstring .= "Check your distribution to make sure one these files is included.\n\n"; die $errstring; } if ( -e $RESULT_CACHE) { require $RESULT_CACHE; $mCache = new ResultCache; } else { $mCache = undef; } # **** **** **** **** **** **** **** **** **** **** **** **** **** # Get the search results. They may be cached, or we have to call glimpse if (($USE_CACHE == 1) && $mCache) { $mCache->LoadResult(*glines,$cachefile, $maxfiles); } else { # Security note: using $indexdir on the command line could be dangerous if a directory really exists whose name contains shell control characters. 10/17/97 --GB #$cmd = "$GLIMPSE_LOC -j -z -y $OPT_file $OPT_linenums $OPT_age $OPT_case $OPT_whole $OPT_errors -H . " . Added -U -W --> bgopal oct/6/96 $cmd = "$GLIMPSE_LOC -U -W -j -z -y $OPT_file $OPT_linenums $OPT_age $OPT_case $OPT_whole $OPT_errors -H $indexdir " . "$OPT_filter '$QS_query' 2>&1 |"; # Fool perl -T into accepting $cmd for execution. (as per Peter Bigot) --GB 10/17/97 # We assume that we have sufficiently checked the parameters to be safe at this point. $cmd =~ /^(.*)$/; $cmd = $1; ### DEBUG # print "
start time: $starthour:$startmin:$startsec
\n"; # $utime = (times)[0]; # $stime = (times)[1]; # print "
time after init: $utime, $stime
\n"; # ($sec, $min, $hour, $other) = localtime(time); # print "
now (after init): $hour:$min:$sec
\n"; # print "\n"; # Save pid of the pipe command so we can do cleanup later. if (!($gpid = open(GOUT, $cmd ))) { &err_noglimpse($cmd); } @glines = ; close(GOUT); # check the return code $rc = $? >> 8; if($rc!=0){ # it's an error! &err_badglimpse(@glines); } # Save results to cache file if we are doing caching and there are more than maxfiles hits if (($USE_CACHE == 2)&&($mCache)&&($#glines >= $maxfiles)) { $cachefile = $mCache->SaveResult($maxfiles,*glines); } } # Create the initial output and print it # This is now done by an object, mOutput. # Depending on what class mOutput is, the content may be determined differently. #print $mOutput->makeInitialOutput($pquery, $title, $QS_file, $QS_lines); print $header; if($QS_debug){ print "
cmd: $cmd
\n"; } ### DEBUG # $utime = (times)[0]; # $stime = (times)[1]; # print "
time after glimpse: $utime, $stime
\n"; # ($sec, $min, $hour, $other) = localtime(time); # print "
now (after glimpse): $hour:$min:$sec
\n"; $prevfile = ""; $lcount = 0; $fcount = 0; # Added "line:" label; should fix ignore maxlines bug --GB 7/24/97 line: foreach $line (@glines) { $_ = $line; if($QS_debug){ print "
glimpse: $_
\n"; } # Date output is from glimpse source file agrep/agrep.c:aprint_file_time. # Without matching it exactly, we too often screw up recognizing # whether or not there's a title column, since glimpse drops that # output when it can't find a document title. # Current format is: Mon day FullYear (mmm [d]d yyyy) if($QS_lines){ # look for line number, too (/^(\S+)\s+(\S+)\s*(([^\\:]|\\:|\\\\)*):\s*(\w\w\w\s+\d+\s+\d\d\d\d):\s*(\d+)\s*:(.*)/) || next; $file = $1; $link=$2; $title=$3; $date = $5; $line = $6; $string = $7; }else{ (/^(\S+)\s+(\S+)\s*(([^\\:]|\\:|\\\\)*):\s*(\w\w\w\s+\d+\s+\d\d\d\d):(.*)/) || next; $file = $1; $link = $2; $title = $3; $date = $5; $string = $6; } # Strip trailing spaces from title, as per M. Ernst. --1/28/98 GB $title =~ s/\s+$//; # Removed local copy pointers --GB 1/7/98 # ##### CHANGE FOR LOCAL COPY POINTERS -- mdsmith # # modify the local file to get the localurl # $localurl = $file; # $localurl =~ s/$indexdir/$urlpath/; # ##### END CHANGE FOR LOCAL COPY POINTERS -- mdsmith if($QS_debug){ print "
Webglimpse: file=$file link=$link title=$title date=$date line=$line string=$string
\n"; } # replace the \:'s and \\'s in the title with just :'s $title =~ s/\\\\/\\/g; $title =~ s/\\:/:/g; # skip the file if it isn't in this index directory directory ### commented out! # next unless $file =~ s|^$indexdir||o; # skip if the file is a .gh or .glimpse file next if ($file =~ /\.gh/) || ($file =~ /\.glimpse_/); if ($file ne $prevfile) { $linecount = 0; $charcount = 0; if ($fcount>=$maxfiles) { print $mOutput->limitMaxFiles($maxfiles); $file = ""; # Keep the real # of lines retrieved! The "at least" message can be in the output module. # $fcount = "at least $fcount"; # $lcount = "at least $lcount"; last line; } print $mOutput->{end_file_marker} if ( $prevfile ne "" ); $prevfile = $file ; if($title eq "No Title") { $title = $link; } else { if($title eq ""){ $title = $link; } } # Removed Local Copy Pointers --GB 1/7/98 (We still use the local copy for jump-to-line) # ##### CHANGE FOR LOCAL COPY POINTERS -- mdsmith # ##### Change to include OPTION for local copy pointers --GB 9/17/97 ##JUST TO AVOID WARNINGS $QS_localcopy = ''; # if ($QS_localcopy eq 'n') { print $mOutput->makeLinkOutput($link,$title,$date); #print $mOutput->makeLinkOutput($link,$title,""); # } else { # print # "
",$title,"", # ", (local copy), $date
" ; # } # ##### END CHANGE FOR LOCAL COPY POINTERS -- mdsmith # Added META description if exists, as per Darryl Fuller's suggestion. --GB 7/24/97 # print $mOutput->makeStartFileDesc($metadesc); $fcount++ ; } $lcount++ ; $linecount++; if ($linecount>$maxlines) { # print "
  • Limit of $maxlines matched " . # "lines per file exceeded...\n" if # $linecount==$maxlines && $maxlines > 0; # # print $mOutput->limitMaxLines($maxlines) if $linecount==($maxlines+1) && $maxlines > 0; next line; } # if ($charcount >= $maxchars) { # print "***\n"; # next line; # } if ($SUPPRESS_HTML_TAGS && $file =~ /$SUPPRESS_HTML_TAGS/o) { $string =~ s#\]*\>?##g; } else { # we shouldn't suppress tags, but we need to do basic # substitutions $string =~ s/\&/\&/g; $string =~ s/\/\>/g; } if($string !~ /^\s*$/){ if($QS_lines){ if ($charcount + length($string) >$maxchars) { $string = substr($string,0,$maxchars - $charcount - length($string)); } # BOLDING if ($OPT_case) { $string =~ s#$highlight#$&#gio; } else { $string =~ s#$highlight#$&#go; } # unused $length = length($indexdir); # Added $link as argument for use in BASE HREF tag # Trim spaces from $line as per Jan Holler. 10/17/97 --GB $line =~ s/\ //g; $linkto = "$FSSERV$indexdir\?link=$link&file=$file&line=$line#mfs"; print $mOutput->makeJumpToLine($linkto, $line, $string); }else{ if ($charcount + length($string) >$maxchars) { $string = substr($string,0,$maxchars - $charcount - length($string)); } # BOLDING if ($OPT_case) { $string =~ s#$highlight#$&#gio; } else { $string =~ s#$highlight#$&#go; } print $mOutput->makeLine($string); } } $charcount += length($string); } # If we jumped out because of max files, we already printed the necessary ending codes # otherwise, do it now. ($fcount < $maxfiles) && print $mOutput->makeEndHits($file); if (($fcount >= $maxfiles) && $USE_CACHE && $mCache && $HAVE_CUSTOM_OUTPUT) { print $mOutput->makeNextHits($indexdir, $cachefile, $QS_query, $maxfiles, $maxlines, $maxchars); } if ($HAVE_CUSTOM_OUTPUT) { print $mOutput->makeNewQuery($indexdir, $maxfiles, $maxlines, $maxchars, $QS_file,$QS_lines,$QS_age,$QS_case, $QS_whole,$QS_errors, $QS_filter, $QS_query); } #print $mOutput->makeFinalOutput($QS_query, $lcount, $fcount); print $footer; ### DEBUG # $utime = (times)[0]; # $stime = (times)[1]; # $ctime = (times)[1]; # $cstime = (times)[1]; # print "

    time after formatting: $utime, $stime, $ctime, $cstime
    \n"; # ($sec, $min, $hour, $other) = localtime(time); # print "
    now: $hour:$min:$sec
    \n"; unlink "/tmp/.glimpse_tmp.$gpid"; exit(0); ########################################################################## sub www_form_urldecode { # Added 10/18/97 as per Peter Bigot --GB local($_) = @_; # Reverse the encoding: plus goes to space, then unhex encoded chars s/\+/ /g; s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge; return $_; } ########################################################################## sub diag_exit { # exit on error exit -1; } ########################################################################## sub err_noneighborhood { local($_) = @_; # neighborhood does not exist print <

    File not found

    There is no neighborhood for file $_. Either the file does not exist or the neighborhood file does not exist. EOM &diag_exit; } ########################################################################## sub err_noquery { # The script was called without a query. # Provide an ISINDEX type response for browsers # without form support. print " Glimpse Gateway

    Glimpse Gateway

    This is a gateway to Glimpse. Type a pattern to search in your browser's search dialog.

    What is Glimpse ?

    Glimpse (which stands for GLobal IMPicit SEarch) is an indexing and query system that allows you to search through all your files very quickly. For example, a search for Schwarzkopf allowing two misspelling errors in 5600 files occupying 77MB took 7 seconds on a SUN IPC. Glimpse supports most of agrep's options (agrep is our powerful version of grep) including approximate matching (e.g., finding misspelled words), Boolean queries, and even some limited forms of regular expressions.
    Glimpse's running time is typically slower than systems tems using inverted indexes, but its index is an order of magnitude smaller (typically 2-5% of the size of the files).

    Authors of Glimpse

    Udi Manber, Sun Wu, and Burra Gopal
    Department of Computer Science, University of Arizona, Tucson, AZ 85721.
    glimpse\@cs.arizona.edu

    Glimpse
    glimpse\@cs.arizona.edu
    "; &diag_exit; } ########################################################################## sub err_noglimpse { local($_) = @_; # # Glimpse was not found # Report a useful message # print " Glimpse not found

    Glimpse not found

    Using $_

    This gateway relies on Glimpse search tool. If it is installed, please set the correct path in the script file. Otherwise obtain the latest version from ftp.cs.arizona.edu "; &diag_exit; } ########################################################################## sub err_badglimpse { my(@glines) = @_; # # Glimpse had an error # Report a useful message # print " Glimpse error

    Glimpse error

    The search parameters caused an error in the call to Glimpse.

    Please try your search again with different parameters.


    Output from Glimpse:
    @glines
    


    "; &diag_exit; } ########################################################################## sub err_noindex { local ($indexdir) = @_; # Glimpse index was not found # Give recommendations for indexing print "Glimpse Index not found\n"; print "\n"; print "\n"; print "

    Glimpse Index in directory '$indexdir' not found

    \n"; print "Glimpse cannot proceed without index.\n"; print "Please check if the directory being searched is indexed\n"; print "by glimpseindex.\n"; print "\n"; print "\n"; &diag_exit; } ########################################################################## sub err_insecurepath { # Path user requested contains ".." characters print "Path not accepted\n"; print "\n"; print "\n"; print "

    Insecure Path Not Accepted

    \n"; print "Please specify a path not containing ".." \n"; print "\n"; print "\n"; &diag_exit; } ########################################################################## sub err_conf { # Glimpse archive Configuration File was not found print "Glimpse Archive Configuration File not found\n"; print "\n"; print "\n"; print "

    Glimpse Archive Configuration File not found

    \n"; print "Cannot open configuration file $indexdir/archive.cfg\n"; print "\n"; print "\n"; &diag_exit; } ########################################################################## sub err_badquery { print $header; print $WEBGLIMPSE_HOME; print "

    Query is too broad

    \n"; print "The query \"$pquery\" doesn't contain any words and ". "thus will take too much time. Please refine your query.\n"; print $footer; &diag_exit; } ########################################################################## sub err_locked { print $header; print "

    Indexing in progress

    \n"; print "The archive is currently reindexing. Please try your query later.\n"; print $footer; &diag_exit; } # Also find at the same time. sub lookup_titledesc{ local($file) = @_; local($intitle, $title, $donetitle, $inmetadesc, $metadesc, $donemetadesc); if (($file =~ m/^\s*-\s*$/) || ($file =~ m/^\s*\&/)) { # Don't let anybody open stdin, or specific descriptors. &err_noneighborhood ($file); die ("UNREACHABLE REACHED"); } $intitle = 0; $donetitle = 0; $inmetadesc = 0; $donemeta = 0; $title = ''; $metadesc = ''; if (open(IN, "<$file")) { # Stop looking for & <META...> if reach </HEAD> -- GB 7/24/97 line: while (<IN> && !(/\<\/head/i) && (($donetitle == 0) || ($donemeta ==0))) { chomp; if((/\<title\>(.*)$/i)) { $intitle = 1; $title = $1; } elsif ($intitle) { $title .= " $_"; } if ($intitle && $title =~ s#.*##i) { $donetitle = 1; $intitle = 0; } if((/\.*$##) { $donemetadesc = 1; $inmetadesc = 0; } } close(IN); } # if there's no title, just return "", let webglimpse write 'No title'. # if($title eq ""){ # $title="No title"; # } # Maximum chars for meta description; should be settable by option. $metadesc = substr($metadesc, 0, $MAX_METADESC_LEN); # trim blanks off of title $title =~ s/^\s+//; $title =~ s/\s+$//; return ($title,$metadesc); }