Приглашаем посетить
Грибы (grib.niv.ru)

Page Control

#!/usr/local/bin/perl

$NOT = $NO = 0;
$ONLY = $YES = 1;

############################################################################
#                                                                          #
# PageControl                       Version 2.0                            #
# Written by Craig A. Patchett      craig@patchett.com                     #
# Created 3/12/96                   Last Modified 12/10/96                 #
#                                                                          #
# Copyright 1997 Craig Patchett & Matthew Wright.  All Rights Reserved.    #
# This program is part of The CGI/Perl Cookbook from John Wiley & Sons.    #
# License to use this program or install it on a server (in original or    #
# modified form) is granted only to those who have purchased a copy of The #
# CGI/Perl Cookbook. (This notice must remain as part of the source code.) #
#                                                                          #
############################################################################


############################################################################
# Define configuration constants                                           #
############################################################################

# @NO_COUNT_HOSTS holds a list of host names or addresses that will not be 
# included in the visitor counts. Partial names and addresses are fine (i.e. 
# somewhere.com will match somebody.somewhere.com and 111.222.333. will 
# match 111.222.333.444).

@NO_COUNT_HOSTS = ('mydomain.com');

# If $ALLOW_HOSTS equals $NOT (or 0) then any hosts in @ALLOW_HOSTS will  
# be barred from accessing pages. Otherwise ONLY hosts in @ALLOW_HOSTS  
# will be allowed access.

$ALLOW_HOSTS = $NOT;
@ALLOW_HOSTS = ('badhost.com', 'badguy@domain.com', '100.100.100');

# $AUTH_CHECK should be set to $YES or $NO depending on whether or not you 
# want the program to check to see if the visitor is currently authenticated. 
# If not, the program will automatically call the Authenticate program 
# provided elsewhere in this book.

$AUTH_CHECK = $YES;

# $REPEAT_LIMIT is the minimum time in seconds that must separate visits from
# the same host in order for them to be considered unique

$REPEAT_LIMIT = 30;

# $LOG_DIR is the full path to the directory where your log files will be 
# kept (Note that it should include a directory delimiter at the end.)

$LOG_DIR = '/home/web/pagecontrol/logs/';

# $BASE_DIR is the full path to the root directory from which pages can
# be accessed. (Note that it should include a directory delimiter at the 
# end.)

$BASE_DIR = '/home/protected/pages';

# $RELATIVE_URL is the URL for the root directory for relative links, usually 
# $BASE_DIR

$RELATIVE_URL = 'http://www.domain.com/pctest';

# $LOCK_DIR is the full path to the directory where all lock files will be 
# placed (Note that it should include a directory delimiter at the end.) If 
# you're using a UNIX web server with flock it must be a directory outside 
# the web space, i.e. '/tmp/'

$LOCK_DIR = '/tmp/';

# $LOG_FILE is the name of the main access log file. It will be created
# and updated in $LOG_DIR.

$LOG_FILE = 'access_log';

# $S is the character(s) that will be used to separate fields in the log 
# file. It has a short name to make the code easier to read
# (Warning: Don't change this once you've started using the program
# without updating any existing log files)

$S = "\t";

# $LOG_HEADER is an optional field name header for the log file. It will  
# only be written when the file is first created

$LOG_HEADER =  'user_id' . $S . 'remote_addr' . $S . 'host_name' . $S .
               'visit_time' . $S . 'page_name' . $S . 'refer_page' . $S .
               'browser' . $S . 'error_code\n';

# $LINK_INFO holds the default format for displaying link statistics.
# $LINK_COUNT_NONE holds the default message for links that have not been 
# accessed yet.

$LINK_INFO = '(<<COUNT>> accesses since <<FIRST>>)';
$LINK_COUNT_NONE = '(No accesses yet)';

# $DATE_FORMAT holds the default format for link dates (in &format_date() 
# form)

$DATE_FORMAT = '<m>/<d>/<yr>';

# $LOCAL_FORMAT holds the &format_date() format for the local time
# $VISIT_FORMAT holds the &format_date() format for various visit dates (first, 
# last, etc.) $MOD_FORMAT holds the &format_date() format for the file 
# modification time

