1 # Vend::Dispatch - Handle Interchange page requests
3 # Copyright (C) 2002-2009 Interchange Development Group
4 # Copyright (C) 2002 Mike Heins <mike@perusion.net>
6 # This program was originally based on Vend 0.2 and 0.3
7 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public
20 # License along with this program; if not, write to the Free
21 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
24 package Vend::Dispatch;
26 use vars qw($VERSION);
29 use POSIX qw(strftime);
31 use Vend::Interpolate;
34 use autouse 'Vend::Error' => qw/get_locale_message interaction_error do_lockout full_dump/;
39 use Vend::CounterFile;
40 no warnings qw(uninitialized numeric);
72 return if $Vend::Sent;
74 if (defined $possible and ! $::Pragma->{download}) {
75 push @Vend::Output, (ref $possible ? $possible : \$possible);
78 if($::Pragma->{download}) {
79 $H->respond(ref $possible ? $possible : \$possible);
81 elsif($Vend::MultiOutput) {
82 for my $space (keys %Vend::OutPtr) {
83 my $things = $Vend::OutPtr{$space} || [];
84 for my $ptr (@$things) {
85 my $subs = $Vend::OutFilter{$space} || [];
86 for my $sub (@$subs) {
87 $sub->($Vend::Output[$ptr]);
91 for(grep $_, @Vend::Output) {
97 Vend::Interpolate::substitute_image($_);
104 # Parse the mv_click and mv_check special variables
106 my ($ref, $click, $extra) = @_;
107 my($codere) = '[-\w_#/.]+';
110 #::logDebug("Looking for click $click");
111 if($params = $::Scratch->{$click}) {
112 # Do nothing, we found the click
113 #::logDebug("Found scratch click $click = |$params|");
115 elsif(defined ($params = $Vend::Cfg->{OrderProfileName}{$click}) ) {
116 # Do nothing, we found the click
117 $params = $Vend::Cfg->{OrderProfile}[$params];
118 #::logDebug("Found profile click $click = |$params|");
120 elsif(defined ($params = $Global::ProfilesName->{$click}) ) {
121 # Do nothing, we found the click
122 $params = $Global::Profiles->[$params];
123 #::logDebug("Found profile click $click = |$params|");
125 elsif($params = $::Scratch->{"mv_click $click"}) {
126 $::Scratch->{mv_click_arg} = $click;
128 elsif($params = $::Scratch->{mv_click}) {
129 $::Scratch->{mv_click_arg} = $click;
132 #::logDebug("Found NO click $click");
134 } # No click processor
136 my($var,$val,$parameter);
137 $params = interpolate_html($params);
138 my(@param) = split /\n+/, $params;
146 ($var,$val) = split /[\s=]+/, $parameter, 2;
147 $val =~ s/&#(\d+);/chr($1)/ge;
149 $extra->{$var} = $val
154 ## This is the set of variables we don't want to dump or save in
155 ## sessions for security reasons.
156 @Global::HideCGI = qw(
160 mv_credit_card_number
164 # This is the set of CGI-passed variables to ignore, in other words
165 # never set in the user session. If set in the mv_check pass, though,
167 %Global::Ignore = qw(
188 mv_credit_card_number 1
189 mv_credit_card_cvv2 1
194 sub set_file_permissions {
197 $r = $Vend::Cfg->{'ReadPermission'};
198 if ($r eq 'user') { $p = 0400; $u = 0277; }
199 elsif ($r eq 'group') { $p = 0440; $u = 0227; }
200 elsif ($r eq 'world') { $p = 0444; $u = 0222; }
201 else { die "Invalid value for ReadPermission\n"; }
203 $w = $Vend::Cfg->{'WritePermission'};
204 if ($w eq 'user') { $p += 0200; $u &= 0577; }
205 elsif ($w eq 'group') { $p += 0220; $u &= 0557; }
206 elsif ($w eq 'world') { $p += 0222; $u &= 0555; }
207 else { die "Invalid value for WritePermission\n"; }
209 $Vend::Cfg->{'FileCreationMask'} = $p;
210 $Vend::Cfg->{'Umask'} = $u;
220 @{$set}{@keys} = @CGI::values{@keys};
223 $set = \%CGI::values;
225 if( $Vend::Cfg->{CreditCardAuto} and $CGI::values{mv_credit_card_number} ) {
231 mv_credit_card_exp_month
232 mv_credit_card_exp_year
233 mv_credit_card_exp_all
235 mv_credit_card_reference
238 ) = encrypt_standard_cc(\%CGI::values);
243 if($restrict = $Vend::Session->{restrict_html} and ! ref $restrict) {
244 $restrict = [ map { lc $_ } split /\s+/, $restrict ];
245 $Vend::Session->{restrict_html} = $restrict;
248 while (my ($key, $value) = each %$set) {
249 # values explicly ignored in configuration
250 next if defined $Global::Ignore{$key};
251 next if defined $Vend::Cfg->{FormIgnore}{$key};
254 # We add any checkbox ordered items, but don't update --
255 # we don't want to order them twice
256 next if ($key =~ m/^quantity\d+$/);
259 # Admins should know what they are doing
261 $::Values->{$key} = $value;
264 elsif ($restrict and $value =~ /</) {
265 # Allow designer to allow only certain HTML tags from trusted users
266 # Will go away when current session ends...
267 # [ script start character handled in [value ...] ITL tag
268 $value = Vend::Interpolate::filter_value(
274 $::Values->{$key} = $value;
278 $value =~ s/<//ig;
279 $value =~ s/[//g;
280 $::Values->{$key} = $value;
286 # Update the user-entered fields.
288 add_items() if defined $CGI::values{mv_order_item};
291 if($CGI::values{mv_check}) {
292 my(@checks) = split /\s*[,\0]+\s*/, delete $CGI::values{mv_check};
294 foreach $check (@checks) {
295 parse_click $::Values, $check, \%CGI::values;
299 check_save if defined $CGI::values{mv_save_session};
308 if($CGI::values{mv_click}) {
309 @clicks = split /\s*[\0]+\s*/, delete $CGI::values{mv_click};
312 if(defined $CGI::values{mv_click_map}) {
313 my(@map) = split /\s*[\0]+\s*/, delete $CGI::values{mv_click_map};
314 foreach $click (@map) {
315 push (@clicks, $click)
316 if defined $CGI::values{"mv_click.$click.x"}
317 or defined $CGI::values{"$click.x"}
318 or $click = $CGI::values{"mv_click_$click"};
322 foreach $click (@clicks) {
323 parse_click \%CGI::values, $click;
325 } while $CGI::values{mv_click};
330 my $file = $CGI::values{mv_data_file};
331 my $mode = $CGI::values{mv_acl_mode} || '';
332 if($::Scratch->{mv_deliver} !~ m{(^|\s)$file(\s|$)}
334 ! Vend::UserDB::userdb(
341 $Vend::StatusLine = "Status: 403\nContent-Type: text/html";
342 my $msg = get_locale_message(403, <<EOF);
343 <b>Authorization Required</b>
345 This server could not verify that you are authorized to access the document
354 $Vend::StatusLine = "Status: 404\nContent-Type: text/html";
355 my $msg = get_locale_message(404, <<EOF, $file);
358 The requested file %s was not found on this server.
365 my $size = -s $CGI::values{mv_data_file};
366 $CGI::values{mv_content_type} ||= 'application/octet-stream';
367 $Vend::StatusLine = <<EOF;
368 Content-Type: $CGI::values{mv_content_type}
369 Content-Length: $size
372 Vend::Util::readfile($CGI::values{mv_data_file}, undef, undef,
373 {encoding => 'raw'}));
380 search => \&do_search,
381 deliver => \&do_deliver,
386 or return interaction_error("quantities");
388 my($missing,$next,$status,$final,$result_hash);
390 # Set shopping cart if necessary
391 # Vend::Items is tied, remember!
392 $Vend::Items = $CGI::values{mv_cartname}
393 if $CGI::values{mv_cartname};
395 #::logDebug("Default order route=$::Values->{mv_order_route}");
396 ## Determine the master order route, if routes
397 ## are not set in CGI values (4.7.x default)
400 and ! defined $::Values->{mv_order_route}
403 my $curr = $Vend::Cfg->{Route};
404 my $repos = $Vend::Cfg->{Route_repository};
406 if($curr->{master}) {
407 # Default route is master
410 next unless $curr eq $repos->{$_};
411 $::Values->{mv_order_route} = $_;
417 next unless $repos->{$_}->{master};
418 $::Values->{mv_order_route} = $_;
424 #::logDebug("Default order route=$::Values->{mv_order_route}");
428 # If the user sets this later, will be used
429 delete $Vend::Session->{mv_order_number};
431 if (defined $CGI::values{mv_order_profile}) {
432 ($status,$final,$missing) =
433 check_order($CGI::values{mv_order_profile});
436 $status = $final = 1;
438 #::logDebug("Profile status status=$status final=$final errors=$missing");
441 if ($status and defined $::Values->{mv_order_route}) {
442 # This checks only route order profiles
443 #::logDebug("Routing order, pre-check");
444 ($status, $provisional, $missing)
446 $::Values->{mv_order_route},
452 $final = $provisional if ! $final;
454 #::logDebug("Routing status status=$status final=$final errors=$missing");
456 $CGI::values{mv_nextpage} = $CGI::values{mv_successpage}
457 if $CGI::values{mv_successpage};
458 $CGI::values{mv_nextpage} = $::Values->{mv_orderpage}
459 if ! $CGI::values{mv_nextpage};
462 $CGI::values{mv_nextpage} = $CGI::values{mv_failpage}
463 if $CGI::values{mv_failpage};
464 $CGI::values{mv_nextpage} = find_special_page('needfield')
465 if ! $CGI::values{mv_nextpage};
469 return 1 unless $final;
472 if (defined $::Values->{mv_order_route}) {
473 # $ok will not be defined unless Route "supplant" was set
474 # $order_no will come back so we don't issue two of them
475 #::logDebug("Routing order $::Values->{mv_order_route}");
476 ($ok, $order_no, $result_hash) = route_order(
477 $::Values->{mv_order_route},
483 $result_hash = {} unless $result_hash;
486 $Vend::Track->finish_order () if $Vend::Track;
488 # This function (followed down) now does the rudimentary
489 # backend ordering with AsciiTrack and the order report.
490 # If the "supplant" option was set in order routing it will
491 # not be used ($ok would have been defined)
494 #::logDebug("Order number=$order_no\n");
495 $ok = mail_order(undef, $order_no || undef) unless defined $ok;
496 #::logDebug("Order number=$order_no, result_hash=" . ::uneval($result_hash));
498 # Display a receipt if configured
500 my $not_displayed = 1;
503 display_special_page(
504 find_special_page('failed'),
505 errmsg('Error transmitting order(%s): %s', $!, $@),
508 elsif (! $result_hash->{no_receipt} ) {
511 my $receipt = $result_hash->{receipt}
512 || $::Values->{mv_order_receipt}
513 || find_special_page('receipt');
514 #::logDebug("selected receipt=$receipt");
515 display_special_page($receipt);
518 #::logDebug("not_displayed=$not_displayed");
522 'Display of receipt on order number %s failed: %s',
523 $::Values->{mv_order_number},
530 run_macro($Vend::Cfg->{OrderCleanup});
534 #::logDebug("returning order_number=$order_no, not_displayed=$not_displayed");
535 return $not_displayed;
540 or return interaction_error("quantities");
542 $CGI::values{mv_nextpage} = $CGI::values{mv_orderpage}
543 if $CGI::values{mv_orderpage};
545 $CGI::values{mv_nextpage} = $CGI::values{mv_orderpage}
546 || find_special_page('order')
547 if ! $CGI::values{mv_nextpage};
552 update_user() unless $CGI::values{mv_data_auto_number};
554 update_user() if $CGI::values{mv_data_auto_number};
562 back => sub { return 1 },
566 or return interaction_error("quantities");
573 $CGI::values{mv_nextpage} = find_special_page('canceled')
574 if ! $CGI::values{mv_nextpage};
579 $form_action{go} = $form_action{return};
581 # Process the completed order or search page.
585 # Prevent using keys operation more than once
586 my @cgikeys = keys %CGI::values;
588 my @multis = grep /^mv\d\d?_/, @cgikeys;
590 ## Only operates on up to 100 items to prevent "amplification"
591 ## which could result in DOS attacks
596 my $val = delete $CGI::values{$_};
597 # Have to handle nulls somehow....
603 $hash{$key}[$idx] = $val;
605 while (my ($k, $v) = each %hash) {
606 $CGI::values{$k} = join "\0", @$v;
610 my @filters = grep /^[mu][vi]_filter:/, @cgikeys;
613 last FILTERS unless @filters;
614 foreach my $key (@filters) {
615 next unless $key =~ /^ui_|^mv_/;
616 my $val = delete $CGI::values{$key};
617 $key =~ s/^.._filter://;
619 if($val =~ /checkbox/) {
620 $CGI::values{$key} = $Tag->filter($val, $CGI::values{$key}, $key);
623 next unless defined $CGI::values{$key};
624 $CGI::values{$key} = $Tag->filter($val, $CGI::values{$key}, $key);
629 if($CGI::values{mv_form_profile}) {
630 my ($status) = check_order(
631 $CGI::values{mv_form_profile},
633 $CGI::values{mv_individual_profile},
635 return 1 if defined $status and ! $status;
638 my $orig_todo = $CGI::values{mv_todo};
642 my $todo = $CGI::values{mv_todo};
644 # Maybe we have an imagemap input, if not, use $doit
645 if($orig_todo ne $todo) {
646 # Don't mess with it, changed in click
648 elsif (defined $CGI::values{'mv_todo.x'}) {
649 my $x = $CGI::values{'mv_todo.x'};
650 my $y = $CGI::values{'mv_todo.y'};
651 my $map = $CGI::values{'mv_todo.map'};
652 # Called with action_map and not package id
653 # since "autouse" is possibly in force...found
655 $todo = action_map($x,$y,$map);
657 elsif( my @todo = grep /^mv_todo\.\w+(?:\.x)?$/, @cgikeys ) {
660 delete $CGI::values{$_};
661 s/^mv_todo\.(\w+)(?:\.[xy])?$/$1/;
666 $todo = $CGI::values{mv_doit} || 'back' if ! $todo;
668 #::logDebug("todo=$todo after mv_click");
671 #Now determine the action on the todo
672 if (defined $Vend::Cfg->{FormAction}{$todo}) {
673 $sub = $Vend::Cfg->{FormAction}{$todo};
675 elsif (not $sub = $form_action{$todo} ) {
676 unless ($sub = Vend::Util::codedef_routine('FormAction', $todo)) {
677 interaction_error(::errmsg("Invalid action %s passed for processing.\n", $todo));
682 $status = $sub->($todo);
687 my $template = <<EOF;
688 Sorry, there was an error in processing this form action. Please
689 report the error or try again later.
691 $template .= "\n\nError: %s\n"
692 if $Global::DisplayErrors && $Vend::Cfg->{DisplayErrors}
694 $template = get_locale_message(500, $template, $err);
695 $template .= "($err)";
700 if($CGI::values{mv_cleanup}) {
701 my(@checks) = split /\s*[,\0]+\s*/, delete $CGI::values{mv_cleanup};
703 foreach $check (@checks) {
704 parse_click $::Values, $check, \%CGI::values;
712 my ($cat, $job, $itl, $parms) = @_;
715 #::logGlobal("running job in cat=$cat");
718 $g = $Global::Catalog{$cat};
719 unless (defined $g) {
720 logGlobal( "Can't find catalog '%s' for jobs group %s" , $cat, $job );
726 logError("Run jobs group=%s pid=%s", $job || 'INTERNAL', $$);
728 Vend::Server::set_process_name("job $cat $job");
730 my $jobscfg = $Vend::Cfg->{Jobs};
735 my @jobdirs = ([$jobscfg->{base_directory} || 'etc/jobs', 0]);
737 if (is_yes($jobscfg->{use_global}) || is_yes($Global::Jobs->{UseGlobal})) {
738 push (@jobdirs, ["$Global::ConfDir/jobs", 1]);
742 for my $r (@jobdirs) {
744 ($d, $global_dir) = @$r;
745 #::logGlobal("check directory=$d for $job");
747 next unless -d "$d/$job";
755 $tmp = $Global::AllowedFileRegex->{$cat};
756 $Global::AllowedFileRegex->{$cat} = qr{^$dir};
759 my @f = glob("$dir/*");
760 @f = grep ! -d $_, @f;
761 @f = grep $_ !~ /$Vend::Cfg->{HTMLsuffix}$/, @f;
762 @f = grep $_ =~ /$jobscfg->{suffix}$/, @f;
764 #::logGlobal("found jobs piece file=$_");
765 push @itl, [$_, readfile($_)];
769 $Global::AllowedFileRegex->{$cat} = $tmp;
775 push @itl, ["Passed ITL", $itl];
778 my (@out, $errors, $failure);
780 # remove bogus session created by logError
781 undef $Vend::Session;
785 my ($trackdb, $trackid);
787 if ($jobscfg->{trackdb}) {
788 if ($trackdb = database_exists_ref($jobscfg->{trackdb})) {
789 $trackid = $trackdb->set_slice('', [qw(name begin_run pid)],
790 [$job, Vend::Interpolate::mvtime(undef, {}, '%Y-%m-%d %H:%M'), $$]);
793 ::logError ("Invalid jobs tracking database $jobscfg->{trackdb}");
798 # Run once at beginning
799 run_macro($jobscfg->{initialize});
801 # initialize or autoload can create session
802 # but must handle all aspects
803 unless ($Vend::Session) {
804 $CGI::values{mv_tmp_session} = 1;
808 $CGI::remote_addr ||= 'none';
809 $CGI::useragent ||= 'commandline';
812 # Run once at beginning of each job
813 run_macro($jobscfg->{autoload});
815 push @out, interpolate_html($_->[1]);
817 # Run once at end of each job
818 run_macro($jobscfg->{autoend});
823 # job terminated due to an error
826 $failure = errmsg('Job terminated with an error: %s', $@);
827 logError ("Job group=%s pid=%s terminated with an error: %s", $job || 'INTERNAL', $$, $@);
829 # remove flag for this job
830 Vend::Server::flag_job($$, $cat, 'furl');
834 $trackdb->set_field($trackid, 'end_run',
835 Vend::Interpolate::mvtime(undef, {}, '%Y-%m-%d %H:%M'));
839 logError("Empty job=%s", $job);
841 my $out = join "", @out;
842 my $filter = $jobscfg->{filter} || 'strip';
843 $out = Vend::Interpolate::filter_value($filter, $out);
844 if ($errors && is_no($jobscfg->{ignore_errors})) {
845 $out = join("\n\n", $failure, $out);
847 $out .= full_dump() if is_yes($jobscfg->{add_session});
849 logError("Finished jobs group=%s pid=%s", $job || 'INTERNAL', $$);
853 # don't send email and/or write log entry if job returns
854 # no output (in spirit of the cron daemon)
857 if(my $addr = $parms->{email} || $jobscfg->{email}) {
858 my $subject = $jobscfg->{subject} || 'Interchange results for job: %s';
859 $subject = errmsg($subject, $job);
860 my $from = $jobscfg->{from} || $Vend::Cfg->{MailOrderTo};
861 Vend::Interpolate::tag_mail($addr,
866 reply_to => $jobscfg->{reply_to},
867 mailer => "Interchange $::VERSION",
868 extra => $jobscfg->{extra_headers},
875 if($jobscfg->{log}) {
876 logData($jobscfg->{log}, $out);
886 die "REQUEST_METHOD is not defined" unless defined $CGI::request_method
889 if ($Global::HostnameLookups && !$CGI::remote_host && $CGI::remote_addr && !$CGI::values{mv_tmp_session}) {
890 $CGI::remote_host = gethostbyaddr(Socket::inet_aton($CGI::remote_addr),Socket::AF_INET);
893 # The great and really final AOL fix
895 $host = $CGI::remote_host;
896 $CGI::ip = $CGI::remote_addr;
898 if($Global::DomainTail and $host) {
899 $host =~ /\.([A-Za-z]+)$/;
902 my $level = (defined($Global::CountrySubdomains->{$tld}) && $host =~ $Global::CountrySubdomains->{$tld}) ? 2 : 1;
904 $host =~ s/.*?((?:[-A-Za-z0-9]+\.){$level}[A-Za-z]+)$/$1/;
906 elsif($Global::IpHead) {
907 $host = $Global::IpQuad == 0 ? 'nobody' : '';
909 @ip = split /\./, $CGI::ip;
911 $CGI::ip = join ".", @ip[0 .. ($Global::IpQuad - 1)] if $Global::IpQuad;
916 # Fix Cobalt/CGIwrap problem
917 if($Global::Variable->{CGIWRAP_WORKAROUND}) {
918 $CGI::path_info =~ s!^$CGI::script_name!!;
921 $CGI::host = $host || $CGI::ip;
923 $CGI::user = $CGI::remote_user, undef $CGI::authorization
924 if $CGI::remote_user;
926 if ($Global::FullUrl) {
927 if ($Global::FullUrlIgnorePort or $CGI::server_port eq '80') {
928 $CGI::server_port = '';
931 $CGI::server_port = ":$CGI::server_port";
933 $CGI::script_name = $CGI::server_name . $CGI::server_port . $CGI::script_path;
936 $CGI::script_name = $CGI::script_path;
940 use vars qw/@NoHistory/;
943 mv_credit_card_number
950 $Vend::Session->{History} = []
951 unless defined $Vend::Session->{History};
952 shift @{$Vend::Session->{History}}
953 if $#{$Vend::Session->{History}} >= $Vend::Cfg->{History};
954 if( $CGI::values{mv_no_cache} ) {
955 push (@{$Vend::Session->{History}}, [ 'expired', {} ]);
960 push @save, delete $CGI::values{$_};
963 push (@{$Vend::Session->{History}}, [ $CGI::path_info, { %CGI::values } ]);
965 for(my $i = 0; $i < @NoHistory; $i++) {
966 next unless defined $save[$i];
967 $CGI::values{$NoHistory[$i]} = $save[$i];
975 # Parse the invoking URL and dispatch to the handling subroutine.
978 process => \&do_process,
980 &UI::Primitive::ui_acl_global();
984 search => \&do_search,
986 obtain => \&do_order,
988 $Vend::StatusLine = "Status: 204 No content";
989 my $extra_click = $Vend::FinalPath;
990 $extra_click =~ s:/:\0:g;
991 $CGI::values{mv_click} = $CGI::values{mv_click}
992 ? "$CGI::values{mv_click}\0$extra_click"
1000 sub update_global_actions {
1001 @action{keys %{$Global::ActionMap}} = (values %{$Global::ActionMap})
1002 if $Global::ActionMap;
1003 @form_action{keys %{$Global::FormAction}} = (values %{$Global::FormAction})
1004 if $Global::FormAction;
1008 my ($cat, $http) = @_;
1012 if($Global::Catalog{$cat}) {
1013 $CGI::script_path = $Global::Catalog{$cat}->{script};
1014 $CGI::script_name = $CGI::script_path;
1018 unless (defined $Global::Selector{$CGI::script_name}) {
1019 my $msg = get_locale_message(
1021 "Undefined catalog: %s",
1022 $CGI::script_name || $cat,
1024 $Vend::StatusLine = <<EOF;
1025 Status: 404 Not Found
1026 Content-Type: text/plain
1032 # No close_cat() necessary
1036 if($Global::Foreground) {
1038 tie %hash, 'Tie::ShadowHash', $Global::Selector{$CGI::script_name} ;
1039 $Vend::Cfg = \%hash;
1042 $Vend::Cfg = $Global::Selector{$CGI::script_name};
1045 $Vend::Cat = $Vend::Cfg->{CatalogName};
1046 $Vend::ReadOnlyCfg = $Global::ReadOnlyCfg{$Vend::Cat};
1048 my $catref = $Global::Catalog{$Vend::Cat};
1049 if(! $Global::Foreground and defined $catref->{directive}) {
1052 while ( ($key, $val) = each %{$catref->{directive}}) {
1053 #::logDebug("directive key=$key val=" . ::uneval($val));
1054 ${"Global::$key"} = $val;
1058 # See if it is a subcatalog
1059 if (defined $Vend::Cfg->{BaseCatalog}) {
1060 my $name = $Vend::Cfg->{BaseCatalog};
1061 my $ref = $Global::Catalog{$name};
1063 $Vend::Cfg = $Global::Selector{$ref->{'script'}};
1064 for(keys %{$c->{Replace}}) {
1065 undef $Vend::Cfg->{$_};
1067 copyref $c, $Vend::Cfg;
1068 if($Vend::Cfg->{Variable}{MV_LANG}) {
1069 my $loc = $Vend::Cfg->{Variable}{MV_LANG};
1070 $Vend::Cfg->{Locale} = $Vend::Cfg->{Locale_repository}{$loc}
1071 if defined $Vend::Cfg->{Locale_repository}{$loc};
1075 if ($Global::Foreground) {
1077 tie %hash, 'Tie::ShadowHash', $Vend::Cfg->{Variable};
1078 $::Variable = \%hash;
1079 $::Pragma = { %{ $Vend::Cfg->{Pragma} } };
1082 $::Variable = $Vend::Cfg->{Variable};
1083 $::Pragma = $Vend::Cfg->{Pragma};
1087 if($Vend::Cfg->{DeliverImage}
1088 and $CGI::request_method eq 'GET'
1089 and $CGI::path_info =~ /\.(\w+)$/
1090 and $mt = Vend::Util::mime_type($CGI::path_info)
1091 and $mt =~ m{^image/}
1095 my $imgdir = $Vend::Cfg->{ImageDir};
1096 my $fn = $CGI::path_info;
1097 #::logDebug("deliver image: method=$CGI::request_method type=$mt fn=$fn");
1099 ## Won't resend any images beginning with admin/
1100 $fn =~ s{^admin/}{};
1102 $imgdir = $Vend::Cfg->{ImageDirSecure}
1103 if $Vend::Cfg->{ImageDirSecure};
1105 $Vend::tmp_session = 1;
1106 Vend::Tags->deliver($mt, { location => "$imgdir$fn" } );
1110 if (defined $Global::SelectorAlias{$CGI::script_name}) {
1111 my $real = $Global::SelectorAlias{$CGI::script_name};
1112 unless ( $CGI::secure or
1113 $Vend::Cfg->{SecureURL} =~ m{$CGI::script_name$} and
1114 $Vend::Cfg->{VendURL} !~ m{/nph-[^/]+$} and
1115 $Vend::Cfg->{VendURL} !~ m{$CGI::script_name$} )
1117 $Vend::Cfg->{VendURL} =~ s!$real!$CGI::script_name!;
1118 $Vend::Cfg->{SecureURL} =~ s!$real!$CGI::script_name!;
1122 if($Global::HitCount and ! $cat) {
1123 my $ctr = new Vend::CounterFile
1124 "$Global::ConfDir/hits.$Vend::Cat";
1128 if ($Vend::Cfg->{SetGroup}) {
1130 $) = "$Vend::Cfg->{SetGroup} $Vend::Cfg->{SetGroup}";
1134 logGlobal( "Can't set group to GID %s: %s",
1135 $Vend::Cfg->{SetGroup}, $msg
1137 logError("Can't set group to GID %s: %s",
1138 $Vend::Cfg->{SetGroup}, $msg
1143 if($Vend::Cfg->{XHTML}) {
1144 $Vend::Xtrailer = ' /';
1145 $Vend::Xquote = '"';
1148 $Vend::Xtrailer = '';
1152 $::Limit = $Vend::Cfg->{Limit} || {};
1154 chdir $Vend::Cfg->{VendRoot}
1155 or die "Couldn't change to $Vend::Cfg->{VendRoot}: $!\n";
1156 POSIX::setlocale(POSIX::LC_ALL, $Vend::Cfg->{ExecutionLocale});
1157 set_file_permissions();
1158 umask $Vend::Cfg->{Umask};
1160 Vend::Server::parse_cgi($http) unless $Global::mod_perl;
1162 #show_times("end cgi and config mapping") if $Global::ShowTimes;
1165 if (my $subname = $Vend::Cfg->{SpecialSub}{request_init}) {
1166 #::logDebug(errmsg("running subroutine '%s' for %s", $subname, 'request_init'));
1167 my $sub = $Vend::Cfg->{Sub}{$subname} || $Global::GlobalSub->{$subname};
1174 ::logError("Error running %s subroutine %s: %s", 'request_init', $subname, $@);
1178 #show_times("end open_database") if $Global::ShowTimes;
1183 put_session() if $Vend::HaveSession;
1184 close_session() if $Vend::SessionOpen;
1192 my $content_ref = shift;
1193 my $inspect_sub = shift;
1196 if(ref $macro eq 'ARRAY') {
1199 elsif ($macro =~ /^[-\s\w,]+$/) {
1200 @mac = grep /\S/, split /[\s,]+/, $macro;
1209 if ($m =~ /^\w+$/) {
1210 my $sub = $Vend::Cfg->{Sub}{$m} || $Global::GlobalSub->{$m}
1212 my $call = join(',', caller());
1214 my $msg = errmsg("Unknown macro '%s' from %s.", $m, $call);
1215 if($Vend::Cfg->{CatalogName}) {
1223 $ret = $sub->($content_ref);
1225 elsif($m =~ /^\w+-\w+$/) {
1226 $ret = Vend::Interpolate::tag_profile($m);
1229 $ret = interpolate_html($m);
1233 unless ($inspect_sub->($m, $ret)) {
1246 ## If returns false then was a 404 no catalog or a delivered image
1247 open_cat('', $http) or return 1;
1249 Vend::Server::set_process_name("$Vend::Cat $CGI::host");
1251 run_macro($Vend::Cfg->{Preload});
1253 $CGI::user = Vend::Util::check_authorization($CGI::authorization)
1254 if defined $CGI::authorization;
1256 my($sessionid, $seed);
1258 $sessionid = $CGI::values{mv_session_id} || undef
1259 and $sessionid =~ s/\0.*//s;
1261 # save for robot check with explicit session id
1262 my $sessionid_from_cgi = $sessionid;
1264 $::Instance->{CookieName} = $Vend::Cfg->{CookieName};
1266 if($CGI::values{mv_tmp_session}) {
1267 #::logDebug("setting tmp_session");
1268 $Vend::tmp_session = $Vend::new_session = 1;
1269 $sessionid = 'nsession';
1271 $Vend::Cfg->{ScratchDefault}{mv_no_count} = 1;
1272 $Vend::Cfg->{ScratchDefault}{mv_no_session_id} = 1;
1274 elsif ($sessionid and $CGI::values{mv_force_session}) {
1277 elsif ($::Instance->{CookieName} and defined $CGI::cookie) {
1278 $CGI::cookie =~ m{$::Instance->{CookieName}=($Vend::Cfg->{CookiePattern})};
1279 $seed = $sessionid = $1;
1280 $::Instance->{ExternalCookie} = $sessionid || 1;
1281 $Vend::CookieID = $Vend::Cookie = 1;
1283 elsif (defined $CGI::cookie and $CGI::cookie =~ /\bMV_SESSION_ID=(\w{8,32})[:_]([-\@.:A-Za-z0-9]+?)\b/) {
1287 if (is_ipv4($host) || is_ipv6($host)) {
1288 $CGI::cookiehost = $host;
1290 elsif ($host =~ /[A-Za-z0-9][-\@A-Za-z.0-9]+/) {
1291 $CGI::cookieuser = $host;
1294 last SESSION_COOKIE;
1298 $Vend::CookieID = $Vend::Cookie = 1;
1302 Vend::Server::set_process_name("$Vend::Cat $CGI::host $sessionid");
1304 $::Instance->{CookieName} = 'MV_SESSION_ID' if ! $::Instance->{CookieName};
1306 $CGI::host = 'nobody' if $Vend::Cfg->{WideOpen};
1309 if(my $id = $::Variable->{MV_SESSION_ID}) {
1310 $sessionid = $CGI::values{$id} if $CGI::values{$id};
1313 if(! $sessionid and $CGI::redirect_status and $Vend::Cfg->{RedirectCache}) {
1314 $Vend::tmp_session = $Vend::new_session = 1;
1315 $sessionid = 'nsession';
1317 $Vend::Cfg->{ScratchDefault}{mv_no_count} = 1;
1318 $Vend::Cfg->{ScratchDefault}{mv_no_session_id} = 1;
1319 $Vend::write_redirect = 1;
1322 if (! $sessionid and $Vend::Cfg->{FallbackIP}) {
1323 $sessionid = generate_key($CGI::remote_addr . $CGI::useragent);
1327 elsif (! $::Instance->{ExternalCookie} and $sessionid !~ /^\w+$/) {
1328 my $msg = get_locale_message(
1330 "Malformed session identifier",
1332 $Vend::StatusLine = <<EOF;
1333 Status: 403 Unauthorized
1334 Content-Type: text/plain
1337 logGlobal("$msg: $sessionid");
1343 #::logDebug ("session='$sessionid' cookie='$CGI::cookie' chost='$CGI::cookiehost'");
1348 $Vend::SessionID = $sessionid;
1349 $Vend::SessionName = session_name();
1350 if($Vend::tmp_session) {
1351 $Vend::Session = {};
1355 # get_session will return a value if a session is read,
1356 # if not it will return false and a new session has been created.
1357 # The IP address will be counted for robot_resolution
1358 if(! get_session($seed) and ! $::Instance->{ExternalCookie}) {
1359 retire_id($sessionid);
1363 if(! $Vend::CookieID) {
1364 if( is_retired($sessionid) ) {
1368 my $compare_host = $CGI::secure
1369 ? ($Vend::Session->{shost})
1370 : ($Vend::Session->{ohost});
1372 if($Vend::Cfg->{WideOpen}) {
1373 # do nothing, no host checking
1375 elsif(! $compare_host) {
1377 $Vend::Session->{shost} = $CGI::remote_addr;
1384 elsif ($compare_host ne $CGI::remote_addr) {
1389 if ($now - $Vend::Session->{'time'} > $Vend::Cfg->{SessionExpire}) {
1390 if($::Instance->{ExternalCookie}) {
1394 retire_id($sessionid);
1399 elsif($Vend::Cfg->{RobotLimit}) {
1400 if ($now - $Vend::Session->{'time'} > ($::Limit->{lockout_reset_seconds} || 30) ) {
1401 $Vend::Session->{accesses} = 0;
1404 $Vend::Session->{accesses}++;
1405 #::logDebug("accesses=$Vend::Session->{accesses} admin=$Vend::admin");
1406 if($Vend::Session->{accesses} > $Vend::Cfg->{RobotLimit}
1415 if (Vend::Session::count_ip() && !do_lockout()) {
1417 # Here they can get it back if they pass expiration time
1418 my $wait = $::Limit->{robot_expire} || 1;
1420 $msg = errmsg(<<EOF, $wait);
1421 Too many new ID assignments for this IP address. Please wait at least %d hours
1422 before trying again. Only waiting that period will allow access. Terminating.
1424 $msg = get_locale_message(403, $msg);
1426 ::logError('Too many IDs, %d hour wait enforced.', $wait);
1428 $Vend::StatusLine = <<EOF;
1429 Status: 403 Forbidden
1430 Content-Type: text/plain
1441 #::logDebug("session name='$Vend::SessionName'\n");
1443 $Vend::Calc_initialized = 0;
1444 $CGI::values{mv_session_id} = $Vend::Session->{id} = $Vend::SessionID;
1446 if($Vend::admin and my $subname = $Vend::Cfg->{SpecialSub}{admin_init}) {
1447 my $sub = $Vend::Cfg->{Sub}{$subname} || $Global::GlobalSub->{$subname};
1453 ::logError("Error running %s subroutine %s: %s", 'admin_init', $subname, $@);
1457 if(my $vspace = $CGI::values{mv_values_space}) {
1458 $::Values = $Vend::Session->{values_repository}{$vspace} ||= {};
1459 $Vend::ValuesSpace = $vspace;
1462 $Vend::Session->{'arg'} = $Vend::Argument = ($CGI::values{mv_arg} || undef);
1464 my ($new_source, $already_expired);
1466 if ($CGI::values{mv_pc} and $CGI::values{mv_pc} eq 'RESET') {
1467 $Vend::Session->{source} = '';
1469 # Expire cookie, if applicable.
1470 if ( length ($Vend::Cfg->{SourceCookie}{name}) ) {
1471 my $sc = $Vend::Cfg->{SourceCookie};
1472 Vend::Util::set_cookie(
1476 @{$sc}{qw(domain path secure)}
1478 $already_expired = 1;
1481 last SOURCEPRIORITY;
1484 #::logDebug('$Session->{source} before SourcePriority loop: %s', $Vend::Session->{source});
1485 foreach (@{$Vend::Cfg->{SourcePriority}}) {
1486 #::logDebug("Looking at $_");
1487 if ($_ eq 'mv_pc') {
1488 #::logDebug('$CGI::values{mv_pc} is %s', $CGI::values{mv_pc});
1489 if ($CGI::values{mv_pc} and $CGI::values{mv_pc} =~ /\D/) {
1490 $new_source = $CGI::values{mv_pc};
1491 $new_source =~ s/[\r\n\t]//g;
1492 $Vend::Session->{source} = $new_source;
1493 last SOURCEPRIORITY;
1497 elsif (/^cookie-(.+)/) {
1498 my $cookie_source = Vend::Util::read_cookie($1);
1499 #::logDebug("Cookie $1 is $cookie_source");
1500 if (length $cookie_source) {
1501 $cookie_source =~ s/[\r\n\t]//g;
1502 $Vend::Session->{source} = $cookie_source;
1503 last SOURCEPRIORITY;
1507 elsif ($_ eq 'session') {
1508 #::logDebug('$sessionid is %s', $sessionid);
1510 last SOURCEPRIORITY;
1514 elsif (/^session-(.+)/) {
1515 #::logDebug('$Session->{%s} is %s', $1, $Vend::Session->{$1});
1516 if (length $Vend::Session->{$1}) {
1517 last SOURCEPRIORITY;
1522 #::logDebug('$CGI::values{%s} is %s', $_, $CGI::values{$_});
1523 if (length $CGI::values{$_}) {
1524 $new_source = $CGI::values{$_};
1525 $new_source =~ s/[\r\n\t]//g;
1526 $Vend::Session->{source} = $new_source;
1527 last SOURCEPRIORITY;
1532 #::logDebug('$Session->{source} after SourcePriority loop: %s', $Vend::Session->{source});
1534 # Set a cookie if applicable.
1536 # Obviously must be true
1537 length ($Vend::Cfg->{SourceCookie}{name})
1540 # and, we didn't already clear it in SOURCEPRIORITY
1544 # any time we have a new source, we want to
1545 # reset--even if it's unchanged from the last
1546 # value to reset the expiration
1547 length ($new_source)
1550 # or, our cookie is different from $Session->{source},
1551 # whatever the reason
1552 Vend::Util::read_cookie($Vend::Cfg->{SourceCookie}{name})
1554 $Vend::Session->{source}
1559 # there's something in source worth preserving,
1560 length ($Vend::Session->{source})
1563 # and we want the expiration reset with every access,
1564 $Vend::Cfg->{SourceCookie}{autoreset}
1568 my $sc = $Vend::Cfg->{SourceCookie};
1569 #::logDebug('Resetting SourceCookie %s to %s', $sc->{name}, $Vend::Session->{source});
1570 Vend::Util::set_cookie(
1572 $Vend::Session->{source},
1573 @{$sc}{qw(expire domain path secure)}
1579 and $CGI::request_method eq 'GET'
1580 and ($Vend::Cfg->{BounceReferrals} or
1581 ($Vend::Robot and $Vend::Cfg->{BounceReferralsRobot}))) or
1582 ($Vend::Robot and $sessionid_from_cgi and $Vend::Cfg->{BounceRobotSessionURL})
1584 my $path = $CGI::path_info;
1588 map { "$_=$CGI::values{$_}\n" }
1589 grep { !$Vend::Cfg->{BounceReferrals_hide}->{$_} }
1590 sort keys %CGI::values;
1591 my $url = vendUrl($path eq '' ? $Vend::Cfg->{DirectoryIndex} : $path, undef, undef, { form => $form, match_security => 1 });
1592 $url = header_data_scrub($url);
1593 my $msg = get_locale_message(
1595 "Redirected to %s.",
1598 $Vend::StatusLine = <<EOF;
1601 Content-Type: text/plain
1606 #::logDebug("bouncing to $url");
1611 $Vend::Session->{'user'} = $CGI::user;
1613 Vend::Server::set_process_name("$Vend::Cat $CGI::host $sessionid " . $Vend::Session->{username} || '-');
1615 $CGI::pragma = 'no-cache'
1616 if delete $::Scratch->{mv_no_cache};
1617 #show_times("end session get") if $Global::ShowTimes;
1619 $Vend::FinalPath = $Vend::Session->{last_url} = $CGI::path_info;
1621 if( defined $Vend::Session->{path_alias}{$Vend::FinalPath} ) {
1624 = $Vend::Session->{path_alias}{$Vend::FinalPath};
1625 delete $Vend::Session->{path_alias}{$Vend::FinalPath}
1626 if delete $Vend::Session->{one_time_path_alias}{$Vend::FinalPath};
1629 url_history($Vend::FinalPath) if $Vend::Cfg->{History};
1631 Vend::Server::set_process_name("$Vend::Cat $CGI::host $sessionid " . ($Vend::Session->{username} || '-') . " $Vend::FinalPath");
1634 $Vend::Track = Vend::Track->new
1635 if ($Vend::Cfg->{UserTrack} or $Vend::Cfg->{TrackFile})
1636 and not ($Vend::admin and ! $::Variable->{MV_TRACK_ADMIN});
1639 if($Vend::Cfg->{DisplayErrors} and $Global::DisplayErrors) {
1640 $SIG{"__DIE__"} = sub {
1642 put_session() if $Vend::HaveSession;
1643 my $content = get_locale_message(500, <<EOF, $msg);
1644 <html><head><title>Fatal Interchange Error</title></head><body>
1645 <h1>FATAL error</h1>
1649 response(\$content);
1654 # Do it here so we can use autoloads and such
1655 Vend::Interpolate::reset_calc() if $Global::Foreground;
1656 Vend::Interpolate::init_calc();
1658 new Vend::Parse; # enable catalog usertags within dispatch routines
1661 last ROUTINES unless index($Vend::FinalPath, "/$Vend::Cfg->{ProcessPage}/") == 0;
1662 while ($Vend::FinalPath =~ s{/$Vend::Cfg->{ProcessPage}/(locale|language|currency)/([^/]*)/}{/$Vend::Cfg->{ProcessPage}/}) {
1663 $::Scratch->{"mv_$1"} = $2;
1665 $Vend::FinalPath =~ s{/$Vend::Cfg->{ProcessPage}/page/}{/};
1668 if(my $locale = $::Scratch->{mv_language}) {
1669 $Global::Variable->{LANG}
1670 = $::Variable->{LANG} = $locale;
1674 for my $routine (@{$Vend::Cfg->{DispatchRoutines}}) {
1681 #show_times("end DispatchRoutines") if $Global::ShowTimes;
1683 for my $macro ( $Vend::Cfg->{Filter}, $Vend::Session->{Filter}) {
1685 if (ref($macro) ne 'HASH') {
1686 logError("Bad CGI filter '%s'", $macro);
1690 Vend::Interpolate::input_filter_do($_, { op => $macro->{$_} } );
1694 ## Here we initialize new features
1695 if(my $ary = $Vend::Cfg->{Init}) {
1696 undef $Vend::Cfg->{Init};
1698 my ($source, $touch) = @$_;
1700 open INITOUT, "> $touch"
1703 "Unable to open init file %s for feature init", $touch,
1709 $out = Vend::Interpolate::interpolate_html(
1710 Vend::Util::readfile($source)
1716 print INITOUT errmsg(
1717 "Results of init at %s: ",
1718 POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime()),
1725 run_macro($Vend::Session->{Autoload});
1726 #show_times("end session Autoload macro") if $Global::ShowTimes;
1728 # If the cgi-bin program was invoked with no extra path info,
1729 # just display the catalog page.
1730 if (! $Vend::FinalPath || $Vend::FinalPath =~ m:^/+$:) {
1731 #::logDebug("request_uri=$CGI::request_uri script_path=$CGI::script_path");
1732 if($CGI::request_uri !~ /^$CGI::script_path/) {
1733 $Vend::FinalPath = $CGI::request_uri;
1734 # remove any trailing query string
1735 $Vend::FinalPath =~ s/\?.*//;
1736 #::logDebug("FinalPath now $Vend::FinalPath");
1739 $Vend::FinalPath = find_special_page('catalog');
1743 if($CGI::put_ref and my $act = $Vend::Cfg->{Special}{put_handler} ) {
1744 $Vend::FinalPath =~ s:^/*:$act/:;
1747 $Vend::FinalPath =~ s:^/+::;
1748 $Vend::FinalPath =~ s/(\.html?)$//
1749 and $Vend::Extension = $1;
1750 $Vend::Session->{extension} = $1 || '';
1755 if(ref $Vend::Session->{alias_table}) {
1756 $record = $Vend::Session->{alias_table}{$Vend::FinalPath};
1757 $Vend::Cfg->{AliasTable} ||= 'alias';
1761 $Vend::Cfg->{AliasTable}
1766 $adb = database_exists_ref($Vend::Cfg->{AliasTable})
1768 $record = $adb->row_hash($Vend::FinalPath)
1772 $Vend::FinalPath = $record->{real_page};
1774 # This prevents filesystem access when we never want it
1775 # If base page is not passed we allow normal resolution
1776 $record->{base_page}
1777 and $Vend::ForceFlypage = $record->{base_page};
1781 ## Here we populate CGI variables if desired
1782 ## Explicitly passed variables override this
1784 $record->{base_control}
1786 $ref = get_option_hash($record->{base_control})
1790 next if defined $CGI::values{$_};
1791 $CGI::values{$_} = $ref->{$_};
1797 #::logDebug("path=$Vend::FinalPath mv_action=$CGI::values{mv_action}");
1800 if (defined $CGI::values{mv_action}) {
1801 $CGI::values{mv_todo} = $CGI::values{mv_action}
1802 if ! defined $CGI::values{mv_todo}
1803 and ! defined $CGI::values{mv_doit};
1804 $Vend::Action = $CGI->{mv_ui} ? 'ui' : 'process';
1805 $CGI::values{mv_nextpage} = $Vend::FinalPath
1806 if ! defined $CGI::values{mv_nextpage};
1809 ($Vend::Action) = $Vend::FinalPath =~ m{\A([^/]*)};
1810 $Vend::Action =~ s/-/_/g; # allow hyphens as synonyms for underscores for SEO prettiness
1813 #::logGlobal("action=$Vend::Action path=$Vend::FinalPath");
1815 if(defined $Vend::Cfg->{ActionMap}{$Vend::Action}) {
1816 $sub = $Vend::Cfg->{ActionMap}{$Vend::Action};
1817 $CGI::values{mv_nextpage} = $Vend::FinalPath
1818 if ! defined $CGI::values{mv_nextpage};
1822 $sub = $action{$Vend::Action};
1825 #show_times("end path/action resolve") if $Global::ShowTimes;
1829 $status = $sub->($Vend::FinalPath);
1830 #show_times("end action") if $Global::ShowTimes;
1836 (undef $Vend::RedoAction, redo DOACTION) if $Vend::RedoAction;
1841 my $template = <<EOF;
1842 Sorry, there was an error in processing this form action. Please
1843 report the error or try again later.
1845 $template .= "\n\nError: %s\n"
1846 if $Global::DisplayErrors && $Vend::Cfg->{DisplayErrors}
1848 $template = get_locale_message(500, $template, $err);
1849 $template .= "($err)";
1850 undef $Vend::write_redirect;
1851 response($template);
1854 $CGI::values{mv_nextpage} = $Vend::FinalPath
1855 if ! defined $CGI::values{mv_nextpage};
1857 do_page() if $status;
1858 #show_times("end page display") if $Global::ShowTimes;
1860 for my $routine (@{$Vend::Cfg->{CleanupRoutines}}) {
1866 $Vend::Track->filetrack() if $Vend::Track;
1871 Vend::Server::set_process_name('done');
1875 #show_times("end dispatch cleanup") if $Global::ShowTimes;