#!/usr/bin/env perl
#/scripts 775
# CGITRAP (1998 Eric Boesch, ericboesch@gmail.com), from
#########################################################################
#
# CGITAP Copyright 1996 Charles D. Johnson, ScendTek Internet Corp.
# Permission granted to use and modify this application so long as the
# copyright above is maintained, modifications are documented, and
# credit is given for any use of the application.
#
# Many thanks to Steven Brenner for his ReadParse procedure!
# ReadParse was my first introduction to the CGI interface and
# parsing FORM POST Data.
#
# CGITAP - CGI Script Analysis and Debugging Utility
#
#########################################################################
#
# CGITRAP additions: CGI header parsing; timing info; target script may
# come first; can work invisibly unless apparent errors occur; works with
# my Netscape server; command-line debugging within replicated CGI
# environment (see debug_call); stdout separated from stderr. Tested on
# Perl 5.00[345], httpd Netscape|Apache, OS solaris|linux.
# Comments, extensions, and bug fixes welcome.
#
# Invocation same as cgitap's (see http://scendtek.com/cgitap).
#
# Configuration section:
use strict;
use warnings;
require 5;
my ($quiet_if_ok, $log_fn, $debug_call, $execute, $time_info, $wrapper_name,
$tmp_dir, $paranoid, $post_stdin, $time0, $setuid);
# Edit the value of $tmp_dir below. All files generated by cgitrap
# will be placed in that directory.
$tmp_dir = "/tmp";
# To do command-line debugging of non-Perl programs, change $debug_call and
# $post_stdin. See the HTML documentation for more details on these.
$debug_call = 'perl -d';
$post_stdin = 1;
# Set $quiet_if_ok if you do not want to see _any_ cgitap
# output unless an error is detected while running the script.
$quiet_if_ok = 1;
# Set $log_fn if you want to record all of the URLs processed.
# POST form data will be converted to GET format in the URL.
# $log_fn = 'html.log';
# If you want to debug the page offline, and you DON'T want to run the page
# online because of unwanted side effects, then set $execute to false.
$execute = 1;
# If you have Time::HiRes, uncomment the following line:
# use Time::HiRes qw(time);
# It's probably reasonably safe to use setuid on this script, because
# that usually means REDUCING rights to that of the web user. However,
# setuid scripts are disabled on many OSes and inherently insecure on
# a few others (though those are probably obsolete).
$setuid = 0; # Change to 1 to enable
# If cgitrap rejects your valid header, go to the section with the
# "OK header line" comment and add your header to the OK list. If cgitrap
# accepts an invalid header, you might not get any kind of useful output
# online (although the myscript_dbg.pl file will be usable). Temporarily
# setting $paranoid to 1 may help; the down side is that your CGI and HTML
# header sections will be ignored whether they're valid or not.
# (You can also create a backup copy of cgitrap, "cgitrap.bak", with
# $paranoid preset to 1. That will also allow you to debug modifications to
# cgitrap itself using URL http://myhost/cgi-bin/cgitrap.bak/cgitrap/myscript.)
BEGIN {
$paranoid = 0;
$time_info = 1; # Show running times?
$wrapper_name = "cgitrap"; # The start of this script's name
$time0 = time if $time_info;
}
# This program removes itself from the SCRIPT_NAME environment variable.
# Consequently, programs that generate URLs from SCRIPT_NAME may generate
# links to non-debugged URLs. To make sure that debugging-enabled URLs
# always point to other debugging-enabled URLs, you may want to alter those
# programs so that they check for the existence of REAL_SCRIPT_NAME. (If
# you use the symbolic-link invocation method, this is unnecessary.)
#
# End of configuration section.
#########################################################################
my $hide_me = 1; # Remove CGITRAP from CGI env. variables?
my $show_debug = $paranoid || !$quiet_if_ok;
my @time = ();
$tmp_dir = '' if !defined($tmp_dir);
$tmp_dir .= '/' if $tmp_dir ne '';
push @time, [$time0, ''] if $time_info; # Start time.
my $content_text = "Content-type:text/html\n";
my $head = $content_text;
my $body = "\n";
my $this;
$body .= "CGITRAP - CGI Script Analysis and Debugging Utility";
$0 =~ m:([^/]+)$:; # separate path and this script's name
$this = $1; # this script name
my ($prog_exe, $prog);
if (index($this, $wrapper_name) >= 0) {
# translate some of the environment because wrapper is in the path
my $path = $ENV{ PATH_INFO };
unless ($path) {
print "$head$body";
print "
CGITRAP must have a target CGI script specified:
\n";
print "Example: http://yourserver.com/cgi-bin/cgitrap/script
\n";
exit(0);
}
$path =~ m:/([^/]+)(.*):;
$prog = $1;
$prog_exe = $prog;
if ($hide_me) {
$ENV{ REAL_SCRIPT_NAME } = "$ENV{ SCRIPT_NAME }/$prog";
$ENV{ PATH_INFO } = $2; # remove cgi-script program name from path
$ENV{ SCRIPT_NAME } =~ s:[^/]+$:$prog:; # adjust the script name variable
if (defined($ENV{ SCRIPT_FILENAME })) {
# adjust the script filename variable
$ENV{ SCRIPT_FILENAME } =~ s/[^\/]+$/$prog/;
}
$ENV{ PATH_TRANSLATED } =~ s/\/$prog//; # remove script name from path
}
}
else {
# cgitrap is not in the path, we are using the symbolic link
# method of execution
$prog = "$this";
$prog_exe = "${this}.tap";
}
$body .= <
General Diagnostics
CGI Script = $prog (via $prog_exe)
EOF
my $prog_path;
if ($ENV{ SERVER_SOFTWARE } =~ /cern/i) {
$prog_path = $ENV{ SCRIPT_NAME }; # cern uses SCRIPT_NAME
$prog_path =~ s/[^\/]+$/$prog_exe/;
} elsif (defined($ENV{ SCRIPT_FILENAME })) {
$prog_path = $ENV{ SCRIPT_FILENAME };
$prog_path =~ s/[^\/]+$/$prog_exe/;
} else {
$prog_path = "./$prog_exe";
}
if (-e $prog_path) {
unless (-x _) {
$body .= "%% WARNING %% - Designated script: $prog_exe does not "
. "have execute permissions\n";
$show_debug = 1;
}
if (-u _ || -g _ || -k _) {
$body .= "%% WARNING %% - Designated script: $prog_exe has setuid, "
. "setgid and/or sticky bit set\n";
$show_debug = 1;
}
} else {
$body .= "%% WARNING %% - Target script: $prog_exe ($prog_path) does not " .
"exist\n";
$show_debug = 1;
}
$body .= "\n\n\n";
# Dump the environment variables
$body .= "
CGI Environment
";
my ($var);
foreach $var (sort keys(%ENV)) {
$body .= "$var = $ENV{$var}\n";
}
$body .= "\n\n\n";
my ($form_data, $form_data_hash_r) = (ReadParse())[0,2];
my %form_data = %$form_data_hash_r;
my $have_post_data = uc($ENV{ REQUEST_METHOD } || '') eq 'POST' ?
$post_stdin ? 2 : 1 : 0;
# 0: There is no post data.
# 1: In debug call, don't redirect stdin to the post data.
# 2: In debug call, redirect stdin to the post data.
$ENV{ POST_DATA } = $form_data if $have_post_data;
CREATE_LOG:
if (defined($log_fn)) {
$log_fn = "$tmp_dir$log_fn" if substr($log_fn,0,1) ne '/';
open LOG, ">>$log_fn"
or do {
$body .= "
WARNING: Could not append log script "
. dir_full_name($log_fn) . " ($!).\n";
$show_debug = 1;
last CREATE_LOG;
};
print LOG "http://$ENV{ SERVER_NAME }$ENV{ SCRIPT_NAME }$ENV{ PATH_INFO }" .
($form_data ? "?$form_data" : "") . "\n";
close LOG;
}
CREATE_DEBUG:
{
if ($debug_call) { # Create the debugging wrapper program.
my $prog_no_ext = $prog;
$prog_no_ext =~ s/\.pl$//;
my $debug_fn = "${prog_no_ext}_dbg.pl";
$ENV{ PATH } =~ s/^://;
my $filename = "$tmp_dir$debug_fn";
# unlink $filename;
open(DEBUG, ">$filename")
or do {
my $dfn = dir_full_name($tmp_dir);
$body .= <WARNING: Could not write debug script $dfn$debug_fn ($!).
(Make sure that user ID $> has write permission for $dfn.)
EOF
$show_debug = 1;
last CREATE_DEBUG;
};
print DEBUG < \"\Q$val\E\",\n";
}
print DEBUG <
$ENV{'REQUEST_METHOD'} Form Data
EOF
foreach $var (keys %form_data) {
$body .= "$var = $form_data{ $var }\n";
}
$body .= "\n\n
\n";
if (!$execute) {
print <Dummy Page
Execution disabled.
\$execute set to false in cgitrap.
$body
EOF
exit;
}
unless (-e $prog_path) {
print <Exiting because target script is not present!
EOF
exit;
}
my $have_stderr = 1;
close STDERR;
open STDERR, ">${tmp_dir}stderr.tmp"
or do {
$body .= "
WARNING: Could not write file " . dir_full_name() .
"stderr.tmp ($!).\n";
$show_debug = 1;
$have_stderr = 0;
};
my $syscall = ($have_post_data ? 'echo $POST_DATA | ' : '') . "$prog_path";
push(@time, [time, "Setup"]) if $time_info;
my @get = `$syscall`;
push(@time, [time, "Running"]) if $time_info;
close STDERR;
delete($ENV{ POST_DATA });
my ($err_stat, $err_msg) = ($?, $!);
my $stderr;
if ($have_stderr) {
open STDERR_IN, "<${tmp_dir}stderr.tmp";
if (read STDERR_IN, $stderr, 100000) {
$show_debug = 1;
$body .= "
STDERR:\n" . html_quote($stderr);
}
close STDERR_IN;
}
if ($err_stat) {
$body .= <
Exit value: @{[$err_stat >> 8]}
System return code: @{[$err_stat & 0xff]}
Error message: $err_msg
EOF
}
my $have_cookie = 0;
if (!@get) {
print "$head$body\nCould not start $prog to execute (or no output)!\n";
exit;
}
if ($paranoid) {
print $content_text;
} else { # Check header syntax
# Print every valid CGI header line, and ignore all others.
# If $have_type is still false after the entire header has been read,
# add a "content-type: text" line.
# Do I have a content-type header or acceptable substitute?
my $have_type;
my $header_lines = 0;
foreach (@get) {
$header_lines++;
if (/^content-type:/i) {
if (!/^content-type:[ \t]*text\/html\b/i) {
if ($show_debug) {
$body .= "Suppressing $_ output because of errors.
\n";
$_ = $content_text;
} else {
# Print non-HTML content verbatim if error-free.
splice @get, 0, $header_lines - 1;
print @get;
exit;
}
}
$have_type = 1;
} elsif (/^location:/i || /^uri:/i) {
# Acceptable substitutes for a content-type header.
$have_type = 1;
} elsif (/^expires:/i || /^date:/i || /^set-cookie:/i || m:^HTTP/:i ||
/^status:/i || /^pragma:/i || /^content-disposition:/i ||
/^refresh:/i
) {
} # OK header line.
elsif ($_ ne "\n" && $_ ne "\r\n") { # Bad header line -- ignore.
$body .= "
Unknown CGI header ignored-- $_";
$show_debug = 1;
next;
} else {
last;
}
print;
}
if (!$have_type) {
print $content_text;
$show_debug = 1;
$body .= "
CGI header does not specify content-type or URI.\n";
}
}
print "\n";
# End of CGI header section.
$show_debug ||= $err_stat;
if (!$show_debug) {
# Suppress debugging output
output_array(\@get);
print_time();
exit;
}
$body .= <
CGI Script Output for $prog (via $prog_exe)
EOF
$body .= html_quote(join('',@get)) . "
\n";
if ($paranoid) {
print $body, <CGITAP Complete, Resulting HTML document follows...
EOF
}
output_array(\@get);
if (!$paranoid) {
print "
HTML document complete, CGITAP output follows...
",
"
$body"; # Print CGITAP's content
}
push(@time, [time, "Cleanup"]) if $time_info;
print_time();
sub print_time {
return unless $time_info;
my $old_time = ${shift @time}[0];
foreach (@time) {
my ($new_time, $name) = @$_;
printf "$name: %.2f seconds\n", $new_time - $old_time;
$old_time = $new_time;
}
}
####################################################################
# Copyright 1994 Steven E. Brenner
# Unpublished work.
# Permission granted to use and modify this library so long as the
# copyright above is maintained, modifications are documented, and
# credit is given for any use of the library.
#
# Thanks are due to many people for reporting bugs and suggestions
# especially Meng Weng Wong, Maki Watanabe, Bo Frese Rasmussen,
# Andrew Dalke, Mark-Jason Dominus and Dave Dittrich.
#
# see http://www.seas.upenn.edu/~mengwong/forms/ or
# http://www.bio.cam.ac.uk/web/ for more information
#
# ReadParse was modified by ScendTek Internet Corp.; 7/3/95
#
# ReadParse
# Reads in GET or POST data, converts it to unescaped text, and puts
# one key=value in each member of the list "@in"
# Also creates key/value pairs in %in, using '\0' to separate multiple
# selections
#
# Removed format flexibility to obtain adherence to STRICT standards -- emb.
#
# Returns three references: a scalar string, an array, and a hash.
sub ReadParse {
my $in;
my @in = ();
my %in = ();
my ($i, $loc, $key, $val);
# Read in text
if ($ENV{'REQUEST_METHOD'} eq "GET") {
$in = $ENV{'QUERY_STRING'};
} elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$in,$ENV{'CONTENT_LENGTH'});
}
$in = '' if !defined($in);
@in = split(/&/,$in);
my $var;
foreach $var (@in) {
# Convert plus's to spaces
$var =~ s/\+/ /g;
# Convert %XX from hex numbers to alphanumeric (moved: ScendTek; 7/3/95)
$var =~ s/%(..)/pack("c",hex($1))/ge;
# Split into key and value.
($key, $val) = split(/=/,$var,2); # splits on the first =.
# Convert %XX from hex numbers to alphanumeric
#$key =~ s/%(..)/pack("c",hex($1))/ge;
#$val =~ s/%(..)/pack("c",hex($1))/ge;
# Associate key and value
$in{$key} .= " :: " if (defined($in{$key})); # :: is the multiple separator (ScendTek; 7/3/95)
$in{$key} .= $val;
}
# return 1; # just for fun
return ($in, \@in, \%in);
}
# Script's content.
# A fallible attempt is made to remove