$LOCAL_FORMAT = 'Local time is <weekday>, <month> <d>, <year> at <time>';
$VISIT_FORMAT = '<month> <d>, <year>';
$MOD_FORMAT = '<month> <d>, <year> at <time>';

# $AUTH_URL is the URL for the Authenticate program you wish to use
# for authenticating visitors. If you choose not to use the authentication
# option then this should be set to ''.

$AUTH_URL = 'http://www.domain.com/cgi-bin/authenticate.cgi';

# $AUTH_DIR is the full path to the directory where your authentication
# files will be kept. (Note that it should include a directory delimiter at 
# the end.)

$AUTH_DIR = '/home/protected/track/';

# $MAX_AGE is the maximum age in minutes since it was last accessed that a 
# file in $AUTH_DIR should still be considered valid.

$MAX_AGE = 10;

# $NOT_ALLOWED_URL is the URL of the file to link to if the current host is
# not allowed access. If it is left undefined then a default message will be
# shown instead. 

$NOT_ALLOWED_URL = '';

# $MAX_WAIT is the maximum number of seconds the lock program will wait for a
# locked file to become unlocked

$MAX_WAIT = 5;

# $NAME_LEN is the maximum filename length in characters (not including 
# extensions)

$NAME_LEN = 32;

# $ERROR_PAGE is the full path to the HTML file that contains the template 
# for an error message. This message should contain '<<ERROR_TITLE>>', 
# '<<ERROR_NAME>>', and '<<ERROR_MSG>>' (without quotes) wherever you want 
# the page title, error name, and error message to appear.

$ERROR_PAGE = '/home/web/error.html';

# $PERMISSIONS is the type of permissions you want to give to the files
# the program creates. They are expressed in standard UNIX octal format 
# (i.e. 0666). If your server is running on a system that doesn't support 
# file permissions then you should set $PERMISSIONS to 0.

$PERMISSIONS = 0666;

# $REQUIRE_DIR is the directory in which all of your required files are
# placed.  On most systems, if you leave the required files in the same
# directory as the CGI program, you can leave this variable blank.  
# Otherwise, if you move the required files to another directory, specify
# the full or relative path here.

$REQUIRE_DIR = 'require';


############################################################################
# Get required subroutines which need to be included.                      #
############################################################################

# Push $REQUIRE_DIR onto the @INC array for include file directories
# and list required files.

push(@INC, $REQUIRE_DIR) if $REQUIRE_DIR;

require 'addrhost.pl';
require 'error.pl';
require 'formdate.pl';
require 'locksubs.pl';
require 'template.pl';
require 'ipconvrt.pl';
require 'authchck.pl';


############################################################################
# Initialize other constants                                               #
############################################################################

$PROGRAM_URL = "http://$ENV{'SERVER_NAME'}$ENV{'SCRIPT_NAME'}";
@DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
         'Saturday');
@MONTHS = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 
           'August', 'September', 'October', 'November', 'December');
$DAY_SECS = 86400;


############################################################################
# Determine current date and time                                          #
############################################################################

$page_time = time;
$VAR{'DATENOW'} = &format_date($page_time, $LOCAL_FORMAT);
($page_year, $page_year_day) = (localtime($page_time))[5,7];


############################################################################
# Call the authentication routine if the authentication option is set      #
############################################################################

if ($AUTH_CHECK) { 
    ($VAR{'USER_ID'}, $VAR{'PREVVISIT'}) =  &auth_check(1);
    $VAR{'PREVVISIT'} = &format_date($VAR{'PREVVISIT'}, $MOD_FORMAT);
}


############################################################################
# Set the requested page based on how the program was called               #
############################################################################

if ($ENV{'DOCUMENT_URI'}) {                       # Called as SSI   
    $page_name = $ENV{'DOCUMENT_URI'};
    $ssi=$YES;
}
else { $page_name = $ENV{'PATH_INFO'} }
$request = $page_name = substr($page_name, 1);


