CGI-Alert
view release on metacpan or search on metacpan
lib/CGI/Alert.pm view on Meta::CPAN
# -*- perl -*-
#
# CGI::Alert.pm - notify a human about errors/warnings in CGI scripts
#
# $Id: 98 $
#
package CGI::Alert;
use strict;
use warnings;
use Carp;
###############################################################################
# BEGIN user-configurable section
# If set (by caller, via emit_http_headers), emit HTTP headers
our $Emit_HTTP_Headers = 0;
# If set (by caller, via emit_html_headers), _and_ CGI.pm is loaded,
# emit these extra headers from http_die
our @Extra_HTML_Headers;
# By default, send notifications to this address. We could try to be
# clever about stat'ing the calling script and finding the owner, but
# why go to so much effort?
our $Maintainer = 'webmaster';
# Expressions to filter from the email. We don't want to send passwords,
# credit card numbers, or other sensitive info out via email.
our @Hide = (qr/(^|[\b_-])passw/i);
# Default text shown to the remote (web) user if we die. This tells
# the user that something went wrong, but that a responsible party
# has been informed.
our $Browser_Text = <<'-';
<h1><font color="red">Uh-Oh!</font></h1>
<p>
The script handling your request died with the following error:
</p>
<pre>
[MSG]
</pre>
<p>
If that indicates a problem you can fix, please do so.
</p>
<p>
Otherwise, don't panic: I have sent a notification to the
[MAINTAINER], providing details of the error.
</p>
-
# For stack trace: names of the fields returned by caller(), in order.
our @Caller_Fields =
qw(
package
filename
line
subroutine
hasargs
wantarray
evaltext
is_require
hints
bitmask
);
#
# Package globals, checked at END time.
#
our @cgi_params; # CGI inputs (GET/POST), set at INIT time
my @warnings; # Warnings, both plain...
my @warnings_traced; # ...and with stack trace.
# For debugging this module, and running tests. Set by t/*.t to a
# file path. We write our email to this file, instead of running sendmail.
our $DEBUG_SENDMAIL = '';
# END user-configurable section
###############################################################################
# One exportable (on request) function: http_die
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(http_die);
# Program name of our caller
our $ME = $ENV{REQUEST_URI} || $0 || "<???>";
# Module version, on one line for MakeMaker
our $VERSION = 2.09;
############
# import # If called with "use CGI::Alert 'foo@bar'", send mail to foo@bar
############
sub import {
my $i = 1;
while ($i < @_) {
# Is it a valid exported function? Skip.
if (defined &{$_[$i]}) {
$i++
}
elsif ($_[$i] =~ m!^-{0,2}hide=(.+)$!) { # RE to filter out?
my $hide = $1; # Our input
my $re; # ...how we interpret it
if ($hide =~ m!^/(.*)/$!) { $re= "qr/$1/" }
elsif ($hide =~ m!^m(.)(.*)\1$!) { $re= "qr/$2/" }
elsif ($hide =~ m!^(qr(.)(.*)\2[ismx]*)$!) { $re= $1 }
else { $re= "qr/$hide/" }
# Make sure it can be parsed as a regex.
my $result = eval $re;
if ($@) {
lib/CGI/Alert.pm view on Meta::CPAN
# Anything left over? E.g., 'http_die' ? Pass it along to Exporter
CGI::Alert->export_to_level(1, @_);
}
##################
# Final override. This is run after the import, and thus has the last
# say on who gets notified.
#
# We examine our URL. If it's of the form "/~user/something", assume
# that "user" is debugging, and would prefer that notifications go just
# to him/her.
##################
INIT {
# Invoked from user URL (~user/...) ? Debugging -- send mail to him/her
if (($ENV{REQUEST_URI} || "") =~ m!/(~|%7e)([^/]+)/!i) {
# Does user actually exist?
if (getpwnam($2)) {
$Maintainer = $2;
}
}
# If called with CGI parameters, remember them now. Otherwise, our
# caller could call Delete_all() (from CGI.pm) or otherwise clear
# the params, so we wouldn't have them when our END handler is called.
if (exists $INC{'CGI.pm'}) {
eval {
# Each element of @cgi_params is an array ref: first element is
# the param name, everything else is one or more values.
foreach my $p (CGI::param()) {
push @cgi_params, [ $p, CGI::param($p) ];
}
};
print STDERR __PACKAGE__, ": error in eval: $@\n" if $@;
}
}
###############################################################################
# BEGIN helper functions
###############
# _basename # Poor man's implementation, to avoid including File::Basename
###############
sub _basename($) {
my $f = shift;
$f =~ m!/([^/]+)$!
and return $1;
return $f;
}
##################
# _stack_trace # returns pretty stack trace
##################
sub _stack_trace() {
my @levels;
# Get a full callback history, first-is-first (that is, the
# main script is first, instead of the usual most-recent-first).
# @levels will be a LoH, an array containing hashrefs.
#
# See perlfunc(1) for details on caller() and the 'DB' hack.
my $i = 0;
my @call_info;
while (do { { package DB; @call_info = caller($i++) } } ) {
unshift @levels, {
(map { $_ => shift @call_info } @Caller_Fields),
args => [ @DB::args ],
};
}
# The last few levels of subroutine calls are all inside this
# module. Exclude them.
while ($levels[-1]->{filename} =~ m!/Alert\.pm$!) {
pop @levels;
}
# Last function in the trace is the one that invoked warn/die.
# Instead of showing our local sub name, show 'warn' or 'die'.
if ($levels[$#levels]->{subroutine} =~ /^CGI::Alert::_(warn|die)$/) {
$levels[$#levels]->{subroutine} = $1;
}
# Determine the length of the longest filename
my $maxlen = -1;
for my $lev (@levels) {
my $len = length( _basename($lev->{filename}) );
$maxlen < $len
and $maxlen = $len;
}
my $retval = ''; # Returned string.
my $indent = " "; # Function indentation level
my $last_filename = ''; # Last filename seen
for my $l (@levels) {
my $filename = _basename($l->{filename});
# Same as last file seen? Don't bother to display it.
if ($filename eq $last_filename) {
$filename =~ s|.| |g;
}
else {
$last_filename = $filename; # remember for next time
}
# Filename, line number, and subroutine name.
$retval .= sprintf(" %-*s : %4d %s%s(", $maxlen, $filename,
$l->{line},
$indent, $l->{subroutine});
# Function arguments, in parenthesized list.
my $comma = '';
for my $arg (@{$l->{args}}) {
# Perform minor adjustments on each arg
if (!defined $arg) {
$arg = 'undef';
}
elsif (!ref $arg) { # not a ref: must be a string, or a number
$arg =~ s|\n|\\n|g; # escape newlines
$arg =~ /\D/ # quote strings
and $arg = "\"$arg\"";
}
$retval .= "$comma $arg";
$comma = ',';
( run in 2.843 seconds using v1.01-cache-2.11-cpan-75ffa21a3d4 )