1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
|
#!/usr/bin/perl -Tw
use strict;
use warnings;
#-----------------------------------------------------------------------------
# cdinsert.pl - updated for hostmonster.com site
#
# Web interface to "cdlabelgen"
# March 2013 v4.00+: Now run solely on remote web hosts and only runs cdlabelgen so
# only outputs PostScript files. Does not create PDF or images or local files.
#
# Creates CD Jewel Case Inserts, files output in PostScript format.
# This script is similar to the script used for the Online Interface at:
# http://www.aczoom.com/tools/cdinsert/
# and is provided as an example. There is no documention for this script,
# other than program comments in this file itself.
# -----------------------------------------------------------------------
my $VERSION = "4.12";
# Updated: Mar 2013: only outputs .ps files, so can be used on most any web host
# Updated: Aug 2007: added support for barcodegen using --tray-overlay
# Updated: Oct 2008: added support for double width DVDs --double-case
# Updated: Dec 2008: add -sPAPERSIZE to ps2pdf also, for correct A4 size
# Updated: Nov 2009: add nocoverheading option
# Updated: Mar 2013: remove PDF, image, and local files, only outputs Poscript files
# Created: March 2001, by Avinash Chopde <avinash@aczoom.com> www.aczoom.com
# -----------------------------------------------------------------------
# sudo cpan -i GD::Barcode::Image or use your web host cPanel to install this module.
#
# Note that there is bug https://rt.cpan.org/Ticket/Display.html?id=20297
# that has not been fixed in long time. It will cause QRcode generation to
# be unable to auto-select version, so a lot of input will fail.
# Fix it locally by changing to this in QRCode.pm init, make it 0 not 1:
# $oSelf->{Version} = $rhPrm->{Version} || 0; # now auto-select works
# ----
# Files/folders in any PATH dir:
# cdlabelgen barcodegen jpeg2ps jpegtran
# ----
# Edit the top section of this CGI script to point to files at your site.
# Edit cdlabelgen @where_is_the_template to point to 'cdlabelgen/postscript/' at your site.
#
#-----------------------------------------------------------------------------
# Hostmonster.com updates - may not need it here, but do need to add it to ~/perl5/bin/barcodegen also
# Necessary to pick up the Perl modules installed using the web host cPanel at hostmonster
BEGIN {
my $b__dir = (-d '/home2/aczoomco/perl'?'/home2/aczoomco/perl':( getpwuid($>) )[7].'/perl');
unshift @INC,$b__dir.'5/lib/perl5',$b__dir.'5/lib/perl5/x86_64-linux-thread-multi',map { $b__dir . $_ } @INC;
}
# --------------
use CGI 3.21 qw(escapeHTML); # at least 2.47 for upload, 2.50 for Vars, 3.21 for POST_MAX fix
$CGI::POST_MAX=1024 * 800; # max size posts accepted, bytes
use CGI::Carp qw(fatalsToBrowser);
use POSIX qw(floor);
use Socket qw(:DEFAULT :crlf);
use IO::File;
use File::Temp; # File::Temp->new files get removed when object goes out of context
#-----------------------------------------------------------------------------
# Change these variables to match your site
# Following vars need to be set specifically for each site
my $HOMEDIR="/home2/aczoomco"; # hostmonster.com
# Need to run programs: barcodegen cdlabelgen jpegtran jpeg2ps
# security blanket (make sure all folders/files are non-writeable by others!)
$ENV{'PATH'}="$HOMEDIR/bin:$HOMEDIR/perl5/bin:/usr/bin:"; # hostmonster
if (! -d $HOMEDIR) { # This triggers only on test machine, not on actual web host
$HOMEDIR="/home/cgi";
# $ENV{'PATH'}="/usr/bin"; # Just need /usr/bin in PATH for this, which is already present
}
my $TMPDIR = "$HOMEDIR/tmp";
my $LOGFILENAME = "$TMPDIR/cgi-bin-log.txt";
# Nothing below this needs changing, usually
#-----------------------------------------------------------------------------
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer
my $start_time = time();
my $SCRIPT = "cdinsert-ps.pl"; # name of script
my $DEBUG = 0; # 0 no debug messages, 1 some messages, 2 more.
$SIG{HUP} = $SIG{INT} = $SIG{QUIT} = $SIG{PIPE} = $SIG{TERM} = \&sighandler;
# Create the CGI object
my $QUERY = new CGI;
# logfile gets two lines per job - start time, and ending time.
# Keep track of jobs run ... Important: need to manually truncate/clean out this log file.
my $LOGFILE = IO::File->new($LOGFILENAME, ">>") # open LOGFILE in append mode
or &errorexit("Could not open logfile $LOGFILENAME: $!");
# verbose logging - script progress as well as error messages are here
# these will be inserted in a PostScript file - so they should always be single lines each
my @MESSAGES = ();
# input form text is stored in this file, file needed to pass as argument to cdlabelgen
my $SAVINPUT = File::Temp->new(TEMPLATE => 'cdlIXXXXX', DIR => $TMPDIR, SUFFIX => '.txt');
# Name this job
# my $TDATE = sprintf("%02d%02d%1d", (localtime($start_time))[3], (localtime($start_time))[2], (localtime($start_time))[1]/10); # current date hour minute(tens)
my $TDATE = sprintf("%02d%02d", (localtime($start_time))[3], (localtime($start_time))[2]); # current date hour
my $WORKID = "cd$TDATE-" . floor(rand(1e4)); # max 4 digits
my $REDIRECT="< /dev/null 2>&1";
# -- cmd 1:
my $CDLBL_E = "cdlabelgen $REDIRECT";
# -- cmd 2:
# barcodegen - from GD::Barcode::Image to be available
my $BCODEFILE = 0; # will be created only if needed
my $BCODE_E = "barcodegen $REDIRECT --format=EPS --border=7 --write="; # append: $BCODEFILE
my $CDL_BCODE_ARGS=" --tray-overlay-scaleratio=1,-0.1,0.1 --tray-overlay "; # append $BCODEFILE
# -- cmd 3: support for jpeg input files - handle it like barcodegen above
# PostScript cannot handle Progressive JPEG's, so have to run
# jpegtran on every input JPG file to convert it to baseline JPEG
# replace the following words below:
# UPLOADEDFILE with the .jpg file name uploaded from form
# TEMPFILE with a temp file name
# OUTPUTFILE with the .eps file to create
my $JPG2PS_E = "jpegtran -outfile TEMPFILE UPLOADEDFILE && jpeg2ps -r 72 -o OUTPUTFILE TEMPFILE";
my $CEPSFILE; # temp file for .eps version of uploaded cover .jpg file
my $TEPSFILE; # temp file for .eps version of uploaded tray .jpg file
my $TRANFILE; # temp file for both cover/tray jpegtran conversion
#-----------------------------------------------------------------------------
# Start the work, read in web form http POST data
# select((select(STDOUT), $| = 1)[0]); # autoflush, not needed since we don't output HTML
my $hostname = $QUERY->remote_host() || '';
my $hostaddr = $QUERY->remote_addr() || '';
my $message = "$WORKID v$VERSION [" . localtime($start_time) . "] Starting job for $hostname\n";
push @MESSAGES, $message;
print $LOGFILE $message;
#----------------------------------------------------------------
my $cgierror = $QUERY->cgi_error(); # post too big, or user hit STOP, etc...
if ($cgierror) {
if ($cgierror =~ /413/) {
&errorexit("Uploaded files too large?<br/> Received too much data - got <b>" . int($ENV{'CONTENT_LENGTH'}/1024) . "</b> KBytes, can only receive maximum of <b>" . int($CGI::POST_MAX/1024) . "</b> KBytes.");
} else {
&errorexit($cgierror);
}
}
# collect form variables, untainting as needed (// operator returns list, so assign to my list)
# untaint everything even if not used in expresses - perl cannot handle modifiers correctly
# https://rt.perl.org/rt3/Public/Bug/Display.html?id=17867 [bug] taint mode and instruction modifier
# Also see explanation in http://perldoc.perl.org/perlsec.html :
# For efficiency reasons, Perl takes a conservative view of whether data is tainted. If an expression contains tainted data, any subexpression may be considered tainted, even if the value of the subexpression is not itself affected by the tainted data. An exception is ?: operator, so use that to untaint things.
# And a "if modifier" suffix to a statement is all one expression, so best to untaint everything.
#
my $useragent = $QUERY->user_agent() || 'CGI query - no useragent';
my $referer = $QUERY->referer() || '';
my $intitle = $QUERY->param('title') || '';
my $insubtitle = $QUERY->param('subtitle') || '';
my $inclogo = $QUERY->param('clogo') || '';
my $intlogo = $QUERY->param('tlogo') || '';
my ($incimage) = ($QUERY->param('cimage') || '') =~ /([\d\w\-\.]+)/; # no / allowed in name
my ($intimage) = ($QUERY->param('timage') || '') =~ /([\d\w\-\.]+)/; # no / allowed in name
my $incimagefile = $QUERY->param('cimagefile');
$incimagefile = $QUERY->tmpFileName($incimagefile) if ($incimagefile); # get actual temp file name
$incimagefile = ($incimagefile || '') =~ /(.*)/; # yes, really need this, is local file path of uploaded temp file
$incimagefile = $1;
my $intimagefile = $QUERY->param('timagefile');
$intimagefile = $QUERY->tmpFileName($intimagefile) if ($intimagefile); # get actual temp file name
$intimagefile = ($intimagefile || '') =~ /(.*)/; # yes, really need this, is local file path of uploaded temp file
$intimagefile = $1;
my $incontents = $QUERY->param('contents') || '';
$incontents =~ s/$CR?$LF/\n/g; # fix all CR/LF chars
# for many of the booleans below, need to untaint them even though not passing them to `cmd`
# due to perl optimizations https://rt.perl.org/rt3/Public/Bug/Display.html?id=17867
my ($incdcase) = ($QUERY->param('cdcase') || '') =~ /([\d\w\-\.]+)/; # untaint
my $innotrayhd = $QUERY->param('notrayheading') ? 1 : 0;
my $innocoverhd = $QUERY->param('nocoverheading') ? 1 : 0;
my $inscaleitems = $QUERY->param('scaleitems') ? 1 : 0;
my $ina4paper = $QUERY->param('a4paper') ? 1 : 0;
my $insplititems = $QUERY->param('splititems') ? 1 : 0;
my $infilename = $QUERY->param('filename') || '';
my $infile = $QUERY->upload('filename') || ''; # get file handle
my ($inbcodetype) = ($QUERY->param('bcodetype') || '') =~ /([\d\w\-\.]*)/; # no / allowed in name
my $inbcodetext = $QUERY->param('bcodetext') || '';
my $gotstring = ($incontents =~ /\S+/);
my $gotfile = ($infilename =~ /\S+/);
if ($DEBUG > 1) {
push @MESSAGES, "Got these values from the form:\n";
my ($key, $value);
my %params = $QUERY->Vars();
while (($key, $value) = each %params) {
# assuming value is single string - if multi-valued, need
# to split on \0 to get array of values...
$value = "<supplied string>" if ( $key =~ /^string$/ );
push @MESSAGES, " $key = '$value'\n";
}
push @MESSAGES, "Some environment vars:\n";
push @MESSAGES, " user_agent = '$useragent'\n";
push @MESSAGES, " referer = '$referer'\n";
}
# Jan02: accept empty input, most common error, so better to accept it
# don't quit, even on no gotstring or file .. may have inlogo or incimagefile for example.
# ($gotstring || $gotfile || $intitle || $insubtitle || $incimage || $intimage)
# or errorexit("Nothing to do - empty input - no fields entered!");
my $null_in_contents = 0;
my $num_items = 0; # number of items or lines in the input text
if ($gotfile) { # ignore $gotstring, file takes precedence
if ($gotstring) {
push @MESSAGES, "** Warning: user entered text as well as filename, ignoring text.\n",
}
while (<$infile>) {
# read each line to get correct EOLN value for this platform (works?)
s/$CR?$LF/\n/; # variables from Socket package
print $SAVINPUT $_;
$null_in_contents += (index($_, "\000") + 1);
$num_items += 1;
}
close $infile; # now $SAVINPUT string has the input text it - from form, or file
push @MESSAGES, "... read in uploaded file: $infile\n" if ($DEBUG >= 1);
} else {
print $SAVINPUT $incontents;
$null_in_contents += (index($incontents, "\000") + 1);
my @items = split(/\n/, $incontents);
$num_items = $#items + 1;
}
$SAVINPUT->close(); # close and flush file so it can passed as argument to cdlabelgen
# now exit if file was bad - NULLs in it, etc.
# some people post binary files here, and ghostscript gs hangs on
# such text, so have to remove invalid characters
# don't really know a sure-fire way of detecting binary files or
# deleting all non-printable chars (ISO-Latin1, ASCII, etc??)
# so, doing something that is probably good enough in most cases
# this is just more protection - there may be code above to return
# errors if a non-text file is uploaded for the list of items.
if ($null_in_contents > 0) {
errorexit("'$infilename' - not ASCII or Latin1 text. Found null character in input ($null_in_contents).");
}
my $start_processing_time = time();
# ----------------------------------------------------------------------
# untaint variables...
# Title and Subtitle should use entire string as entered by user
# but - do escape any non-alphanumeric character, this should take
# care of shell metacharacters such as " $ etc
# Don't quote the title or subtitle:
# single quotes are a problem since another \' inside the string gets ignored.
# double quotes are a problem since most \ 's are preserved \) remains \)
# s/([`"\$\\])/\\$1/g; # use this if enclosing title in double quotes "
# s/(\W)/\\$1/g; # use this if NOT enclosing title in any quotes " or ',
# is safest since every non-alpha-numeric character is escaped.
$intitle =~ /(.*)/; # yes, really need this.
$intitle = $1;
$intitle =~ s/(\W)/\\$1/g;
$insubtitle =~ /(.*)/; # yes, really need this.
$insubtitle = $1;
$insubtitle =~ s/(\W)/\\$1/g;
$inbcodetext =~ /(.*)/; # yes, really need this.
$inbcodetext = $1;
$inbcodetext =~ s/(\W)/\\$1/g;
push @MESSAGES, "after untaint: title($intitle) subtitle($insubtitle) clogo($inclogo) tlogo($intlogo)\n"
if ($DEBUG >= 1);
# jpg file conversions handle incimage incimagefile and intimage intimagefile
if ($incimagefile || $intimagefile) {
# jpegtran output temp file to be used by both cover and/or tray conversions
$TRANFILE = File::Temp->new(TEMPLATE => 'cdlTXXXXX', DIR => $TMPDIR, SUFFIX => '.jpg');
$TRANFILE->close(); # we only need file name so can close this handle
if ($incimagefile) {
if ($incimage) {
push @MESSAGES, "** Warning: user selected built-in Cover Image and uploaded Image, ignoring built-in.\n";
}
$CEPSFILE = File::Temp->new(TEMPLATE => 'cdlCXXXXX', DIR => $TMPDIR, SUFFIX => '.eps');
$incimage = $CEPSFILE->filename(); # replace incimage with .eps file to use
my $cmd = $JPG2PS_E;
# UPLOADEDFILE with the .jpg file name uploaded from form
# TEMPFILE with a temp file name
# OUTPUTFILE with the .eps file to create
$cmd =~ s/UPLOADEDFILE/$incimagefile/g;
$cmd =~ s/TEMPFILE/$TRANFILE/g;
$cmd =~ s/OUTPUTFILE/$CEPSFILE/g;
&do_cmd($cmd);
}
if ($intimagefile) {
if ($intimage) {
push @MESSAGES, "** Warning: user selected built-in Tray Image and uploaded Image, ignoring built-in.\n";
}
$TEPSFILE = File::Temp->new(TEMPLATE => 'cdlYXXXXX', DIR => $TMPDIR, SUFFIX => '.eps');
$intimage = $TEPSFILE->filename(); # replace intimage with .eps file to use
my $cmd = $JPG2PS_E;
# UPLOADEDFILE with the .jpg file name uploaded from form
# TEMPFILE with a temp file name
# OUTPUTFILE with the .eps file to create
$cmd =~ s/UPLOADEDFILE/$intimagefile/g;
$cmd =~ s/TEMPFILE/$TRANFILE/g;
$cmd =~ s/OUTPUTFILE/$TEPSFILE/g;
&do_cmd($cmd);
}
}
# compute -S and -T scale factors.
# use the special value "0.0" if image is to be printed as background,
# otherwise use no scaling (1.0 scale factor).
my $clogoscale = ($inclogo) ? "1.0" : "0.0";
my $tlogoscale;
$incdcase = '' unless ($incdcase);
if ($incdcase =~ /^normal/) {
$tlogoscale = ($intlogo) ? "1.0" : "fill2"; # fill2: fill endcaps too
} else {
$tlogoscale = ($intlogo) ? "1.0" : "fill1"; # fill1: just fill tray
}
# ---- compute page offset for A4 and gs command modifications
if ($ina4paper) {
$ina4paper = "-y 1.5"; # default
$ina4paper = "-y 0.8" if ($incdcase =~ /^(dvd)|(envelope)|(double)/);
}
#-----------------------------------------------------------------------------
# check if barcode generation has to be performed - this requires
# barcodegen - from GD::Barcode::Image to be available
if ($inbcodetype && $inbcodetext) {
$BCODEFILE = File::Temp->new(TEMPLATE => 'cdlBXXXXX', DIR => $TMPDIR, SUFFIX => '.eps');
$BCODE_E .= $BCODEFILE . " --type '$inbcodetype' $inbcodetext";
$CDL_BCODE_ARGS .= $BCODEFILE;
&do_cmd($BCODE_E);
$BCODEFILE->close();
}
#-----------------------------------------------------------------------------
# 1: run cdlabelgen to create .ps file
my @cmdargs = ();
push(@cmdargs, "-c $intitle") if ($intitle); # no quotes around title...
# don't use single quotes, embedded \' causes problems in title/subtitle
push(@cmdargs, "-s $insubtitle") if ($insubtitle); # no quotes around title...
push(@cmdargs, "-e '$incimage'") if ($incimage);
push(@cmdargs, "-S '$clogoscale'") if ($incimage);
push(@cmdargs, "-E '$intimage'") if ($intimage);
push(@cmdargs, "-T '$tlogoscale'") if ($intimage);
push(@cmdargs, "-f $SAVINPUT");
push(@cmdargs, "-D");
push(@cmdargs, "-m") if ($incdcase =~ /^slimcase/);
push(@cmdargs, "-M") if ($incdcase =~ /^envelope/);
push(@cmdargs, "--create-dvd-inside") if ($incdcase =~ /^dvdinside/);
push(@cmdargs, "--create-dvd-outside") if ($incdcase =~ /^dvdoutside/);
push(@cmdargs, "--double-case") if ($incdcase =~ /^doublecase/);
push(@cmdargs, "-p") if (! $inscaleitems);
push(@cmdargs, "-b") if ($innotrayhd);
push(@cmdargs, "-C") if ($innocoverhd);
push(@cmdargs, $ina4paper) if ($ina4paper);
push(@cmdargs, $CDL_BCODE_ARGS) if ("$BCODEFILE");
# if number of items is very large, print some items on the cover also
# Note: even though it should not matter, this requires $insplititems to be untainted
push(@cmdargs, "-v " . int($num_items/2)) if ($num_items > 250 || $insplititems);
# push(@cmdargs, "-o $WORKFILE.ps"); # not this, just print to stdout
# cdlabelgen arguments:
# -c <category> Set the category (title) for the CD
# -s <subtitle>
# -d <date> default: YYCC-MM-YY
# -D don't print date
# -f <filename> input filename
# -e <cover_epsfile>
# -E <tray_epsfile>
# -m for slim cd cases
# --create-dvd-inside for inside inserts for DVD cases
# -M for CD envelope
# -p clip text - don't scale down item (if required to fit to a column)
# -b don't print the plaque (title/subtile) on tray_card
# -y 1.5 or -y 0.8 for A4 paper
my $cmd = join(' ', $CDLBL_E, @cmdargs);
my $output_str = &do_cmd($cmd);
# -----
my $end_time = time();
my $time_taken = $end_time - $start_time;
my $receive_time_taken = $start_processing_time - $start_time;
my $time_taken_units = ($time_taken <= 1) ? "1 second" : "$time_taken seconds";
$message = "$WORKID took $time_taken secs";
$message .= " (download $receive_time_taken) " if ($incimagefile || $intimagefile);
# $message .= " [error]" if ($errflag);
$message .= "\n";
push @MESSAGES, $message;
# Output the .ps file to stdout
# Include the messages from this script as comments in the Postscript file
my $ps_messages = join('% ', @MESSAGES);
# Insert the messages in the output string
# Line "% WEB-SCRIPT-MESSAGES-HERE\n" from the cdlabelgen template.ps is replaced
$output_str =~ s/% WEB-SCRIPT-MESSAGES-HERE\n/\% ${ps_messages}/;
# If we didn't exit, we can output the .ps file
# return the .ps file as result of this submit button execution
# output HTML headers for sending the postscript file - only if no error occured.
print STDOUT $QUERY->header(-type => 'application/postscript',
-charset => "ISO-8859-1",
-expires => "now",
-content_disposition => "inline; filename=cdinsert.ps");
print STDOUT $output_str; # Output PS file. All done with STDOUT and this script.
# Log file update with the last message sent
print $LOGFILE $message;
exit(0);
#-----------------------------------------------------------------------------
# Subroutines
# They may add status messages to @MESSAGES and in rare cases, write to $LOGFILE
sub do_cmd {
# Runs given command, and returns command output
my ($cmd) = @_;
push @MESSAGES, "$WORKID cmd: $cmd\n";
my $out = `$cmd`;
my $returncode = ($? >> 8);
if ($returncode != 0) {
push @MESSAGES, "cmd failed $returncode: $out\n";
&errorexit("Command $cmd failed");
}
$out;
}
#-----------------------------------
sub sighandler {
my $str = "$WORKID user or system terminated --\n";
print $LOGFILE $str;
exit(1);
}
#-----------------------------------
sub errorexit {
# Report the given message using full HTML headers , and exit with error status
my($mesg) = @_;
my $str = "Error running cdlabelgen: $mesg";
print $LOGFILE $str; # to this script's logfile
print STDERR $str; # goes to Apache error_log
# We never print CGI headers in the main script except at the very end, so
# if we are quitting earlier, need to output full HTML headers.
print STDOUT $QUERY->header, # create the HTTP header
$QUERY->start_html('Error running cdlabelgen'), # start the HTML
$QUERY->h1($str), # level 1 header
$QUERY->h2("Script messages:"), # level 1 header
$QUERY->pre(escapeHTML(join('', @MESSAGES))), # display the collected status messages too
$QUERY->p("---"), # display the collected status messages too
$QUERY->end_html;
exit(2);
}
#----------------------------------------
|