############################################################################
# Check to see if this is a request for a link and parse accordingly       #
############################################################################

if ($page_name =~ m|^(.+)/link:(.+)|) {
    
    # This is an internal link
    
    $link_file = $1;
    $link_name = $2;
    $link_name =~ tr/+/ /;
    $link_request = 1;
}
elsif ($page_name =~ s|^([a-zA-Z]+:/)(.+)|$1/$2|) {

    # This is a URL for an external link
    
    $requested_link = $page_name;
    $link_request = 2;
}
elsif ($page_name !~ m|\.s?html?(\?[^.]*)*$|i) {
        
    # This is a relative external link
    
    $requested_link = $page_name;
    $link_request = 3;
}
elsif ($page_name =~ m|^tmp:(.+)|) {
    
    # This is a temporary page
    
    $page_name = $1;
    $temporary_page = $YES;
}


############################################################################
# If this is an html page make sure it exists and is readable              #
############################################################################

if (!$link_request) {

    # Set the page path

    $page_path = "$BASE_DIR$page_name";

    # Check to see if it's a valid, readable file
    
    if (!(-f $page_path)) { 
        $error_msg = "$page_name is an invalid file name ($!).";
        $VAR{'USER_ID'} = '*';
        $access_error = 2;
    }
    elsif (!(-r $page_path)) { 
        $error_msg = "$page_name is not readable ($!).";
        $VAR{'USER_ID'} = '*';
        $access_error = 3;
    }
    elsif (!open(PAGE, $page_path)) {
        $error_msg = "$page_name could not be opened ($!).";
        $VAR{'USER_ID'} = '*';
        $access_error = 4;
    }


############################################################################
# Read the requested page into memory and check for options near the top   #
############################################################################

    for ($line = 1; <PAGE>; ++$line) {
        if (($line < 3) && /<!-- *PAGE OPTIONS/i) {
            
            # Check for name of count file
            
            if (/COUNT="([^"]+)"/i) { $count_file = $1 }
    
            # Check for default statistics footer switch
            
            if (/FOOTER(="([^"]+)")?/gi) { 
                $footer_file = $2;
                $footer = $YES 
            }
            
            # Check for link count switch
            
            if (/LINKCOUNT/i) { $link_count = $YES }
            
            # Check for link count format specification
            
            if (/LINKINFO="([^"]+)"/i) { $link_info = $1 }
            
            # Check for variable substitution
            
            if (/VARIABLES/i) { $variables = $YES }
        }
        else { push (@page_data, $_) }
    }
    close(PAGE);
    
    # If this is a temporary page then delete it
    
    if ($temporary_page) { 
        unlink($page_path) || &error("$page_name could not be deleted ($!).");
    }


############################################################################
# Set the count and link file names if not set already                     #
############################################################################

    # Check first to see if a count file name was specified in the page file
    
    if (!$count_file) {
    
        # If not, remove directory separators and periods from $page_path
        
        $count_file = $page_name;
        $count_file =~ tr/\/\\:\.//d;
        
        # Shorten the name if necessary and add the directory info and 
        # extension
        
        if ($NAME_LEN && ($NAME_LEN < length($count_file))) {
            $count_file = substr($count_file, -$NAME_LEN);
        }
        $count_file .= '.log';
    }
    $count_path = "$LOG_DIR$count_file";
    
    # Set the link file name
    
    $link_file = $count_file;
    $link_file =~ s/\.[^.]*$//;
    $link_file .= '.lnk';
    
    
############################################################################
# Check to see if we should ignore this host for counting purposes         #
############################################################################
    
    foreach $host (@NO_COUNT_HOSTS) {
        if (($ENV{'REMOTE_HOST'} =~ /$host/i) || ($ENV{'REMOTE_ADDR'} =~ /$host/)) {
            $ignore = $YES;
            last;
        }
    }
}


############################################################################
# Read in any link information for this page                               #
############################################################################

# Set link file path

$link_path = "$LOG_DIR$link_file";

# If this is a link and the link file doesn't exist, set up an error message

