#!/usr/bin/perl
#
# MiniVend version 3.05
#
# $Id: minivend,v 1.13 1997/12/09 09:24:51 mike Exp mike $
#
# This program is largely based on Vend 0.2
# Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
#
# Portions from Vend 0.3
# Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
#
# Enhancements made by and
# Copyright 1996, 1997 by Michael J. Heins <mikeh@iac.net>
#
# See the file 'Changes' for information.
#
# This program is free software; most users can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later
# version. The only exception is that any organization employing
# uninvited commercial email solicitations, commonly known as
# SPAM, may not use MiniVend for a period of one year after any
# SPAM incident. Failure to discontinue use immediately upon
# written notice will cause the charge of a $10,000 per day license
# fee until such time as MiniVend use is discontinued. The author
# of MiniVend, Michael J. Heins, shall be the sole judge of what
# constitutes a SPAM incident.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

use Config;
BEGIN {

	eval {
		require 5.004;
		require FindBin;
		1 and $Global::VendRoot = $FindBin::RealBin;
		1 and $Global::VendRoot =~ s/.bin$//i ;
	};
($Global::VendRoot = $ENV{MINIVEND_ROOT})
	if defined $ENV{MINIVEND_ROOT};
	
$Global::VendRoot = $Global::VendRoot || '/home/minivend';
$Global::ConfigFile = 'minivend.cfg';

die "MiniVend not configured. Did you make a catalog?\n"
	unless -f "$Global::VendRoot/$Global::ConfigFile";

$Global::ConfDir = "$Global::VendRoot/etc";

$Global::SendMailLocation = '/usr/lib/sendmail';

if($Config{osname} =~ /win32/i) {
	$Global::Windows = 1;
}
# Uncomment next line if you want to guarantee use of DB_File
#$ENV{MINIVEND_DBFILE} = 1;

# Uncomment next line if you want to use no DBM, sessions
# stored in files and databases in memory (or mSQL)
#$ENV{MINIVEND_NODBM} = 1;

# Uncomment AND SET next line to set PGP path to somewhere besides
# the MiniVend user path
#$ENV{PGPPATH} = '/usr/local/pgp';

$Global::ErrorFile = 'error.log';
}


### END CONFIGURABLE VARIABLES

use lib "$Global::VendRoot/lib";
use vars qw($VERSION);

$VERSION = '3.05';
use strict;
use Fcntl;

# BSD, among others, defines sendmail to be in /usr/sbin, and
# we want to make sure the program is there. Insert the location
# of you sendmail binary (the configure script should do this)
$Global::SendMailLocation = ($Global::Windows and $Global::SendMailLocation) ||
	(-x $Global::SendMailLocation and $Global::SendMailLocation) ||
	(-x '/usr/lib/sendmail' and '/usr/lib/sendmail') ||
	(-x '/usr/sbin/sendmail' and '/usr/sbin/sendmail') ||
	$Config{sendmail};

#select a DBM

BEGIN {
	chdir $Global::VendRoot
		or die "Couldn't change directory to $Global::VendRoot: $!\n";
	$Global::GDBM = $Global::DB_File = $Global::Msql = $Global::DBI = 0;
	eval {require Msql and $Global::Msql = 1};
	# This is for standard DBI
	eval {require DBI and $Global::DBI = 1};

	# This is for experimenting with Win32::ODBC
	#use DBI::W32ODBC;
	#$Global::DBI = 1;

	AUTO: {
		last AUTO if 
			(defined $ENV{MINIVEND_DBFILE} and $Global::DB_File = 1);
		last AUTO if 
			(defined $ENV{MINIVEND_NODBM} and $Global::Msql == 1);
		eval {require GDBM_File and $Global::GDBM = 1} ||
		eval {require DB_File and $Global::DB_File = 1};
	}

	if($Global::GDBM) {
		require Vend::Table::GDBM;
		import GDBM_File;
		$Global::GDBM = 1;
	}
	elsif($Global::DB_File) {
		require Vend::Table::DB_File;
		import DB_File;
		$Global::DB_File = 1;
	}
	else {
		require Vend::Table::InMemory;
	}
}



use Vend::Server;
use Vend::Session;
use Vend::Config;
use Vend::Imagemap;

# GLIMPSE
use Vend::Glimpse;
# END GLIMPSE

use Vend::Scan;
use Vend::TextSearch;
use Vend::Order;
use Vend::Data;
use Vend::Util;
use Vend::Interpolate;
use Vend::ValidCC;
use Vend::Cart;
use File::CounterFile;
use File::Path;

# OPTION_EXTENSION
#use Getopt::Long;
# END OPTION_EXTENSION

# STATICPAGE
use Vend::PageBuild;
# END STATICPAGE

my $H;
sub http {
	$H;
}

$Global::ConfigFile = "$Global::VendRoot/$Global::ConfigFile"
    if ($Global::ConfigFile !~ m.^/.);
$Global::ErrorFile = "$Global::VendRoot/$Global::ErrorFile"
    if ($Global::ErrorFile !~ m.^/.);

sub check_html {
	my($out) = @_;

	unless($Global::CheckHTML) {
		logError("Can't check HTML: No global CheckHTML defined. Contact admin.");
	}

	my $file = POSIX::tmpnam();
	open(CHECK, "|$Global::CheckHTML > $file 2>&1")	or die "Couldn't fork: $!\n";
	print CHECK $$out;
	close CHECK;
	my $begin = "<!-- HTML Check via '$Global::CheckHTML'\n";
	my $end   = "\n-->";
	my $check = readfile($file);
	unlink $file					or die "Couldn't unlink temp file $file: $!\n";
	$$out .= $begin . $check . $end;
	return;
}
	
sub response {
	my ($type,$output,$debug) = @_;
	#$Vend::content_type = $type;
	check_html(\$output) if defined $Vend::CheckHTML;
	my $content = $Vend::ContentType || "text/$type";
	http()->respond($content,$output,$debug);
}

## INTERFACE ERROR

# An incorrect response was returned from the browser, either because of a
# browser bug or bad html pages.

sub interaction_error {
    my($msg) = @_;
    my($page);

    logError ("Difficulty interacting with browser: $msg");

    $page = readin($Vend::Cfg->{'Special'}->{'interact'});
    if (defined $page) {
		$page =~ s#\[message\]#$msg#ig;
		response('html',interpolate_html($page));
    }
	else {
		logError("Missing special page: interact");
		response('plain',"$msg\n");
    }
}

## EXPECT FORM

# Check that a form is being submitted.

sub minidump() {
	my $s = $Vend::Session;
	my $v = $Vend::Session->{'values'};
    my $host = http()->Client_Hostname;
    my $ip   = http()->Client_IP_Address;
    my $query = http()->Query;
	my $out = <<EOF;
Full client host name:  $host
Full client IP address: $ip
Query string was:       $query
EOF
	for(qw/browser last_url prev_url/) {
		$out .= "$_: $s->{$_}\n";
	}

	for(keys %{$s->{carts}}) {
		$out .= scalar @{$s->{carts}->{$_}};
		$out .= " items in $_ cart.\n";
	}

	return $out;
}

sub expect_form {
    if ( uc($CGI::request_method) ne 'POST') {
		interaction_error("Request method '$CGI::request_method' for form submission is not POST\n");
		logError(minidump());
		return 0;
    }

	return 1 if $Global::TolerateGet;

    if ( lc($CGI::content_type) ne 'application/x-www-form-urlencoded') {
		interaction_error("Content type '$CGI::content_type' for form submission" .
				" is not application/x-www-form-urlencoded");
		logError(minidump());
		return 0;
    }
    return 1;
}


## ACTIONS SPECIFIED BY THE INVOKING URL

## DO CATALOG

# Display the initial catalog page.

sub do_catalog {
    do_page($Vend::Cfg->{'Special'}->{'catalog'});
}


## DO PAGE

sub display_special_page {
    my($name, $subject) = @_;
    my($page);
	
	$subject = $subject || 'unspecified error';
	
    $page = readin($name);
    die "Missing special page: $name\n" unless defined $page;
    $page =~ s#\[subject\]#$subject#ig;
    return response('html',interpolate_html($page));
}

# Displays the catalog page NAME.  If the file is not found, displays
# the special page 'missing'.
# 

sub display_page {
    my($name, $arg) = @_;
    my($page);

	if($Vend::Cfg->{'ExtraSecure'} and
		$Vend::Cfg->{AlwaysSecure}->{$name}
		and !$CGI::secure) {
		$name = $Vend::Cfg->{'Special'}->{'violation'};
	}

    $page = readin($name);
	# Try for on-the-fly if not there
	if(! defined $page) {
		$page = fly_page($name,$arg);
	}

    if (defined $page) {
		response('html',interpolate_html($page));
		return 1;
    } else {
		logData($Vend::Cfg->{'LogFile'}, format_log_msg('page not found'))
			if defined $Vend::Cfg->{CollectData}->{nopage};
		display_special_page($Vend::Cfg->{'Special'}->{'missing'}, $name);
		return 0;
    }
}


sub cache_page {
    my($name, $arg) = @_;
    my($page);
	my $pagedir = $Vend::Cfg->{ScratchDir} . '/PageCache';
	my $pagename =  generate_key($name,$arg);

	if($page = readfile("$pagedir/$pagename.html")) {
#print("Hit cache $pagename\n") if $Global::DEBUG;
    	response('html', $page);
		return 1;
	}

	$page = readin($name);
	# Try for on-the-fly if not there
	if(! defined $page) {
		$page = fly_page($name, $arg);
	}

    if (defined $page) {
    	$page = cache_html($page);
		if(defined $Vend::CachePage) {
			logData($Vend::Cfg->{'LogFile'}, format_log_msg('add to page cache'))
				if defined $Vend::Cfg->{CollectData}->{cache};
			open PAGECACHE, ">$pagedir/$pagename.html"
				or do {
					logGlobal "Page cache failure: $!";
					die "Page cache failure: $!\n";
				};
			print PAGECACHE $page;
			close PAGECACHE;
		}
		put_session();
    	response('html',$page);
		return 1;
    }
	else {
		logData($Vend::Cfg->{'LogFile'}, format_log_msg('page not found'))
			if defined $Vend::Cfg->{CollectData}->{nopage};
		return display_special_page($Vend::Cfg->{'Special'}->{'missing'}, $name);
    }
}

