#!/usr/bin/perl -- require 5; =item overview AXS Script Set, Administration Module Copyright 1997-2001 by Fluid Dynamics Please adhere to the copyright notice and conditions of use as described at the URL below. For latest version and help files, visit: http://www.xav.com/scripts/axs/ =cut my $VERSION = '2.3.0.0024'; my %FORM = (); my %PREF = (); my %const = (); my $all_code = <<'END_OF_CODE'; # You should place the log.txt and axs.dat files in the same directory as # this script. If you do, you won't have to change the variables below. # If you want to put the files somewhere else, enter the full path to these # files: $LogFile = 'log.txt'; $prefs = 'axs.dat'; # Other examples: # $LogFile = '/usr/www/users/xav/log.txt'; # $LogFile = 'c:/axs/log.txt'; # Enter your anchor page. This will form a link at the top of each AXS # output document: $link_url = '/'; $link_title = 'My Home Page'; # Once the script is working to your satisfaction, set the $AllowDebug # variable to zero: $AllowDebug = 1; # ________________________________________________________________________ # Protect AXS with a username and password. Both are case sensitive. You # can leave them blank to disable password locking. This is the default: $Username = 'leo.crawford'; $Password = 'Office'; # Other examples: # $Username = 'root'; # $Password = 'IronMAN'; # You can allow anyone access to your graphs, while continuing to protect # your "Customize" page with a username and password. If you do this, # web visitors will be free to view your statistics, but they won't be # able to delete the log file or change your settings. To allow web # visitors to see your graphs without entering a username or password, set # this to 1: $AllowAnonymousForGraphs = 1; # set to 1 to allow # ________________________________________________________________________ # Most of you shouldn't have to change anything below this line. If you # try the script out and it doesn't work, the help files will suggest # changes to the following lines. # The request method can be either GET or POST. Setting the method to GET # will cause the username and password data to be exposed to the web server # logs. Using GET is inadvisable if others have access to your web server # logs. $Request_Method = 'POST'; # The URL to this script: $This_Script_Address = &query_env('SCRIPT_NAME'); # The admininstrator's email address - use *single* quotes: $Admin_Email_Address = &query_env('SERVER_ADMIN', 'nobody@localhost'); # Example: # $Admin_Email_Address = 'president@whitehouse.gov'; # Your favorite network lookup services: $nslookup = 'http://www.xav.com/cgi-bin/nslookup.cgi'; $whois = 'http://www.xav.com/scripts/axs/whois.pl?a='; # Alternate (previous) whois script was: # $whois = 'http://www.networksolutions.com/cgi-bin/whois/whois?'; # AXS can collapse web addresses which include the default document. # This prevents you from having two database entries for a single file, # like http://www.ms.com/ and http://www.ms.com/index.html: $DefaultDoc = 'index.html'; # If you'd like, local files can show up as their HTML title instead of # their URL. For example, visits to http://www.xav.com/ would show up in # your graphs as "Home Page". To use this option, enter the URL-title # pairs below, and set the top variable to "1": $UseLocalAddressTitlePairs = 0; # Set to "1" to enable. %LocalAddressTitlePairs = ( 'http://www.xav.com/' , 'Home Page', 'http://www.xav.com/scripts/' , 'Scripts Page', 'http://www.xav.com/scripts/axs/' , 'AXS Script Page', ); # Uncomment this line if you receive errors about invalid Content-Type # headers. (to support command-line parameters, the HTTP headers are # only sent back if the SERVER_SOFTWARE env var is defined; most web # servers should set this, but if you're doesn't then you have to set # it manually by uncommenting the line below) #$ENV{'SERVER_SOFTWARE'} = 1; # No further editing is necessary, but feel free to play around. The # first 1,000 lines of this script are straight HTML and JavaScript, so # you can safely customize the look and feel of the output even if you # don't know Perl. # # ________________________________________________________________________ %GraphOptions = ( 's01' => 'Web Browser (Netscape 3.01 Gold)', 's02' => 'Abbreviated Browser (Netscape 3.X)', 's02a' => 'Browser Wars (Netscape)', 's03' => 'Operating System (Windows 98)', 's04' => 'Visitors Top Level Domains (.com)', 's05' => 'First Level Domains (xav.com)', 's06' => 'Full Server Address (noc.xav.com)', 's07' => 'Visitor IP Address (206.134.243.3)', 's08' => 'Hits from Other Sites (Full URL)', 's09' => 'Hits from Other Sites (Domain Only)', 's10' => 'Hyperlinks Followed From This Site', 's11' => 'Hits to Local Documents', 's12' => 'Average Number of Hits Per Visitor', 's13' => 'Hits By Day of Year', 's14' => 'Hits By Day of the Week', 's15' => 'Hits By Hour of the Day', ); @DatabaseOptions = ('Sort All by Time','Sort All by Visitor','Visitor Flow Only'); @LongWeekDays = ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'); @ShortWeekDays = ('SUN','MON','TUE','WED','THU','FRI','SAT'); @LongMonths = ('January','February','March','April','May','June','July','August','September','October','November','December'); @ShortMonths = ('JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'); @ShortDayNames = ('YEST','TOD','TOM'); $total_corrupt_rows = 0; sub Header { return <<"END_OF_HTML"; AXS Script Set - Administrator's Page END_OF_HTML } sub HTML_Header { return <<"EOM";
AXS by Fluid Dynamics
[ - Main Menu - Customize - Back to $link_title - ]

EOM } sub Footer { my ($b_is_login) = @_; print ''; if ($b_is_login) { print <<"EOM";

Main Menu - Customize - Log Out

EOM } print <<"EOM";

AXS Script Set Version $VERSION is copyright 1997-2001 by Fluid Dynamics.
Visit the AXS Page for help files and most recent version.
EOM } sub PrintMainPage { local $_; $cur_hits = 0; if (open(LOG, "<$LogFile")) { binmode(LOG); while (defined($_ = )) { $cur_hits++; } close(LOG); } $html_filter = &html_encode( $PREF{'Filter'} ); print <<"EOM";
AXS keeps records on visits to your site. This companion script, AX-ADMIN, allows you to display these records in meaningful graph and database formats. We currently have $cur_hits hits to work with.

Enter the number of recent hits you'd like to view, or leave blank for all. Enter "L" to view hits since your last visit on $PREF{'last_string'}.

Create Graphs Based On:

EOM # This is a Perl loop - you don't need to edit it: foreach $OptionCode (sort keys %GraphOptions) { print <<"EOM"; $GraphOptions{$OptionCode}
EOM } # end "foreach $OptionCode". print <<"EOM";



AXS logo

Graphing Filters:

By default, AXS will graph all hits in the database. However, with these filters, you can restrict graphs to recent hits, critical files, or both.

    Graph only hits since my last visit on $PREF{'last_string'}
    Graph only hits from yesterday and today, or specify:

Start Date (mm-dd-year)
End Date (mm-dd-year)
Filter String

The filter string may contain a file name, server name, or browser type - if this field is used, all graphs will be designed from log entries with contain this string as a pattern match.

EOM foreach ('pl','cgi') { if (-e "ax.$_") { print "

See Also:

Getting Started: Instructions for Tagging HTML Pages

"; last; } } print <<"EOM";
EOM } # ________________________________________________________________________ sub PrintJavaMainPage { print <<"EOM"; EOM } # ________________________________________________________________________ sub PrintCustomizePage { my $graph_options = ''; foreach $OptionCode (sort keys %GraphOptions) { $graph_options .= <<"EOM"; $GraphOptions{$OptionCode}
EOM } my $webmaster_logging = ''; if (($ENV{'HTTP_COOKIE'}) and ($ENV{'HTTP_COOKIE'} =~ m!axs_no_log=1!i)) { $webmaster_logging = <<"EOM";

Currently, your visits ARE NOT being logged. This is the recommended state. To begin logging yourself again, click here: Log My Visits.

EOM } else { $webmaster_logging = <<"EOM";

Currently, your visits are being logged. To stop logging yourself (recommended), click here: Do Not Log My Visits.

EOM } print &SetDefaults(<<"EOM", \%PREF);

Because you'll typically generate the same graphs repeatedly, AXS allows you to specify default settings. Enter your most common settings below. Later, AX-ADMIN will select these values automatically.

The text box holds the number of recent hits you're interested in. You can enter a letter to view recent hits through the day of your last visit.

Most Common Graphs:

$graph_options

Graphing Filters:

By default, AXS will graph all hits in the database. However, with these filters, you can restrict graphs to recent hits, critical files, or both.

    Graph only hits since my last visit on $PREF{'last_string'}
    Graph only hits from yesterday and today, or specify:

Start Date (mm-dd-year)
End Date (mm-dd-year)
Filter String

The filter string may contain a file name, server name, or browser type - if this field is used, all graphs will be designed from log entries with contain this string as a pattern match.

Graphics Output:

Sort data numerically, with most hits on top
By default, graphs are alphabetically sorted by key
Follow links by opening a separate window
Highlight the percentage column in graphs
Compress web addresses that include pound signs
http://www.xav.com/links.html#localsites becomes
http://www.xav.com/links.html
Compress web addresses that include the default document, $DefaultDoc
http://www.xav.com/$DefaultDoc becomes
http://www.xav.com/
Use military time
3:45 PM becomes 15:45

Set the maximum width of graphs to pixels.

Set the maximum displayed characters in data strings to .

The URL to the folder containing the "red.gif" and "tracker.jpg" images:
You can use http://www.xav.com/images/ if you want. There is no guarantee that the images will always be hosted there, so it is best if you can host the images on your own server.

Local web pages will be any web pages which contain this substring in their URL:



Webmaster Logging

$webmaster_logging

This feature requires that you have cookies enabled. Selecting Do Not Log My Visits will set a cookie to your browser that tells the ax.pl script to not log your visits. You will need to do this for each browser you use, and you will need to repeat this process every time you delete cookies.



Log Management:

By default, all entries will be deleted. You may choose to delete only hits older than a certain date:  (mm-dd-year)