if (($link_request == 1) && !(-e $link_path)) {
    $error_msg = "\"$link_file\" is not a valid link file.";
    $VAR{'USER_ID'} = '*';
    $access_error = 6;
}
elsif ($link_request < 2) {
    if (&lock($link_file, $LOCK_DIR, $MAX_WAIT)) { &error("$Error_Message") }
    if (!(-e $link_path) || (-z $link_path)) {
        open(LINK, ">>$link_path") || &error("Could not create $link_file ($!).");
        chmod $PERMISSIONS, $link_path if $PERMISSIONS;
        print LINK "$page_name\n";
    }
    else {
        open(LINK, $link_path) || &error("Could not open $link_file ($!).");
    
        # Make sure the link file's page name matches the current page
        
        $file_page = <LINK>;
        chop($file_page) if $file_page =~ /\n$/;
        if ($link_request) {
            $page_name = $file_page;
        }
        if ($file_page ne $page_name) { 
            if (&unlock($link_file, $LOCK_DIR)) { &error("$Error_Message") }
            &error("The link file names for $page_name and $file_page are the same.");
        }
    
        # Read the link data into memory
        
        while ($link = <LINK>) {
            chop($link);
            chop($link_names{$link} = <LINK>);
            chop($link_titles{$link} = <LINK>);
            chop($link_count{$link} = <LINK>);
            chop($link_added{$link} = <LINK>);
            chop($link_first{$link} = <LINK>);
            chop($link_last{$link} = <LINK>);
            $links{$link_names{$link}} = $link;
        }   
    }
    close(LINK);
    if (&unlock($link_file, $LOCK_DIR)) { &error("$Error_Message") }

    # If this is a link request, make sure it's a valid link name
    
    if ($link_request) {
        $requested_link = $links{$link_name};
        if (!$requested_link) {
            $error_msg = "\"$link_name\" is not a valid link name for this page.";
            $VAR{'USER_ID'} = '*';
            $access_error = 5;
        }
    }
}


############################################################################
# Determine whether or not this host is allowed access                     #
############################################################################

# Go through the list of allowed/disallowed hosts and check for a match

foreach $host (@ALLOW_HOSTS) {
    if (($ENV{'REMOTE_HOST'} =~ /$host/i) || ($ENV{'REMOTE_ADDR'} =~ /$host/)) {
        
        # There's a match so see if it means this host is allowed or not 
        # allowed
        
        $allowed = $ALLOW_HOSTS ? $YES : $NO;
        last;
    }
}

# Check to see if there was no match and the list is of allowed hosts

if (!defined($allowed)) { 
    $allowed = ($ALLOW_HOSTS == $ONLY) ? $NO : $YES;
}

# Set flags if access denied

if ($allowed == $NO) { 
    $VAR{'USER_ID'} = '*';
    $access_error = 1;
}


############################################################################
# Update site access log                                                   #
############################################################################

# Convert the IP to a host name if no host name given

if (!$ENV{'REMOTE_HOST'} || ($ENV{'REMOTE_HOST'} =~ /\d+\.\d+\.\d+\.\d+/)) {
    $VAR{'HOST_NAME'} = &addr_to_host($ENV{'REMOTE_ADDR'});
}
else { $VAR{'HOST_NAME'} = $ENV{'REMOTE_HOST'} }

if (!$ignore) {

    # Go ahead and append the log file
    
    if (&lock($LOG_FILE, $LOCK_DIR, $MAX_WAIT)) { 
        &error("$Error_Message");
    }
    $access_log = "$LOG_DIR$LOG_FILE";
    open(LOG, ">>$access_log") || &error("$access_log could not be opened ($!).");
    
    # If we just created the log file, make sure its permissions are set 
    # properly and write the header line if one has been specified
    
    if (-z $access_log) { 
        chmod $PERMISSIONS, $access_log if $PERMISSIONS;
        if ($LOG_HEADER) { print LOG $LOG_HEADER }
    }
    
    # Add the new info to the log file then close and unlock the file
    
    if ($access_error) { $page_name = $request }
    print LOG "$VAR{'USER_ID'}$S$ENV{'REMOTE_ADDR'}$S$VAR{'HOST_NAME'}$S";
    print LOG "$page_time$S$page_name$S$ENV{'HTTP_REFERER'}$S";
    print LOG "$ENV{'HTTP_USER_AGENT'}$S$access_error\n";
    close(LOG);
    
    if (&unlock($LOG_FILE, $LOCK_DIR)) { &error("$Error_Message") }
}

