(Originally sent to Corey Golberg author of webinject. Due to lack of
time he has been unable to roll a new relase with my patch so I am
publishing it here. -- rouilj)

Hello:

I am using your tool to create test cases for our web applications and
running them under nagios. I noticed the following bug in the
      documentation:

The case option is "verifypositivenext" not "verifynextpositive" as in
the online documentation. The same is true for the negative case.

As I was developing the test cases I added a few features. The
attached patch file will implement the following in webinject 1.41 and
also provides a Changelog of the changes with more info. The changes
fall into the following categories:

 Nagios integration:

    New option -D that allows setting of config file options from the
      command line. Useful for using nagios variables to change URL
      based on the host being monitored. Useful to set up cases that
      are run in nagios mode using "-D reporttype=nagios" but by
      default run verbosely when run by hand.

    New config file setting nagioslog that enables logging of http
      responses and requests. Very useful when set to onfail to
      diagnose reason for failure.

    All die calls replaced by call to InternalError that send the
      error to stdout and exits with unknown state if
      reporttype=nagios. Dies or reports error to stderr in other
      report cases.

    Return messages for nagios and other report types include the
      errormessage or description1 argument for the testcase.

    Connection timeouts (caused by the timeout parameter) are no
      longer treated as critical and return a warning state in
      reporttype=nagios mode.

    Performance data now includes the number of test cases as well as
      the time to run.


 Logging changes:

    httplog file now has headers rather then footers for the output of
      each test case. The header also has the the test number and the
      pass/fail status. Makes it easier to find the log you are looking
      for.


 Configuration usability changes

    New config variables postbody...postbody5.

    The postbody case argument takes file=> for regular url encoded
      data as well. This is useful for passing login data (with -D
      postbody=/file/name) from external programs rather then recording
      it in the test files.

   The logrequest and logresponse case options can now handle the
      value "onfail".

   Each case can have it's own timeout option.

   Each case can be tagged with a keyword and only cases that have
      keywords matched by the regular expression in the "tags" config
      file option will be executed. Sort of a generalization of the
      xpath function.

 Other usability changes

   Default umask set to 077 to prevent http.log files and temp files
      with passwords and usernames from being readable. Result files
      are explicitly made world readable.

   Passing any arguments to webinject.pl makes it run in command line
      mode regardless of the executable name. (e.g. check_webinject -c
      .... will run in command mode rather than gui mode).

   All output files (http log, temp xml files, result
      reports, gnuplot data) are written to the directory/prefix
      specified with the -o flag. If -o is not specified, the
      directory where the config file is located is used.

 Bug fixes:

   Posting of xml data no longer generates an error if the postbody
      doesn't contain a 'file=>...' argument.

Again thanks for a very useful tool. Hopefully you will find my
changes acceptable and add them to the released code to improve
the utility of your tool.

				Sincerely yours,

-- 
				-- rouilj

John Rouillard
System Administrator
Renesys Corporation
603-643-9300 x 111


===== cut here =======