The access log will grow by about a kilobyte for every six hits, eventually becoming too large for processing (it's currently at $LogSizeKiloBytes kb - $Advice). We recommend deleting the log every so often. Before doing so, you'll want to generate your favorite graphs and save them to your system as HTML files, as a record of how your site traffic evolves over time.



EOM } sub Authenticate { $Target = ($FORM{'Target'} eq 'Preferences') ? 'Preferences' : ''; return <<"END_OF_HTML";



Please be advised!!

Use of secured computer and network facilities requires prior authorization. Unauthorized access is prohibited. Usage may be subject to security testing and monitoring. Abuse is subject to criminal, civil, and extra-legal prosecution.

Username:
Password:

This script is operated by $Admin_Email_Address, or a third party reachable via that address. Contact them for help with usernames and passwords. Technical issues, bug reports, and the like may be directed to noc\@xav.com.

END_OF_HTML } # ________________________________________________________________________ sub DatabaseFlowDescription { return <<"END_OF_HTML";

Below is a flow chart of your visitors. Visits are shown with newer hits at the top, and older hits towards the bottom, with timestamps taken from the time of first visit. Successive visits by the same user are grouped together, so that you can view the path taken through your site.

The time interval between hits is given in Hour:Minute:Second format, followed by the number of days, if any.

Note that in most cases, the same individual will have different IP addresses with each network logon. Alternately, the same IP address may represent different visitors over time. Sampling a smaller number of hits over a shorter time period reduces the probability of these errors occuring.

END_OF_HTML } # ________________________________________________________________________ sub DatabaseTimeDescription { return <<"END_OF_HTML";

Each hit below is listed in the order it was counted, with the most recent hits listed first.

END_OF_HTML } # ________________________________________________________________________ =item GraphSummary Usage: print &GraphSummary(); Dependencies: $const{'truncated_keys'} =cut sub GraphSummary { $relevant_hits = &AddCommas($relevant_hits); $NumGraphLines = &AddCommas($NumGraphLines); $SummaryText = "

Summary:

There were $total_hits total hits analyzed"; if ($total_corrupt_rows) { $SummaryText .= " ($total_corrupt_rows data points were corrupt)"; } $SummaryText .= ". Of these, $relevant_hits were "; if ($NumGraphLines) { $SummaryText .= "relevant, and they resulted in $NumGraphLines lines in the table. " } else { $SummaryText .= 'relevant. '; } if (!$FilterString) { $SummaryText .= "No string matching was done against the access log. "; } elsif ($FilterString =~ m!^host:(.*)$!i) { $SummaryText .= "Searched only hits whose hostname matched \"" . html_encode($1) . "\". "; } elsif ($FilterString =~ m!^ip:(.*)$!i) { $SummaryText .= "Searched only hits whose IP address matched \"" . html_encode($1) . "\". "; } elsif ($FilterString =~ m!^from:(.*)$!i) { $SummaryText .= "Searched only hits whose referers matched \"" . html_encode($1) . "\". "; } elsif ($FilterString =~ m!^to:(.*)$!i) { $SummaryText .= "Searched only hits in which the document hit matched \"" . html_encode($1) . "\". "; } elsif ($FilterString =~ m!^browser:(.*)$!i) { $SummaryText .= "Searched only hits in which the browser name matched \"" . html_encode($1) . "\". "; } else { $SummaryText .= "Searched only records whose text matched \"" . html_encode($FilterString) . "\". "; } if (($StartString) && ($EndString)) { $SummaryText .= "Restricted to hits occurring between $StartString, and $EndString.

"; } elsif ($StartString) { $SummaryText .= "Restricted to hits occurring on or after $StartString.

"; } elsif ($EndString) { $SummaryText .= "Restricted to hits occurring on or before $EndString.

"; } else { $SummaryText .= "The log was not filtered by date.

"; } if ($const{'truncated_keys'}) { $SummaryText .= "

$const{'truncated_keys'} of the text keys were longer than $PREF{'MaxChars'} characters, and were truncated in the display. This behavior can be controlled with the \"maximum displayed characters\" setting on the Customize page.

\n"; } #0013 - added descriptive sentence about how local web pages are defined $SummaryText .= <<"END_OF_HTML";

Local web pages are those whose URL contains the substring "$PREF{'My_Web_Address'}". All other documents are considered remote web pages.

END_OF_HTML #0013 - end changes return $SummaryText; } #----------------------------------------------------------------------- sub JavaLib { return <<'END_OF_HTML'; END_OF_HTML } #----------------------------------------------------------------------- %FORM = (); &WebFormL( \%FORM ); my $b_is_login = 0; $err = ''; Err: { if ($FORM{'SetCookie'}) { $hostname = $ENV{'HTTP_HOST'} || $ENV{'SERVER_NAME'}; $hostname = lc($2) if ($hostname =~ m!^([^\.]+)(.*)$!); print "Set-Cookie: axs_no_log=$FORM{'CookieValue'}; expires=Thu, 24-Sep-2020 20:58:18 GMT; domain=$hostname; path=/\015\012"; print "Content-Type: text/html\015\012\015\012"; print ""; last Err; } print "Pragma: no-cache\015\012"; print "Content-Type: text/html\015\012\015\012"; if ($0 =~ m!^(.*)(\\|/)!) { unless (chdir($1)) { $err = "unable to chdir to local script folder '$1' - $!"; next Err; } print "\015\012"; } $const{'is_demo'} = (-e 'is_demo') ? 1 : 0; # Build generic timestamp for all functions: @MyT = localtime(time); # The following guesses the script address when $ENV is undefined, which # happens during command-line mode: unless ($This_Script_Address) { $This_Script_Address = ''; $This_Script_Address = $1 if ($0 =~ m!([^\\|\/]+)$!); } print &Header; if (($AllowDebug) && (&query_env('QUERY_STRING') =~ m!^debugme$!i)) { &PrintDebugInfo(1); last Err; } $FilterString = $FORM{'Filter'} || ''; #changed 0022 $err = &check_regex($FilterString); next Err if ($err); ($err, $b_is_login, %PREF) = &AuthPref($prefs); next Err if ($err); last Err unless ($b_is_login); if ($FORM{'Target'} && ($FORM{'Target'} eq 'LogOut')) { print &Authenticate; $b_is_login = 0; last Err; } $err = &check_regex($PREF{'My_Web_Address'}); next Err if ($err); # Next, we open the log file and import all the records. This is *only* # done if we're going to make graphs this time: if ($FORM{'show_data'} || $FORM{'MakeGraphs'} || $FORM{'terminate'}) { print "\r\n"; # Allows the "L" flag to date-filter database results (for reverse # compatibility): if ($FORM{'show_data'} && ($FORM{'maximum'} !~ m!^\d*$!)) { $FORM{'since_last'} = 'on'; } # If date filtering is enabled, the dates are converted into a format # that makes sense to AXS: ($StartNumber,$StartString,$EndNumber,$EndString) = &FormatDates($FORM{'start_date'}, $FORM{'end_date'}, $FORM{'recent'}, $FORM{'since_last'}, $PREF{'last_number'}); # Open the log file and store all of the hits in the # @LINES array. Run whichever filters are necessary, for date/time # or by-file filtering. This preps @LINES and also $total_hits. unless (open(LOGFILE,"<$LogFile")) { &PrintDebugInfo(0); last Err; } binmode(LOGFILE); if ($FilterString eq '') { $FILTER = '(\|[^\|]*){10,10}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString =~ m!^host:(.*)$!i) { $FILTER = '\|[^\|]*'.$1.'[^\|]*(\|[^\|]*){9,9}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString =~ m!^ip:(.*)$!i) { $FILTER = '\|[^\|]*\|[^\|]*'.$1.'[^\|]*(\|[^\|]*){8,8}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString =~ m!^from:(.*)$!i) { $FILTER = '\|[^\|]*\|[^\|]*\|[^\|]*'.$1.'[^\|]*(\|[^\|]*){7,7}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString =~ m!^to:(.*)$!i) { $FILTER = '\|[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*'.$1.'[^\|]*(\|[^\|]*){6,6}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString =~ m!^browser:(.*)$!i) { $FILTER = '\|[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*\|[^\|]*'.$1.'[^\|]*(\|[^\|]*){5,5}\|(\d*)\|\d*\|(\d*)'; } elsif ($FilterString) { $FILTER = '.*'.$FilterString.'(.*)\|(\d*)\|\d*\|(\d*)\|(export\|)?\r?$'; } else { $FILTER = '(\|[^\|]*){10,10}\|(\d*)\|\d*\|(\d*)'; } #print "\r\n"; $total_hits = 0; if ($StartNumber || $EndNumber || $FilterString) { $EndSearchNow = 0; while (defined($_ = )) { # make sure each row is strictly valid: unless (m!^\|([^\|]+)\|([^\|]+)\|([^\|]*)\|([^\|]*)\|([^\|]*)\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|(export\|)?\r?$!) { $total_corrupt_rows++; next; } $total_hits++; next unless (($EndSearchNow) || (m!^$FILTER!)); $ThisYDAY = $2 * 1000 + $3 + 1900000; next if (($StartNumber) && ($StartNumber > $ThisYDAY)); if ($EndNumber && ($EndNumber < $ThisYDAY)) { $EndSearchNow = 'true'; next; } push(@LINES,$_); } } else { while (defined($_ = )) { # make sure each row is strictly valid: unless (m!^\|([^\|]+)\|([^\|]+)\|([^\|]*)\|([^\|]*)\|([^\|]*)\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|\d+\|(export\|)?\r?$!) { $total_corrupt_rows++; next; } $total_hits++; push(@LINES, $_); } } close(LOGFILE); $total_hits = &AddCommas($total_hits); #print "\r\n"; } # End importing data. # Now we print HTML banner which goes at the top of every page: print &HTML_Header; # Finished printing HTML header. Now determine which subprocedure(s) to # invoke based on the input: if ($FORM{'show_data'}) { if ($FORM{'format'} eq 'Sort All by Time') { &show_data; } else { &show_data_flow; } last Err; } &make_stats(5,'Web Browser Full Name',0) if ($FORM{'s01'}); &make_stats(5,'Web Browser Type and Version','short') if ($FORM{'s02'}); &make_stats(5,'Web Browser Type','med') if ($FORM{'s02a'}); &make_stats(5,'Operating System','os') if ($FORM{'s03'}); &make_stats(1,'TLD','tld') if ($FORM{'s04'}); &make_stats(1,'Domain','abbr') if ($FORM{'s05'}); &make_stats(1,'Remote Server','full') if ($FORM{'s06'}); &make_stats(2,'IP Address',0) if ($FORM{'s07'}); &make_stats(3,'Referring URL','') if ($FORM{'s08'}); &make_stats(3,'Referring URL','domain') if ($FORM{'s09'}); &make_stats(4,'Links Followed','remote') if ($FORM{'s10'}); &make_stats(4,'Document Hit','local') if ($FORM{'s11'}); &avg_docs if ($FORM{'s12'}); &make_stats_year(13,'Day of the Year',0) if ($FORM{'s13'}); &make_stats_week(12,'Day of the Week',0) if ($FORM{'s14'}); &make_stats_hour(8,'Hour of the Day',0) if ($FORM{'s15'}); &kill_it if ($FORM{'terminate'}); last Err if ($graph_made); # If no graphs were made, then show the intro page, or allow # the user to set his preferences. Each of these pages will use the # massive Java library: print &JavaLib; if (($FORM{'Target'}) && ($FORM{'Target'} eq 'Preferences')) { # show preferences: $LogSizeKiloBytes = int((-s $LogFile) / 1000); if ($LogSizeKiloBytes < 500) { $Advice = 'that is not too bad'; } elsif ($LogSizeKiloBytes < 1000) { $Advice = 'it is starting to get up there'; } else { $Advice = 'you may want to delete it'; } $LogSizeKiloBytes = &AddCommas($LogSizeKiloBytes); &PrintCustomizePage; } else { # show main page: &PrintJavaMainPage; &PrintMainPage; } last Err; } continue { print "

Error: $err.

\n"; } &Footer($b_is_login); # This is the end - everything below is a sub-procedure called above. # ________________________________________________________________________ # Prints a line of the graph: # # Format is &print_line(Name,Value) where Name is something # like 'Netscape 3' and Value is the number of hits. # name percent number picture sub print_line { ($N,$V) = @_; print "$N"; print ''; print sprintf("%.2f",($V * $RH100)); print '%'; print "$V"; # traps minimum width at 1, since width=0 is ignored by browser: $width = int($multiplier * $V) || 1; print "\"";"; # print '

'; print '', "\n"; } # End Print Line. # Prints a line of the graph. Allows Value = 0. # # Format is &print_line_allow0(Name,Value) where Name is something # like 'Tuesday' and Value is the number of hits. # name percent number picture sub print_line_allow0 { ($N,$V) = @_; $N .= ' ' x (12 - length($N)); print "$N"; print ''; print sprintf("%.2f",($V * $RH100)); print '%'; if ($V) { print $V; print ''; # traps minimum width at 1, since width=0 is ignored by browser: $width = int($multiplier * $V) || 1; print "\"";"; # Above comments out image-using graphs: # print '

'; } else { print '0
'; } print '', "\n"; } # End Print Line/Allow 0 # Begin Main Graphing Procedure: sub make_stats { ($q, $graph_name, $detail) = @_; print '





' if $graph_made; print ''; print ''; $relevant_hits = 0; $max_var = 0; undef(%ASTA); foreach $RECORD (@LINES) { @xSQL = split(/\|/,$RECORD); # Special case of referring URLs - the script makes sure first # that there is a non-zero entry in field 3, and then discards # those which appear to be local to the web site. If the query # is being made for domain name only, the script runs a pattern # match on (somthing)//(something)/(whatever) and saves the first # two fields. Local file links are discarded for domain-only # queries. if ($q == 3) { next unless ($xSQL[3]); # To protect against those with blank $PREF{'My_Web_Address'} variables, this # code will show *all* referrers if $PREF{'My_Web_Address'} is blank. I feel # that this is a better solution that showing *no* referers. # # code was: # next if ($xSQL[3] =~ /$PREF{'My_Web_Address'}/i); next if (($PREF{'My_Web_Address'}) && ($xSQL[3] =~ /$PREF{'My_Web_Address'}/i)); if (($detail eq 'domain') && ($xSQL[3] =~ m!^([^\/]+)\/\/([^\/]*)!)) { $xSQL[3] = $1.'//'.$2; next if ($1 =~ m!file!i); } # strip "#" signs from URL's: elsif (($PREF{'HidePoundSigns'}) && ($xSQL[3] =~ m!([^\#]+)!)) { $xSQL[3] = $1; } } # $q = 1 indicates a query on the server name. this code # abbreviates the server names to either TLD, host.TLD, or # ' IP Address Only' in the case of non-alpha hosts. elsif ($q == 1) { if ($xSQL[1] =~ /([^\.]+)\.([^\.|\d]+)$/) { if ($detail eq 'tld') { $xSQL[1] = $2; } elsif ($detail eq 'abbr') { $xSQL[1] = $1.'.'.$2; } } else { $xSQL[1] = ' IP Address Only'; } } # Exit Points & Local Documents: elsif ($q == 4) { if ($detail eq 'remote') { next if ($xSQL[14] ne 'export'); # Again, only limit to local web pages if the $PREF{'My_Web_Address'} variable # is populated: next if (($PREF{'My_Web_Address'}) && ($xSQL[4] =~ /$PREF{'My_Web_Address'}/i)); } elsif ($detail eq 'local') { #commented out 0020 $xSQL[4] = $xSQL[3] if ($xSQL[14] eq 'export'); # Again, only limit to local web pages if the $PREF{'My_Web_Address'} variable # is populated: next unless ($xSQL[4] =~ /$PREF{'My_Web_Address'}/i); } # strip # signs from URL's: if (($PREF{'HidePoundSigns'}) && ($xSQL[4] =~ m!([^\#]+)!)) { $xSQL[4] = $1; } if ($xSQL[4] =~ m|([^\/]+)//([^\/]+):80\/(.*)|) { $xSQL[4] = "$1//$2/$3"; } if (($PREF{'HideDefaultDoc'}) && ($xSQL[4] =~ m!(.*)/$DefaultDoc$!i)) { $xSQL[4] = "$1/"; } } # Operating System and Short Web Browser Name: elsif ($q == 5) { if ($FORM{'bd1'}) { # browser detail 1 my $browser_type = &get_browser_name($xSQL[5]); next unless ($browser_type eq $FORM{'bd1'}); $xSQL[5] = &get_browser_ver($xSQL[5]); } elsif ($FORM{'bd2'}) { # browser detail 2 my $browser_type = &get_browser_ver($xSQL[5]); next unless ($browser_type eq $FORM{'bd2'}); } elsif ($FORM{'bd3'}) { # browser/os detal 3 my $os_type = &get_os_type($xSQL[5]); next unless ($os_type eq $FORM{'bd3'}); } else { $xSQL[5] = &get_os_type($xSQL[5]) if ($detail eq 'os'); $xSQL[5] = &get_browser_name($xSQL[5]) if ($detail eq 'med'); $xSQL[5] = &get_browser_ver($xSQL[5]) if ($detail eq 'short'); } } $ASTA{$xSQL[$q]}++; $relevant_hits++; $max_var++ unless ($max_var >= $ASTA{$xSQL[$q]}); } # Finish loop through each hit in log file. $const{'truncated_keys'} = 0; $multiplier = ($PREF{'MaxWidth'} / $max_var) if ($max_var); $RH100 = 100 / $relevant_hits if ($relevant_hits); if ($relevant_hits < 1) { print ''; } elsif (($q == 3) || ($q == 4)) { # q3/4 => hits to local, hits from remote, etc. URL's. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { &print_line(&url_format($_),$ASTA{$_}); } } elsif (($q == 1) && ($detail eq 'abbr')) { # q1 => server names. my ($val, $dislay_val) = ('', ''); foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $val = $display_val = $_; if (($PREF{'MaxChars'}) and (length($val) > $PREF{'MaxChars'})) { $const{'truncated_keys'}++; $display_val = substr($val, 0, $PREF{'MaxChars'}); } if (/ IP Address Only/) { &print_line('IP Address Only',$ASTA{$_}); } else { &print_line("" . &html_encode($display_val) . "",$ASTA{$_}); } } } elsif ($q == 2) { # q2 => IP addresses. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $htmlsafe = html_encode($_); &print_line("$htmlsafe",$ASTA{$_}); } } elsif (($q == 5) and ($detail eq 'med')) { my ($val, $dislay_val) = ('', ''); # q* => other. q5 = browser type, os type, etc. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $val = $display_val = $_; if (($PREF{'MaxChars'}) and (length($val) > $PREF{'MaxChars'})) { $const{'truncated_keys'}++; $display_val = substr($val, 0, $PREF{'MaxChars'}); } &print_line( "" . &html_encode($display_val) ."", $ASTA{$_} ); } } elsif (($q == 5) and ($detail eq 'short')) { my ($val, $dislay_val) = ('', ''); # q* => other. q5 = browser type, os type, etc. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $val = $display_val = $_; if (($PREF{'MaxChars'}) and (length($val) > $PREF{'MaxChars'})) { $const{'truncated_keys'}++; $display_val = substr($val, 0, $PREF{'MaxChars'}); } &print_line( "" . &html_encode($display_val) ."", $ASTA{$_} ); } } elsif (($q == 5) and ($detail eq 'os')) { my ($val, $dislay_val) = ('', ''); # q* => other. q5 = browser type, os type, etc. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $val = $display_val = $_; if (($PREF{'MaxChars'}) and (length($val) > $PREF{'MaxChars'})) { $const{'truncated_keys'}++; $display_val = substr($val, 0, $PREF{'MaxChars'}); } &print_line( "" . &html_encode($display_val) ."", $ASTA{$_} ); } } else { my ($val, $dislay_val) = ('', ''); # q* => other. q5 = browser type, os type, etc. foreach ($NUMS ? (sort {$ASTA{$b} <=> $ASTA{$a} || $a cmp $b} keys %ASTA) : (sort keys %ASTA)) { $val = $display_val = $_; if (($PREF{'MaxChars'}) and (length($val) > $PREF{'MaxChars'})) { $const{'truncated_keys'}++; $display_val = substr($val, 0, $PREF{'MaxChars'}); } &print_line( &html_encode($display_val), $ASTA{$_} ); } } $NumGraphLines = scalar (keys %ASTA); print "
' . html_encode($graph_name) . ': Hits: Graph:
No matches found for your search. Sorry.
\n"; print &GraphSummary; $graph_made++; } # Begin Main Graphing Procedure for Day of Year: sub make_stats_year { @DayCount = (); # Do we have a leap year, or a non-leap year? # leap years are divisible by 4. However, every 100 years # is an exception (non-leap), and every 400 years is an # exception to that (leap). $this_year = (localtime(time))[5] + 1900; # Assume normal year: @mon_array = (0,31,59,90,120,151,181,212,243,273,304,334); $total_days_year = 365; if (($this_year % 4) == 0) { # year is divisible by 4, is leap, probably if ((($this_year % 100) == 0) && (($this_year % 400) != 0)) { # is divisible by 100, and not divisible by 400; # standard exception, leave this as a non-leap year } else { # ok world we have a leap year: @mon_array = (0,31,60,91,121,152,182,213,244,274,305,335); $total_days_year = 366; } } print '





' if $graph_made; print ''; print ''; undef($max_var); undef(%ASTA); $relevant_hits = scalar @LINES; my $min_day = 366; my $max_day = -1; foreach (@LINES) { $ThisDay = (split(/\|/,$_))[13]; $max_day = $ThisDay if ($ThisDay > $max_day); $min_day = $ThisDay if ($ThisDay < $min_day); $DayCount[$ThisDay]++; $max_var++ unless ($max_var >= $DayCount[$ThisDay]); } $multiplier = ($PREF{'MaxWidth'} / $max_var) if ($max_var); if ($relevant_hits) { $RH100 = 100 / $relevant_hits; } $month_count = 0; # error correct $min_day = 0 if ($min_day == 366); $max_day = $total_days_year if ($max_day == -1); for (0..($total_days_year - 1)) { $month_count++ if ($_ == $mon_array[$month_count + 1]); $mday = (($_ - $mon_array[$month_count]) + 1); $day = "$LongMonths[$month_count] $mday"; next if ($_ < $min_day); last if ($_ > $max_day); $NumGraphLines++; &print_line_allow0($day, $DayCount[$_]); } print "
Day of Year: Hits:Graph:
\n"; print &GraphSummary; $graph_made++; } # End Graph for Day of Year. # Begin Main Graphing Procedure for Day of Week sub make_stats_week { @DayCount = (); print '





' if $graph_made; print ''; print ''; undef($max_var); undef(%ASTA); $relevant_hits = scalar @LINES; foreach (@LINES) { $ThisDay = (split(/\|/,$_))[12]; $DayCount[$ThisDay]++; $max_var++ unless ($max_var >= $DayCount[$ThisDay]); } $multiplier = ($PREF{'MaxWidth'} / $max_var) if ($max_var); if ($relevant_hits) { $RH100 = 100 / $relevant_hits; } # q12 => LongWeekDays for (0..6) { &print_line_allow0($LongWeekDays[$_],$DayCount[$_]); } $NumGraphLines = 7; print "
Day of Week: Hits: Graph:
\n"; print &GraphSummary; $graph_made++; } # End Graph for Day of Week # Begin Main Graphing Procedure for Hour of Day: sub make_stats_hour { print '





' if $graph_made; print ''; print ''; undef($max_var); undef(%ASTA); $relevant_hits = scalar @LINES; foreach (@LINES) { $ThisHour = (split(/\|/,$_))[8]; $HourCount[$ThisHour]++; $max_var++ unless ($max_var >= $HourCount[$ThisHour]); } $multiplier = ($PREF{'MaxWidth'} / $max_var) if ($max_var); if ($relevant_hits) { $RH100 = 100 / $relevant_hits; } for (0..23) { print '"; # traps minimum width at 1, since width=0 is ignored by browser: $width = int($multiplier * $V) || 1; print "', "\n"; } $NumGraphLines = 24; print "
Hour of Day: Hits: Graph:
'; if ($PREF{'UseMilTime'}) { print "$_:00"; } else { if ($_ == 0) { print 'Midnight'; } elsif ($_ < 12) { print $_.' AM'; } elsif ($_ == 12) { print 'High noon'; } else { print $_ - 12; print ' PM'; } } print ' '; $V = $HourCount[$_]; print sprintf("%.2f",($V * $RH100)); print '%'; if ($V) { print "$V\"";"; } else { print '0
'; } print '
\n"; print &GraphSummary; $graph_made++; } # End make_stats_hour. sub avg_docs { $internal_hits = 0; $unique_ip_count = 0; foreach (@LINES) { @terms = split(/\|/,$_); $unique_ip_count++ unless ($IP{$terms[2]}); $IP{$terms[2]}++; $internal_hits++ if ($terms[4] =~ /$PREF{'My_Web_Address'}/i); } if ($unique_ip_count) { $avg_docs_per_visitor = $internal_hits / $unique_ip_count; } else { $avg_docs_per_visitor = 0; } $avg_docs_per_visitor = sprintf("%.3f",$avg_docs_per_visitor); $relevant_hits = $internal_hits; my $ac_internal_hits = &AddCommas($internal_hits); $unique_ip_count = &AddCommas($unique_ip_count); print '





' if $graph_made; print <Average Number of Hits Per Visitor
The average number of documents viewed per visitor is $avg_docs_per_visitor.
There have been a total of $ac_internal_hits on local documents from $unique_ip_count unique IP addresses.
EOM print &GraphSummary; $graph_made++; } sub PrettyTime { ($Hour,$Minutes,$Seconds) = @_; #changed 0013 - fixed problems with date rendering $Minutes = reverse(substr(reverse("00$Minutes"), 0, 2)); $Seconds = reverse(substr(reverse("00$Seconds"), 0, 2)); if ($PREF{'UseMilTime'}) { $Hour = reverse(substr(reverse(" $Hour"), 0, 2)); return "$Hour:$Minutes:$Seconds"; } elsif ($Hour < 12) { $Hour = reverse(substr(reverse(" $Hour"), 0, 2)); return "$Hour:$Minutes:$Seconds AM"; } else { $Hour -= 12; $Hour = reverse(substr(reverse(" $Hour"), 0, 2)); return "$Hour:$Minutes:$Seconds PM"; } #end changes } # Begin Show Database Procedure: sub show_data { if ($FORM{'maximum'} =~ /\d+/) { $array_size = scalar @LINES; if ($FORM{'maximum'} < $array_size) { splice(@LINES,0,$array_size - $FORM{'maximum'}); } } print &DatabaseTimeDescription; print '
';

	($relevant_hits,$NumGraphLines) = (0,0);
	foreach (reverse @LINES) {
		$relevant_hits++;
		($VisitHost,$IPAddress,$T3,$T4,$Browser,$SS,$MM,$HH,$Day,$T10,$Year,$T12,$Redirect) = (split(/\|/,$_))[1..12,14];
		$Referer = $T3 ? &url_format($T3) : '';
		$WebPage = &url_format($T4);
		$HourMinSec = &PrettyTime($HH,$MM,$SS);
		$WeekDay = $LongWeekDays[$T12];
		$Month = $LongMonths[$T10];
		$Year += 1900;
		$Redirect = ($Redirect eq 'export') ? 1 : 0;

		#changed 0015 - security fix
		foreach ($VisitHost, $IPAddress, $Browser) {
			$_ = html_encode($_); #changed 0015 - security fix
			}


		print "A visitor from $VisitHost ($IPAddress)\n";
		if (($Redirect) && ($Referer ne $WebPage)) {
			print "was redirected to $WebPage\n";
			print "from $Referer\n";
			}
		elsif ($Redirect) {
			print "visited $WebPage\n";
			}
		else {
			if ($Referer) {
				print "arrived from $Referer,\n";
				}
			else {
				print "arrived without a refering URL,\n";
				}
			print "and visited $WebPage\n";
			}
		print "at $HourMinSec on $WeekDay, $Month $Day, $Year.\n";
		print "This visitor used $Browser.\n";
		print "\n";
		}
	print '
'; print &GraphSummary; $graph_made++; } # End Show Database Procedure. # Begin Show Database-Sytle Visitor Flow: sub show_data_flow { if ($FORM{'maximum'} =~ /\d+/) { $array_size = @LINES; if ($FORM{'maximum'} < $array_size) { splice(@LINES,0,$array_size - $FORM{'maximum'}); } } ($total_ips,$multiple_hit_ips) = (0,0); $delimiter = 'Flow_Chart_Delimiter'; foreach (@LINES) { next unless (m!^\|([^\|]+)\|([^\|]+)!); if ($IPFLOW{$2}) { $IPFLOW{$2} .= $delimiter.$_; } else { push(@IPS,$2); $IPFLOW{$2} = $_; $total_ips++; } } print &DatabaseFlowDescription; print '
';

foreach $key (reverse @IPS) {
	@LINES = split(/$delimiter/,$IPFLOW{$key});

	$num_hits = scalar @LINES;
	if (($num_hits > 1) || ($FORM{'format'} eq 'Sort All by Visitor')) {

		# Multiple documents visited; generate flow chart:

		$multiple_hit_ips++ if ($num_hits > 1);
		@terms = split(/\|/,$LINES[0]);

		$HourMinSec = &PrettyTime($terms[8],$terms[7],$terms[6]);

		if ($num_hits > 2) {
			$NumTimes = "$num_hits times";
			}
		elsif ($num_hits == 1) {
			$NumTimes = 'once';
			}
		else {
			$NumTimes = 'twice';
			}

		#changed 0015 - security fix
		foreach ($terms[1], $terms[2], $terms[3], $terms[4], $terms[5]) {
			$_ = html_encode($_); #changed 0015 - security fix
			}


$FullYear = 1900 + $terms[11];

print <<"EOM";

A visitor from $terms[1] ($terms[2]) was logged $NumTimes, starting at $HourMinSec on $LongWeekDays[$terms[12]], $LongMonths[$terms[10]] $terms[9], $FullYear. The initial browser was $terms[5]. EOM print ' This visitor first '; $first = 'true'; foreach (@LINES) { @terms = split(/\|/,$_); if ($first ne 'true') { $ThisTime = ((((($terms[13] * 24) + $terms[8]) * 60) + $terms[7]) * 60) + $terms[6]; # $INT is the time interval in seconds: $INT = ($ThisTime - $PrevTime); #changed 0013 - fixed date rendering problem $seconds = int($INT % 60); $minutes = int(($INT % 3600) / 60); $hours = int(($INT % 86400) / 3600); $minutes = reverse(substr(reverse("00$minutes"), 0, 2)); $seconds = reverse(substr(reverse("00$seconds"), 0, 2)); $hours = reverse(substr(reverse("00$hours"), 0, 2)); #end changes print " $hours:$minutes:$seconds"; if ($days = int($INT/86400)) { print " and $days day"; print 's' if ($days > 1); } print " later, "; } if (($terms[14] eq 'export') && ($terms[3] ne $terms[4])) { print "was redirected to " . &url_format($terms[4]) . "\n"; print " from " . &url_format($terms[3]) . "\n\n"; } elsif ($terms[14] eq 'export') { # Image redirect print "dropped by " . &url_format($terms[3]) . "\n\n"; } else { if ($terms[3]) { print "arrived from " . &url_format($terms[3]) . "\n"; } else { print "arrived without a refering URL,\n"; } print " and visited " . &url_format($terms[4]) . "\n"; print "\n"; } $first = 'false'; $PrevTime = ((((($terms[13] * 24) + $terms[8]) * 60) + $terms[7]) * 60) + $terms[6]; } # End foreach hit per IP. } # End test of more than one hit per IP. } # End foreach loop through all IP's. print <<"EOM";

Summary:

There were visits from $total_ips distinct IP addresses. However, only $multiple_hit_ips of these visited more than one document.

EOM $graph_made++; } # End Show Database-Style Visitor Flow. # Begin Export/Delete Log Procedure: sub kill_it { my $err = ''; Err: { $graph_made++; if ($const{'is_demo'}) { $err = "the deletion of the access log is not allowed in the online demo"; next Err; } unless (open(NEWLOG,">$LogFile")) { $err = "unable to open log file '$LogFile' for writing - $!"; next Err; } binmode(NEWLOG); if ($StartNumber) { $NumLogEntries = scalar @LINES; foreach (@LINES) { print NEWLOG; } } close(NEWLOG); $NewLogSize = -s $LogFile; print <<"EOM";

Access Log Deleted:

The log file has been successfully deleted.

EOM print <<"EOM" if ($StartNumber);

Hits since $StartString were retained. There are now $NumLogEntries entries in the access log. The new log size is $NewLogSize bytes.

EOM print <<"EOM";
EOM last Err; } continue { print "

Error: $err.

\n"; } } # This is the routine to support the new "Browser Wars" report. # The routine for the "Abbreviated Browser" report has been renamed get_browser_ver sub get_browser_name { local $_ = shift; return 'Unknown/Other' unless ($_); # I reformatted the code below to make it appear more tabular and easier to read. # You may have to clean up the tabs on some lines. # I found that my email and text editors don't match. if (m!Opera.(\d)!i) { return 'Opera'; } elsif (m!Mozilla/(\d)!i) { if (m!compatible!i) { if (m!WebTV!i) { return 'WebTV'; } elsif (m!AOL!i) { return 'AOL\'s Browser'; } elsif (m!MSIE!i) { return 'Internet Explorer'; } elsif (m!iCab!i) { return 'iCab'; } elsif (m!Mozilla/3.01.\(compatible;?\)!) { return 'Cache/Proxy server'; } elsif (m!Powermarks!i) { return 'Powermarks bookmark thing'; } elsif (m!FDSE.robot!i) { return 'Spider/Crawler'; } elsif (m!NetMind-Minder!i) { return 'Spider/Crawler'; } elsif (m!BorderManager!i) { return 'Cache/Proxy server'; } else { return 'Unknown/Other'; } } else { return 'Netscape'; } } elsif (m!(Microsoft Internet Explorer)|(MSIE)!i) { return 'Internet Explorer'; } elsif (m!MSProxy!i) { return 'Cache/Proxy server'; } elsif (m!(Crawler)|(Spider)|(Scooter)|(bot)!i) { return 'Spider/Crawler'; } elsif (m!(IWENG)|(aolbrowser)!i) { return 'AOL\'s Browser'; } elsif (m!Lynx!i) { return 'Lynx'; } elsif (m!WebExplorer!i) { return 'IBM WebExplorer'; } elsif (m!QuarterDeck!i) { return 'QuarterDeck Mosaic'; } elsif (m!SPRY!i) { return 'Compuserve\'s SPRY Mosaic'; } elsif (m!Enhanced_Mosaic!i) { return 'NCSA Mosaic (Enhanced)'; } elsif (m!Mosaic!i) { return 'NCSA Mosaic'; } elsif (m!PRODIGY!i) { return 'Prodigy\'s Browser'; } else { return 'Unknown/Other'; } } # end sub get_browser_name # This is the routine to support the old "Abbreviated Browser" report. # It has been renamed from get_browser_name sub get_browser_ver { local $_ = shift; return 'Unknown/Other' unless ($_); if (m!Opera.(\d)!i) { return "Opera v$1.x"; } elsif (m!Mozilla/(\d)!i) { if (m!compatible!i) { if (m!WebTV!i) { return 'WebTV'; } elsif (m!AOL (\d).(\d)!i) { return "AOL's Browser v$1.$2"; } elsif (m!AOL-IWENG (\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!MSIE.?(\d).(\d)!i) { return "Internet Explorer v$1.$2"; } elsif (m!iCab (\d).(\d)!i) { return "iCab v$1.$2"; } elsif (m!Konqueror!i) { return 'Konqueror'; } elsif (m!Powermarks!i) { return 'Powermarks bookmark thing'; } elsif (m!Mozilla/3.01.\(compatible;?\)!) { return 'Cache/Proxy server (Unknown/Other)'; } elsif (m!BorderManager!i) { return 'Cache/Proxy server (Border Manager)'; } elsif (m!FDSE.robot!i) { return 'Spider/Crawler (FDSE)'; } elsif (m!NetMind-Minder!i) { return 'Spider/Crawler (NetMind)'; } elsif (m!openfind!i) { return 'Spider/Crawler (Openfind)'; } elsif (m!WebWasher!i) { return 'Spider/Crawler (WebWasher)'; } elsif (m!WISEnutbot!i) { return 'Spider/Crawler (WISEnut)'; } elsif (m!WebWasher!i) { return 'Spider/Crawler (WebWasher)'; } else { return 'Unknown/Other'; } } elsif (m!Mozilla/(\d).(\d)!i) { my $nsver = $1; if ($nsver >= 5) { $nsver++; } return "Netscape v$nsver.$2"; } else { return "Netscape v$1.x"; } } elsif (m!Microsoft Internet Explorer/(\d)!i) { return "Internet Explorer v$1.x"; } elsif (m!MSIE/(\d)!i) { return "Internet Explorer v$1.x"; } elsif (m!MSProxy!i) { return 'Cache/Proxy server (MSProxy)'; } elsif (m!FAST-WebCrawler!i) { return 'Spider/Crawler (AllTheWeb)'; } elsif (m!Scooter!i) { return 'Spider/Crawler (Altavista)'; } elsif (m!Ask Jeeves!i) { return 'Spider/Crawler (Ask Jeeves)'; } elsif (m!Googlebot!i) { return 'Spider/Crawler (Google)'; } elsif (m!(Crawler)|(Spider)|(bot)!i) { return 'Spider/Crawler (Unknown/Other)'; } elsif (m!Teleport Pro!i) { return 'Teleport Pro Offline Browser'; } elsif (m!IWENG/(\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!aolbrowser/(\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!Lynx!i) { return 'Lynx'; } elsif (m!WebExplorer!i) { return 'IBM WebExplorer'; } elsif (m!QuarterDeck!i) { return 'QuarterDeck Mosaic'; } elsif (m!SPRY!i) { return 'Compuserve\'s SPRY Mosaic'; } elsif (m!Enhanced_Mosaic!i) { return 'NCSA Mosaic (Enhanced)'; } elsif (m!Mosaic!i) { return 'NCSA Mosaic'; } elsif (m!PRODIGY!i) { return 'Prodigy\'s Browser'; } else { return 'Unknown/Other'; } } # end sub get_browser_ver sub get_os_type { local($_) = shift; return 'Unknown Platform' unless $_; if (m!(win95)|(windows 95)!oi) { return 'Windows 95'; } elsif (m!(win 9x 4.9|windows millennium)!oi) { return 'Windows ME'; } elsif (m!(win98)|(windows 98)!oi) { return 'Windows 98'; } elsif (m!Windows NT 5!i) { return 'Windows 2000'; } # 2 lines below will show different versions of NT # I think this is not useful because 3.5 is very rare and so many are unknown #elsif (m!(Windows NT 4)|(winNT ?4)!oi) { return 'Windows NT 4'; } #elsif (m!(Windows NT 3)|(winNT ?3)!oi) { return 'Windows NT 3.5'; } # The following line catches the remaining NT entries without a version, which is most of them elsif (m!(Windows NT)|(winNT)!oi) { return 'Windows NT'; } elsif (m!win16!oi) { return 'Windows 16-bit'; } elsif (m!win32!oi) { return 'Windows 32-bit'; } elsif (m!Windows 3.1!oi) { return 'Windows 3.1'; } elsif (m!Windows!oi) { if (m!32bit!oi) { return 'Windows 32-bit'; } else { return 'Windows 16-bit'; } } elsif (m!Window!oi) { return 'X Windows'; } elsif (m!Mac!oi) { if (m!(PPC)|(PowerPC)!oi) { return 'Macintosh (PowerPC)'; } else { return 'Macintosh (68K)'; } } elsif (m!FreeBSD!oi) { return 'UNIX (FreeBSD)'; } elsif (m!HP-UX!oi) { return 'UNIX (HP-UX)'; } elsif (m!Linux!oi) { return 'UNIX (Linux)'; } elsif (m!SunOS!oi) { return 'UNIX (SunOS)'; } elsif (m!(X11)|(Lynx)!oi) { return 'UNIX (Unknown/Other)'; } elsif (m!Amiga!oi) { return 'Amiga'; } elsif (m!OS/2!oi) { return 'OS/2'; } elsif (m!IWENG!oi) { return 'Windows 16-bit'; } elsif (m!WebTV!oi) { return 'WebTV'; } else { return 'Unknown Platform'; } } # end sub get_os_type sub get_browser_name_OLD { local $_ = shift; return 'Other Agents' unless ($_); if (m!Mozilla/(\d)!i) { if (m!compatible!i) { if (m!WebTV!i) { return 'WebTV'; } elsif (m!AOL.(\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!AOL-IWENG (\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!MSIE.(\d)!i) { return "MSIE v$1.x"; } elsif (m!Opera!i) { return 'Opera'; } elsif (m!Mozilla/3.01.\(compatible;?\)!) { return 'Cache/Proxy server'; } elsif (m!Powermarks!i) { return 'Powermarks bookmark thing'; } elsif (m!FDSE.robot!i) { return 'FDSE robot'; } elsif (m!NetMind-Minder!i) { return 'NetMind-Minder'; } elsif (m!BorderManager!i) { return 'Border Manager'; } else { return 'Other Agents'; } } elsif (m!Mozilla/4.(\d)!i) { return "Netscape v4.$1"; } else { return "Netscape v$1.x"; } } elsif (m!Microsoft Internet Explorer/(\d)!i) { return "MSIE v$1.x"; } elsif (m!IWENG/(\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!aolbrowser/(\d)!i) { return "AOL's Browser v$1.x"; } elsif (m!Lynx!i) { return 'Lynx'; } elsif (m!WebExplorer!i) { return 'IBM WebExplorer'; } elsif (m!QuarterDeck!i) { return 'QuarterDeck Mosaic'; } elsif (m!SPRY!i) { return 'Compuserve\'s SPRY Mosaic'; } elsif (m!Enhanced_Mosaic!i) { return 'NCSA Mosaic (Enhanced)'; } elsif (m!Mosaic!i) { return 'NCSA Mosaic'; } elsif (m!PRODIGY!i) { return 'Prodigy\'s Browser'; } else { return 'Other Agents'; } } sub get_os_type_OLD { # this guy needs some work: check for NT 3.51/4.0/5.0; check for Linux # versus Unix or whatever; make sure Netscape returns Windows 98 instead # of Windows 95. local($_) = shift; return 'Unknown Platform' unless $_; if (m!(win95)|(windows 95)!oi) { return 'Windows 95'; } #changed 0016 elsif (m!(win 9x 4.9|windows millennium)!oi) { return 'Windows ME'; } elsif (m!(win98)|(windows 98)!oi) { return 'Windows 98'; } elsif (m!Windows NT 5!i) { return 'Windows 2000'; } elsif (m!(winNT)|(Windows NT)!oi) { return 'Windows NT'; } elsif (m!win16!oi) { return 'Windows 16-bit'; } elsif (m!win32!oi) { return 'Windows 32-bit'; } elsif (m!Windows 3.1!oi) { return 'Windows 3.1'; } elsif (m!Windows!oi) { if (m!32bit!oi) { return 'Windows 32-bit'; } else { return 'Windows 16-bit'; } } elsif (m!Window!oi) { return 'X Windows'; } elsif (m!Mac!oi) { if (m!PPC!oi) { return 'Macintosh (PowerPC)'; } elsif (m!PowerPC!oi) { return 'Macintosh (PowerPC)'; } else { return 'Macintosh (68K)'; } } elsif (m!Amiga!oi) { return 'Amiga'; } elsif (m!OS/2!oi) { return 'OS/2'; } elsif (m!X11!oi) { if (m!HP-UX!oi) { return 'UNIX (HP-UX)'; } elsif (m!Linux!oi) { return 'UNIX (Linux)'; } elsif (m!SunOS!oi) { return 'UNIX (SunOS)'; } else { return 'UNIX (Other/Unspecified)'; } } elsif (m!IWENG!oi) { return 'Windows 16-bit'; } elsif (m!Lynx!oi) { return 'UNIX (Other/Unspecified)'; } elsif (m!WebTV!oi) { return 'WebTV'; } else { return 'Unknown Platform'; } } sub quickparse { my ($str) = @_; my %hash = (); my $pair = ''; foreach $pair (split(m!\&!s, $str)) { next unless ($pair =~ m!^(.*?)=(.*)$!s); $hash{$1} = &url_decode($2); } return %hash; } sub url_format { # URL Format takes a URL and turns it into a hyperlink with an # abbreviated (no "http://") viewable output. Links from Altavista # and other search engines are formatted logically: local($_) = shift; if ((m!$PREF{'My_Web_Address'}!i) && (m!http://(.*)!i)) { # Use %LocalAddressTitlePairs if it exists: if ($UseLocalAddressTitlePairs == 1) { foreach $Address (keys %LocalAddressTitlePairs) { return "$LocalAddressTitlePairs{$_}" if (m!^$Address$!i); } } return $1; } my %hash = (); my ($linktext, $trailtext) = ($_, ''); if (($_ !~ /\?/) && (m!http://(.*)!i)) { $linktext = $1; } elsif ($_ !~ m!\?!) { #def } elsif (m!://([^/]+)\.google\.([^/]+)/\w+\?(.*)$!i) { ($host, $tld, $data) = ($1, $2, $3); %hash = &quickparse( $data ); $start = $hash{'start'} || 0; $terms = $hash{'q'} || $hash{'as_q'} || 'unknown'; if ($hash{'num'}) { $end = $start + $hash{'num'}; } else { $end = $start + 10; } $start++; ($linktext, $trailtext) = ( "$host.google.$tld", "$terms $start-$end" ); } elsif (/\:\/\/([^\/]*)altavista\.([^\/|\?]*)(.*)\?.*q\=([^\&]+).*stq\=(\d+)/i) { ($Host,$Domain,$Terms,$Rank) = ($1,$2,&url_decode($4),$5); ($linktext, $trailtext) = ("$Host.altavista.$Domain", "$Terms ".($Rank+1).'-'.($Rank+10) ); } elsif (/\:\/\/([^\/]*)altavista\.([^\/|\?]*)(.*)\?.*q=([^\&]+).*navig(\d+)?/i) { ($Host,$Domain,$Terms,$Rank) = ($1,$2,&url_decode($4),($5?$5:0)); ($linktext, $trailtext) = ("$Host.altavista.$Domain", "$Terms ".($Rank+1).'-'.($Rank+10) ); } elsif (/\:\/\/([^\/]*)altavista\.([^\/|\?]*)(.*)\?.*q\=([^\&]+)/i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($4)); ($linktext, $trailtext) = ("$Host.altavista.$Domain", "$Terms 1-10" ); } elsif (/\:\/\/([^\/]*)webcrawler\.([^\/]+)(.*)\?(s|search|searchText)\=([^\&]+).*\&start\=(\d+).*perPage\=(\d+)/i) { ($Host,$Domain,$Terms,$Rank,$Increment) = ($1,$2,&url_decode($5),$6,$7); ($linktext, $trailtext) = ( "$Host.webcrawler.$Domain" , "$Terms ".($Rank+1)."-".($Rank+$Increment) ); } elsif (/\:\/\/([^\/]*)webcrawler\.([^\/]+).*(s|search|searchText)\=([^\&]+)/i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($4)); ($linktext, $trailtext) = ( "$Host.webcrawler.$Domain", "$Terms 1-25" ); } elsif (/\:\/\/([^\/]*)metacrawler\.([^\/]+).*general\=([^\&]+).*start\=(\d+).*rpp\=(\d+)/i) { ($Host,$Domain,$Terms,$Rank,$Increment) = ($1,$2,&url_decode($3),$4,$5); ($linktext, $trailtext) = ( "$Host.metacrawler.$Domain", "$Terms ".($Rank+1)."-".($Rank+$Increment) ); } elsif (/\:\/\/([^\/]*)metacrawler\.([^\/]+).*general\=([^\&]+)/i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.metacrawler.$Domain", "$Terms 1-25" ); } elsif (/\:\/\/([^\/]*)netfind.aol\.([^\/]+).*start=(\d+).*&search=([^\&]+).*start=(\d+).*perPage=(\d+)/i) { ($Host,$Domain,$Terms,$Rank,$Increment) = ($1,$2,&url_decode($5),$4,$6); ($linktext, $trailtext) = ( "$Host.netfind.aol.$Domain", "$Terms ".($Rank+1)."-".($Rank+$Increment) ); } elsif (/\:\/\/([^\/]*)netfind\.aol\.([^\/]+).*search=([^\&]+)/i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.netfind.aol.$Domain", "$Terms 1-25" ); } elsif (/\:\/\/([^\.]*)\.infoseek\.com(.*)\?.*qt=([^\&]+).*st=(\d+)?/i) { ($Host,$Rank,$Terms) = ($1,$5,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.infoseek.com", "$Terms ".($Rank+1).'-'.($Rank+10) ); } elsif (/\:\/\/([^\.]*)\.infoseek\.com(.*)\?.*qt=([^\&]+)/i) { ($Host,$Terms) = ($1,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.infoseek.com", "$Terms 1-10" ); } elsif (/\:\/\/([^\.]*)\.infoseek\.com(.*)\?.*oq=([^\&]+).*(st=)?(\d+)?/i) { ($Host,$Rank,$Terms) = ($1,$5,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.infoseek.com", "$Terms ".($Rank+1).'-'.($Rank+10) ); } elsif (/\:\/\/([^\.]*)\.infoseek\.com(.*)\?.*oq=([^\&]+).*(st=)?(\d+)?/i) { ($Host,$Terms) = ($1,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.infoseek.com", "$Terms 1-10" ); } elsif (/\:\/\/([^\.]*)\.excite\.com(.*)\?(.*)/i) { ($Host,$rank,$Increment) = ($1,0,10); @parts = split(/\&/,$3); foreach $part (@parts) { if ($part =~ /^search=(.*)/) { $terms = $1; $terms =~ tr/+/ /; $terms =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C',hex($1))/eg; } if ($part =~ /^perPage=(.*)/) { $Increment = $1; } if ($part =~ /^start=(.*)/) { $rank = ($1 + $Increment); } } ($linktext, $trailtext) = ( "$Host.excite.com", "$terms " . ($rank + 1) . "-" . ($rank + $Increment) ); } elsif (m!://([^\/]*)\.yahoo\.([^\/]*).*\?.*p=([^\&]+).*b=(\d+)!i) { ($Host,$Domain,$Terms,$Rank) = ($1,$2,&url_decode($3),$4); ($linktext, $trailtext) = ( "$Host.yahoo.$Domain", "$Terms ".$Rank.'-'.($Rank + 19) ); } elsif (m!://([^\/]*)\.yahoo\.([^\/]*).*\?.*p=([^\&]+)!i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.yahoo.$Domain", "$Terms 1-20" ); } elsif (/\:\/\/([^\/]*)\.hotbot\.([^\/]*).*\?.*MT=([^\&]+).*base=(\d+)/i) { ($Host,$Domain,$Terms,$Rank,$NextRank) = ($1,$2,&url_decode($3),($4+11),($4+20)); ($linktext, $trailtext) = ( "$Host.hotbot.$Domain", "$Terms $Rank-$NextRank" ); } elsif (/\:\/\/([^\/]*)\.hotbot\.([^\/]*).*\?.*MT=([^\&]+)/i) { ($Host,$Domain,$Terms) = ($1,$2,&url_decode($3)); ($linktext, $trailtext) = ( "$Host.hotbot.$Domain", "$Terms 1-10" ); } elsif (/http\:\/\/(.*)/i) { $linktext = $1; } if (($PREF{'MaxChars'}) and (length($linktext) > $PREF{'MaxChars'})) { $linktext = substr( $linktext, 0, $PREF{'MaxChars'} ); $const{'truncated_keys'}++; } if ($trailtext) { if (($PREF{'MaxChars'}) and (length($trailtext) > $PREF{'MaxChars'})) { $trailtext = substr( $trailtext, 0, $PREF{'MaxChars'} ); $const{'truncated_keys'}++; } return '' . $linktext . ' ' . $trailtext . ''; } else { return '' . $linktext . ''; } } # End url_format procedure. =item AuthPref Usage: ($err, $b_is_login, %PREF) = &AuthPref( $prefs_file ); =cut sub AuthPref { my ($PrefsFile) = @_; my $b_is_login = 0; my $err = ''; Err: { local $_; # Try to open the prefs file: unless (open(PREF, "<$PrefsFile")) { $err = "unable to open file '$PrefsFile' for reading - $!"; next Err; } binmode(PREF); # Initialize $PREF{'format'} and {'maximum'} %PREF = ( 'format', '', 'maximum', '', 'end_date', '', 'start_date', '', 'since_last', '', 'last_number', '', 'last_number_temp', '', 'AuthIP', '', 'last_string', '', 'MaxWidth', 400, 'MaxChars', 128, 'My_Web_Address' => &query_env('HTTP_HOST'), 'images_folder' => 'http://www.xav.com/images/', 'Filter', '', 'recent', '', 'NumSort', 'CHECKED', 'NewWindow', 'CHECKED', 'Highlight', 'CHECKED', 'HideDefaultDoc', 'CHECKED', 'HidePoundSigns','CHECKED', 'UseMilTime', '', ); while (defined($_ = )) { next unless (m!^([^\|]+)\|([^\|]*)!); $PREF{$1} = $2; } close(PREF); # Now authenticate this user: AUTH: { if (($AllowAnonymousForGraphs == 1) && ($FORM{'Target'} ne 'Preferences') && (!$FORM{'terminate'})) { $b_is_login = 1; last AUTH; } if ($ENV{'REMOTE_ADDR'} && ($ENV{'REMOTE_ADDR'} eq $PREF{'AuthIP'})) { $b_is_login = 1; last AUTH; } if (($Password eq $FORM{'password'}) && ($Username eq $FORM{'username'})) { $PREF{'AuthIP'} = $ENV{'REMOTE_ADDR'}; $b_is_login = 1; last AUTH; } if (($FORM{'password'}) || ($FORM{'username'})) { # check to see if crypt is in effect: if (($FORM{'username'} eq $Username) and ($Password eq crypt($FORM{'password'},substr(0,2,$FORM{'password'})))) { $b_is_login = 1; last AUTH; } print "

Invalid username or password!

\n"; print "feel free to try again...\n"; } else { print &Authenticate; } last Err; } # User authenticated, continue parsing the preferences. Save them if # necessary: $ThisDayNum = ($MyT[5] * 1000) + $MyT[7] + 1900000; if (($PREF{'last_number_temp'} < $ThisDayNum) || (!$PREF{'last_number'})) { $PREF{'last_number'} = $PREF{'last_number_temp'}; $PREF{'last_string'} = $PREF{'last_string_temp'}; } $PREF{'last_number_temp'} = $ThisDayNum; $PREF{'last_string_temp'} = (&DateByNum((@MyT)[4,3],$MyT[5]+1900))[0]; if ($FORM{'incoming'}) { if ($const{'is_demo'}) { $err = "the saving of preferences has been disabled in the on-line demo"; next Err; } $FORM{'images_folder'} = &Trim($FORM{'images_folder'}); for ('maximum','MaxWidth','MaxChars','My_Web_Address','start_date','end_date','Filter','format', 'images_folder') { $PREF{$_} = $FORM{$_}; delete $FORM{$_}; } for (keys %GraphOptions,'since_last','recent','NumSort','NewWindow','Highlight','HideDefaultDoc','HidePoundSigns', 'UseMilTime') { $PREF{$_} = $FORM{$_} ? 'CHECKED' : ''; delete $FORM{$_}; } } $PREF{'MaxWidth'} = 400 unless ($PREF{'MaxWidth'}); # abbreviated flag for numerical sorting: $NUMS = $PREF{'NumSort'} eq 'CHECKED' ? 1 : 0; # abbreviate bgcolor attribute: if ($PREF{'Highlight'} eq 'CHECKED') { $BGCOLOR = 'BGCOLOR="#dddddd"'; } else { $BGCOLOR = ''; } if ($PREF{'NewWindow'}) { $TARGET = ' TARGET="_blank"'; } else { $TARGET = ''; } if (($ENV{'REMOTE_ADDR'}) and ($ENV{'REMOTE_ADDR'} eq $PREF{'AuthIP'})) { # only write out preferences for authenticated users: unless (open(PREF, ">$prefs")) { $err = "unable to open file '$prefs' for writing - $!"; next Err; } binmode(PREF); foreach $key (sort keys %PREF) { next if (($key eq 'AuthIP') and ($FORM{'Target'}) and ($FORM{'Target'} eq 'LogOut')); print PREF $key.'|'; print PREF $PREF{$key} if ($PREF{$key}); print PREF "|\n"; } close(PREF); } last Err; } return ($err, $b_is_login, %PREF); } sub DateIsValid { ($MM,$DD,$YYYY) = @_; for ($MM,$DD,$YYYY) { return 0 unless m!^\d*$!; } return 0 if (($MM < 1) || ($MM > 12) || ($DD < 1)); if ($YYYY % 4) { return 0 if ($DD > (31,29,31,30,31,30,31,31,30,31,30,31)[$MM-1]); } else { return 0 if ($DD > (31,28,31,30,31,30,31,31,30,31,30,31)[$MM-1]); } return 1; } sub GetYDAY { ($MM,$DD,$YYYY) = @_; if (($YYYY % 4) == 0) { return ((0,31,60,91,121,152,182,213,244,274,305,335)[$MM] + $DD - 1); } else { return ((0,31,59,90,120,151,181,212,243,273,304,334)[$MM] + $DD - 1); } } sub DateByNum { # this is failing for YDAY sometimes. # accepts computer date, returns text string, yday. ($MM, $DD, $YYYY) = @_; # test: # print "\n"; $DD--;$DD++; if ($YYYY < 1000) { if ($YYYY < 50) { $YYYY += 2000; } else { $YYYY += 1900; } } $YDAY = &GetYDAY($MM,$DD,$YYYY); $DaysSince1970 = int(($YYYY-1970)*365.25) + $YDAY + 1; $WeekDay = $LongWeekDays[(localtime($DaysSince1970 * 86400))[6]]; # test: # print "\n"; return ("$LongMonths[$MM] $DD, $YYYY", $YDAY); } sub FormatDates { ($StartInput, $EndInput, $Recent, $SinceLast, $LastNumber) = @_; ($StartNumber, $StartString, $EndNumber, $EndString) = (0,'',0,''); ($MM,$DD,$YYYY) = (0,0,0); MMDDYY: for ($StartInput) { if ($_) { if (m!^\s*(\d{2,2})\D*(\d{2,2})\D*(\d{2,4})?!) { ($MM,$DD,$YYYY) = ($1,$2,($3 ? $3 : $MyT[5])); last MMDDYY if &DateIsValid($MM,$DD,$YYYY); } if (m!^\s*(\d{1,2})\D+(\d{1,2})\D*(\d{2,4})?!) { ($MM,$DD,$YYYY) = ($1,$2,($3?$3:$MyT[5])); last MMDDYY if &DateIsValid($MM,$DD,$YYYY); } LITERAL_MONTH: { if (m!^\s*(\D+)(\d{1,2})\D*(\d{2,4})?!) { ($MonthString,$DD,$YYYY) = ($1,$2,($3?$3:$MyT[5])); } elsif (m!^\s*(\d{1,2})(\D*)(\d{2,4})?!) { ($MonthString,$DD,$YYYY) = ($2,$1,($3?$3:$MyT[5])); } else { last LITERAL_MONTH; } for ($MM=1;$MM<=12;$MM++) { if ($MonthString =~ m!$ShortMonths[$MM-1]!i) { last MMDDYY if &DateIsValid($MM,$DD,$YYYY); last LITERAL_MONTH; } } } for $X (0..6) { if (m!$ShortWeekDays[$X]!i) { ($MM,$DD,$YYYY) = (localtime(time-((7+$MyT[6]-$X)%7)*86400))[4,3,5]; $MM++; last MMDDYY; } } for $X (0..2) { if (m!$ShortDayNames[$X]!i) { ($MM,$DD,$YYYY) = (localtime(time+($X-1)*86400))[4,3,5]; $MM++; last MMDDYY; } } } if ($Recent) { ($MM,$DD,$YYYY) = (localtime((time-86400)))[4,3,5]; $MM++; last MMDDYY; } } # End MMDDYY. if ($MM && $DD && defined($YYYY)) { # kick INT mode, and correct for human->computer month indexing: $MM--; if ($YYYY < 1000) { # User is entering an abbreviated date. Is it 01 for 2001, or 99 for 1999? if ($YYYY < 50) { $YYYY += 2000; } else { $YYYY += 1900; } } ($StartString,$YDAY) = &DateByNum($MM,$DD,$YYYY); $StartNumber = ($YYYY * 1000) + $YDAY; } elsif ($SinceLast) { # or "if $StartInput existed maybe but didn't successfully match anything, and $Recent was not defined, # but $SinceLast is... $StartNumber = $LastNumber; $YYYY = int($LastNumber/1000); $YDAY = $LastNumber % 1000; $DaysSince1970 = int(($YYYY-1970)*365.25) + $YDAY + 1; ($DD,$MM,$YYYY,$WeekDay) = (localtime($DaysSince1970 * 86400))[3..6]; $YYYY += 1900; $WeekDay = $LongWeekDays[$WeekDay]; $StartString = "$WeekDay, $LongMonths[$MM] $DD, $YYYY"; } # Zero out: ($MM,$DD,$YYYY) = (0,0,0); MMDDYY: for ($EndInput) { if ($_) { if (m!^\s*(\d{2,2})\D*(\d{2,2})\D*(\d{2,4})?!) { ($MM,$DD,$YYYY) = ($1,$2,($3?$3:$MyT[5])); last MMDDYY if &DateIsValid($MM,$DD,$YYYY); } if (m!^\s*(\d{1,2})\D+(\d{1,2})\D*(\d{2,4})?!) { ($MM,$DD,$YYYY) = ($1,$2,($3?$3:$MyT[5])); last MMDDYY if &DateIsValid($MM,$DD,$YYYY); } LITERAL_MONTH: { if (m!^\s*(\D+)(\d{1,2})\D*(\d{2,4})?!) { ($MonthString,$DD,$YYYY) = ($1,$2,($3?$3:$MyT[5])); } elsif (m!^\s*(\d{1,2})(\D*)(\d{2,4})?!) { ($MonthString,$DD,$YYYY) = ($2,$1,($3?$3:$MyT[5])); } else { last LITERAL_MONTH; } for ($MM=1;$MM<=12;$MM++) { if ($MonthString =~ m!$ShortMonths[$MM-1]!i) { last MMDDYY if &DateIsValid($MM,$DD,$YYYY); last LITERAL_MONTH; } } } for $X (0..6) { if (m!$ShortWeekDays[$X]!i) { ($MM,$DD,$YYYY) = (localtime(time-((7+$MyT[6]-$X)%7)*86400))[4,3,5]; $MM++; last MMDDYY; } } for $X (0..2) { if (m!$ShortDayNames[$X]!i) { ($MM,$DD,$YYYY) = (localtime(time+($X-1)*86400))[4,3,5]; $MM++; last MMDDYY; } } } if ($Recent || $SinceLast) { ($MM,$DD,$YYYY) = (localtime(time))[4,3,5]; $MM++; last MMDDYY; } } # End MMDDYY. if ($MM && $DD && defined($YYYY)) { # kick INT mode, and correct for human->computer month indexing: $MM--; if ($YYYY < 1000) { # User is entering an abbreviated date. Is it 01 for 2001, or 99 for 1999? if ($YYYY < 50) { $YYYY += 2000; } else { $YYYY += 1900; } } ($EndString,$YDAY) = &DateByNum($MM,$DD,$YYYY); unless ($Recent || $SinceLast) { $EndNumber = ($YYYY * 1000) + $YDAY; } } return ($StartNumber, $StartString, $EndNumber, $EndString); } =item PrintDebugInfo() This runs a filesystem test against $LogFile and dumps a ton of (hopefully) useful information to the screen. =cut sub PrintDebugInfo { my ($verbose) = @_; my $err = ''; Err: { print "

Testing log file '$LogFile'...

\n"; if (-e $LogFile) { print "

File exists.

\n"; } else { print "

Warning: file does not exist.

\n"; } if (open(FILE,">>$LogFile")) { binmode(FILE); close(FILE); print "

Success: log file is writable.

\n"; } else { print "

Error: unable to write to the log file - $! - $^E.

\n"; print "

Resolve this error by creating an empty file named '$LogFile' (if one doesn't already exist) and making it writable.

\n"; } print "

Testing preferences file '$prefs'...

\n"; if (-e $prefs) { print "

File exists.

\n"; } else { print "

Warning: file does not exist.

\n"; } if (open(FILE,">>$prefs")) { binmode(FILE); close(FILE); print "

Success: prefs file is writable.

\n"; } else { print "

Error: unable to write to the prefs file - $! - $^E.

\n"; print "

Resolve this error by creating an empty file named '$prefs' (if one doesn't already exist) and making it writable.

\n"; } unless ($verbose) { print "

Vist the debug page for more detailed information.

\n"; last Err; } print <<"EOM";

AXS Debug Screen

This is one tool, of many, to help you out. Read the trouble-shooting guide if you need more detailed assistance.

Standard Debugging Information:

Script Version: $VERSION
Script file: $0
Perl version: $]
Operating system: $^O

Environment Variables:

EOM foreach (sort keys %ENV) { print "\n"; } print <<"EOM";
" . &html_encode($_) . ":" . &html_encode(substr($ENV{$_},0,60)) . "

Visit the AXS help page for more information. AXS is copyright 1997-2001 by Fluid Dynamics.
EOM last Err; } } sub AddCommas { $_ = reverse shift; s!(\d{3,3})!$1,!g; $_ = reverse $_; s!^,!!o; return $_; } #changed 0022 =item check_regex Usage: $err = &check_regex($pattern); Checks against ?{} code-executing expressions. Uses an eval wrapper to confirm that the expression is valid. =cut sub check_regex { my ($pattern) = @_; my $err = ''; Err: { if ($pattern =~ m!\?\{!) { $err = sprintf("query pattern '%s' contains illegal ?{} code-executing regular expression", &html_encode($pattern)); next Err; } eval '"foo" =~ m!$pattern!;'; if ($@) { $err = sprintf("unable to evaluate pattern '%s' - %s", &html_encode($pattern), &html_encode($@)); undef($@); next Err; } } return $err; } =item WebFormL Usage: &WebFormL( \%FORM ); Returns a by-reference hash of all name-value pairs submitted to the CGI script. updated: 8/21/2001 Dependencies: &url_decode &query_env =cut sub WebFormL { my ($p_hash) = @_; my @Pairs = (); if ('POST' eq &query_env('REQUEST_METHOD')) { my $buffer = ''; my $len = &query_env('CONTENT_LENGTH',0); read(STDIN, $buffer, $len); @Pairs = split(m!\&!, $buffer); } elsif (&query_env('QUERY_STRING')) { @Pairs = split(m!\&!, &query_env('QUERY_STRING')); } else { @Pairs = @ARGV; } local $_; foreach (@Pairs) { next unless (m!^(.*?)=(.*)$!s); my ($name, $value) = (&url_decode($1), &url_decode($2)); if ($$p_hash{$name}) { $$p_hash{$name} .= ",$value"; } else { $$p_hash{$name} = $value; } } } sub url_decode { local $_ = defined($_[0]) ? $_[0] : ''; tr!+! !; s!\%([a-fA-F0-9][a-fA-F0-9])!pack('C', hex($1))!eg; return $_; } =item query_env Usage: my $remote_host = &query_env('REMOTE_HOST'); Abstraction layer for the %ENV hash. Why abstract? Here's why: 1. adds safety for -T taint checks 2. always returns '' if undef; prevent -w warnings =cut sub query_env { my ($name,$default) = @_; if (($ENV{$name}) and ($ENV{$name} =~ m!^(.*)$!s)) { return $1; } elsif (defined($default)) { return $default; } else { return ''; } } =item Trim Usage: my $word = &Trim(" word \t\n"); Strips whitespace and line breaks from the beginning and end of the argument. =cut sub Trim { local $_ = defined($_[0]) ? $_[0] : ''; s!^[\r\n\s]+!!o; s![\r\n\s]+$!!o; return $_; } sub Assert { return if ($_[0]); my ($package, $file, $line) = caller(); print "Content-Type: text/html\015\012\015\012"; print "

Assertion Error:
Package: $package
File: $file
Line: $line


"; } =item SetDefaults Usage: my $text = &SetDefaults( $html, \%params ); Takes $html, which is an HTML fragment including FORM elements, and sets all default attributes to match %params. Requires strict format: Generally will accept double-quoted attributes, and unquoted attributes which don't contain any embedded space. In the case of replacing "hidden"-type fields, will only insert new values for hidden form elements that do not already have a value. This code will insert CHECKED and SELECTED attributes for the appropriate form elements, but will not overwrite existing CHECKED and SELECTED attributes. The recommended way to formulate your input forms is to not use these explicit defaults. The code will overwrite default VALUE="x" values for INPUT TEXT and INPUT PASSWORD and TEXTAREA. Dependencies: &html_encode =cut sub SetDefaults { my ($text, $p_params) = @_; &Assert('HASH' eq ref($p_params)); my @array = split(m!<(INPUT|SELECT|TEXTAREA)([^\>]+?)\>!is, $text); my $finaltext = $array[0]; my $x = 1; for ($x = 1; $x < $#array; $x += 3) { my ($tag, $attribs, $trail) = (uc($array[$x]), $array[$x+1], $array[$x+2]); Tweak: { my $tag_name = ''; if ($attribs =~ m! NAME\s*=\s*\"([^\"]+?)\"!is) { $tag_name = $1; } elsif ($attribs =~ m! NAME\s*=\s*(\S+)!is) { $tag_name = $1; } else { # we cannot modify what we do not understand: last Tweak; } # does the user have an over-ride value defined for this? last Tweak unless (defined($$p_params{$tag_name})); my $setval = &html_encode($$p_params{$tag_name}); if ($tag eq 'INPUT') { # discover VALUE and TYPE my $type = 'TEXT'; if ($attribs =~ m! TYPE\s*=\s*\"([^\"]+?)\"!is) { $type = uc($1); } elsif ($attribs =~ m! TYPE\s*=\s*(\S+)!is) { $type = uc($1); } # discover VALUE and TYPE my $value = ''; if ($attribs =~ m! VALUE\s*=\s*\"([^\"]+?)\"!is) { $value = $1; } elsif ($attribs =~ m! VALUE\s*=\s*(\S+)!is) { $value = $1; } # we can only set values for known types: if (($type eq 'RADIO') or ($type eq 'CHECKBOX')) { # this code does not overwriting existing explicit CHECKED attributes in INPUT tags # we avoid this because it would be expensive to distinguish between CHECKED as an attribute and the literal CHECKED inside a VALUE="" or other attribute; we *could* do it, but it would be expensive, and since SetDefaults is only called on pre-formatted forms, we choose long-term efficiency # should this be checked? if ($setval eq $value) { $attribs = " CHECKED$attribs"; } } elsif (($type eq 'TEXT') or ($type eq 'PASSWORD') or ($type eq 'HIDDEN')) { # but only hidden fields if value is null: last Tweak if (($type eq 'HIDDEN') and ($value ne '')); # replace any existing VALUE tag: my $qm_value = quotemeta($value); $attribs =~ s! VALUE\s*=\s*\"$qm_value\"! VALUE="$setval"!iso; $attribs =~ s! VALUE\s*=\s*$qm_value! VALUE="$setval"!iso; # add the tag if it's not present (i.e. if no VALUE was present in original tag) my $qm_setval = quotemeta($setval); unless ($attribs =~ m! VALUE="$qm_setval"!s) { $attribs = " VALUE=\"$setval\"$attribs"; } } } elsif ($tag eq 'SELECT') { # this code does not overwriting existing explicit SELECTED attributes in OPTION tags # does not support