# Give them an error message if there was something wrong with their request

if ($access_error > 1) { &error($error_msg) }


############################################################################
# Show them to the door if they are not allowed access                     #
############################################################################

if ($allowed == $NO) {
    if ($NOT_ALLOWED_URL) {
        print "Location:$NOT_ALLOWED_URL\n\n";
        exit(0);
    }
    else {
        &error('You are not authorized to access this page.',
           '*** ACCESS DENIED ***');
    }
}


############################################################################
# Process link requests and redirect accordingly                           #
############################################################################

if ($link_request) {
    if (!$ignore && $link_request == 1) {   
    
        # Update the data for this link
        
        ++$link_count{$requested_link};
        if (!$link_first{$requested_link}) { 
            $link_first{$requested_link} = $page_time;
        }
        $link_last{$requested_link} = $page_time;
    
        # Write out the link data
        
        &write_links;
    }

    # Redirect to the linked file and exit the program
    
	if (!($requested_link =~ m|[^/]+://|)) {
	        
        if ($requested_link =~ /^#/) {
            
            # This is an internal anchor
            
            $requested_link = "$PROGRAM_URL/$page_name$requested_link";
        }
        else {
            
            # This is a relative link
            
            $requested_link = "$RELATIVE_URL/$requested_link";
        }
    }
    print "Location: $requested_link\n\n";
    exit(0);
}


############################################################################
# Retrieve and update page counts                                          #
############################################################################

# Set the backup file name

$backup_path = $count_path;
$backup_path =~ s/\.[^.]*$//;
$backup_path .= '.bak';
        
# Lock the count file for this page

if (&lock($count_file, $LOCK_DIR, $MAX_WAIT)) { &error("$Error_Message") }

# Create and initialize the count file if necessary

$total_visits = $day_visits = $today_visits = 0;
if (!(-e $count_path)) { 
    if (!open(COUNT, ">$count_path")) {
        $open_err = $!;
        if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }
        &error("Could not create count file $count_file ($open_err).");
    }
    chmod $PERMISSIONS, $count_path if $PERMISSIONS;
    $first_visit = time;
    print COUNT "$page_name\n$total_visits\n$first_visit\n" if !$ignore;
}
else {

    # Make sure we can open the count file
    
    if (!open(COUNT, $count_path)) {
        $open_err = $!;
        if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }
        &error("Could not open count file $count_file ($open_err).");
    }
    
    # Make sure the count file's page name matches the current page
    
    $file_page = <COUNT>;
    chop($file_page);
    if ($file_page ne $page_name) { 
        if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }
        &error("The count file names for $page_name and $file_page are the same.");
    }
    # Read the old total visits and first visit date
    
    $total_visits = <COUNT>;
    chop($total_visits);
    $first_visit = <COUNT>;
    chop($first_visit);
    
    # Read through the visit data and calculate statistics
    
    while (($visit_time = <COUNT>) && ($visit_host = <COUNT>)) {
        chop($visit_time);
        chop($visit_host);
        $last_visit = $visit_time;
        
        # Is it within the last 24 hours?
        
        if ($page_time - $visit_time < $DAY_SECS) {
            
            # Update the counts
            
            ++$day_visits;
            ($year, $year_day) = (localtime($visit_time))[5,7];
            if ($year == $page_year && $year_day == $page_year_day) { 
                ++$today_visits;
            }          
            
            # Save the visit data
            
            push(@visit_data, $visit_time, $visit_host);
            
            # Save the visit time if it's our current host
            
            if ($visit_host eq $ENV{'REMOTE_ADDR'}) { 
                $prev_visit = $visit_time;
            }
        }
    }
}
close(COUNT);