# Display the catalog page NAME.

sub do_page {
    my($name, $arg) = @_;

	logData($Vend::Cfg->{LogFile}, format_log_msg("$name " . $arg || undef))
		if defined $Vend::Cfg->{CollectData}->{page};

	if($Vend::Cfg->{PageCache}	and
		$CGI::cookie			and
		! defined $Vend::Cfg->{NoCache}->{$name})
	{
		cache_page($name, $arg || undef) and $Vend::Session->{'page'} = $name;
	}
	else { 
		display_page($name, $arg || undef) and $Vend::Session->{'page'} = $name;
		put_session();
	}
}


## DO ORDER

# Order an item with product code CODE.

sub do_order
{
    my($code,$path,$catalog) = @_;
    my($i, $found, $item, $save, %att);
	
#warn ("do_order: '" . (join "','", @_) . "'\n") if $Global::DEBUG;
	my($cartname,$page) = split m:/:, $path || '', 2;

	my $cart = get_cart $cartname;

    my $base = product_code_exists_tag($code, $catalog || undef);

    if (! $base ) {
		logError("Attempt to order missing product code: $code");
		display_special_page($Vend::Cfg->{'Special'}->{'noproduct'}, $code);
		return;
    }

	if(defined $Vend::Cfg->{CollectData}->{basket}) {
		$cartname = $cartname || 'main';
		my $string = "code=$code";
		$string .= " cart=" . ($cartname || '');
		$string .= " page=" . ($page 	 || '');
		$string .= " base=$base";
		logData $Vend::Cfg->{LogFile}, format_log_msg($string);
	}

    INC: {

		# Check that the item has not been already ordered.
		$found = -1;

		# Check to see if we should push an already-ordered item instead of 
		# ignoring it 
		my $separate =
				$Vend::Cfg->{SeparateItems} ||
				$CGI::values{mv_separate_items} ||
				(
					defined $Vend::Session->{scratch}->{mv_separate_items}
				 && is_yes( $Vend::Session->{scratch}->{mv_separate_items} )
				 );
		last INC if $separate;

		foreach $i (0 .. $#$cart) {
			if ($cart->[$i]->{'code'} eq $code) {
				next unless $cart->[$i]->{mv_ib} eq $base;
				$found = $i;
			}
		}

	} # INC

    # And if not found or separate, start with a single quantity.
    if ($found == -1) {
		$item = {'code' => $code, 'quantity' => 1, mv_ib => $base};
		if($Vend::Cfg->{UseModifier}) {
			foreach $i (@{$Vend::Cfg->{UseModifier}}) {
				$item->{$i} = '';
			}
		}
		push @$cart, $item;
    }

	my $limit;
	if($limit = $Vend::Cfg->{OrderLineLimit} and $#$cart >= $limit ) {
		@$cart = ();
		my $msg = <<EOF;
WARNING:
Possible bad robot. Cart limit of $limit exceeded.  Cart emptied.
EOF
		my $cmd;
		if($cmd = $Vend::Cfg->{LockoutCommand}) {
			my $host = http()->Client_IP_Address;
			$cmd =~ s/%s/$host/ or $cmd .= " $host";
			$msg .= "Performing lockout command '$cmd'.";
			system $cmd;
			$msg .= "Bad status $? from '$cmd': $!\n"
				if $?;
		}
		logError $msg;
		logGlobal $msg;
	}

    order_page($page);		# display the order page
    put_session();
}


## DO SEARCH

sub do_search {
	my($c) = \%CGI::values;

	if($Vend::Cfg->{SearchCache} and $CGI::cookie) {
		my($key,$page) = check_search_cache($c);
		return response('html',$page) if defined $page;
		$c->{mv_cache_key} = $key if defined $key;
	}

	my $status = perform_search($c,@_);
	put_session if $Vend::Session->{scratch}->{mv_put_session};
	return $status;
}

sub do_scan {
	my($argument,$path) = @_;
	my ($key,$page);

	if($Vend::Cfg->{SearchCache} and $CGI::cookie) {
		($key,$page) = check_scan_cache($path);
		return response('html',$page) if defined $page;
	}

	my($c) = { mv_cache_key => $key || '' };

	find_search_params($c,$path);

	my $status = perform_search($c,$argument);
	put_session if $Vend::Session->{scratch}->{mv_put_session};
	return $status;
}

sub fake_scan {

	my($argument,$path) = @_;
	my ($key,$page);
	my $c = {};
	find_search_params($c,$path);
	return perform_search($c,$argument);
}

# Returns undef if interaction error
sub update_quantity {
	my($h, $i, $quantity, $modifier, $cart);

    return 1 unless defined  $CGI::values{"quantity0"};

	$cart = get_cart($CGI::values{mv_cartname});

	if(ref $Vend::Cfg->{UseModifier}) {
		foreach $h (@{$Vend::Cfg->{UseModifier}}) {
			foreach $i (0 .. $#$cart) {
				$modifier = $CGI::values{"$h$i"} || undef;
				if (defined($modifier)) {
					$modifier =~ s/\0+/\0/g;
					$modifier =~ s/\0$//;
					$modifier =~ s/^\0//;
					$modifier =~ s/\0/, /g;
					$cart->[$i]->{$h} = $modifier;
					$Vend::Session->{'values'}->{"$h$i"} = $modifier;
					#delete $Vend::Session->{'values'}->{"$h$i"};
				}
			}
		}
	}

	foreach $i (0 .. $#$cart) {
    	$quantity = $CGI::values{"quantity$i"};
    	if (defined($quantity) && $quantity =~ m/^\d+$/) {
        	$cart->[$i]->{'quantity'} = $quantity;
    	}
    	elsif (defined($quantity) && $quantity =~ m/^[\d.]+$/
				and $Vend::Cfg->{FractionalItems} ) {
        	$cart->[$i]->{'quantity'} = $quantity;
    	}
		# This allows a multiple input of item quantity to
		# pass -- FIRST ONE CONTROLS
		elsif (defined $quantity && $quantity =~ s/\0.*//) {
			$CGI::values{"quantity$i"} = $quantity;
			redo;
		}
		elsif (defined $quantity) {
			my $item = $cart->[$i]->{'code'};
        	interaction_error("'$quantity' for item $item is not numeric\n");
        	return undef;
    	}
		else {
        	interaction_error("Variable '$quantity' not passed from form\n");
        	return undef;
    	}
    }

	# If the user has put in "0" for any quantity, delete that item
    # from the order list.
    toss_cart($cart);

	1;

}

sub add_items {

	my($items,$quantities,$bases) = @_;
	my(@items);
	my($code,$found,$item,$base,$quantity,$i,$j,$q);
	my(@quantities);
	my(@bases);
	my($attr,%attr);

	@items = split /\0/, $items;

	my $cartname = $CGI::values{mv_cartname};

	my $cart = get_cart($cartname);

	if($quantities ||= '') {
		@quantities = split /\0/, $quantities;
	}

	$bases = $bases || $CGI::values{mv_order_mv_ib} || '';
	if($bases) {
		@bases = split /\0/, $bases;
	}

	foreach $attr (@{$Vend::Cfg->{UseModifier}}) {
		$attr{$attr} = [];
		next unless defined $CGI::values{"mv_order_$attr"};
		@{$attr{$attr}} = split /\0/, $CGI::values{"mv_order_$attr"};
	}

	my $separate =
				$Vend::Cfg->{SeparateItems} ||
				$CGI::values{mv_separate_items} ||
				(
					defined $Vend::Session->{scratch}->{mv_separate_items}
				 && is_yes( $Vend::Session->{scratch}->{mv_separate_items} )
				 );
	$j = 0;
	foreach $code (@items) {
		$quantity = $quantities[$j] ||= 1;
		$base = product_code_exists_tag($code, $bases[$j] || undef);
		if (! $base ) {
			logError("Attempt to order missing product code: $code");
			return;
		}

		if(defined $Vend::Cfg->{CollectData}->{basket}) {
			$cartname = $cartname || 'main';
			logData $Vend::Cfg->{LogFile},
				format_log_msg(
				"FORM ORDER code=$code qty=$quantity cart=$cartname base=$base");
		}


		INCREMENT: {

			# Check that the item has not been already ordered.
			# But let us order separates if so configured
			$found = -1;
			last INCREMENT if $separate;

			foreach $i (0 .. $#$cart) {
				if ($cart->[$i]->{'code'} eq $code) {
					next unless $base eq $cart->[$i]->{mv_ib};
					$found = $i;
					# Increment quantity. This is different than
					# the standard handling because we are ordering
					# accessories, and may want more than 1 of each
					$cart->[$i]->{'quantity'} += $quantity;
				}
			}
		} # INCREMENT

		# An if not, start of with a single quantity.
		if ($found == -1) {
			$item = {'code' => $code, 'quantity' => $quantity, mv_ib => $base};
			if($Vend::Cfg->{UseModifier}) {
				foreach $i (@{$Vend::Cfg->{UseModifier}}) {
					$item->{$i} = $attr{$i}->[$j];
				}
			}
			my $next = $#$cart + 1;
			push @$cart, $item;
			$CGI::values{"quantity$next"} = $quantity;
		}
		$j++;
	}
}
	
## DO FINISH

# Finish an incomplete order.