diff -Nru webinject/Changelog webinject-working/Changelog
--- webinject/Changelog	1970-01-01 00:00:00.000000000 +0000
+++ webinject-working/Changelog	2006-09-29 21:20:01.000000000 +0000
@@ -0,0 +1,142 @@
+2006-Sep-28 John P. Rouillard  rouilj at renesys.com/rouilj at cs.umb.edu
+
+* webinject.pl (processcasefile,httplog) A new config file setting "nagioslog"
+takes the values "onfail" and "yes". This forces an http logging to be
+turned on when run with reporttype=nagios. Also when run in this mode,
+the logfile is appended to rather  then overwritten. This helps
+diagnose intermittent failures when run under nagios. Since the file
+is appended to, it can be viewed afer other nagios runs have occurred
+and you will still have the data from the failure report.
+
+* webinject.pl (httplog) Http log files now have seperators at the
+beginning of each test's output. It includes the pass/fail status of
+the test generating the output, the id number for the test and the
+time the test was run. This makes it easier to review a log file with
+multiple appended tests in it as well as find a specific failing test
+in a single run.
+
+* webinject.pl (httppost_form_urlencoded) The postbody test argument
+can use 'file=>filename' for urlencoded data just like text/xml
+data. The filename can be passed in using a postbody configuration
+element.
+
+* webinject.pl (processcasefile,convertbackxml) Added new config file
+settings:
+
+   postbody, postbody1 ... postbody5
+
+settable via command line using -D postbody=....  or via
+<postbody>...</postbody> statements in the config file. This allows
+external programs to create data (e.g. login credentials) dynamically
+into a file and webinject can use that data. This allows us to keep
+login data in once place rather then scattered throughout a bunch of
+tests. (In reality they are just command line settable variables as
+they are replaced throughout the test files not just in postbody
+statements).
+
+* webinject.pl (many functions) All die calls were replaced with calls
+to InternalError. This standardizes the error output. It also allows
+proper outout to be provided if running under nagios as it reports the
+error to stdout and exits with unknown status if the
+reporttype=nagios. For other reporttypes it uses die if the continue
+argument is not set. If the continue arg is set just reports error to
+stderr and returns.
+
+* webinject.pl (httplog) The logrequest and logresponse test case
+options now support the onfail setting.
+
+* webinject.pl (engine, httpget, httppost_form_urlencoded,
+httppost_xml, httppost_form_data) Each case can have it's own timeout
+setting overriding the one in config. If not set, the one in config is
+used.
+
+* webinject.pl (engine, processcasefile) If you want to run just a
+subset of the tests, e.g you have a testfile that logs into a web
+application and runs 30 tests, but you modified the implmentation code
+for the website and only need to run two of the 30 tests. To do this,
+I added support for a config file parameter called "tags". Each case
+now has an optional parameter called "tags". The tags case parameter
+is a space or comma seperated list of words/tags that allows selection
+of this test case. The config file setting is a perl regular
+expression that selects cases using their tag. If a config file tags
+setting is defined only cases with a matching tags parameter are
+executed. An empty or non-existant tags parameter does not match a
+tags setting.
+
+For the example above, if login is the tag for the login sequence and
+scheduler is the tag for the two tests you want to run then:
+
+  webinject -D tags="login|scheduler" -c config.xml
+
+will run them. Note that the tags setting has to match word
+boundaries, so the tag "loginout" won't be matched by the example
+above.
+
+2006-Sep-26 John P. Rouillard rouilj at renesys.com/rouilj at cs.umb.edu
+
+* webinject.pl (getoptions, processcasefile, main) On the command line
+arguments of the form '-D configparam=value' will override the setting
+for the configparam in the config file.  E.G. webinject -c config.xml
+-D reporttype=nagios will run the test using the report type of nagios
+regardless of the setting in the config file. Also baseurl and other
+values can be set without needing to edit the config file.
+
+* webinject.pl (main, engine) Default umask for files is 077 To
+prevent temp files and http log files (that may have passwords) from
+being publicaly readable. The report files have their mode changed to
+be publically readable.
+
+* webinject.pl (main) If webinject.pl is called without arguments it
+assumes it is called as a command line application regardless of
+name. This allows any name for the script (say check_webinject) to
+start the engine in non-gui mode by specifying an argument.
+
+* webinject.pl (main, engine, convtestcases, fixsinglecase, plotlog,
+gnuplotcfg) All output files (http log, temp xml files, result
+reports, gnuplot data) are written to the directory/prefix specified
+with the -o flag. If -o is not specified, the directory where the
+config file is located is used. The location of the webinject program
+isnot used to determine a writable directory since it will almost
+always fail if it is installed in /usr/bin or other directory.
+
+Configfile must now be a valid path from the current directory where
+webinject is invoked rather than a path from the location where
+webinject is installed.
+
+* webinject.pl (engine) If the -o flag specifies an existing
+directory, it does not need a trailing / to be specified. Note that
+this prevents an output spec of: /dir/mydir from creating files:
+/dir/mydirhttp.log /dir/mydirresults.http etc. They will always be
+placed inside of mydir.
+
+* webinject.pl (engine) return messages for nagios and other report
+types include the errormessage or description1 argument for the
+testcase.
+
+* webinjet.pl (engine) Connection timeouts (caused by the timeout
+parameter) are no longer treated as critical and return a warning
+state in reporttype=nagios mode.
+
+* webinject.pl (many functions) Error messages for file errors changed
+to report the filename.
+
+* webinject.pl (httppost_xml0 Posting of xml data no longer generates
+an error if the postbody doesn't contain a 'file=>...' argument.
+
+* webinject.pl (finaltasks) nagios performance data now includes the
+number of test cases as well as the time to run.
+
+** Internal
+
+* webinject.pl (engine) $opt_output is always initialized simplifying
+the code and reducing duplication.
+
+
+
+Notes:
+
+case command is verifypositivenext not verifynextpositive ditto for
+negative case.
+
+Can we use multiple xpath nodes to allow command line paths through
+the test cases?
diff -Nru webinject/webinject.pl webinject-working/webinject.pl
--- webinject/webinject.pl	2006-01-04 18:10:30.000000000 +0000
+++ webinject-working/webinject.pl	2006-09-29 14:10:21.000000000 +0000
@@ -15,7 +15,7 @@
 #    GNU General Public License for more details.
 
 
-our $version="1.41";
+our $version="1.41renesys2";
 
 use strict;
 use LWP;
@@ -27,11 +27,13 @@
 use Crypt::SSLeay;  #for SSL/HTTPS (you may comment this out if you don't need it)
 use XML::Parser;  #for web services verification (you may comment this out if aren't doing XML verifications for web services)
 use Error qw(:try);  #for web services verification (you may comment this out if aren't doing XML verifications for web services)
-#use Data::Dumper;  #uncomment to dump hashes for debugging   
+use Data::Dumper;  #uncomment to dump hashes for debugging   
 
 
 $| = 1; #don't buffer output to STDOUT
 
+umask(077); # don't make our temp files and http.log world readable.
+            # may have passwords
 
 our ($timestamp, $dirname);
 our (%parsedresult);
@@ -45,11 +47,11 @@
 our (%case, $verifylater, $verifylaterneg);
 our (%config);
 our ($currentdatetime, $totalruntime, $starttimer, $endtimer);
-our ($opt_configfile, $opt_version, $opt_output);
+our ($opt_configfile, $opt_version, $opt_output, %clvariables);
 our ($reporttype, $returnmessage, %exit_codes);
+our ($httplogfh);
 
-
-if (($0 =~ /webinject.pl/) or ($0 =~ /webinject.exe/)) {  #set flag so we know if it is running standalone or from webinjectgui
+if (($0 =~ /webinject.pl/) or ($0 =~ /webinject.exe/) or ($#ARGV != -1)) {  #set flag so we know if it is running standalone or from webinjectgui
     $gui = 0; 
     engine();
 }
@@ -67,15 +69,21 @@
     our ($startruntimer, $endruntimer, $repeat);
     our ($curgraphtype);
     our ($casefilecheck, $testnum, $xmltestcases);
+    my $currentcasefilebase = "";
 
     # undef local values
-    map { $case{$_} = undef } qw/method description1 description2 sleep/;
+    map { $case{$_} = undef } qw/method description1 description2 sleep timeout/;
         
     if ($gui == 1) { gui_initial(); }
         
     getdirname();  #get the directory webinject engine is running from
         
     getoptions();  #get command line options
+
+    getdirname($opt_configfile) if defined $opt_configfile;
+
+    $opt_output = $dirname if ( ! defined($opt_output) );
+    $opt_output .= "/" if -d $opt_output && $opt_output !~ m@/$@;
         
     whackoldfiles();  #delete files leftover from previous run (do this here so they are whacked each run)
         
@@ -118,16 +126,12 @@
         
     #open file handles
     unless ($reporttype) {  #we suppress most logging when running in a plugin mode
-        if ($opt_output) {  #use output location if it is passed from the command line
-            open(HTTPLOGFILE, ">$opt_output"."http.log") or die "\nERROR: Failed to open http.log file\n\n";   
-            open(RESULTS, ">$opt_output"."results.html") or die "\nERROR: Failed to open results.html file\n\n";    
-            open(RESULTSXML, ">$opt_output"."results.xml") or die "\nERROR: Failed to open results.xml file\n\n";
-        }
-        else {
-            open(HTTPLOGFILE, ">$dirname"."http.log") or die "\nERROR: Failed to open http.log file\n\n";   
-            open(RESULTS, ">$dirname"."results.html") or die "\nERROR: Failed to open results.html file\n\n";    
-            open(RESULTSXML, ">$dirname"."results.xml") or die "\nERROR: Failed to open results.xml file\n\n";
-        }
+	open(RESULTS, ">$opt_output"."results.html") or InternalError("ERROR: Failed to open ${opt_output}results.html file");    
+	open(RESULTSXML, ">$opt_output"."results.xml") or InternalError("ERROR: Failed to open ${opt_output}results.xml file");
+	# result files world readable since they don't have passwords
+	# (e.g. s get url or post data) in them.
+	chmod(0644, "${opt_output}results.xml");
+	chmod(0644, "${opt_output}results.html");
     }
         
     unless ($reporttype) {  #we suppress most logging when running in a plugin mode 
@@ -173,13 +177,16 @@
         convtestcases();
             
         fixsinglecase();
-          
-        $xmltestcases = XMLin("$dirname"."$currentcasefile".".$$".".tmp", VarAttr => 'varname'); #slurp test case file to parse (and specify variables tag)
+
+	$currentcasefilebase = $currentcasefile;
+	$currentcasefilebase =~ s#.*/##;
+	
+        $xmltestcases = XMLin("$opt_output"."$currentcasefilebase".".$$".".tmp", VarAttr => 'varname'); #slurp test case file to parse (and specify variables tag)
+
         #print Dumper($xmltestcases);  #for debug, dump hash of xml   
         #print keys %{$configfile};  #for debug, print keys from dereferenced hash
-            
         #delete the temp file as soon as we are done reading it    
-        if (-e "$dirname"."$currentcasefile".".$$".".tmp") { unlink "$dirname"."$currentcasefile".".$$".".tmp"; }        
+        if (-e "$opt_output"."$currentcasefilebase".".$$".".tmp") { unlink "$opt_output"."$currentcasefilebase".".$$".".tmp"; }        
             
             
         $repeat = $xmltestcases->{repeat};  #grab the number of times to iterate test case file
@@ -198,6 +205,17 @@
                     $testnum = $xnode; 
                 }
                  
+		# implement tag handling. Done here to stop substitutions
+		# of PARSEDRESULT from cases/paths through tests that aren't
+		# used.
+		if ( defined($config{tags}) && $config{tags} ) {
+		    # if no tag defined, ignore untagged cases
+		    next if !defined($xmltestcases->{case}->{$testnum}->{tags});
+		    # if the tag doesn't match ignore the case
+		    next if $xmltestcases->{case}->{$testnum}->{tags} !~
+			m/\b($config{tags})\b/;
+		}
+
                 $isfailure = 0;
                     
                 if ($gui == 1){
@@ -220,7 +238,7 @@
 			verifynegative verifynegative1 verifynegative2 verifynegative3
 			parseresponse parseresponse1 parseresponse2 parseresponse3 parseresponse4 parseresponse5
 			verifyresponsecode logrequest logresponse sleep errormessage
-			verifypositivenext verifynegativenext/) {
+			verifypositivenext verifynegativenext timeout tags/) {
 		  $case{$_} = $xmltestcases->{case}->{$testnum}->{$_};
 		  if ($case{$_}) { convertbackxml($case{$_}); }
 		}
@@ -321,7 +339,7 @@
                     
                 verify();  #verify result from http response
                     
-                httplog();  #write to http.log file
+                httplog($testnum);  #write to http.log file
                     
                 plotlog($latency);  #send perf data to log file for plotting
                     
@@ -362,7 +380,12 @@
                             $returnmessage = $case{errormessage}; 
                         }
                         else { 
-                            $returnmessage = "Test case number $testnum failed"; 
+                            $returnmessage = "Test case number $testnum '" .
+				(defined $case{errormessage}?
+				     $case{errormessage}:
+                                     $case{description1}) . 
+				     "' failed" .
+				(defined $case{timedout}? " exceeded timeout " . ($case{timeout}?$case{timeout}:$config{'timeout'}) . "s)":"");
                         }
                         #print "\nReturn Message : $returnmessage\n"
                     }
@@ -579,12 +602,19 @@
     
     $cookie_jar->add_cookie_header($request);
     #print $request->as_string; print "\n\n";
-        
+    # override the timeout with the case timeout.
+    $useragent->timeout("$case{timeout}") if $case{timeout};
+
     $starttimer = time();
     $response = $useragent->request($request);
     $endtimer = time();
+
+    # unconditionally restore the config timeout.
+    $useragent->timeout("$config{timeout}");
+
     $latency = (int(1000 * ($endtimer - $starttimer)) / 1000);  #elapsed time rounded to thousandths 
     #print $response->as_string; print "\n\n";
+    $case{'timedout'} = 1 if $response->status_line =~ m/^500.*timeout/;
         
     $cookie_jar->extract_cookies($response);
     #print $cookie_jar->as_string; print "\n\n";
@@ -605,10 +635,27 @@
 }
 #------------------------------------------------------------------
 sub httppost_form_urlencoded {  #send application/x-www-form-urlencoded HTTP request and read response
+    my @postbody;
         
     $request = new HTTP::Request('POST',"$case{url}");
     $request->content_type("$case{posttype}");
-    $request->content("$case{postbody}");
+    $case{postbody} =~ m~file=>(.*)~i;
+    if ( defined($1) ) {
+	open(POSTBODY, "$dirname"."$1") or InternalError("Error: Failed to open postbody file $dirname$1");  #open file handle   
+        #read the file into an array. Process each
+	# line with chomp to normalize line endings.
+	while (<POSTBODY>) {
+	    chomp;
+	    push @postbody, $_;
+	}
+	close(POSTBODY);
+    } else {
+	$postbody[0] = $case{postbody}
+    }
+        
+    $request = new HTTP::Request('POST', "$case{url}"); 
+    $request->content_type("$case{posttype}");
+    $request->content(join("\r\n", @postbody));  #load the contents of the file into the request body
     
     if ($case{addheader}) {  #add an additional HTTP Header if specified
         my @addheaders = split(/\|/, $case{addheader});  #can add multiple headers with a pipe delimiter
@@ -621,9 +668,16 @@
     
     $cookie_jar->add_cookie_header($request);
     #print $request->as_string; print "\n\n";
+    # override the timeout with the case timeout.
+    $useragent->timeout("$case{timeout}") if $case{timeout};
+
     $starttimer = time();
     $response = $useragent->request($request);
     $endtimer = time();
+
+    # unconditionally restore the config timeout.
+    $useragent->timeout("$config{timeout}");
+
     $latency = (int(1000 * ($endtimer - $starttimer)) / 1000);  #elapsed time rounded to thousandths 
     #print $response->as_string; print "\n\n";
         
@@ -635,9 +689,14 @@
     
     #read the xml file specified in the testcase
     $case{postbody} =~ m~file=>(.*)~i;
-    open(XMLBODY, "$dirname"."$1") or die "\nError: Failed to open text/xml file\n\n";  #open file handle   
-    my @xmlbody = <XMLBODY>;  #read the file into an array   
-    close(XMLBODY);
+    my @xmlbody;
+    if ( defined($1) ) {
+	open(XMLBODY, "$dirname"."$1") or InternalError("Error: Failed to open text/xml file $dirname$1");  #open file handle   
+	@xmlbody = <XMLBODY>;  #read the file into an array   
+	close(XMLBODY);
+    } else {
+	$xmlbody[0] = $case{postbody}
+    }
         
     $request = new HTTP::Request('POST', "$case{url}"); 
     $request->content_type("$case{posttype}");
@@ -654,9 +713,16 @@
     
     $cookie_jar->add_cookie_header($request);
     #print $request->as_string; print "\n\n";    
+    # override the timeout with the case timeout.
+    $useragent->timeout("$case{timeout}") if $case{timeout};
+
     $starttimer = time(); 
     $response = $useragent->request($request); 
     $endtimer = time(); 
+
+    # unconditionally restore the config timeout.
+    $useragent->timeout("$config{timeout}");
+
     $latency = (int(1000 * ($endtimer - $starttimer)) / 1000);  #elapsed time rounded to thousandths 
     #print $response->as_string; print "\n\n";    
 
@@ -713,9 +779,16 @@
         $case{addheader} = '';
     }
     
+    # override the timeout with the case timeout.
+    $useragent->timeout("$case{timeout}") if $case{timeout};
+
     $starttimer = time();
     $response = $useragent->request($request);
     $endtimer = time();
+
+    # unconditionally restore the config timeout.
+    $useragent->timeout("$config{timeout}");
+
     $latency = (int(1000 * ($endtimer - $starttimer)) / 1000);  #elapsed time rounded to thousandths 
     #print $response->as_string; print "\n\n";
         
@@ -946,11 +1019,11 @@
         
     #process the config file
     if ($opt_configfile) {  #if -c option was set on command line, use specified config file
-        open(CONFIG, "$dirname"."$opt_configfile") or die "\nERROR: Failed to open $opt_configfile file\n\n";
+        open(CONFIG, "$opt_configfile") or InternalError("ERROR: Failed to open config file $opt_configfile");
         $configexists = 1;  #flag we are going to use a config file
     }
     elsif (-e "$dirname"."config.xml") {  #if config.xml exists, read it
-        open(CONFIG, "$dirname"."config.xml") or die "\nERROR: Failed to open config.xml file\n\n";
+        open(CONFIG, "$dirname"."config.xml") or InternalError("ERROR: Failed to open config file ${dirname}config.xml file");
         $configexists = 1;  #flag we are going to use a config file
     } 
         
@@ -981,7 +1054,7 @@
         foreach (@configfile) {
                 
             if (/<testcasefile>/) {   
-                $firstparse = $';  #print "$' \n\n";
+                $firstparse = $';  #print "$' \n\n"; #'" for emacs
                 $firstparse =~ m~</testcasefile>~;
                 $filename = $`;  #string between tags will be in $filename
                 #print "\n$filename \n\n";
@@ -995,8 +1068,8 @@
                 push @casefilelist, "testcases.xml";  #if no files are specified in config.xml, default to testcases.xml
             }
             else {
-                die "\nERROR: I can't find any test case files to run.\nYou must either use a config file or pass a filename " . 
-                    "on the command line if you are not using the default testcase file (testcases.xml).";
+                InternalError("ERROR: I can't find any test case files to run.\nYou must either use a config file or pass a filename " . 
+                    "on the command line if you are not using the default testcase file (testcases.xml).");
             }
         }
     }
@@ -1027,7 +1100,7 @@
     }
         
     elsif (($#ARGV + 1) > 2) {  #too many command line args were passed
-        die "\nERROR: Too many arguments\n\n";
+        InternalError("ERROR: Too many arguments on command line");
     }
         
     #print "\ntestcase file list: @casefilelist\n\n";
@@ -1037,13 +1110,19 @@
     foreach (@configfile) {
 
         for my $config_const (qw/baseurl baseurl1 baseurl2 gnuplot proxy timeout
-                globaltimeout globalhttplog standaloneplot/) {
+                globaltimeout globalhttplog standaloneplot nagioslog postbody postbody1 postbody2 postbody3 postbody4 postbody5 tags/) {
 
             if (/<$config_const>/) {
                 $_ =~ m~<$config_const>(.*)</$config_const>~;
                 $config{$config_const} = $1;
                 #print "\n$_ : $config{$_} \n\n";
             }
+
+	    # allow override/setting from command line
+	    if ( defined($clvariables{$config_const}) ) {
+		$config{$config_const} = $clvariables{$config_const};
+	    }
+
         }
             
         if (/<reporttype>/) {   
@@ -1083,6 +1162,12 @@
     }  
         
     close(CONFIG);
+
+    # allow override/setting from command line
+    if ( defined($clvariables{'reporttype'}) ) {
+	$reporttype = $clvariables{'reporttype'};
+	$nooutput = "set";
+    }
 }
 #------------------------------------------------------------------
 sub convtestcases {  
@@ -1090,8 +1175,9 @@
     #we convert certain chars so xml parser doesn't puke.
         
     my @xmltoconvert;        
+    my $currentcasefilebase = "";
         
-    open(XMLTOCONVERT, "$dirname"."$currentcasefile") or die "\nError: Failed to open test case file\n\n";  #open file handle   
+    open(XMLTOCONVERT, "$dirname"."$currentcasefile") or InternalError("Error: Failed to open test case file $dirname$currentcasefile");  #open file handle   
     @xmltoconvert = <XMLTOCONVERT>;  #read the file into an array
         
     $casecount = 0;
@@ -1110,8 +1196,10 @@
     }  
         
     close(XMLTOCONVERT);   
-        
-    open(XMLTOCONVERT, ">$dirname"."$currentcasefile".".$$".".tmp") or die "\nERROR: Failed to open temp file for writing\n\n";  #open file handle to temp file  
+
+    $currentcasefilebase = $currentcasefile;
+    $currentcasefilebase =~ s#.*/##;
+    open(XMLTOCONVERT, ">$opt_output"."$currentcasefilebase".".$$".".tmp") or InternalError("ERROR: Failed to open temp file ${opt_output}${currentcasefilebase}.$$.tmp for writing (may need to specify output dir -o)");  #open file handle to temp file  
     print XMLTOCONVERT @xmltoconvert;  #overwrite file with converted array
     close(XMLTOCONVERT);
 }
@@ -1120,18 +1208,20 @@
                    #add a dummy testcase to fix this situation
         
     my @xmltoconvert;
+    my $currentcasefilebase = $currentcasefile;
+    $currentcasefilebase =~ s#.*/##;            
         
     if ($casecount == 1) {
             
-        open(XMLTOCONVERT, "$dirname"."$currentcasefile".".$$".".tmp") or die "\nError: Failed to open temp file\n\n";  #open file handle   
+        open(XMLTOCONVERT, "$opt_output"."$currentcasefilebase".".$$".".tmp") or InternalError("Error: Failed to open temp ${opt_output}${currentcasefilebase}.$$.tmp file");  #open file handle   
         @xmltoconvert = <XMLTOCONVERT>;  #read the file into an array
             
         for(@xmltoconvert) { 
             s/<\/testcases>/<case id="2" description1="dummy test case"\/><\/testcases>/g;  #add dummy test case to end of file   
         }       
         close(XMLTOCONVERT);
-            
-        open(XMLTOCONVERT, ">$dirname"."$currentcasefile".".$$".".tmp") or die "\nERROR: Failed to open temp file for writing\n\n";  #open file handle   
+
+        open(XMLTOCONVERT, ">$opt_output"."$currentcasefilebase".".$$".".tmp") or InternalError("ERROR: Failed to open temp file ${opt_output}${currentcasefilebase}.$$.tmp for writing");  #open file handle   
         print XMLTOCONVERT @xmltoconvert;  #overwrite file with converted array
         close(XMLTOCONVERT);
     }
@@ -1145,6 +1235,12 @@
     $_[0] =~ s~{BASEURL}~$config{baseurl}~g;
     $_[0] =~ s~{BASEURL1}~$config{baseurl1}~g;
     $_[0] =~ s~{BASEURL2}~$config{baseurl2}~g;
+    $_[0] =~ s~{POSTBODY}~$config{postbody}~g;
+    $_[0] =~ s~{POSTBODY1}~$config{postbody1}~g;
+    $_[0] =~ s~{POSTBODY2}~$config{postbody2}~g;
+    $_[0] =~ s~{POSTBODY3}~$config{postbody3}~g;
+    $_[0] =~ s~{POSTBODY4}~$config{postbody4}~g;
+    $_[0] =~ s~{POSTBODY5}~$config{postbody5}~g;
     $_[0] =~ s~{PARSEDRESULT}~$parsedresult{parseresponse}~g; 
     $_[0] =~ s~{PARSEDRESULT1}~$parsedresult{parseresponse1}~g; 
     $_[0] =~ s~{PARSEDRESULT2}~$parsedresult{parseresponse2}~g; 
@@ -1158,39 +1254,67 @@
         
     my @a = @_;  #make a copy of the arguments
         
-    map { s/[^-\w.,!~'()\/ ]/sprintf "%%%02x", ord $&/eg } @a;
+    map { s/[^-\w.,!~'()\/ ]/sprintf "%%%02x", ord $&/eg } @a; #' for emacs
     return wantarray ? @a : $a[0];
 }
 #------------------------------------------------------------------
 sub httplog {  #write requests and responses to http.log file
-        
-    unless ($reporttype) {  #we suppress most logging when running in a plugin mode
-        
-        if ($case{logrequest} && ($case{logrequest} =~ /yes/i)) {  #http request - log setting per test case
-            print HTTPLOGFILE $request->as_string, "\n\n";
+
+    my $loganyway = 0;
+    if (defined($config{nagioslog}) && ($reporttype && $reporttype eq "nagios") ) {
+	$loganyway = 1 if ($config{nagioslog} =~ /onfail/i) &&
+	    ($isfailure > 0);
+	$loganyway = 1 if $config{nagioslog} =~ /yes/i;
+    }
+
+    if ( ! $reporttype || $loganyway ) {  #we suppress most logging when running in a plugin mode
+	if (! defined( $httplogfh )) {
+	    if ( defined($config{nagioslog}) && ($reporttype && $reporttype eq "nagios") ) {
+		# append to log file for nagioslog because 
+		# another run may have occurred between the time of
+		# the failing run and somebody can look at the log.
+		open($httplogfh, ">>$opt_output"."http.log") or InternalError("ERROR: Failed to open ${opt_output}http.log file");
+	    } else {
+		open($httplogfh, ">$opt_output"."http.log") or InternalError("ERROR: Failed to open ${opt_output}http.log file");
+	    }
+	}
+
+        if (($case{logrequest} && ($case{logrequest} =~ /yes/i)) or
+            ($case{logresponse} && ($case{logresponse} =~ /yes/i)) or
+            ($config{globalhttplog} && ($config{globalhttplog} =~ /yes/i)) or
+            (($config{globalhttplog} && ($config{globalhttplog} =~ /onfail/i)) && ($isfailure > 0))
+           ) {     
+	    my $time=localtime();
+	    my $type=($isfailure > 0 ? "failed": "passed");
+	    print $httplogfh "\n**** LOG SEPARATOR STARTING $type TEST id=$_[0] at $time ****\n\n\n";
+        }
+
+        if ($case{logrequest} && (($case{logrequest} =~ /yes/i) ||
+				  ($case{logrequest} =~ /onfail/i &&
+				      ($isfailure > 0))
+				   )) {  #http request - log setting per test case
+            print $httplogfh $request->as_string, "\n\n";
         } 
             
-        if ($case{logresponse} && ($case{logresponse} =~ /yes/i)) {  #http response - log setting per test case
-            print HTTPLOGFILE $response->as_string, "\n\n";
+        if ($case{logresponse} && (($case{logresponse} =~ /yes/i) ||
+				  ($case{logresponse} =~ /onfail/i &&
+				      ($isfailure > 0))
+				   )) {  #http response - log setting per test case
+            print $httplogfh $response->as_string, "\n\n";
         }
             
         if ($config{globalhttplog} && ($config{globalhttplog} =~ /yes/i)) {  #global http log setting
-            print HTTPLOGFILE $request->as_string, "\n\n";
-            print HTTPLOGFILE $response->as_string, "\n\n";
+            print $httplogfh $request->as_string, "\n\n";
+            my $response = $response->as_string;
+	    print $httplogfh ($response ? $response : "(error: no response from server)"), "\n\n";
         }
             
         if (($config{globalhttplog} && ($config{globalhttplog} =~ /onfail/i)) && ($isfailure > 0)) { #global http log setting - onfail mode
-            print HTTPLOGFILE $request->as_string, "\n\n";
-            print HTTPLOGFILE $response->as_string, "\n\n";
+            print $httplogfh $request->as_string, "\n\n";
+            my $response = $response->as_string;
+	    print $httplogfh ($response ? $response : "(error: no response from server)"), "\n\n";
         }
             
-        if (($case{logrequest} && ($case{logrequest} =~ /yes/i)) or
-            ($case{logresponse} && ($case{logresponse} =~ /yes/i)) or
-            ($config{globalhttplog} && ($config{globalhttplog} =~ /yes/i)) or
-            (($config{globalhttplog} && ($config{globalhttplog} =~ /onfail/i)) && ($isfailure > 0))
-           ) {     
-                print HTTPLOGFILE "\n************************* LOG SEPARATOR *************************\n\n\n";
-        }
     }
 }
 #------------------------------------------------------------------
@@ -1212,11 +1336,11 @@
         $time = "$months{$mon} $mday $hours $min $sec $year";
             
         if ($plotclear eq 'yes') {  #used to clear the graph when requested
-            open(PLOTLOG, ">$dirname"."plot.log") or die "ERROR: Failed to open file plot.log\n";  #open in clobber mode so log gets truncated
+            open(PLOTLOG, ">$opt_output"."plot.log") or InternalError("ERROR: Failed to open file ${opt_output}plot.log");  #open in clobber mode so log gets truncated
             $plotclear = 'no';  #reset the value 
         }
         else {
-            open(PLOTLOG, ">>$dirname"."plot.log") or die "ERROR: Failed to open file plot.log\n";  #open in append mode
+            open(PLOTLOG, ">>$opt_output"."plot.log") or InternalError("ERROR: Failed to open file ${opt_output}plot.log");  #open in append mode
         }
           
         printf PLOTLOG "%s %2.4f\n", $time, $value;
@@ -1229,7 +1353,7 @@
     #do this unless: monitor is disabled in gui, or running standalone mode without config setting to turn on plotting     
     unless ((($gui == 1) and ($monitorenabledchkbx eq 'monitor_off')) or (($gui == 0) and ($config{standaloneplot} ne 'on'))) {  
         
-        open(GNUPLOTPLT, ">$dirname"."plot.plt") || die "Could not open file\n";
+        open(GNUPLOTPLT, ">$opt_output"."plot.plt") || InternalError("Could not open gnuplot command file ${opt_output}plot.plt");
         print GNUPLOTPLT qq|
 set term png 
 set output \"plot.png\"
@@ -1266,7 +1390,7 @@
     
     unless ($reporttype) {  #we suppress most logging when running in a plugin mode
         #these handles shouldn't be open    
-        close(HTTPLOGFILE);
+        close($httplogfh) if defined $httplogfh;
         close(RESULTS);
         close(RESULTSXML);
     }    
@@ -1283,18 +1407,20 @@
                             'WARNING' , 1,
                             'CRITICAL', 2,);
 
-	    my $end = defined $config{globaltimeout} ? "$config{globaltimeout};;0" : ";;0";
+	    my $perf = defined $config{globaltimeout} ? "time=${totalruntime}s;$config{globaltimeout};;0 count=$totalruncount" : "time=${totalruntime}s;;;0 count=$totalruncount";
 
-            if ($casefailedcount > 0) {
-	        print "WebInject CRITICAL - $returnmessage |time=$totalruntime;$end\n";
+            if ($casefailedcount > 0 &&
+		$returnmessage !~ m/exceeded timeout/ ) {
+	        print "WebInject CRITICAL - $returnmessage |$perf\n";
                 exit $exit_codes{'CRITICAL'};
             }
-            elsif (($config{globaltimeout}) && ($totalruntime > $config{globaltimeout})) { 
-                print "WebInject WARNING - All tests passed successfully but global timeout ($config{globaltimeout} seconds) has been reached |time=$totalruntime;$end\n";
+            elsif (($config{globaltimeout}) && ($totalruntime > $config{globaltimeout}) || $casefailedcount ) { 
+                print "WebInject WARNING - All $totalruncount tests passed successfully but global timeout ($config{globaltimeout} seconds) has been reached |$perf\n" if ! $casefailedcount ;
+                print "WebInject WARNING - $returnmessage |$perf\n" if $casefailedcount ;
                 exit $exit_codes{'WARNING'};
             }
             else {
-                print "WebInject OK - All tests passed successfully in $totalruntime seconds |time=$totalruntime;$end\n";
+                print "WebInject OK - All $totalruncount tests passed successfully in $totalruntime seconds |$perf\n";
                 exit $exit_codes{'OK'};
             }
         }
@@ -1315,14 +1441,14 @@
         # <reporttype>external:/home/webinject/Plugin.pm</reporttype>
         elsif ($reporttype =~ /^external:(.*)/) { 
             unless (my $return = do $1) {
-                die "couldn't parse $1: $@\n" if $@;
-                die "couldn't do $1: $!\n" unless defined $return;
-                die "couldn't run $1\n" unless $return;
+                InternalError("couldn't parse plugin $1: $@") if $@;
+                InternalError("couldn't do $1: $!") unless defined $return;
+                InternalError("couldn't run $1") unless $return;
             }
         }
 
         else {
-            print STDERR "\nError: only 'nagios', 'mrtg', 'external', or 'standard' are supported reporttype values\n\n";
+            InternalError("Error: only 'nagios', 'mrtg', 'external', or 'standard' are supported reporttype values", 1);
         }
             
     }
@@ -1361,28 +1487,32 @@
 }
 #------------------------------------------------------------------
 sub getdirname {  #get the directory webinject engine is running from
-        
-    $dirname = $0;    
+    my $path = $_[0] || $0;
+    $dirname = $path;
     $dirname =~ s~(.*/).*~$1~;  #for nix systems
     $dirname =~ s~(.*\\).*~$1~; #for windoz systems   
-    if ($dirname eq $0) { 
+    if ($dirname eq $path) { 
         $dirname = './'; 
     }
 }    
 #------------------------------------------------------------------
 sub getoptions {  #command line options
-        
+    my $help=0;
     Getopt::Long::Configure('bundling');
     GetOptions(
         'v|V|version'   => \$opt_version,
         'c|config=s'    => \$opt_configfile,
         'o|output=s'    => \$opt_output,
         'n|no-output'   => \$nooutput,
+	'D|define=s'    => \%clvariables,
+	'h|help'        => \$help,
         ) 
         or do {
             print_usage();
             exit();
         };
+    print_usage(), exit() if $help;
+
     if ($opt_version) {
 	print "WebInject version $version\nFor more info: http://www.webinject.org\n";
   	exit();
@@ -1390,9 +1520,23 @@
     sub print_usage {
         print <<EOB
     Usage:
-      webinject.pl [-c|--config config_file] [-o|--output output_location] [-n|--no-output] [testcase_file [XPath]]
-      webinject.pl --version|-v
+      $0 [-c|--config config_file] [-o|--output output_location] [-n|--no-output] [-D|--define cfgfile_param=value] [-h|--help] [testcase_file [XPath]]
+      $0 --version|-v
 EOB
     }
 }
 #------------------------------------------------------------------
+
+sub InternalError {
+    my ($error, $continue) = @_;
+
+    if ( $reporttype eq 'nagios' ) {
+	print STDOUT "$error: in $currentcasefile called from $opt_configfile\n";
+	exit 3;
+    }
+    if ($continue) {
+	print STDERR "\n$error\n\n";
+    } else {
+	die("\n$error\n\n");
+    }
+}