# Update counts if it's not a duplicate and we're not ignoring

if (!$ignore && ($page_time - $prev_visit > $REPEAT_LIMIT)) {
    ++$total_visits;
    ++$day_visits;
    ++$today_visits;
    push (@visit_data, $page_time, $ENV{'REMOTE_ADDR'});
}
elsif (!$ignore) { $VAR{'DUPLICATE'} = $YES }

# Create a backup file

if (!open(BACKUP, ">$backup_path")) {
    $open_err = $!;
    if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }
    &error("Could not create backup file $backup_file ($open_err).");
}
# Write the new data into the backup file

print BACKUP "$page_name\n$total_visits\n$first_visit\n";
$, = $\ = "\n";
print BACKUP @visit_data;
close(BACKUP);
$\ = "";

# Copy the backup file to the count file and unlock the count file

unlink($count_path);
if (!rename($backup_path, $count_path)) {
    $open_err = $!;
    if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }
    &error("Could not restore count file $count_file ($open_err).");
}
if (&unlock($count_file, $LOCK_DIR)) { &error("$Error_Message") }


############################################################################
# Save all the variables into the VAR array                                #
############################################################################

$VAR{'TOTALVISITS'} = $total_visits;
$VAR{'24HRVISITS'} = $day_visits;
$VAR{'TODAYVISITS'} = $today_visits;
$VAR{'FIRSTVISIT'} = &format_date($first_visit, $VISIT_FORMAT);
$VAR{'LASTVISIT'} = &format_date($last_visit, $VISIT_FORMAT);
$VAR{'PREVVISIT'} = &format_date($prev_visit, $MOD_FORMAT) if $prev_visit;
$file_mod = $^T - int((-M $page_path) * $DAY_SECS);
$VAR{'LASTMOD'} = &format_date($file_mod, $MOD_FORMAT);


############################################################################
# Process body of page                                                     #
############################################################################

# Pass through the page, substituting variables where necessary
    
$start_body = $body_found = $NO;
$line_num = 0;