sub do_finish {
	my($page) = shift || $Vend::Cfg->{'CheckoutPage'};
	$page =~ s/^finish_?_?//;
    order_page($page);
    put_session();
}

# Update the user-entered fields.
sub update_data {
	my($key,$value);
    # Update a database record

	unless (defined $CGI::values{'mv_data_table'} and 
		    defined $CGI::values{'mv_data_key'}      ) {
		logError("Attempted database operation without table, fields, or key.\n" .
				 "Table: '$CGI::values{'mv_data_table'}'\n" .
				 "Fields:'$CGI::values{'mv_data_fields'}'\n" .
				 "Key:   '$CGI::values{'mv_data_key'}'  \n"     );
		
		return undef;
	}

	my $function	= $CGI::values{'mv_data_function'};
	my $table		= $CGI::values{'mv_data_table'};
	my $prikey		= $CGI::values{'mv_data_key'};
	my ($ref, $type, $db, $msql, $database);

	$ref = $Vend::Cfg->{Database}->{$table} || '';

	$Vend::WriteDatabase{$table} = 1;

	if( $ref->{'type'} eq '7') {
		$type = 'sql';
		$msql = 'm';
		$database = $Vend::Cfg->{MsqlDB};
	}
	elsif($ref->{'type'} eq '8') {
		$type = 'sql';
		$database = $table;
	}
	elsif ($function =~ /(m)?sql/i) {
		$type = 'sql';
		$msql = $1 || '';
		$database = $Vend::Cfg->{MsqlDB} if $msql;
	}
	elsif ($db = database_exists_ref($table)) {
		$type = 'dbm';
		$db = $db->ref();
	}
	else {
		interaction_error("Non-existent database table '$table'");
		return undef;
	}

	my @fields		= split /\s*,\s*/, $CGI::values{'mv_data_fields'};

	$function = $function =~ /insert/i ? 'insert' : 'update';

	my (%data);
	for(@fields) {
		$data{$_} = [];
	}

    while (($key, $value) = each %CGI::values) {
        next unless defined $data{$key};
		@{$data{$key}} = split /\0/, $value;
	}

	unless ($data{$prikey}) {
		logError("No key '$prikey' found in database $function operation.\n" .
				 "Table: '$CGI::values{'mv_data_table'}'\n" .
				 "Key:   '$CGI::values{'mv_data_key'}'  \n"   );
		return undef;
	}

	my ($query,$i);
	my (@k);
	my (@v);
	my (@c);

	my $select_key;
	for($i = 0; $i < @{$data{$prikey}}; $i++) {
		@k = (); @v = ();
		for(keys %data) {
			next unless (($value = $data{$_}->[$i]) || $CGI::values{mv_update_empty});
			push(@k, $_);
			$value =~ s/'/\\'/g;
			$select_key = $value if $_ eq $prikey;
			push(@v, $value);
		}
		if($type eq 'sql' and $function eq 'insert') {
			sql_query('set', "", "delete from $table where code = '$select_key'",
						$msql || undef, $database);
			$query = "insert into $table (";
			$query .= join ",", @k;
			$query .= ") VALUES ('";
			$query .= join "','", @v;
			$query .= "')";
		}
		elsif ($type eq 'sql' and $function eq 'update') {
			$query = "UPDATE $table SET ";
			my $what;
			@c = ();
			while (@k) {
				( ($key = shift @k), ($value = shift @v), next )
					if $k[0] eq $prikey;
				$what = (shift @k) . "='" . (shift @v) . "'";
				push @c, $what;
			}
			$query .= join ", ", @c;
			$query .= " WHERE $key = '$value'";
		}
		else {
			my $field;
			$key = $data{$prikey}->[$i];
			while($field = shift @k) {
				$value = shift @v;
				next if $field eq $prikey;
				$db->set_field($key, $field, $value);
			}
		}
				
		#logGlobal("\nquery: $query\n");
		sql_query('set', "", $query, $msql || undef, $database)
			if $type eq 'sql';
	}


}

# Parse the mv_click and mv_check special variables
sub parse_click {
	my ($ref, $click, $extra) = @_;
    my($codere) = '[\w-_#/.]+';
	my $params = $Vend::Session->{'scratch'}->{$click} || return 1;
	my($var,$val,$parameter);
	$params = interpolate_html($params);
	my(@param) = split /\n+/, $params;

	for(@param) {
		next unless /\S/;
		next if /^\s*#/;
		s/^[\r\s]+//;
		s/[\r\s]+$//;
		$parameter = $_;
		($var,$val) = split /[\s=]+/, $parameter, 2;
		$val =~ s/&#(\d+);/chr($1)/ge;
		$ref->{$var} = $val;
		$extra->{$var} = $val
			if defined $extra;
	}
}

# This is the set of CGI-passed variables to ignore, in other words
# never set in the user session.  If set in the mv_check pass, though,
# they will stick.
my %Ignore = qw(
	mv_todo  1
	mv_todo.submit.x  1
	mv_todo.submit.y  1
	mv_todo.return.x  1
	mv_todo.return.y  1
	mv_todo.checkout.x  1
	mv_todo.checkout.y  1
	mv_todo.todo.x  1
	mv_todo.todo.y  1
	mv_todo.map  1
	mv_doit  1
	mv_check  1
	mv_click  1
	mv_nextpage  1
	mv_credit_card_number  1
	);

sub resolve_argument {
	my($pointer, $key, $ref) = @_;
::logGlobal("resolve_argument: '$pointer $key'");
	$ref = \%CGI::values unless ref $ref;
	return {split /\0/, $ref->{$key}}
		if $pointer eq 'HASH';
	return [split /\0/, $ref->{$key}]
		if $pointer eq 'ARRAY';
	return $key
		if $pointer eq 'LITERAL';
	($Vend::Cfg->{SubArgs} = $key, return)
		if $pointer eq 'SAFE';

	my(@args) = split /\0/, ($ref->{$key} || '');
	my $out = [];
	for (@args) {
		next unless $_;
		if (/^(HASH|ARRAY|CODE|LITERAL|SAFE)\((.*)\)$/) {
			# recursive
			push @$out, resolve_argument($1,$2);
		}
		else {
			push @$out, $ref->{$_}
		}
	}
	my $args = 'sub ' . ($Vend::Cfg->{SubArgs}->{$key} || '');
::logGlobal("resolve_argument call sub: '$args', '$key', '" . join(" ", @$out) . "'");
	return tag_perl($args, $key, (scalar @$out ? @$out : undef) );
}
		

# Update the user-entered fields.
sub update_user {
	my($key,$value);
    # Update the user-entered fields.

	if (defined $CGI::values{'mv_order_item'} and 
		$value = $CGI::values{'mv_order_item'} ) {
		my $quantities = $CGI::values{mv_order_quantity} ||= '';
		add_items($value,$quantities);
		delete $CGI::values{mv_order_quantity};
		delete $CGI::values{mv_order_item};
	}

	if( $Vend::Cfg->{CreditCardAuto} and $CGI::values{mv_credit_card_number} ) {
		#logGlobal join "\n",
			#encrypt_standard_cc(\%CGI::values);
		(
			$Vend::Session->{'values'}->{mv_credit_card_valid},
			$Vend::Session->{'values'}->{mv_credit_card_info},
			$Vend::Session->{'values'}->{mv_credit_card_exp_month},
			$Vend::Session->{'values'}->{mv_credit_card_exp_year},
			$Vend::Session->{'values'}->{mv_credit_card_exp_all},
			$Vend::Session->{'values'}->{mv_credit_card_type},
			$Vend::Session->{'values'}->{mv_credit_card_error}
		)
			= encrypt_standard_cc(\%CGI::values);
	}	

	my(@args);
    while (($key, $value) = each %CGI::values) {
        next if defined $Ignore{$key};
        next if defined $Vend::Cfg->{FormIgnore}->{$key};
        next if ($key =~ m/^quantity\d+/);

		# We add any checkbox ordered items, but don't update -- 
		# we don't want to order them twice
        $Vend::Session->{'values'}->{$key} = $value;

		# Find arguments to subroutines
		next unless index($key, 'mv_arg') == 0;

        delete $Vend::Session->{'values'}->{$key};

		$key =~ s/^mv_arg(.*)/$1/;
		next if $key =~ /\D/;

		if($value =~ s/^(HASH|ARRAY|CODE|LITERAL|SAFE)\((.*)\)$/$2/) {
			my $pointer = $1;
			$args[$key] = resolve_argument($pointer,$value);
		}
		else {
			$args[$key] = $CGI::values{$value};
		}

    }

	if(defined $CGI::values{'mv_subroutine'}) {
		my $arg = $Vend::Cfg->{SubArgs}->{$CGI::values{'mv_subroutine'}} || '';
		$Vend::Session->{return_value} =
			tag_perl
			(
				'sub ' . $arg,
				$CGI::values{'mv_subroutine'},
				@args,
			);
	}

	if(defined $CGI::values{'mv_check'}) {
		delete $Vend::Session->{'values'}->{mv_nextpage};
		my(@checks) = split /\s*[,\0]+\s*/, $CGI::values{'mv_check'};
		my($check);
		foreach $check (@checks) {
				parse_click $Vend::Session->{'values'}, $check, \%CGI::values;	
		}
	}

	check_save if defined $CGI::values{'mv_save_session'};

}

sub do_sub {
	my($args, $path) = @_;
	my($routine, @args) = split /\//, $args;

	for(@args) {
		s/\%([0-9a-fA-F][0-9a-fA-F])/chr( hex ($1) )/ge;
	}
	my $arg = $Vend::Cfg->{SubArgs}->{$routine} || '';

	# Check security of admin routines
	unless ($Global::AdminSub->{$routine} and ! check_security($routine,3) ) {
		
		$Vend::Session->{return_value} = tag_perl
										(
											'sub ' . $arg,
											$routine,
											@args,
										);
	}

	return do_page($path);
}

## DO PROCESS

# Find an action from the submitted value
sub minivend_action {
	my ($todo) = (@_);

	return undef unless defined $todo;
	$todo = lc $todo;
	
	if(defined $Vend::Cfg->{'ActionMap'}->{$todo}) {
		return  $Vend::Cfg->{'ActionMap'}->{$todo};
	}
	for (keys %{$Vend::Cfg->{'ActionMap'}}) {
		return $Vend::Cfg->{'ActionMap'}->{$_} if $todo =~ /$_/i;
	}
	return $todo;
}


# Process the completed order or search page.

sub do_process {
    my($i, $doit, $quantity, $todo, $page, $key, $value);
	my($status, $nextpage, $orderpage, $ordered_items);

    expect_form() || return;

	my($click, @clicks);
	if(defined $CGI::values{'mv_click'}) {
		@clicks = split /\s*[\0]+\s*/, $CGI::values{'mv_click'};
	}

	if(defined $CGI::values{'mv_click_map'}) {
		my(@map) = split /\s*[\0]+\s*/, $CGI::values{'mv_click_map'};
		foreach $click (@map) {
			push (@clicks, $click)
				if defined $CGI::values{"mv_click.$click.x"};
		}
	}

	foreach $click (@clicks) {
		parse_click \%CGI::values, $click;	
	}

    $doit = $CGI::values{'mv_doit'};
    $todo = $CGI::values{'mv_todo'};

    $nextpage = $CGI::values{'mv_nextpage'} || $Vend::Session->{'page'};
    $orderpage = $CGI::values{'mv_orderpage'} || $Vend::Cfg->{'Special'}->{'order'};
    $ordered_items = $CGI::values{'mv_order_item'};

	# Maybe we have an imagemap input, if not, use $doit
    if (!defined $todo) {
		if (defined $CGI::values{'mv_todo.x'}) {
				my $x = $CGI::values{'mv_todo.x'};
				my $y = $CGI::values{'mv_todo.y'};
				my $map = $CGI::values{'mv_todo.map'};
				$todo = action_map($x,$y,$map);
		}
		elsif (defined $CGI::values{'mv_todo.submit.x'}) {
			$todo = 'submit';
		}
		elsif (defined $CGI::values{'mv_todo.checkout.x'}) {
			$todo = 'checkout';
		}
		elsif (defined $CGI::values{'mv_todo.return.x'}) {
			$todo = 'return';
		}
		else {
			$todo = $doit if defined $doit;
		}
	}

	$todo = minivend_action($todo);

	#Check again, see if we have a todo
    if (!defined $todo) {
			interaction_error("No action passed from form\n");
			return;
    }

	if ($todo eq 'search') {
		update_user();
    	put_session();
		return do_search();
    }
	elsif ($todo eq 'submit') {
		update_user();
		update_quantity() || return; #Return on error
		my($ok);
		my($missing,$next,$status,$final);
		my($values) = $Vend::Session->{'values'};

		# Set shopping cart
		
		$Vend::Items = get_cart $CGI::values{mv_cartname};

	  CHECK_ORDER: {

		if (defined $CGI::values{'mv_order_profile'}) {
			($status,$final,$missing) =
				check_order($CGI::values{'mv_order_profile'});
			update_user();
		}
		else {
			$status = $final = 1;
		}

		if($status) {
			$next = $Vend::Session->{'values'}->{'mv_successpage'} || $orderpage;
			display_page($next) unless $final;
		}
		else {
			$next = $CGI::values{'mv_failpage'} || $Vend::Cfg->{'Special'}->{'needfield'};
			display_special_page($next, $missing);
			last CHECK_ORDER;
		}

		last CHECK_ORDER unless $final;

		($status, $missing) = check_required($values);
		if (!$status) {
			display_special_page($Vend::Cfg->{'Special'}->{'needfield'}, $missing);
			put_session();
			return;
		}

		if($Vend::Cfg->{CyberCash} and defined $CGI::values{mv_cyber_mode}) {
			$status = cyber_charge();
			unless($status) {
				display_special_page(
					$Vend::Cfg->{Special}->{failed},
					$Vend::Session->{cybercash_error}
				);
				return;
			}
		}
				
		# This function (followed down) now does the backend ordering
		$ok = mail_order();

		# Display a receipt if configured

		if ($ok && $Vend::Session->{'values'}->{'mv_order_receipt'}) {
	    	display_special_page($Vend::Session->{'values'}->{'mv_order_receipt'});
		}
		elsif ($ok && $Vend::Cfg->{'ReceiptPage'}) {
	    	display_special_page($Vend::Cfg->{'ReceiptPage'});
		}
		elsif ($ok) {
	    	display_special_page($Vend::Cfg->{'Special'}->{'confirmation'});
		} else {
	    	display_special_page($Vend::Cfg->{'Special'}->{'failed'});
		}

		# Remove the items
		@$Vend::Items = ();
	  }

    }
	elsif ($todo eq 'refresh') {
		update_user();
		update_quantity() || return; #Return on error
		order_page($orderpage);
    }
	elsif ($todo eq 'set') {
		update_data();
		display_page($Vend::Session->{'values'}->{mv_nextpage} || $nextpage);
    }
	elsif ($todo eq 'return') {
		update_user();
		update_quantity() || return; #Return on error
		display_page($Vend::Session->{'values'}->{mv_nextpage} || $nextpage);
    }
	elsif ($todo eq 'checkout') {
		update_user();
		unless(update_quantity()) {
			interaction_error("quantities");
			return;
		}
		my $next = $CGI::values{'mv_checkout'} || $orderpage;
		display_page($next);
	}
	elsif ($todo eq 'control') {
		update_user();
		do_page($Vend::Session->{'values'}->{mv_nextpage} || $nextpage);
		return;
	}
	elsif ($todo eq 'secure') {
		if ($CGI::secure) {
			$Vend::Session->{'secure'} = 1;
			update_user();
			do_page($Vend::Session->{'values'}->{mv_nextpage} || $nextpage);
			return;
		}
		else {
			do_page($Vend::Cfg->{'Special'}->{'violation'});
			return;
		}
    }
	elsif ($todo eq 'unsecure') {
		$Vend::Session->{'secure'} = 0;
		do_page($nextpage);
		return;
	}
	elsif ($todo eq 'cancel') {
		$Vend::Session->{'values'}->{'credit_card_no'} = 'xxxxxxxxxxxxxxxxxxxxxx';
		$Vend::Session->{'values'}->{'credit_card_exp'} = 'xxxxxxxx';
		$Vend::Session->{'login'} = '';
		my $frames = $Vend::Session->{'frames'};
		put_session();
		get_session();
		init_session();
		$Vend::Session->{'frames'} = $frames;
		display_page($Vend::Cfg->{'Special'}->{'canceled'});
    }
	else {
		interaction_error(
          "Form variable 'mv_todo or mv_doit' value '$todo' not recognized\n");
		return;
    }
    put_session();
}

# does message for page build
sub do_msg {
    my ($msg, $size) = @_;
    $size = 60 unless defined $size;
    my $len = length $msg;

    return "$msg.." if ($len + 2) >= $size;
    $msg .= '.' x ($size - $len);
    return $msg;
}

sub change_catalog_directive {
	my($cat, $line) = @_;
	$line =~ s/^\s+//;
	my($dir,$val) = split /\s+/, $line, 2;
	my $ref = Vend::Config::set_directive($dir,$val);
	die "Bad directive '$line'.\n" unless defined $ref;
	$cat->{$ref->[0]} = $ref->[1];
	return 1;
}

sub change_global_directive {
	my($line) = @_;
	chomp $line;
	$line =~ s/^\s+//;
	my($dir,$val) = split /\s+/, $line, 2;
	my $ref = Vend::Config::set_directive($dir,$val,1);
	die "Bad directive '$line'.\n" unless defined $ref;
	no strict 'refs';
	${"Global::" . $ref->[0]} = $ref->[1];
#logGlobal("Set $ref->[0]=$ref->[1]");
	$Global::Structure{$ref->[0]} = $ref->[1];

	dump_structure($Global::Structure, $Global::ConfigFile)
		if $Global::DumpStructure;
	return 1;
}