foreach $line (@page_data) {

    # Check to see if we've started the body of the page yet
    
    if (!$start_body && $line =~ /<BODY.*>/i) { $body_found = $YES }
    
    # Check to see if this is the end of the body of the page
    
    if ($start_body && ($footer || $ssi) && $line =~ /<\/BODY>/i) { last }
    
    # Check to see if there are links on this line that need to be processed
    
    $link = '';
    if ($link_count) {

        # Check first to see if this is the start or end of a LINKSORT block
        
       if ($line =~ m|<(/?)LINKSORT *([^ >]*)|i) {
            if (!$1) {
            
                # This is the start of a block, so check for sort field
                # and order and set the sort flag
                
                $sort_field = $2 ? $2 : 'COUNT';
                $Sort_Reverse = ($line =~ /REVERSE/i);
                $link_sort = $YES;
                undef %block_links;
            }
            elsif ($link_sort && defined(%block_links)) {
                
                # This is the end of an existing block with links in it, so 
                # save the links in the requested sort order
                
                foreach $line (sort by_field (keys %block_links)) { 
                    $page_data[$line_num++] = $line 
                }
                $link_sort = $NO;
            } 
            next;
        }
        
        # Next, check for links     
  
        $line_copy = '';
        while ($line =~ /<A LINKNAME="([^"]+)" HREF="([^"]+)/i) {
            
            # Give the results easy-to-understand names
            
            $link = $2;
            $link_name = $1;
            $before_match = $`;
            $after_match = $';
            
            # If it's already being passed through the program, remove the
            # program name temporarily.
            
            #$link =~ s/^$PROGRAM_URL\///;

            # Check for duplicate link name
            
            if ($link_name && $links{$link_name} 
              && ($links{$link_name} ne $link)) {
                &error("$links{$link_name} and $link have both been given the name \"$link_name\".");
            }
            elsif ($link_name) { 
            
                # If it's a new link then update the link data
                
                if (!$link_names{$link}) {
                    $link_names{$link} = $link_name;
                    $links{$link_name} = $link;
                    $link_added{$link} = $page_time;
                    $link_update = $YES;
                }

                # Modify the link so it's passed through this program

                $name_conv = $link_name;
                $name_conv =~ tr/ /+/;
                $line_copy .= $before_match . "<A HREF=\"$PROGRAM_URL/$link_file/link:$name_conv";
            }
            else {
            
                # There's no link name so we can't process it
                
                $line_copy .= $before_match . "<A HREF=\"$link";
            }
            
            # Save link title if it's on the same line
            
            $title_start = index($after_match, '>') + 1;
            $title_end = index($after_match, '</A>', $title_start);
            if ($title_end > $title_start) { 
                $link_titles{$link} = substr($after_match, $title_start, $title_end - $title_start);
            }
        
            # Only check from this match to the end of the line next time
            
            $line = $after_match;
        }
        
        # Set $line according to whether or not any matches were found
        
        $line = $line_copy ? $line_copy . $after_match : $line;
    }
    
    # Substitute any other variables on this line if the variable option 
    # is on
    
    $line_copy = '';
    $changes = 0;
        
    if ($variables) {
    
        # Scan the line for variables
        
        while ($line =~ /<<([^>]+)>>/) {
            $variable = $1;
            $before_match = $`;
            $after_match = $';
            
            # Check for link variables if allowed, scanning through line
            # and substituting values
            
            undef($value);
            if ($link_count && $variable =~ /^LINK:([^ ]+) *'(.+)'/i) {
                
                # Convert the variable name to uppercase
                
                $variable = $1;
                $variable =~ tr/a-z/A-Z/;
                
                # Translate the link name into the link
                
                $link = $links{$2};
                
                # Substitute the appropriate variables
                
                if ($variable eq 'COUNT' && defined($link_count{$link})) { 
                    $value = sprintf("%0d", $link_count{$link});
                }
                elsif ($variable eq 'TITLE' && $link_titles{$link}) { 
                    $value = $link_titles{$link};
                }
                elsif ($variable eq 'ADDED' && $link_added{$link}) { 
                    $value = &format_date($link_added{$link}, $DATE_FORMAT);
                }
                elsif ($variable eq 'FIRST' && $link_first{$link}) { 
                    $value = &format_date($link_first{$link}, $DATE_FORMAT);
                }
                elsif ($variable eq 'LAST' && $link_last{$link}) { 
                    $value = &format_date($link_last{$link}, $DATE_FORMAT);
                }
                elsif ($variable eq 'INFO') {
                    
                    # Check for a format definition and count for this link
                    
                    $value = $link_info ? $link_info : $LINK_INFO;
                    if ($value && $link_count{$link}) {
                        
                        # Substitue the appropriate variables
                        
                        $count = sprintf("%0d", $link_count{$link});
                        $value =~ s/<<COUNT>>/$count/gi;
                        $first = &format_date($link_first{$link}, $DATE_FORMAT);
                        $value =~ s/<<FIRST>>/$first/gi;
                        $last = &format_date($link_last{$link}, $DATE_FORMAT);
                        $value =~ s/<<LAST>>/$last/gi;
                    }
                    elsif ($value) { $value = $LINK_COUNT_NONE }
                }
            }
            elsif (defined($ENV{$variable})) { $value = $ENV{$variable} }
            elsif (defined($VAR{$variable})) { $value = $VAR{$variable} }
                        
            # Substitute the appropriate value if any are found
        
            if (defined($value)) { ++$changes }
            $line_copy .= $before_match . $value;

            # Only check from this match to the end of the line next time
            
            $line = $after_match;
        }
        
        # Set $line according to whether or not any matches were found
        
        $line = $line_copy ? $line_copy . $after_match : $line;
    }
    
    # Check to see if this line is a link in the middle of a linksort block
    
    if ($link && $link_sort) {
    
        # It is, so save the line and sort field until the end of the block
        
        if    ($sort_field =~ /COUNT/i) { $sort_value = $link_count{$link} }
        elsif ($sort_field =~ /ADDED/i) { $sort_value = $link_added{$link} }
        elsif ($sort_field =~ /LAST/i)  { $sort_value = $link_last{$link} }
        elsif ($sort_field =~ /FIRST/i) { $sort_value = $link_first{$link} }
        
        $block_links{$line} = $sort_value;
    }
    else {

        # Save line depending on presence of 0: and variables
         
        if (!($line =~ s/^0://) || (!$line_copy) 
          || ($line_copy && $changes)) {
            if (!$ssi || $start_body) { $page_data[$line_num++] = $line }
            else { $start_body = $body_found }
        }
    }
}
# Print the MIME header

print "Content-type: text/html\n\n" if !$ssi;

# Print the modified lines

foreach $line (splice(@page_data, 0, $line_num)) { print $line }

# Update link_file

if ($link_update) { &write_links }


############################################################################
# Add footer if requested                                                  #
############################################################################

if ($footer) {
    if ($footer_file) { 
        if (!&parse_template($footer_file, *STDOUT)) {
            &error("Could not open footer file $footer_file ($!).");
        }
    }
    else {
        print "<P><CENTER><HR SIZE=3 WIDTH=75%><I>\n";
        print "<BR>$VAR{'DATENOW'}\n";
        print "<BR>This page was last updated on $VAR{'LASTMOD'}.\n";
        if ($VAR{'TOTALVISITS'} == 1) {
            print "<P>You are the first person to visit this page!\n";
        }
        else {
            print "<P>There have been $VAR{'TOTALVISITS'} visits to this page since $VAR{'FIRSTVISIT'}.\n";
            if ($VAR{'TODAYVISITS'} == 1) {
                print "<BR>There has been one visit to this page so far today, ";
            } 
            else {
                print "<BR>There have been $VAR{'TODAYVISITS'} visits to this page so far today, ";
            }
            if ($VAR{'24HRVISITS'} == 1) {
               print "one visit in the last 24 hours.\n";
            } 
            else {
               print "$VAR{'24HRVISITS'} visits in the last 24 hours.\n";
            }
        }
        print "</I></CENTER>\n";
    }
}


############################################################################
# Finish page if not called as SSI                                         #
############################################################################

print "</BODY></HTML>\n" if ($footer && !$ssi);


############################################################################
# End of main program                                                      #
############################################################################

exit(0);


############################################################################
# Local subroutine to write links file                                     #
############################################################################

sub write_links {

    # Set the backup file name
    
    $backup_path = $link_path;
    $backup_path =~ s/nk$/bk/;
        
    # Lock the link file and create a backup file

    if (&lock($link_file, $LOCK_DIR, $MAX_WAIT)) { 
        &error("$Error_Message");
    }
    
    if (!open(BACKUP, ">$backup_path")) {
        $open_err = $!;
        if (&unlock($link_file, $LOCK_DIR)) { &error("$Error_Message") }
        &error("Could not create backup file $backup_file ($open_err).");
    }
    
    # Write the updated link data into the backup file
    
    print BACKUP "$page_name\n";
    foreach $link_name (sort keys %links) {
        $link = $links{$link_name};
        print BACKUP "$link\n";
        print BACKUP "$link_name\n";
        print BACKUP "$link_titles{$link}\n";
        print BACKUP sprintf("%0d\n", $link_count{$link});
        print BACKUP "$link_added{$link}\n";
        print BACKUP "$link_first{$link}\n";
        print BACKUP "$link_last{$link}\n";
    }
    close(BACKUP);
    
    # Copy the backup file to the link file and unlock the link file
    
    unlink($link_path);
    rename($backup_path, $link_path) 
        || &error("Could not restore count file $count_file ($!).");
    if (&unlock($link_file, $LOCK_DIR)) { &error("$Error_Message") }
}


############################################################################
# Local subroutine to sort links in link block                             #
############################################################################

sub by_field {
    if ($Sort_Reverse) { $block_links{$b} <=> $block_links{$a} || $b cmp $a }
    else               { $block_links{$a} <=> $block_links{$b} || $a cmp $b }
}