sub add_catalog {
	my($line) = @_;
	$line =~ s/^\s+//;
	my ($var, $name, $val) = split /\s+/, $line, 3;
	Vend::Config::parse_catalog($var,"$name $val")
		or die "Bad catalog line '$line'\n";

	my $g = $Global::Catalog{$name}
				or die "Catalog '$name' not parsed.\n";

	my $c = $Global::Selector{$g->{script}} || {};

	$c->{CatalogName} = $name;

	my $dir = $g->{'dir'};
	my $script = $g->{'script'};

	if(defined $g->{'alias'}) {
		for(@{$g->{alias}}) {
			if (exists $Global::Selector{$_}
				and $Global::SelectorAlias{$_} ne $g->{'script'}) {
				logGlobal "Alias $_ used a second time, skipping.\n";
				next;
			}
			elsif (m![^\w-_:#/.]!) {
				logGlobal "Bad alias $_, skipping.\n";
			}
			$Global::Selector{$_} = $c;
			$Global::SelectorAlias{$_} = $g->{'script'};
		}
	}

	my $msg = <<EOF;
Added/changed catalog $name:

 Directory: $dir
 Script:    $script
EOF
	$msg .= " Base:      $g->{'base'}\n" if $g->{'base'};
	$msg .= " Aliases:   @{$g->{'alias'}}\n" if $g->{'alias'};
	
print("$msg\n") if $Global::DEBUG;
	logGlobal $msg;

	$Global::Selector{$g->{script}} = $c;
}

sub remove_catalog {
	my($name) = @_;
	my $g = $Global::Catalog{$name};
	my @aliases;

	unless(defined $g) {
		logGlobal "Attempt to remove non-existant catalog $name.\n";
		return undef;
	}

	if($g->{alias}) {
		@aliases = @{$g->{alias}};
	}

	for(@aliases) {
		delete $Global::Selector{$_};
		delete $Global::SelectorAlias{$_};
	}
	
	delete $Global::Selector{$g->{script}};
	delete $Global::Catalog{$name};

}

sub config_named_catalog {
	my ($cat_name, $source, $build) = @_;
	my ($g,$c,$conf);

	$g = $Global::Catalog{$cat_name};
	unless (defined $g) {
		logGlobal "Can't find catalog '$cat_name'";
		return undef;
	}
    logGlobal "Config '$g->{'name'}' $source";
    chdir $g->{'dir'}
            or die "Couldn't change to $g->{'dir'}: $!\n";
    $conf = $g->{'dir'} . '/etc';
    eval {
        $c = config($g->{'name'},
					$g->{'dir'},
					$conf,
					$g->{'base'} || undef,
# OPTION_EXTENSION
#					$Vend::CommandLine->{$g->{'name'}} || undef
# END OPTION_EXTENSION
					);
    };
    if($@) {
		my $msg = $@;
        logGlobal "$g->{'name'} config error: $msg";
     	return undef;
    }

	return $c if defined $g->{'base'};

	eval {
# STATICPAGE
		if ($c->{Static}) {
			print "loading static page names...";
			my $basedir = $c->{PageDir};
			open STATICPAGE, "$basedir/.static"
				or warn <<EOF;
Couldn't read static page status file $basedir/.static: $!
EOF
			while(<STATICPAGE>) {
				chomp;
				$c->{StaticPage}->{$_} = 1;
			}
			close STATICPAGE;
		}
# END STATICPAGE
		if($c->{ClearCache}) {
			for('PageCache', 'SearchCache') {
				next unless $c->{$_};
				my $dir = $c->{'ScratchDir'} . "/$_";
				rmtree($dir);
				mkpath ($dir)
					or die "Couldn't make $dir: $!\n";
				eval { logError("Cleared $dir") };
			}
		}
		$Vend::Cfg = $c;	
		read_accessories();
		read_salestax();
		read_shipping();
		open_database();
		(
			$Vend::Cfg->{ItemPriceRoutine},
			$Vend::Cfg->{QuantityPriceRoutine}
		)	= read_pricing();
# STATICPAGE
		if($build) {
			$Vend::BuildingPages = 1;
			# Depends on whether user builds are enabled globally
			build_all($g->{'name'})
				if $Global::UserBuild;
			undef $Vend::BuildingPages;
		}
# END STATICPAGE
		close_database();
	};
	undef $Vend::Cfg;
	undef $Vend::BuildingPages;  # In case of eval error
    if($@) {
		my $msg = $@;
		$msg =~ s/\s+$//;
        logGlobal $g->{'name'} . " config error: $msg";
     	return undef;
    }

	dump_structure($c, $g->{name}) if $Global::DumpStructure;

	return $c;

}

# STATICPAGE
sub build_page {
    my($name,$dir,$check,$scan) = @_;
    my($base,$page);
	my $status = 1;

	unless($scan) {
		$page = readin($name);
		# Try for on-the-fly if not there
		if(! defined $page) {
			$page = fly_page($name);
			$name = $Vend::Cfg->{ItemLinkDir} . $name
				if $Vend::Cfg->{ItemLinkDir};
		}
	}
	else {
		$name =~ s!^$Vend::Cfg->{StaticPath}/!!;
		$page = readfile("$dir/$name", 0);
		my $string = $Vend::Cfg->{VendURL} . "/scan/" ;
		return 0 unless $page =~ m!$string!;
		$name =~ s!$Vend::Cfg->{StaticSuffix}$!!;
		$Vend::ForceBuild = 1;
	}

    if (defined $page) {

		unless($check) {
		  open(BUILDPAGE, ">$dir/$name$Vend::Cfg->{StaticSuffix}")
			or die "Couldn't create file $dir/$name" .
					$Vend::Cfg->{StaticSuffix} . ": $!\n";
		}

		$page = cache_html($page) unless defined $scan;
		unless (defined $Vend::CachePage or defined $Vend::ForceBuild) {
			print "\cH" x 22 . "skipping, dynamic elements.\n";
			$status = 0;
		}
		elsif(! $check) {
			my @post = ();
			my $count = 0;
			my($search, $file, $newpage);
			my $string = $Vend::Cfg->{VendURL} . "/scan/" ;
			while($page =~ s!$string([^?]+)[^"]+!"__POST_" . $count++ . "__"!e) {
				undef $Vend::CachePage;
				undef $Vend::ForceBuild;
				$search = $1;
				print do_msg "\n>> found search $search", 61;
				push @post, $string . $search;
				if(defined $Vend::Found_scan{$search}) {
					pop @post;
					push @post, $Vend::Found_scan{$search};
					print "cached.";
				}
				elsif ($newpage = fake_scan('', $search) ) {
					$file = "scan" . ++$Vend::ScanCount .
									$Vend::Cfg->{StaticSuffix};
					pop @post;
					$Vend::Found_scan{$search} = "$Vend::Cfg->{StaticPath}/$file";
					push @post, $Vend::Found_scan{$search};
					Vend::Util::writefile(">$dir/$file", $newpage)
						or die "Couldn't write $dir/$file: $!\n";
					print "save.";
				}
				else {
					print "skip.";
				}
					
			}
			if(@post) {
				$page =~ s/__POST_(\d+)__/$post[$1]/g;
				print "\n";
			}
		}
		undef $Vend::CachePage;
		undef $Vend::ForceBuild;
				
		return $status if $check;
    	print BUILDPAGE $page;
		close BUILDPAGE;
    }
	else {
		print "\cH" x 20 . "skipping, page not found.\n";
		$status = 0;
	}
	$status;

}


# Build a static page tree from the database
# The session is faked, but all other operations
# should work the same.
sub build_all {
	my($catalog,$outdir) = @_;
	my ($framedir, $g, $sub, $p, $spec, $key, $val);
	my(@files);
#print "Supposed to be building pages...\n" if $Global::DEBUG;
	for(keys %Global::Catalog) {
		next unless $Global::Catalog{$_}->{'name'} eq $catalog;
		$g = $Global::Catalog{$_}->{'script'};
	}
	die "$catalog: no such catalog!\n"
		unless defined $g;
		
	my %build;
	my $build_list = 0;
	if(@Vend::BuildSpec) {
		%build = map { ($_,1) } @Vend::BuildSpec;
		$build_list = 1;
	}

	$spec = $Vend::BuildSpec || $Vend::Cfg->{StaticPattern} || '';
	CHECKSPEC: {
		my $test = 'NevVAIRBbe';
		eval { $test =~ s:^/tmp/whatever/$spec::; };
		die "Bad -files spec '$spec'\n" if $@;
	}
	$Vend::Cfg = $Global::Selector{$g};
	chdir $Vend::Cfg->{'VendRoot'} 
		or die "Couldn't change to $Vend::Cfg{'VendRoot'}: $!\n";
	$Vend::Cfg->{'ReadPermission'} = 'world';
	$Vend::Cfg->{'WritePermission'} = 'user';
	set_file_permissions();
	umask $Vend::Cfg->{'Umask'};

	my $all = $Vend::Cfg->{StaticAll};
	$build_list = 0 if $all;

	my $basedir = $Vend::Cfg->{'PageDir'};

	my $build_file;

	if(-f "$basedir/.build" and -s _) {
		print "Building from files listed in .build file.\n";
		$build_list = 1;
		$build_file = 1;
		$all = 0;
		open(BUILD, "$basedir/.build")
			or die "Couldn't open build spec $basedir/.build: $!\n";
		my $suf = $Vend::Cfg->{StaticSuffix};
		while(<BUILD>) {
			print;
			next if /^\s*#/;
			chomp;
			unless (-f "$basedir/$_") {
				warn "Scheduled page $_ not found.\n";
				next;
			}
			s/($suf)?\s*$//o;
			$build{$_} = 1;
			print "...accepted.\n";
		}
		close BUILD;
	}

	return unless ($all or $build_list or scalar keys %{$Vend::Cfg->{StaticPage}});

print "Really building pages...\n" if $Global::DEBUG;

	# do some basic checks to make sure we don't clobber
    # anything with a value of '/', and have an
	# absolute file path
	$outdir = $outdir || $Vend::Cfg->{StaticDir} || 'static';

	$outdir =~ s:/+$::;
	die "No output directory specified.\n" unless $outdir;
	$outdir = "$Vend::Cfg->{VendRoot}/$outdir"
		unless $outdir =~ m:^/:;

	if($Vend::Cfg->{ClearCache}) {
		print do_msg("Clearing output directory $outdir");
		rmtree($outdir)
			or die "Couldn't clear output directory $outdir: $!\n";
		print "done.\n"
	}

	unless(-d $outdir) {
		! -f $outdir
			or die "Output directory '$outdir' is a file. Abort.\n";
		print do_msg("Making output directory $outdir");
		mkpath ($outdir)
			or die "Couldn't make output directory $outdir: $!\n";
		print "done.\n"
	}

	if(	$Vend::Cfg->{ItemLinkDir} and
		! -d "$outdir/$Vend::Cfg->{ItemLinkDir}" ) {
		print do_msg("Making items directory $outdir/$Vend::Cfg->{ItemLinkDir}");
		mkpath ("$outdir/$Vend::Cfg->{ItemLinkDir}")
				or die "Couldn't make item link directory $outdir: $!";
		print "done.\n"
	}

	if(	$Vend::Cfg->{FrameFlyPage} and
		$framedir = "$outdir/$Vend::Cfg->{FrameLinkDir}" and
		! -d $framedir ) {
		print do_msg("Making frame fly directory $framedir");
		mkpath ($framedir)
				or die "Can't make directory frame fly directory $framedir: $!";
		print "done.\n"
	}

	open_database();
	$Vend::SessionID = '';
	$Vend::SessionName = '';
	init_session();
	$Vend::Session->{'frames'} = 1;
	require File::Find or die "No standard Perl library File::Find!\n";
	$sub = sub {
					my $name = $File::Find::name;
					die "Bad file name $name\n"
						unless $name =~ s:^$basedir/?::;

					if ($spec) {
						return unless $name =~ m!^$spec!o;
					}

					if (-d $File::Find::name) {
						die "$outdir/$name is a file, not a dir.\n"
							if -f "$outdir/$name";
						($File::Find::prune = 1, return)
							if defined $Vend::Cfg->{NoCache}->{$name};
						($File::Find::prune = 1, return)
							if defined $Vend::Cfg->{AdminPage}->{$name};
						return if -d "$outdir/$name";
						mkdir ("$outdir/$name", 0777)
							or die "Couldn't make dir $outdir/$name: $!\n";
						return;
					}
					return unless $name =~ s/$Vend::Cfg->{StaticSuffix}$//o;
					return if defined $Vend::Cfg->{NoCache}->{$name};

					if ($build_list) {
						return unless defined $build{$name};
					}

					return if $Vend::Cfg->{AdminPage}->{$name};

					push @files, $name;
			};
	print do_msg("Finding files...");
	File::Find::find($sub, $Vend::Cfg->{PageDir});
	print "done.\n";
	
	chdir $Vend::Cfg->{'VendRoot'} 
		or die "Couldn't change to $Vend::Cfg{'VendRoot'}: $!\n";

	$Vend::Session->{'pageCount'} = -1;
	my $save = $;
	$ = 0;

	my $static;

	foreach $key (@files) {
		print do_msg("Checking page $key ...");
		$Vend::Cfg->{StaticPage}->{$key} = 1 if $all;
		$static = build_page($key,$outdir, 1);
		unless ($static) {
			$build{$key} = delete $Vend::Cfg->{StaticPage}->{$key};
			$key = '';
			next;
		}
		print "done.\n";
	}

	FLYCHECK: {
		last FLYCHECK unless $Vend::Cfg->{StaticFly};
		my $save = $Vend::Session->{frames};
	  for (@Vend::Productbase) {
		$p = database_ref($_);
		while( ($key,$val) = $p->each_record() ) {
			next if $build_list && ! defined $build{$key};
			next unless $key =~ m{^$spec}o;
			if($Vend::Cfg->{FrameFlyPage}) {
				$Vend::Session->{frames} = 1;
				my $k = $Vend::Cfg->{FrameLinkDir} . "/$key";
				$Vend::Cfg->{StaticPage}->{$k} = 1 if $all;
				print do_msg("Checking part number $key - frames");
				build_page($key,$framedir,1)
					or delete($Vend::Cfg->{StaticPage}->{$k});
				print "done.\n";
				$Vend::Session->{frames} = $save;
			}
			$Vend::Cfg->{StaticPage}->{$key} = 1 if $all;
			print do_msg("Checking part number $key");
			build_page($key,$outdir, 1)
				or (delete($Vend::Cfg->{StaticPage}->{$key}), next);
			print "done.\n";
		}
	  }
		$Vend::Session->{frames} = $save;
	}

	foreach $key (@files) {
		next unless $key;
		print do_msg("Building page $key ...");
		build_page($key,$outdir)
			or ( delete($Vend::Cfg->{StaticPage}->{$key}), next);
		delete $build{$key} if defined $build{$key};
		$Vend::Session->{'pageCount'} = -1;
		print "done.\n";
	}

	FLY: {
	  last FLY unless $Vend::Cfg->{StaticFly};
	  my $save = $Vend::Session->{frames};
	  for (@Vend::Productbase) {
		$p = database_ref($_);
		while( ($key,$val) = $p->each_record() ) {
			FRAMES:
            if($Vend::Cfg->{FrameFlyPage}) {
                my $k = $Vend::Cfg->{FrameLinkDir} . "/$key";
				last FRAMES unless defined $Vend::Cfg->{StaticPage}->{$k};
				print do_msg("Building part number $key - frames");
                $Vend::Session->{frames} = 1;
                build_page($key,$framedir)
                    or delete($Vend::Cfg->{StaticPage}->{$k});
				print "done.\n";
                $Vend::Session->{frames} = $save;
            }
			next unless defined $Vend::Cfg->{StaticPage}->{$key};
			print do_msg("Building part number $key");
			build_page($key,$outdir)
				or (delete($Vend::Cfg->{StaticPage}->{$key}), next);
			$Vend::Session->{'pageCount'} = -1;
			print "done.\n";
		}
	  }
	  $Vend::Session->{frames} = $save;
	}
	open STATICPAGE, ">$basedir/.static"
		or die "Couldn't write static page file: $!\n";
	for(sort keys %{$Vend::Cfg->{StaticPage}}) {
		print STATICPAGE "$_\n";
	}
	close STATICPAGE;

	open UNBUILT, ">$basedir/.unbuilt"
		or die "Couldn't write build exception file: $!\n";
	for(sort keys %build) {
		print UNBUILT "$_\n";
	}
	close UNBUILT;
	unlink "$basedir/.build" if $build_file;

	# Second level of search, no more than 1 is recommended, but
	# can be set by StaticDepth
	my $i;
	STATICDEPTH: 
	for ($i = 0; $i < $Vend::Cfg->{StaticDepth}; $i++) {
		my $num = scalar keys %Vend::Found_scan;
		for(values %Vend::Found_scan) {

			print do_msg("Re-checking $_");
			my $status = build_page($_, $outdir, 0, 1);
			$status = $status ? "done.\n" : "none.\n";
			print $status;
		}
		last STATICDEPTH if $num >= scalar keys %Vend::Found_scan;
	}

	$ = $save;
}
# END STATICPAGE


sub map_cgi {

    my($host, $ip, $user);

    $CGI::request_method = http()->Method;
    die "REQUEST_METHOD is not defined" unless defined $CGI::request_method;

    $CGI::path_info = http()->Path_Info;

	# The great and final AOL fix
	#
    $host = http()->Client_Hostname;
    $ip   = http()->Client_IP_Address;

	if($Global::DomainTail and $host) {
		$host =~ s/.*?([-A-Za-z0-9]+\.[A-Za-z]+)$/$1/;
	}
	elsif($Global::IpHead) {
		$ip =~ s/\.\d+\.\d+\.\d+$//;
		$host = '';
	}
	#
	# end AOL fix

    $CGI::host = $host || $ip;

    $CGI::secure = 1
		if http()->Https_on;

    $user = http()->Authenticated_User;
	#
	# This is removed to guarantee that the user name is REMOTE_USER
	# Needed to guarantee security
    # $user = http()->Client_Ident
	#   unless (defined $user && $user ne '');
	#
    $user = '' unless defined $user;
    $CGI::user = $user;
    $CGI::useragent = http()->User_Agent;
    $CGI::cookie = http()->Cookie;

    #$CGI::content_length = http()->Content_Length;
    $CGI::content_type = http()->Content_Type;
    $CGI::reconfigure_catalog = http()->Reconfigure;
    $CGI::query_string = http()->Query;
    $CGI::referer = http()->Referer;
	unless ($Global::FullUrl) {
		$CGI::script_name = $CGI::script_path = http()->Script;
	}
	else {
		$CGI::script_name = http()->URI;
		$CGI::script_path = http()->Script;
	}

	if("\U$CGI::request_method" eq 'POST') {
		$CGI::post_input = http()->read_entity_body();
	}
	elsif ($Global::TolerateGet) {
		my $idx = index($CGI::query_string,'&');
		if($idx > 0) {
			logError("Triggered TolerateGet: \n" . minidump());
			$CGI::request_method = 'POST';
			$CGI::post_input = $CGI::query_string;
		}
	}
	parse_post();
}

## DISPATCH

# Parse the invoking URL and dispatch to the handling subroutine.

sub dispatch {
	my($http, $debug) = @_;
	$H = $http;

#print Global::DEBUG "begin dispatch: ",  join " ", times(), "\n";
	map_cgi($H);

    my($sessionid, $argument, $path, $rest);
	my(@path);
	my($g, $action);

	unless (defined $Global::Standalone) {
		unless (defined $Global::Selector{$CGI::script_name}) {
			logGlobal("Undefined catalog: $CGI::script_name");
			return '';
		}
		$Vend::Cfg = $Global::Selector{$CGI::script_name};

		# See if it is a subcatalog
		if (defined $Vend::Cfg->{BaseCatalog}) {
			my $name = $Vend::Cfg->{BaseCatalog};
			my $ref = $Global::Catalog{$name};
			my $c = $Vend::Cfg;
			$Vend::Cfg = $Global::Selector{$ref->{'script'}};
            for(keys %{$c->{Replace}}) {
                undef $Vend::Cfg->{$_};
            }
			copyref $c, $Vend::Cfg;
			$Vend::Cfg->{StaticPage} = {}
				unless $Vend::Cfg->{Static};
		}

		if (defined $Global::SelectorAlias{$CGI::script_name}) {
			my $real = $Global::SelectorAlias{$CGI::script_name};
			$Vend::Cfg->{VendURL} =~ s!$real!$CGI::script_name!;
			$Vend::Cfg->{SecureURL} =~ s!$real!$CGI::script_name!;
		}
	}
	else {
		$Vend::Cfg = $Global::Standalone;
	}

	if ($Vend::Cfg->{SetGroup}) {
		eval {$) = "$Vend::Cfg->{SetGroup} $Vend::Cfg->{SetGroup}"};
		if ($@) {
			my $msg = $@;
			logGlobal("Can't set group to GID $Vend::Cfg->{SetGroup}: $msg");
			logError("Can't set group to GID $Vend::Cfg->{SetGroup}: $msg");
		}
	}

	if($Vend::Cfg->{DisplayErrors} and $Global::DisplayErrors) {
		$SIG{"__DIE__"} = sub {
							my $msg = shift;
							response('html', <<EOF);
<HTML><HEAD><TITLE>Fatal MiniVend Error</TITLE></HEAD><BODY>
<H1>FATAL error</H1>
<PRE>$msg</PRE>
</BODY></HTML>
EOF
							exit 0;
						};
	}




	if (defined $CGI::reconfigure_catalog) {
		my $build = $CGI::values{mv_build_static} ? 1 : '';
		return '' unless check_security(0, 1);
		my $msg = $build ? 'yes' : 'no';
		logData("$Global::ConfDir/reconfig", $CGI::script_name, $build);
		logGlobal("Reconfig '$Vend::Cfg->{CatalogName}' rebuild=$msg");
	}

	chdir $Vend::Cfg->{'VendRoot'} 
		or die "Couldn't change to $Vend::Cfg{'VendRoot'}: $!\n";
	set_file_permissions();
	umask $Vend::Cfg->{'Umask'};
	open_database();

    if (defined $CGI::query_string && $CGI::query_string ne '') {
		($sessionid, $argument, $rest) = split(/;/, $CGI::query_string);
		if ($CGI::cookie =~ /\bMV_SESSION_ID=(\w{8})
								: (
									(	\d{1,3}\.   # An IP ADDRESS
										\d{1,3}\.
										\d{1,3}\.
										\d{1,3})
									|	(\w+) )     # A user name
									
									\b/x) {
			$sessionid = $1 unless defined $rest && $rest eq 'RESET';
			$CGI::cookiehost = $3 || '';
			$CGI::cookieuser = $4 || '';
		}
		$Vend::Argument = $argument;
    }

	# Get a cookie if we have no session id (and its there)
    unless (defined $sessionid && $sessionid ne '') {
        if (defined $CGI::cookie and
			$CGI::cookie =~ /\bMV_SESSION_ID=(\w{8})
								: (
									(	\d{1,3}\.   # An IP ADDRESS
										\d{1,3}\.
										\d{1,3}\.
										\d{1,3})
									|	(\w+) )     # A user name
									\b/x)
		{
            $sessionid = $1;
            $CGI::cookiehost = $3 || '';
            $CGI::cookieuser = $4 || '';
        }
	}
#print("session='$sessionid' cookie='$CGI::cookie' chost='$CGI::cookiehost'\n") if $Global::DEBUG;

    if (defined $sessionid && $sessionid ne '') {
		$Vend::SessionID = $sessionid;
    	$Vend::SessionName = session_name();
		get_session();
		my $now = time;
		if ($now - $Vend::Session->{'time'} > $Vend::Cfg->{'SessionExpire'}) {
	    	init_session();
		}
		elsif($Vend::Cfg->{RobotLimit}) {
			if ($now - $Vend::Session->{'time'} > 30) {
				$Vend::Session->{'accesses'} = 0;
			}
			else {
				$Vend::Session->{'accesses'}++;
				if($Vend::Session->{'accesses'} > $Vend::Cfg->{RobotLimit}) {
					my $msg = <<EOF;
WARNING: POSSIBLE BAD ROBOT.  $Vend::Session->{'accesses'} accesses with
no 30 second pause.
EOF
					my $cmd;
					if ($cmd = $Vend::Cfg->{LockoutCommand}) {
						my $host = http()->Client_IP_Address;
						$cmd =~ s/%s/$host/ or $cmd .= " $host";
						$msg .= "Performing lockout command '$cmd'.";
						system $cmd;
						$msg .= "Bad status $?: $!\n" if $?;
					}
					logError $msg;
					logGlobal $msg;
				}
			}
		}

    }
	else {
		new_session();
    }
#print("session name='$Vend::SessionName'\n") if $Global::DEBUG;
	$Vend::Session->{id} = $Vend::SessionID;
	$Vend::Session->{arg} = $Vend::Argument;
	$Vend::Session->{source} = $rest if defined $rest && $rest =~ /[A-Za-z]/;
	$Vend::Session->{user} = $CGI::user;

    $Vend::Session->{prev_url} = $Vend::Session->{last_url}
		if defined $Vend::Session->{last_url};
    $path = $Vend::Session->{last_url} = $CGI::path_info;
    $Vend::Session->{last_search} = $path
		if $path =~ m!^/scan!;

    # If the cgi-bin program was invoked with no extra path info,
    # just display the catalog page.
    if (!defined $path || $path eq '' || $path eq '/') {
		do_catalog();
		release_session() if $Vend::HaveSession;
		close_database();
		undef $H;
		undef $Vend::Cfg;
		return 0;
    }

	$path =~ s:^/::;
    @path = split('/', $path, 2);
    $action = shift @path;

    if    ($action eq 'process')  { do_process();              }
    elsif ($action eq 'scan')     { do_scan($argument,@path);  } 
    elsif ($action eq 'search')   { do_search($argument);      } 
    elsif ($action eq 'order')    { do_order($argument,@path); }
    elsif ($action eq 'obtain')   {
									my($catalog,$page) = split '/', $path[0], 2;
									do_order($argument,$page,$catalog);
								  } 
    elsif ($action eq 'subroutine')    { do_sub($argument,@path); }
    else {
		# will try the on-the-fly page if it fails
		do_page($path, $argument);
    }

#print Global::DEBUG "end dispatch: ",  join " ", times(), "\n";
	release_session() if $Vend::HaveSession;
	close_database();
	undef $H;
	undef $Vend::Cfg;
#print Global::DEBUG "closed all: ",  join " ", times(), "\n";
	return 1;
}

## DEBUG

sub dontwarn {

# STATICPAGE
	$File::Find::name +
	$File::Find::prune +
	$Global::UserBuild +
# END STATICPAGE
	$FindBin::RealBin +
	$Global::AdminSub +
	$Global::DomainTail +
	$Global::FullUrl +
	$Global::IpHead +
	$Global::MailErrorTo +
	$Vend::CheckHTML +
	$Vend::ContentType +

	1;
}


sub dump_env {
    my($var, $value);

    open(Vend::E, ">$Vend::Cfg->{'VendRoot'}/env");
    while(($var, $value) = each %ENV) {
	print Vend::E "export $var='$value'\n";
    }
    close Vend::E;
}

## CGI-BIN INTERFACE PROCESSING

sub unhexify {
    my($s) = @_;

	# Following gets around Perl 5.001m bug
    #$s =~ s/%24/\$/ig;
    #$s =~ s/%5c/\\/ig;

    $s =~ s/%(..)/chr(hex($1))/ge;
    $s;
}

sub parse_post {
	my(@pairs, $pair, $key, $value);
	undef %CGI::values;
	return unless defined $CGI::post_input;
	@pairs = split(/&/, $CGI::post_input);
	foreach $pair (@pairs) {
		($key, $value) = ($pair =~ m/([^=]+)=(.*)/)
			or die "Syntax error in post input:\n$pair\n";
		$key = unhexify($key);
		$value =~ s/\+/ /g;
		$value = unhexify($value);
		# Handle multiple keys
		unless (defined $CGI::values{$key}) {
	 		$CGI::values{$key} = $value;
		}
		else {
			$CGI::values{$key} .= "\0" . $value;
		}
	}
	if($Global::TolerateGet and !$CGI::query_string  ) {
		$CGI::query_string =	$CGI::values{mv_session_id} || '';
		$CGI::query_string .=	';';
		$CGI::query_string .=	$CGI::values{mv_argument}   || '';
		$CGI::query_string .=	';0';
	}
}

## COMMAND LINE OPTIONS

sub parse_options {
	while ($_ = shift @ARGV) {
		last if $_ eq '--';
		if (m/^-c(onfig)?$/i) {
			$Global::ConfigFile = shift @ARGV;
			die "Missing file argument for -config option\n"
				if blank($Global::ConfigFile);
		} elsif (m/^-D(EBUG)?$/i) {
			$Global::DEBUG = 1;
		} elsif (m/^-s(erve)?$/i) {
			$Vend::mode = 'serve';
		} elsif (m/^-b(uild)?$/i) {
			$Vend::mode = 'build'
				unless $Vend::mode eq 'serve';
			die "-b(uild) requires argument\n" unless @ARGV;
			$Vend::CatalogToBuild{shift @ARGV} = 1;
		} elsif (m/^-f(iles)?$/i) {
			$Vend::BuildSpec = shift @ARGV;
			die "Missing file spec for -files option\n"
				if blank($Vend::BuildSpec);
		} elsif (m/^-o(utdir)?$/i) {
			$Vend::OutputDirectory = shift @ARGV;
			die "Missing file argument for -outdir option\n"
				if blank($Vend::OutputDirectory);
		} elsif (m/^-e(xclude)?$/i) {
			die "-e(xclude) requires argument\n" unless @ARGV;
			$Vend::CatalogToSkip{shift @ARGV} = 1;
		} elsif (m/^-u(nixmode)?$/i) {
			$Global::Unix_Mode = 1;
		} elsif (m/^-i(netmode)?$/i) {
			$Global::Inet_Mode = 1;
		} elsif (m/^-v(ersion)?$/i) {
			version();
			exit 0;
		} elsif (m/^-h(elp)?$/i) {
			usage();
			exit 0;
		} elsif (m/^-n(otify)$/i) {
			$Vend::mode = 'notify';
		} elsif (m/^-t(est)$/i) {
			$Vend::mode = 'test';
		} elsif (m:^[^-]: and $Vend::mode eq 'build') {
			@Vend::BuildSpec = ($_);
			while(defined $ARGV[0]) {
				last if $ARGV[0] eq '--';
				push @Vend::BuildSpec, shift @ARGV;
			}
		} else {
		    $? = 2;
			die "Unknown command line option: $_\n" .
				"(Use -help for a list).\n";
		}
	}

# OPTION_EXTENSION
#		while(@ARGV) {
#			parse_extended_options();
#			last;
#		}
# END OPTION_EXTENSION
}

# OPTION_EXTENSION
#%Global::Name = (
#					catalog => '[-\w]+',
#				);
#
#sub parse_stdin {
#	local ($/) = "\n.\n";
#	my ($location, $value);
#	while(<STDIN>) {
#		chomp;
#		($location, $value) = split /\n/, $_, 2;
#		unless($location =~ /($Global::Name{catalog}):(after|before)/o) {
#			warn "Bad input stream $location, skipping\n";
#			next;
#		}
#		print "Trying $location.\n";
#		$Vend::CommandLine->{$1}->{$2}->{'stream'} = $value;
#	}
#}
#
#sub parse_extended_options {
#	my $initial = shift @ARGV;
#	my $catalog;
#	unless ($initial =~ /^(-c|--?catalog)/) {
#		die "Must specify catalog as first item of extended options.\n";
#	}
#	$initial =~ /^--?catalog=(.+)/ and $catalog = $1;
#	$catalog = shift @ARGV unless defined $catalog;
#
#	my $target = 'after';
#
#	if($ARGV[0] =~ /^--?before$/) {
#		shift @ARGV;
#		$target = 'before';
#	}
#	elsif($ARGV[0] =~ /^--?after$/) {
#		shift @ARGV;
#	}
#
#	$Vend::CommandLine->{$catalog} = {}
#		unless defined $Vend::CommandLine->{$catalog};
#
#	my $file = {};
#	my $stream = {};
#	my $ref;
#	$Vend::CommandLine->{$catalog}->{$target} = $ref = {};
#
#	print do_msg("Parsing extended options target=$target $catalog");
#
#	my %options = (
#		"replace=s%"				=> $ref->{replace} ||= {},	
#		"file=s"				=> $ref->{file} ||= '',
#		"structure|define=s%"	=> $ref->{structure} ||= {},	
#	);
#
#	print "done.\n";
#
#	GetOptions($Vend::CommandLine->{$catalog}->{$target}, %options);
#
#}
# END OPTION_EXTENSION
		
		

sub version {
	print "MiniVend version $VERSION Copyright 1995 Andrew M. Wilcox\n";
	print "                      Copyright 1996,1997 Michael J. Heins\n";
}

sub usage {
	version();
	print <<'END';

MiniVend comes with ABSOLUTELY NO WARRANTY.  This is free software, and
you are welcome to redistribute and modify it under the terms of the
GNU General Public License. (Unless you SPAM, in which case you may
not use MiniVend at all).

Command line options:

     -build <catalog> build static page tree for <catalog>
     -config <file>   specify configuration file
     -exclude <name>  exclude catalog <name>
     -files <spec>    filespec (perl regexp OK) for static page tree
     -inetmode        run with Internet-domain socket (TCP)
     -outdir <dir>    specify output directory for static page tree
     -serve           start server
     -test            report problems with config files
     -unix            run with UNIX-domain socket
     -version         display program version
     -DEBUG           run foreground in debug mode
END
}

## FILE PERMISSIONS

sub set_file_permissions {
	my($r, $w, $p, $u);

	$r = $Vend::Cfg->{'ReadPermission'};
	if    ($r eq 'user')  { $p = 0400;   $u = 0277; }
	elsif ($r eq 'group') { $p = 0440;   $u = 0227; }
	elsif ($r eq 'world') { $p = 0444;   $u = 0222; }
	else                  { die "Invalid value for ReadPermission\n"; }

	$w = $Vend::Cfg->{'WritePermission'};
	if    ($w eq 'user')  { $p += 0200;  $u &= 0577; }
	elsif ($w eq 'group') { $p += 0220;  $u &= 0557; }
	elsif ($w eq 'world') { $p += 0222;  $u &= 0555; }
	else                  { die "Invalid value for WritePermission\n"; }

	$Vend::Cfg->{'FileCreationMask'} = $p;
	$Vend::Cfg->{'Umask'} = $u;
}

sub read_socket {
}

sub checkRegexSpeed {
	local($_) = 'x' x 10000;
	my $start = (times)[0];
	my $i;
	for($i = 0; $i < 5000; $i++) { }
	my $overhead = (times)[0] - $start;
	$start = (times)[0];
	for($i = 0; $i < 5000; $i++) { m/^/; }
	my $delta = (times)[0] - $start;
    my $naughty = $delta > $overhead * 10;
	#printf "It seems your code is %s (overhead=%.2f, delta=%.2f)\n",
    #        $naughty ? "contaminated":"clean", $overhead, $delta;
	if($naughty) {
		print q|It seems your code is contaminated by a library with $', $`, or $&.|;
		printf "\n (overhead=%.2f, delta=%.2f)", $overhead, $delta;
		print "\n";
		my $file;
		foreach $file (values %INC) {
			open CHECK, $file or die "open $file: $!\n";
			while (<CHECK>) {
				next unless /[^\$'+]\$['`&]/;
				print "\nHere is a possibly offending line in\n$file:\n\n";
				print;
			}
		}
		close CHECK;
		print <<EOF;

Try setting the environment variable PERL5LIB to the MiniVend directory
and see if it doesn't fix it:
	
	setenv PERL5LIB '$Global::VendRoot/lib'

	          or

	PERL5LIB='$Global::VendRoot/lib'; export PERL5LIB

Fixing this will improve the speed of MiniVend by a small percentage. If
you can't do it, don't worry about it.

EOF
	}
			
}

## MAIN

sub main_loop {
	# Setup
	unless ($Global::Windows) {
		$ENV{'PATH'} = '/bin:/usr/bin';
		$ENV{'SHELL'} = '/bin/sh';
		$ENV{'IFS'} = '';
	}
	srand;
	setup_escape_chars();
	my $status = 0;

	undef $Vend::mode;      # mode will be set by options
	parse_options();
# OPTION_EXTENSION
#	parse_stdin() unless eof();
# END OPTION_EXTENSION

	if (!defined $Vend::mode) {
			usage();
			exit 0;
	}

#print "\n##### DEBUG MODE ON #####\n" if $Global::DEBUG;

	umask 077;
	global_config();
	$| = 1;
	checkRegexSpeed if $Global::DEBUG;
	CATCONFIG: {
		my $i = 0;
		my ($g, $c, $name);
		foreach $name (sort keys %Global::Catalog) {
			$g =  $Global::Catalog{$name};
			next if defined $Vend::CatalogToSkip{$g->{'name'}};
			next if
				$Vend::mode eq 'build' and
				! defined $Vend::CatalogToBuild{$g->{'name'}};
			print "Configuring catalog " . $g->{'name'} . '...';
			if (exists $Global::Selector{$g->{'script'}}) {
				warn "Two catalogs with same script name $g->{'script'}.\n";
				warn "Skipping catalog $g->{'name'}....\n\n";
				next;
			}
			
			eval {
				$c = config_named_catalog($name, "at server startup");
			};

			if ($@ or ! defined $c) {
				my $msg = $@;
				print "\n$msg\n\a$g->{'name'}: error in configuration. Skipping.\n";
				$msg =~ s/\s+$//;
				$msg = " -- $msg" if $msg;
				logGlobal $g->{'name'} . ": config error$msg. Skipping.";
				undef $Global::Selector{$g->{'script'}};
				next;
			}

			$Global::Selector{$g->{script}} = $c;

			# Set up aliases
			if (defined $g->{alias}) {
				for(@{$g->{alias}}) {
					if (exists $Global::Selector{$_}) {
						warn "Alias $_ used a second time, skipping.\n";
						next;
					}
					elsif (m![^\w-_:#/.]!) {
						warn "Bad alias $_, skipping.\n";
					}
					$Global::Selector{$_} = $c;
					$Global::SelectorAlias{$_} = $g->{'script'};
				}
			}
# STATICPAGE
			if ($Vend::CatalogToBuild{$g->{name}}) {
				print <<EOF unless $c->{StaticDir};

Skipping static page build for $g->{name}, StaticDir not set.
EOF
				print "doing static page build\n";
				@Vend::BuildSpec = keys %{$c->{StaticPage}};
				undef %Vend::Found_scan;
				$Vend::ScanCount = 0;

				$Vend::BuildingPages = 1;
				eval {
					build_all($g->{name});
				};
				undef $Vend::BuildingPages;

				if ($@) {
					my $msg = $@;
					print "\n$msg\n\a$g->{'name'}: error building pages. Skipping.\n";
					logGlobal <<EOF;
$g->{'name'}: Page build error. Skipping.
$msg
EOF
				}
			}
# END STATICPAGE
			print "done.\n";
		}
	}

	if ($Vend::mode eq 'serve') {
		# This should never return unless killed or an error
		# We set debug mode to -1 to communicate with the server
		# that no output is desired
		$0 = 'minivend';

        select STDERR; 
        $| = 1;
        select STDOUT;
        $| = 1;

        Vend::Server::run_server($Global::DEBUG);
	}
	elsif ($Vend::mode eq 'notify') {
		send_mail($Global::MailErrorTo, "MiniVend server not responding", <<EOF );
The MiniVend server serving the catalog named '$Vend::Cfg->{CatalogName}' did
not respond when called by the VLINK executable.

EOF
	}
	elsif ($Vend::mode eq 'build') {
				  # Empty, built in CATCONFIG
	}
	elsif ($Vend::mode eq 'test') {
		# Blank by design, this option only tests config files
	}
	else {
		die "No mode!\n";
	}
	
}

eval { main_loop(); };
if ($@) {
	my($msg) = ($@);
	logGlobal( $msg );
	if ($Global::DisplayErrors) {
		print "$msg\n";
	}
}

