File Coverage

blib/lib/Webinject.pm
Criterion Covered Total %
statement 39 41 95.1
branch n/a
condition n/a
subroutine 14 14 100.0
pod n/a
total 53 55 96.3


\n" };
line stmt bran cond sub pod time code
1             package Webinject;
2              
3             # Copyright 2010-2012 Sven Nierlein (nierlein@cpan.org)
4             # Copyright 2004-2006 Corey Goldberg (corey@goldb.org)
5             #
6             # This file is part of WebInject.
7             #
8             # WebInject is free software; you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation; either version 2 of the License, or
11             # (at your option) any later version.
12             #
13             # WebInject is distributed in the hope that it will be useful,
14             # but without any warranty; without even the implied warranty of
15             # merchantability or fitness for a particular purpose. See the
16             # GNU General Public License for more details.
17              
18 1     1   672 use 5.006;
  1         1  
19 1     1   3 use strict;
  1         1  
  1         15  
20 1     1   8 use warnings;
  1         1  
  1         20  
21 1     1   2 use Carp;
  1         1  
  1         48  
22 1     1   464 use LWP;
  1         29189  
  1         27  
23 1     1   426 use HTML::Entities;
  1         3580  
  1         52  
24 1     1   4 use URI;
  1         1  
  1         17  
25 1     1   401 use HTTP::Request::Common;
  1         1515  
  1         46  
26 1     1   401 use HTTP::Cookies;
  1         4674  
  1         22  
27 1     1   676 use XML::Simple;
  1         5938  
  1         5  
28 1     1   474 use Time::HiRes 'time', 'sleep';
  1         974  
  1         4  
29 1     1   755 use Getopt::Long;
  1         7094  
  1         5  
30 1     1   506 use Crypt::SSLeay; # for SSL/HTTPS (you may comment this out if you don't need it)
  1         1759  
  1         26  
31 1     1   177 use XML::Parser; # for web services verification (you may comment this out if aren't doing XML verifications for web services)
  0            
  0            
32             use Error qw(:try); # for web services verification (you may comment this out if aren't doing XML verifications for web services)
33             use Data::Dumper; # dump hashes for debugging
34             use File::Temp qw/ tempfile /; # create temp files
35              
36             our $VERSION = '1.92';
37              
38             =head1 NAME
39              
40             Webinject - Perl Module for testing web services
41              
42             =head1 SYNOPSIS
43              
44             use Webinject;
45             my $webinject = Webinject->new();
46             $webinject->engine();
47              
48             =head1 DESCRIPTION
49              
50             WebInject is a free tool for automated testing of web applications and web
51             services. It can be used to test individual system components that have HTTP
52             interfaces (JSP, ASP, CGI, PHP, AJAX, Servlets, HTML Forms, XML/SOAP Web
53             Services, REST, etc), and can be used as a test harness to create a suite of
54             [HTTP level] automated functional, acceptance, and regression tests. A test
55             harness allows you to run many test cases and collect/report your results.
56             WebInject offers real-time results display and may also be used for monitoring
57             system response times.
58              
59             =head1 CONSTRUCTOR
60              
61             =head2 new ( [ARGS] )
62              
63             Creates an C object.
64              
65             =over 4
66              
67             =item reporttype
68              
69             possible values are 'standard', 'nagios', 'nagios2', 'mrtg' or 'external:'
70              
71             =item nooutput
72              
73             suppress all output to STDOUT, create only logilfes
74              
75             =item break_on_errors
76              
77             stop after the first testcase fails, otherwise Webinject would go on and
78             execute all tests regardless of the previous case.
79              
80             =item timeout
81              
82             Default timeout is 180seconds. Timeout starts again for every testcase.
83              
84             =item useragent
85              
86             Set the useragent used in HTTP requests. Default is 'Webinject'.
87              
88             =item max_redirect
89              
90             Set maximum number of HTTP redirects. Default is 0.
91              
92             =item proxy
93              
94             Sets a proxy which is then used for http and https requests.
95              
96             ex.: http://proxy.company.net:3128
97              
98             with authentication:
99              
100             ex.: http://user:password@proxy.company.net:3128
101              
102             =item output_dir
103              
104             Output directory where all logfiles will go to. Defaults to current directory.
105              
106             =item globalhttplog
107              
108             Can be 'yes' or 'onfail'. Will log the http request and response to a http.log file.
109              
110             =item httpauth
111              
112             Provides credentials for webserver authentications. The format is:
113              
114             ['servername', 'portnumber', 'realm-name', 'username', 'password']
115              
116             =item baseurl
117              
118             the value can be used as {BASEURL} in the test cases
119              
120             =item baseurl1
121              
122             the value can be used as {BASEURL1} in the test cases
123              
124             =item baseurl2
125              
126             the value can be used as {BASEURL2} in the test cases
127              
128             =item standaloneplot
129              
130             can be "on" or "off". Default is off.
131             Create gnuplot graphs when enabled.
132              
133             =item graphtype
134              
135             Defaults to 'lines'
136              
137             =item gnuplot
138              
139             Defines the path to your gnuplot binary.
140              
141             =back
142              
143             =cut
144              
145             sub new {
146             my $class = shift;
147             my (%options) = @_;
148             $| = 1; # don't buffer output to STDOUT
149              
150             my $self = {};
151             bless $self, $class;
152              
153             # set default config options
154             $self->_set_defaults();
155              
156             for my $opt_key ( keys %options ) {
157             if( exists $self->{'config'}->{$opt_key} ) {
158             if($opt_key eq 'httpauth') {
159             $self->_set_http_auth($options{$opt_key});
160             } else {
161             $self->{'config'}->{$opt_key} = $options{$opt_key};
162             }
163             }
164             else {
165             $self->_usage("ERROR: unknown option: ".$opt_key);
166             }
167             }
168              
169             # get command line options
170             $self->_getoptions();
171              
172             return $self;
173             }
174              
175             ########################################
176              
177             =head1 METHODS
178              
179             =head2 engine
180              
181             start the engine of webinject
182              
183             =cut
184              
185             sub engine {
186             #wrap the whole engine in a subroutine so it can be integrated with the gui
187             my $self = shift;
188              
189             if($self->{'gui'}) {
190             $self->_gui_initial();
191             }
192             else {
193             # delete files leftover from previous run (do this here so they are whacked each run)
194             $self->_whackoldfiles();
195             }
196              
197             $self->_processcasefile();
198              
199             # write opening tags for STDOUT.
200             $self->_writeinitialstdout();
201              
202             # create the gnuplot config file
203             $self->_plotcfg();
204              
205             # timer for entire test run
206             my $startruntimer = time();
207              
208             # process test case files named in config
209             for my $currentcasefile ( @{ $self->{'casefilelist'} } ) {
210             #print "\n$currentcasefile\n\n";
211              
212             my $resultfile = {
213             'name' => $currentcasefile,
214             'cases' => [],
215             };
216              
217             if($self->{'gui'}) { $self->_gui_processing_msg($currentcasefile); }
218              
219             my $tempfile = $self->_convtestcases($currentcasefile);
220              
221             my $xmltestcases;
222             eval {
223             $xmltestcases = XMLin( $tempfile,
224             varattr => 'varname',
225             variables => $self->{'config'} ); # slurp test case file to parse (and specify variables tag)
226             };
227             if($@) {
228             my $error = $@;
229             $error =~ s/^\s*//mx;
230             $self->_usage("ERROR: reading xml test case ".$currentcasefile." failed: ".$error);
231             }
232              
233             unless( defined $xmltestcases->{case} ) {
234             $self->_usage("ERROR: no test cases defined!");
235             }
236              
237             # fix case if there is only one case
238             if( defined $xmltestcases->{'case'}->{'id'} ) {
239             my $tmpcase = $xmltestcases->{'case'};
240             $xmltestcases->{'case'} = { $tmpcase->{'id'} => $tmpcase };
241             }
242              
243             #delete the temp file as soon as we are done reading it
244             if ( -e $tempfile ) { unlink $tempfile; }
245              
246             my $repeat = 1;
247             if(defined $xmltestcases->{repeat} and $xmltestcases->{repeat} > 0) {
248             $repeat = $xmltestcases->{repeat};
249             }
250              
251             my $useragent = $self->_get_useragent($xmltestcases->{case});
252              
253             for my $run_nr (1 .. $repeat) {
254              
255             # process cases in sorted order
256             for my $testnum ( sort { $a <=> $b } keys %{ $xmltestcases->{case} } ) {
257              
258             # if an XPath Node is defined, only process the single Node
259             if( $self->{'xnode'} ) {
260             $testnum = $self->{'xnode'};
261             }
262              
263             # create testcase
264             my $case = { 'id' => $testnum };
265              
266             # populate variables with values from testcase file, do substitutions, and revert converted values back
267             for my $key (keys %{$xmltestcases->{'case'}->{$testnum}}) {
268             $case->{$key} = $xmltestcases->{'case'}->{$testnum}->{$key};
269             }
270              
271             my $label = '';
272             if(defined $case->{'label'}) {
273             $label = $case->{'label'}." - ";
274             }
275             $self->_out(qq|Test: $label$currentcasefile - $testnum \n|);
276              
277             $case = $self->_run_test_case($case, $useragent);
278              
279             push @{$resultfile->{'cases'}}, $case;
280              
281             # break from sub if user presses stop button in gui
282             if( $self->{'switches'}->{'stop'} eq 'yes' ) {
283             my $rc = $self->_finaltasks();
284             $self->{'switches'}->{'stop'} = 'no';
285             return $rc; # break from sub
286             }
287              
288             # break here if the last result was an error
289             if($self->{'config'}->{'break_on_errors'} and $self->{'result'}->{'iscritical'}) {
290             last;
291             }
292              
293             # if an XPath Node is defined, only process the single Node
294             if( $self->{'xnode'} ) {
295             last;
296             }
297             }
298             }
299              
300             push @{$self->{'result'}->{'files'}}, $resultfile;
301             }
302              
303             my $endruntimer = time();
304             $self->{'result'}->{'totalruntime'} = ( int( 1000 * ( $endruntimer - $startruntimer ) ) / 1000 ); #elapsed time rounded to thousandths
305              
306             # do return/cleanup tasks
307             return $self->_finaltasks();
308             }
309              
310             ################################################################################
311             # runs a single test case
312             sub _run_test_case {
313             my($self,$case,$useragent) =@_;
314              
315             confess("no testcase!") unless defined $case;
316              
317             # set some defaults
318             $case->{'id'} = 1 unless defined $case->{'id'};
319             $case->{'passedcount'} = 0;
320             $case->{'failedcount'} = 0;
321             $case->{'iswarning'} = 0;
322             $case->{'iscritical'} = 0;
323             $case->{'messages'} = [];
324              
325             $useragent = $self->_get_useragent({1 => $case}) unless defined $useragent;
326              
327             # don't do this if monitor is disabled in gui
328             if($self->{'gui'} and $self->{'monitorenabledchkbx'} ne 'monitor_off') {
329             my $curgraphtype = $self->{'config'}->{'graphtype'};
330             }
331              
332             # used to replace parsed {timestamp} with real timestamp value
333             my $timestamp = time();
334              
335             for my $key (keys %{$case}) {
336             $case->{$key} = $self->_convertbackxml($case->{$key}, $timestamp);
337             next if $key eq 'errormessage';
338             $case->{$key} = $self->_convertbackxmlresult($case->{$key});
339             }
340              
341             # replace host with realserverip in url and add http host header to useragent
342             if($self->{'config'}->{'realserverip'})
343             {
344             my($uri)=URI->new($case->{url});
345             my($host)=$uri->host();
346             $useragent->default_header('Host' => $uri->host());
347             $case->{url}=~s/\Q$host\E/$self->{'config'}->{'realserverip'}/mx;
348             }
349              
350             if( $self->{'gui'} ) { $self->_gui_tc_descript($case); }
351              
352             push @{$case->{'messages'}}, { 'html' => "" }; # HTML: open table column
353             for(qw/description1 description2/) {
354             next unless defined $case->{$_};
355             $self->_out(qq|Desc: $case->{$_}\n|);
356             push @{$case->{'messages'}}, {'key' => $_, 'value' => $case->{$_}, 'html' => "$case->{$_}
" };
357             }
358             my $method;
359             if (defined $case->{method}) {
360             $method = uc($case->{method});
361             } else {
362             $method = "GET";
363             }
364             push @{$case->{'messages'}}, { 'html' => qq|$method $case->{url}
\n| };
365              
366             push @{$case->{'messages'}}, { 'html' => "" }; # HTML: next column
367              
368             my($latency,$request,$response);
369             alarm($self->{'config'}->{'timeout'}+1); # timeout should be handled by LWP, but just in case...
370             eval {
371             local $SIG{ALRM} = sub { die("alarm") };
372             if($case->{method}){
373             if(lc $case->{method} eq "get") {
374             ($latency,$request,$response) = $self->_httpget($useragent, $case);
375             }
376             elsif(lc $case->{method} eq "post") {
377             ($latency,$request,$response) = $self->_httppost($useragent, $case);
378             }
379             else {
380             $self->_usage('ERROR: bad HTTP Request Method Type, you must use "get" or "post"');
381             }
382             }
383             else {
384             ($latency,$request,$response) = $self->_httpget($useragent, $case); # use "get" if no method is specified
385             }
386             };
387             alarm(0);
388             if($@) {
389             $case->{'iscritical'} = 1;
390             } else {
391             $case->{'latency'} = $latency;
392             $case->{'request'} = $request->as_string();
393             $case->{'response'} = $response->as_string();
394              
395             # verify result from http response
396             $self->_verify($response, $case);
397              
398             if($case->{verifypositivenext}) {
399             $self->{'verifylater'} = $case->{'verifypositivenext'};
400             $self->_out("Verify On Next Case: '".$case->{verifypositivenext}."' \n");
401             push @{$case->{'messages'}}, {'key' => 'verifypositivenext', 'value' => $case->{verifypositivenext}, 'html' => "Verify On Next Case: ".$case->{verifypositivenext}."
" };
402             }
403              
404             if($case->{verifynegativenext}) {
405             $self->{'verifylaterneg'} = $case->{'verifynegativenext'};
406             $self->_out("Verify Negative On Next Case: '".$case->{verifynegativenext}."' \n");
407             push @{$case->{'messages'}}, {'key' => 'verifynegativenext', 'value' => $case->{verifynegativenext}, 'html' => "Verify Negative On Next Case: ".$case->{verifynegativenext}."
" };
408             }
409              
410             # write to http.log file
411             $self->_httplog($request, $response, $case);
412              
413             # send perf data to log file for plotting
414             $self->_plotlog($latency);
415              
416             # call the external plotter to create a graph
417             $self->_plotit();
418              
419             if( $self->{'gui'} ) {
420             $self->_gui_updatemontab(); # update monitor with the newly rendered plot graph
421             }
422              
423             $self->_parseresponse($response, $case); # grab string from response to send later
424              
425             # make parsed results available in the errormessage
426             for my $key (keys %{$case}) {
427             next unless $key eq 'errormessage';
428             $case->{$key} = $self->_convertbackxmlresult($case->{$key});
429             }
430             }
431              
432             push @{$case->{'messages'}}, { 'html' => "\n" }; # HTML: next column
433             # if any verification fails, test case is considered a failure
434             if($case->{'iscritical'}) {
435             # end result will be also critical
436             $self->{'result'}->{'iscritical'} = 1;
437              
438             push @{$case->{'messages'}}, {'key' => 'success', 'value' => 'false' };
439             if( $self->{'result'}->{'returnmessage'} ) { # Add returnmessage to the output
440             my $prefix = "case #".$case->{'id'}.": ";
441             if(defined $case->{'label'}) {
442             $prefix = $case->{'label'}." (case #".$case->{'id'}."): ";
443             }
444             $self->{'result'}->{'returnmessage'} = $prefix.$self->{'result'}->{'returnmessage'};
445             my $message = $self->{'result'}->{'returnmessage'};
446             $message = $message.' - '.$case->{errormessage} if defined $case->{errormessage};
447             push @{$case->{'messages'}}, {
448             'key' => 'result-message',
449             'value' => $message,
450             'html' => "FAILED : ".$message.""
451             };
452             $self->_out("TEST CASE FAILED : ".$message."\n");
453             }
454             # print regular error output
455             elsif ( $case->{errormessage} ) { # Add defined error message to the output
456             push @{$case->{'messages'}}, {
457             'key' => 'result-message',
458             'value' => $case->{errormessage},
459             'html' => "FAILED : ".$case->{errormessage}.""
460             };
461             $self->_out(qq|TEST CASE FAILED : $case->{errormessage}\n|);
462             }
463             else {
464             push @{$case->{'messages'}}, {
465             'key' => 'result-message',
466             'value' => 'TEST CASE FAILED',
467             'html' => "FAILED"
468             };
469             $self->_out(qq|TEST CASE FAILED\n|);
470             }
471             unless( $self->{'result'}->{'returnmessage'} ) { #(used for plugin compatibility) if it's the first error message, set it to variable
472             if( $case->{errormessage} ) {
473             $self->{'result'}->{'returnmessage'} = $case->{errormessage};
474             }
475             else {
476             $self->{'result'}->{'returnmessage'} = "Test case number ".$case->{'id'}." failed";
477             if(defined $case->{'label'}) {
478             $self->{'result'}->{'returnmessage'} = "Test case ".$case->{'label'}." (#".$case->{'id'}.") failed";
479             }
480             }
481             }
482             if( $self->{'gui'} ) {
483             $self->_gui_status_failed();
484             }
485             }
486             elsif($case->{'iswarning'}) {
487             # end result will be also warning
488             $self->{'result'}->{'iswarning'} = 1;
489              
490             push @{$case->{'messages'}}, {'key' => 'success', 'value' => 'false' };
491             if( $case->{errormessage} ) { # Add defined error message to the output
492             push @{$case->{'messages'}}, {'key' => 'result-message', 'value' => $case->{errormessage}, 'html' => "WARNED : ".$case->{errormessage}."" };
493             $self->_out(qq|TEST CASE WARNED : $case->{errormessage}\n|);
494             }
495             # print regular error output
496             else {
497             # we suppress most logging when running in a plugin mode
498             push @{$case->{'messages'}}, {'key' => 'result-message', 'value' => 'TEST CASE WARNED', 'html' => "WARNED" };
499             $self->_out(qq|TEST CASE WARNED\n|);
500             }
501             unless( $self->{'result'}->{'returnmessage'} ) { #(used for plugin compatibility) if it's the first error message, set it to variable
502             if( $case->{errormessage} ) {
503             $self->{'result'}->{'returnmessage'} = $case->{errormessage};
504             }
505             else {
506             $self->{'result'}->{'returnmessage'} = "Test case number ".$case->{'id'}." warned";
507             if(defined $case->{'label'}) {
508             $self->{'result'}->{'returnmessage'} = "Test case ".$case->{'label'}." (#".$case->{'id'}.") warned";
509             }
510             }
511              
512             }
513             if( $self->{'gui'} ) {
514             $self->_gui_status_failed();
515             }
516             }
517             else {
518             $self->_out(qq|TEST CASE PASSED\n|);
519             push @{$case->{'messages'}}, {'key' => 'success', 'value' => 'true' };
520             push @{$case->{'messages'}}, {
521             'key' => 'result-message',
522             'value' => 'TEST CASE PASSED',
523             'html' => "PASSED"
524             };
525             if( $self->{'gui'} ) {
526             $self->_gui_status_passed();
527             }
528             }
529              
530             if( $self->{'gui'} ) { $self->_gui_timer_output($latency); }
531              
532             $self->_out(qq|Response Time = $latency sec \n|);
533             $self->_out(qq|------------------------------------------------------- \n|);
534             push @{$case->{'messages'}}, {
535             'key' => 'responsetime',
536             'value' => $latency,
537             'html' => "
".$latency." sec
538              
539             $self->{'result'}->{'runcount'}++;
540             $self->{'result'}->{'totalruncount'}++;
541              
542             if( $self->{'gui'} ) {
543             # update the statusbar
544             $self->_gui_statusbar();
545             }
546              
547             if( $latency > $self->{'result'}->{'maxresponse'} ) {
548             # set max response time
549             $self->{'result'}->{'maxresponse'} = $latency;
550             }
551             if(!defined $self->{'result'}->{'minresponse'} or $latency < $self->{'result'}->{'minresponse'} ) {
552             # set min response time
553             $self->{'result'}->{'minresponse'} = $latency;
554             }
555             # keep total of response times for calculating avg
556             $self->{'result'}->{'totalresponse'} = ( $self->{'result'}->{'totalresponse'} + $latency );
557             # avg response rounded to thousands
558             $self->{'result'}->{'avgresponse'} = ( int( 1000 * ( $self->{'result'}->{'totalresponse'} / $self->{'result'}->{'totalruncount'} ) ) / 1000 );
559              
560             if( $self->{'gui'} ) {
561             # update timers and counts in monitor tab
562             $self->_gui_updatemonstats();
563             }
564              
565              
566             # if a sleep value is set in the test case, sleep that amount
567             if( $case->{sleep} ) {
568             sleep( $case->{sleep} );
569             }
570              
571             $self->{'result'}->{'totalpassedcount'} += $case->{'passedcount'};
572             $self->{'result'}->{'totalfailedcount'} += $case->{'failedcount'};
573              
574             if($case->{'iscritical'} or $case->{'iswarning'}) {
575             $self->{'result'}->{'totalcasesfailedcount'}++;
576             } else {
577             $self->{'result'}->{'totalcasespassedcount'}++;
578             }
579              
580             return $case;
581             }
582              
583             ################################################################################
584             sub _get_useragent {
585             my($self, $testcases) = @_;
586              
587             # keepalive is required for ntml authentication but breaks
588             # https proxy support, so try determince which one we need
589             my $keepalive = 1;
590             if($testcases and $self->{'config'}->{'proxy'}) {
591             for my $nr (keys %{$testcases}) {
592             if($testcases->{$nr}->{'url'} =~ m/^https/gmx) {
593             $keepalive = 0;
594             }
595             }
596             }
597             my $useragent = LWP::UserAgent->new(keep_alive=>$keepalive);
598              
599             # store cookies in our LWP object
600             my($fh, $cookietempfilename) = tempfile();
601             unlink ($cookietempfilename);
602             $useragent->cookie_jar(HTTP::Cookies->new(
603             file => $cookietempfilename,
604             autosave => 1,
605             ));
606             push @{$self->{'tmpfiles'}}, $cookietempfilename;
607              
608             # http useragent that will show up in webserver logs
609             unless(defined $self->{'config'}->{'useragent'}) {
610             $useragent->agent('WebInject');
611             } else {
612             $useragent->agent($self->{'config'}->{'useragent'});
613             }
614              
615             # add proxy support if it is set in config.xml
616             if( $self->{'config'}->{'proxy'} ) {
617             # try IO::Socket::SSL first
618             eval {
619             require IO::Socket::SSL;
620             IO::Socket::SSL->import();
621             };
622             my $proxy = $self->{'config'}->{'proxy'};
623             $proxy =~ s/^http(s|):\/\///mx;
624             # http just works
625             $useragent->proxy('http', 'http://'.$proxy);
626             # authentication?
627             my $proxyuser = '';
628             my $proxypass = '';
629             if($proxy =~ s/^(.*?):(.*?)@(.*)$/$3/gmx) {
630             $proxyuser = $1;
631             $proxypass = $2;
632             }
633             # ssl depends on which class we have
634             if($INC{'IO/Socket/SSL.pm'}) {
635             $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "IO::Socket::SSL";
636             if($proxypass) {
637             $proxy = $proxyuser.':'.$proxypass.'@'.$proxy;
638             }
639             my $con_proxy = 'connect://'.$proxy;
640             $useragent->proxy('https', $con_proxy);
641             } else {
642             # ssl proxy only works this way, see http://community.activestate.com/forum-topic/lwp-https-requests-proxy
643             $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = "Net::SSL";
644             $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
645             $ENV{HTTPS_PROXY} = $proxy;
646             $ENV{HTTPS_PROXY_USERNAME} = $proxyuser;
647             $ENV{HTTPS_PROXY_PASSWORD} = $proxypass;
648             # env proxy breaks the ssl proxy above
649             #$useragent->env_proxy();
650             }
651             }
652              
653             # don't follow redirects unless set by config
654             push @{$useragent->requests_redirectable}, 'POST';
655             $useragent->max_redirect($self->{'config'}->{'max_redirect'});
656              
657             # add http basic authentication support
658             # corresponds to:
659             # $useragent->credentials('servername:portnumber', 'realm-name', 'username' => 'password');
660             if(scalar @{$self->{'config'}->{'httpauth'}}) {
661             # add the credentials to the user agent here. The foreach gives the reference to the tuple ($elem), and we
662             # deref $elem to get the array elements.
663             for my $elem ( @{ $self->{'config'}->{'httpauth'} } ) {
664             #print "adding credential: $elem->[0]:$elem->[1], $elem->[2], $elem->[3] => $elem->[4]\n";
665             $useragent->credentials( $elem->[0].":".$elem->[1], $elem->[2], $elem->[3] => $elem->[4] );
666             }
667             }
668              
669             # change response delay timeout in seconds if it is set in config.xml
670             if($self->{'config'}->{'timeout'}) {
671             $useragent->timeout($self->{'config'}->{'timeout'}); # default LWP timeout is 180 secs.
672             }
673              
674             return $useragent;
675             }
676              
677             ################################################################################
678             # set defaults
679             sub _set_defaults {
680             my $self = shift;
681             $self->{'config'} = {
682             'currentdatetime' => scalar localtime time, #get current date and time for results report
683             'standaloneplot' => 'off',
684             'graphtype' => 'lines',
685             'httpauth' => [],
686             'reporttype' => 'standard',
687             'output_dir' => './',
688             'nooutput' => undef,
689             'realserverip' => '',
690             'baseurl' => '',
691             'baseurl1' => '',
692             'baseurl2' => '',
693             'break_on_errors' => 0,
694             'max_redirect' => 0,
695             'globalhttplog' => 'no',
696             'proxy' => '',
697             'timeout' => 180,
698             'tmpfiles' => [],
699             };
700             $self->{'exit_codes'} = {
701             'UNKNOWN' => 3,
702             'OK' => 0,
703             'WARNING' => 1,
704             'CRITICAL' => 2,
705             };
706             $self->{'switches'} = {
707             'stop' => 'no',
708             'plotclear' => 'no',
709             };
710             $self->{'out'} = '';
711             $self->_reset_result();
712             return;
713             }
714              
715             ################################################################################
716             # reset result
717             sub _reset_result {
718             my $self = shift;
719             $self->{'result'} = {
720             'cases' => [],
721             'returnmessage' => undef,
722             'totalcasesfailedcount' => 0,
723             'totalcasespassedcount' => 0,
724             'totalfailedcount' => 0,
725             'totalpassedcount' => 0,
726             'totalresponse' => 0,
727             'totalruncount' => 0,
728             'totalruntime' => 0,
729             'casecount' => 0,
730             'avgresponse' => 0,
731             'iscritical' => 0,
732             'iswarning' => 0,
733             'maxresponse' => 0,
734             'minresponse' => undef,
735             'runcount' => 0,
736             };
737             return;
738             }
739              
740             ################################################################################
741             # write initial text for STDOUT
742             sub _writeinitialstdout {
743             my $self = shift;
744              
745             if($self->{'config'}->{'reporttype'} !~ /^nagios/mx) {
746             $self->_out(qq|
747             Starting WebInject Engine (v$Webinject::VERSION)...
748             |);
749             }
750             $self->_out("-------------------------------------------------------\n");
751             return;
752             }
753              
754             ################################################################################
755             # write summary and closing tags for results file
756             sub _write_result_html {
757             my $self = shift;
758              
759             my $file = $self->{'config'}->{'output_dir'}."results.html";
760             open( my $resultshtml, ">", $file )
761             or $self->_usage("ERROR: Failed to write ".$file.": ".$!);
762              
763             print $resultshtml
764             qq|
765             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
766              
767            
768            
769             WebInject Test Results
770            
771            
788            
789            
790             \n|; \n";
791            
792             Test
793             Description
Request URL
794             Results
795             Summary
Response Time
796            
797             |;
798             for my $file (@{$self->{'result'}->{'files'}}) {
799             for my $case (@{$file->{'cases'}}) {
800             print $resultshtml qq|
$file->{'name'}
$case->{'id'}
801             for my $message (@{$case->{'messages'}}) {
802             next unless defined $message->{'html'};
803             print $resultshtml $message->{'html'} . "\n";
804             }
805             print $resultshtml "
806             }
807             }
808              
809             print $resultshtml qq|
810            
811            
812             Start Time: $self->{'config'}->{'currentdatetime'}
813             Total Run Time: $self->{'result'}->{'totalruntime'} seconds
814            
815             Test Cases Run: $self->{'result'}->{'totalruncount'}
816             Test Cases Passed: $self->{'result'}->{'totalcasespassedcount'}
817             Test Cases Failed: $self->{'result'}->{'totalcasesfailedcount'}
818             Verifications Passed: $self->{'result'}->{'totalpassedcount'}
819             Verifications Failed: $self->{'result'}->{'totalfailedcount'}
820            
821             Average Response Time: $self->{'result'}->{'avgresponse'} seconds
822             Max Response Time: $self->{'result'}->{'maxresponse'} seconds
823             Min Response Time: $self->{'result'}->{'minresponse'} seconds
824            
825            
826              
827            
828            
829             |;
830             close($resultshtml);
831             return;
832             }
833              
834             ################################################################################
835             # write summary and closing tags for XML results file
836             sub _write_result_xml {
837             my $self = shift;
838              
839             my $file = $self->{'config'}->{'output_dir'}."results.xml";
840             open( my $resultsxml, ">", $file )
841             or $self->_usage("ERROR: Failed to write ".$file.": ".$!);
842              
843             print $resultsxml "\n\n";
844              
845             for my $file (@{$self->{'result'}->{'files'}}) {
846             print $resultsxml " {'name'}."\">\n\n";
847             for my $case (@{$file->{'cases'}}) {
848             print $resultsxml " {'id'}."\">\n";
849             for my $message (@{$case->{'messages'}}) {
850             next unless defined $message->{'key'};
851             print $resultsxml " <".$message->{'key'}.">".$message->{'value'}."{'key'}.">\n";
852             }
853             print $resultsxml " \n\n";
854             }
855             print $resultsxml " \n";
856             }
857              
858             print $resultsxml qq|
859            
860             $self->{'config'}->{'currentdatetime'}
861             $self->{'result'}->{'totalruntime'}
862             $self->{'result'}->{'totalruncount'}
863             $self->{'result'}->{'totalcasespassedcount'}
864             $self->{'result'}->{'totalcasesfailedcount'}
865             $self->{'result'}->{'totalpassedcount'}
866             $self->{'result'}->{'totalfailedcount'}
867             $self->{'result'}->{'avgresponse'}
868             $self->{'result'}->{'maxresponse'}
869             $self->{'result'}->{'minresponse'}
870            
871              
872            
873             |;
874             close($resultsxml);
875             return;
876             }
877              
878             ################################################################################
879             # write summary and closing text for STDOUT
880             sub _writefinalstdout {
881             my $self = shift;
882              
883             if($self->{'config'}->{'reporttype'} !~ /^nagios/mx) {
884             $self->_out(qq|
885             Start Time: $self->{'config'}->{'currentdatetime'}
886             Total Run Time: $self->{'result'}->{'totalruntime'} seconds
887              
888             |);
889             }
890              
891             $self->_out(qq|
892             Test Cases Run: $self->{'result'}->{'totalruncount'}
893             Test Cases Passed: $self->{'result'}->{'totalcasespassedcount'}
894             Test Cases Failed: $self->{'result'}->{'totalcasesfailedcount'}
895             Verifications Passed: $self->{'result'}->{'totalpassedcount'}
896             Verifications Failed: $self->{'result'}->{'totalfailedcount'}
897              
898             |);
899             return;
900             }
901              
902             ################################################################################
903             sub _http_defaults {
904             my $self = shift;
905             my $request = shift;
906             my $useragent = shift;
907             my $case = shift;
908              
909             # Add additional cookies to the cookie jar if specified
910             if($case->{'addcookie'}) {
911             my $cookie_jar = $useragent->cookie_jar();
912             # add cookies to the cookie jar
913             # can add multiple cookies with a pipe delimiter
914             for my $addcookie (split /\|/mx, $case->{'addcookie'}) {
915             my ($ck_version, $ck_key, $ck_val, $ck_path, $ck_domain, $ck_port, $ck_path_spec, $ck_secure, $ck_maxage, $ck_discard) = split(/,/mx, $addcookie);
916             $cookie_jar->set_cookie( $ck_version, $ck_key, $ck_val, $ck_path, $ck_domain, $ck_port, $ck_path_spec, $ck_secure, $ck_maxage, $ck_discard);
917             }
918             $cookie_jar->save();
919             $cookie_jar->add_cookie_header($request);
920             }
921              
922             # add an additional HTTP Header if specified
923             if($case->{'addheader'}) {
924             # can add multiple headers with a pipe delimiter
925             for my $addheader (split /\|/mx, $case->{'addheader'}) {
926             $addheader =~ m~(.*):\ (.*)~mx;
927             $request->header( $1 => $2 ); # using HTTP::Headers Class
928             }
929             }
930              
931             # print $self->{'request'}->as_string; print "\n\n";
932              
933             my $starttimer = time();
934             my $response = $useragent->request($request);
935             my $endtimer = time();
936             my $latency = ( int( 1000 * ( $endtimer - $starttimer ) ) / 1000 ); # elapsed time rounded to thousandths
937             # print $response->as_string; print "\n\n";
938              
939             return($latency,$request,$response);
940             }
941              
942             ################################################################################
943             # send http request and read response
944             sub _httpget {
945             my $self = shift;
946             my $useragent = shift;
947             my $case = shift;
948              
949             $self->_out("GET Request: ".$case->{url}."\n");
950             my $request = new HTTP::Request( 'GET', $case->{url} );
951             return $self->_http_defaults($request, $useragent, $case);
952             }
953              
954             ################################################################################
955             # post request based on specified encoding
956             sub _httppost {
957             my $self = shift;
958             my $useragent = shift;
959             my $case = shift;
960              
961             if($case->{posttype} ) {
962             if( ($case->{posttype} =~ m~application/x\-www\-form\-urlencoded~mx)
963             or ($case->{posttype} =~ m~application/json~mx)
964             )
965             {
966             return $self->_httppost_form_urlencoded($useragent, $case);
967             }
968             elsif($case->{posttype} =~ m~multipart/form\-data~mx) {
969             return $self->_httppost_form_data($useragent, $case);
970             }
971             elsif( ($case->{posttype} =~ m~text/xml~mx)
972             or ($case->{posttype} =~ m~application/soap\+xml~mx)
973             )
974             {
975             return $self->_httppost_xml($useragent, $case);
976             }
977             else {
978             $self->_usage('ERROR: Bad Form Encoding Type, I only accept "application/x-www-form-urlencoded", "multipart/form-data", "text/xml", "application/soap+xml"');
979             }
980             }
981             else {
982             # use "x-www-form-urlencoded" if no encoding is specified
983             $case->{posttype} = 'application/x-www-form-urlencoded';
984             return $self->_httppost_form_urlencoded($useragent, $case);
985             }
986             return;
987             }
988              
989             ################################################################################
990             # send application/x-www-form-urlencoded HTTP request and read response
991             sub _httppost_form_urlencoded {
992             my $self = shift;
993             my $useragent = shift;
994             my $case = shift;
995              
996             $self->_out("POST Request: ".$case->{url}."\n");
997             my $request = new HTTP::Request('POST', $case->{url} );
998             $request->content_type($case->{posttype});
999             $request->content($case->{postbody});
1000              
1001             return $self->_http_defaults($request,$useragent, $case);
1002             }
1003              
1004             ################################################################################
1005             # send text/xml HTTP request and read response
1006             sub _httppost_xml {
1007             my $self = shift;
1008             my $useragent = shift;
1009             my $case = shift;
1010              
1011             my($latency,$request,$response);
1012              
1013             # read the xml file specified in the testcase
1014             $case->{postbody} =~ m~file=>(.*)~imx;
1015             open( my $xmlbody, "<", $1 ) or $self->_usage("ERROR: Failed to open text/xml file ".$1.": ".$!); # open file handle
1016             my @xmlbody = <$xmlbody>; # read the file into an array
1017             close($xmlbody);
1018              
1019             # Get the XML input file to use PARSEDRESULT and substitute the contents
1020             my $content = $self->_convertbackxmlresult(join( " ", @xmlbody ));
1021              
1022             $self->_out("POST Request: ".$case->{url}."\n");
1023             $request = new HTTP::Request( 'POST', $case->{url} );
1024             $request->content_type($case->{posttype});
1025             $request->content( $content ); # load the contents of the file into the request body
1026              
1027             ($latency,$request,$response) = $self->_http_defaults($request, $useragent, $case);
1028              
1029             my $xmlparser = new XML::Parser;
1030             # see if the XML parses properly
1031             try {
1032             $xmlparser->parse($response->decoded_content);
1033              
1034             # print "good xml\n";
1035             push @{$case->{'messages'}}, {'key' => 'verifyxml-success', 'value' => 'true', 'html' => 'Passed XML Parser (content is well-formed)' };
1036             $self->_out("Passed XML Parser (content is well-formed) \n");
1037             $case->{'passedcount'}++;
1038              
1039             # exit try block
1040             return;
1041             }
1042             catch Error with {
1043             # get the exception object
1044             my $ex = shift;
1045             # print "bad xml\n";
1046             # we suppress most logging when running in a plugin mode
1047             if($self->{'config'}->{'reporttype'} eq 'standard') {
1048             push @{$case->{'messages'}}, {'key' => 'verifyxml-success', 'value' => 'false', 'html' => "Failed XML parser on response: ".$ex };
1049             }
1050             $self->_out("Failed XML parser on response: $ex \n");
1051             $case->{'failedcount'}++;
1052             $case->{'iscritical'} = 1;
1053             }; # <-- remember the semicolon
1054              
1055             return($latency,$request,$response);
1056             }
1057              
1058             ################################################################################
1059             # send multipart/form-data HTTP request and read response
1060             sub _httppost_form_data {
1061             my $self = shift;
1062             my $useragent = shift;
1063             my $case = shift;
1064             my %myContent_;
1065             ## no critic
1066             eval "\%myContent_ = $case->{postbody}";
1067             ## use critic
1068              
1069             $self->_out("POST Request: ".$case->{url}."\n");
1070             my $request = POST($case->{url},
1071             Content_Type => $case->{posttype},
1072             Content => \%myContent_);
1073              
1074             return $self->_http_defaults($request, $useragent, $case);
1075             }
1076              
1077             ################################################################################
1078             # do verification of http response and print status to HTML/XML/STDOUT/UI
1079             sub _verify {
1080             my $self = shift;
1081             my $response = shift;
1082             my $case = shift;
1083              
1084             confess("no response") unless defined $response;
1085             confess("no case") unless defined $case;
1086              
1087             if( $case->{verifyresponsecode} ) {
1088             $self->_out(qq|Verify Response Code: "$case->{verifyresponsecode}" \n|);
1089             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode', 'value' => $case->{verifyresponsecode} };
1090              
1091             # verify returned HTTP response code matches verifyresponsecode set in test case
1092             if ( $case->{verifyresponsecode} == $response->code() ) {
1093             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-success', 'value' => 'true', 'html' => 'Passed HTTP Response Code: '.$case->{verifyresponsecode} };
1094             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-messages', 'value' => 'Passed HTTP Response Code Verification' };
1095             $self->_out(qq|Passed HTTP Response Code Verification \n|);
1096             $case->{'passedcount'}++;
1097             }
1098             else {
1099             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-success', 'value' => 'false', 'html' => 'Failed HTTP Response Code: received '.$response->code().', expecting '.$case->{verifyresponsecode} };
1100             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-messages', 'value' => 'Failed HTTP Response Code Verification (received '.$response->code().', expecting '.$case->{verifyresponsecode}.')' };
1101             $self->_out(qq|Failed HTTP Response Code Verification (received |.$response->code().qq|, expecting $case->{verifyresponsecode}) \n|);
1102             $case->{'failedcount'}++;
1103             $case->{'iscritical'} = 1;
1104              
1105             if($self->{'config'}->{'break_on_errors'}) {
1106             $self->{'result'}->{'returnmessage'} = 'Failed HTTP Response Code Verification (received '.$response->code().', expecting '.$case->{verifyresponsecode}.')';
1107             return;
1108             }
1109             }
1110             }
1111             else {
1112             # verify http response code is in the 100-399 range
1113             if($response->as_string() =~ /HTTP\/1.(0|1)\ (1|2|3)/imx ) { # verify existance of string in response
1114             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-success', 'value' => 'true', 'html' => 'Passed HTTP Response Code Verification (not in error range)' };
1115             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-messages', 'value' => 'Passed HTTP Response Code Verification (not in error range)' };
1116             $self->_out(qq|Passed HTTP Response Code Verification (not in error range) \n|);
1117              
1118             # succesful response codes: 100-399
1119             $case->{'passedcount'}++;
1120             }
1121             else {
1122             $response->as_string() =~ /(HTTP\/1.)(.*)/mxi;
1123             if($1) { #this is true if an HTTP response returned
1124             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-success', 'value' => 'false', 'html' => 'Failed HTTP Response Code Verification ('.$1.$2.')' };
1125             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-messages', 'value' => 'Failed HTTP Response Code Verification ('.$1.$2.')' };
1126             $self->_out("Failed HTTP Response Code Verification ($1$2) \n"); #($1$2) is HTTP response code
1127              
1128             $case->{'failedcount'}++;
1129             $case->{'iscritical'} = 1;
1130              
1131             if($self->{'config'}->{'break_on_errors'}) {
1132             $self->{'result'}->{'returnmessage'} = 'Failed HTTP Response Code Verification ('.$1.$2.')';
1133             return;
1134             }
1135             }
1136             #no HTTP response returned.. could be error in connection, bad hostname/address, or can not connect to web server
1137             else
1138             {
1139             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-success', 'value' => 'false', 'html' => 'Failed - No Response' };
1140             push @{$case->{'messages'}}, {'key' => 'verifyresponsecode-messages', 'value' => 'Failed - No Response' };
1141             $self->_out("Failed - No valid HTTP response:\n".$response->as_string());
1142              
1143             $case->{'failedcount'}++;
1144             $case->{'iscritical'} = 1;
1145              
1146             if($self->{'config'}->{'break_on_errors'}) {
1147             $self->{'result'}->{'returnmessage'} = 'Failed - No valid HTTP response: '.$response->as_string();
1148             return;
1149             }
1150             }
1151             }
1152             }
1153             push @{$case->{'messages'}}, { 'html' => '
' };
1154              
1155             for my $nr ('', 1..1000) {
1156             my $key = "verifypositive".$nr;
1157             if( $case->{$key} ) {
1158             $self->_out("Verify: '".$case->{$key}."' \n");
1159             push @{$case->{'messages'}}, {'key' => $key, 'value' => $case->{$key} };
1160             my $regex = $self->_fix_regex($case->{$key});
1161             # verify existence of string in response
1162             if( $response->as_string() =~ m~$regex~simx ) {
1163             push @{$case->{'messages'}}, {'key' => $key.'-success', 'value' => 'true', 'html' => "Passed: ".$case->{$key} };
1164             $self->_out("Passed Positive Verification \n");
1165             $case->{'passedcount'}++;
1166             }
1167             else {
1168             push @{$case->{'messages'}}, {'key' => $key.'-success', 'value' => 'false', 'html' => "Failed: ".$case->{$key} };
1169             $self->_out("Failed Positive Verification \n");
1170             $case->{'failedcount'}++;
1171             $case->{'iscritical'} = 1;
1172              
1173             if($self->{'config'}->{'break_on_errors'}) {
1174             $self->{'result'}->{'returnmessage'} = 'Failed Positive Verification, can not find a string matching regex: '.$regex;
1175             return;
1176             }
1177             }
1178             push @{$case->{'messages'}}, { 'html' => '
' };
1179             }
1180             elsif($nr ne '' and $nr > 5) {
1181             last;
1182             }
1183             }
1184              
1185             for my $nr ('', 1..1000) {
1186             my $key = "verifynegative".$nr;
1187             if( $case->{$key} ) {
1188             $self->_out("Verify Negative: '".$case->{$key}."' \n");
1189             push @{$case->{'messages'}}, {'key' => $key, 'value' => $case->{$key} };
1190             my $regex = $self->_fix_regex($case->{$key});
1191             # verify existence of string in response
1192             if( $response->as_string() =~ m~$regex~simx ) {
1193             push @{$case->{'messages'}}, {'key' => $key.'-success', 'value' => 'false', 'html' => 'Failed Negative: '.$case->{$key} };
1194             $self->_out("Failed Negative Verification \n");
1195             $case->{'failedcount'}++;
1196             $case->{'iscritical'} = 1;
1197              
1198             if($self->{'config'}->{'break_on_errors'}) {
1199             $self->{'result'}->{'returnmessage'} = 'Failed Negative Verification, found regex matched string: '.$regex;
1200             return;
1201             }
1202             }
1203             else {
1204             push @{$case->{'messages'}}, {'key' => $key.'-success', 'value' => 'true', 'html' => 'Passed Negative: '.$case->{$key} };
1205             $self->_out("Passed Negative Verification \n");
1206             $case->{'passedcount'}++;
1207             }
1208             push @{$case->{'messages'}}, { 'html' => '
' };
1209             }
1210             elsif($nr ne '' and $nr > 5) {
1211             last;
1212             }
1213             }
1214              
1215             if($self->{'verifylater'}) {
1216             my $regex = $self->_fix_regex($self->{'verifylater'});
1217             # verify existence of string in response
1218             if($response->as_string() =~ m~$regex~simx ) {
1219             push @{$case->{'messages'}}, {'key' => 'verifypositivenext-success', 'value' => 'true', 'html' => 'Passed Positive Verification (verification set in previous test case)' };
1220             $self->_out("Passed Positive Verification (verification set in previous test case) \n");
1221             $case->{'passedcount'}++;
1222             }
1223             else {
1224             push @{$case->{'messages'}}, {'key' => 'verifypositivenext-success', 'value' => 'false', 'html' => 'Failed Positive Verification (verification set in previous test case)' };
1225             $self->_out("Failed Positive Verification (verification set in previous test case) \n");
1226             $case->{'failedcount'}++;
1227             $case->{'iscritical'} = 1;
1228              
1229             if($self->{'config'}->{'break_on_errors'}) {
1230             $self->{'result'}->{'returnmessage'} = 'Failed Positive Verification (verification set in previous test case), can not find a string matching regex: '.$regex;
1231             return;
1232             }
1233             }
1234             push @{$case->{'messages'}}, { 'html' => '
' };
1235             # set to null after verification
1236             delete $self->{'verifylater'};
1237             }
1238              
1239             if($self->{'verifylaterneg'}) {
1240             my $regex = $self->_fix_regex($self->{'verifylaterneg'});
1241             # verify existence of string in response
1242             if($response->as_string() =~ m~$regex~simx) {
1243             push @{$case->{'messages'}}, {'key' => 'verifynegativenext-success', 'value' => 'false', 'html' => 'Failed Negative Verification (negative verification set in previous test case)' };
1244             $self->_out("Failed Negative Verification (negative verification set in previous test case) \n");
1245             $case->{'failedcount'}++;
1246             $case->{'iscritical'} = 1;
1247              
1248             if($self->{'config'}->{'break_on_errors'}) {
1249             $self->{'result'}->{'returnmessage'} = 'Failed Negative Verification (negative verification set in previous test case), found regex matched string: '.$regex;
1250             return;
1251             }
1252             }
1253             else {
1254             push @{$case->{'messages'}}, {'key' => 'verifynegativenext-success', 'value' => 'true', 'html' => 'Passed Negative Verification (negative verification set in previous test case)' };
1255             $self->_out("Passed Negative Verification (negative verification set in previous test case) \n");
1256             $case->{'passedcount'}++;
1257             }
1258             push @{$case->{'messages'}}, { 'html' => '
' };
1259             # set to null after verification
1260             delete $self->{'verifylaterneg'};
1261             }
1262              
1263             if($case->{'warning'}) {
1264             $self->_out("Verify Warning Threshold: ".$case->{'warning'}."\n");
1265             push @{$case->{'messages'}}, {'key' => "Warning Threshold", 'value' => $case->{''} };
1266             if($case->{'latency'} > $case->{'warning'}) {
1267             push @{$case->{'messages'}}, {'key' => 'warning-success', 'value' => 'false', 'html' => "Failed Warning Threshold: ".$case->{'warning'} };
1268             $self->_out("Failed Warning Threshold \n");
1269             $case->{'failedcount'}++;
1270             $case->{'iswarning'} = 1;
1271             }
1272             else {
1273             $self->_out("Passed Warning Threshold \n");
1274             push @{$case->{'messages'}}, {'key' => 'warning-success', 'value' => 'true', 'html' => "Passed Warning Threshold: ".$case->{'warning'} };
1275             $case->{'passedcount'}++;
1276             }
1277             push @{$case->{'messages'}}, { 'html' => '
' };
1278             }
1279              
1280             if($case->{'critical'}) {
1281             $self->_out("Verify Critical Threshold: ".$case->{'critical'}."\n");
1282             push @{$case->{'messages'}}, {'key' => "Critical Threshold", 'value' => $case->{''} };
1283             if($case->{'latency'} > $case->{'critical'}) {
1284             push @{$case->{'messages'}}, {'key' => 'critical-success', 'value' => 'false', 'html' => "Failed Critical Threshold: ".$case->{'critical'} };
1285             $self->_out("Failed Critical Threshold \n");
1286             $case->{'failedcount'}++;
1287             $case->{'iscritical'} = 1;
1288             }
1289             else {
1290             $self->_out("Passed Critical Threshold \n");
1291             push @{$case->{'messages'}}, {'key' => 'critical-success', 'value' => 'true', 'html' => "Passed Critical Threshold: ".$case->{'critical'} };
1292             $case->{'passedcount'}++;
1293             }
1294             }
1295              
1296             return;
1297             }
1298              
1299             ################################################################################
1300             # parse values from responses for use in future request (for session id's, dynamic URL rewriting, etc)
1301             sub _parseresponse {
1302             my $self = shift;
1303             my $response = shift;
1304             my $case = shift;
1305              
1306             my ( $resptoparse, @parseargs );
1307             my ( $leftboundary, $rightboundary, $escape );
1308              
1309             for my $type ( qw/parseresponse parseresponse1 parseresponse2 parseresponse3 parseresponse4 parseresponse5/ ) {
1310              
1311             next unless $case->{$type};
1312              
1313             @parseargs = split( /\|/mx, $case->{$type} );
1314              
1315             $leftboundary = $parseargs[0];
1316             $rightboundary = $parseargs[1];
1317             $escape = $parseargs[2];
1318              
1319             $resptoparse = $response->as_string;
1320             ## no critic
1321             if ( $resptoparse =~ m~$leftboundary(.*?)$rightboundary~s ) {
1322             $self->{'parsedresult'}->{$type} = $1;
1323             }
1324             ## use critic
1325             elsif(!defined $case->{'parsewarning'} or $case->{'parsewarning'}) {
1326             push @{$case->{'messages'}}, {'key' => $type.'-success', 'value' => 'false', 'html' => "Failed Parseresult, cannot find $leftboundary(.*?)$rightboundary" };
1327             $self->_out("Failed Parseresult, cannot find $leftboundary(*)$rightboundary\n");
1328             $case->{'iswarning'} = 1;
1329             }
1330              
1331             if ($escape) {
1332             if ( $escape eq 'escape' ) {
1333             $self->{'parsedresult'}->{$type} =
1334             $self->_url_escape( $self->{'parsedresult'}->{$type} );
1335             }
1336             if ( $escape eq 'decode' ) {
1337             $self->{'parsedresult'}->{$type} =
1338             decode_entities( $self->{'parsedresult'}->{$type} );
1339             }
1340             }
1341              
1342             #print "\n\nParsed String: $self->{'parsedresult'}->{$type}\n\n";
1343             }
1344             return;
1345             }
1346              
1347             ################################################################################
1348             # read config.xml
1349             sub _read_config_xml {
1350             my $self = shift;
1351             my $config_file = shift;
1352              
1353             my($config, $comment_mode,@configlines);
1354              
1355             # process the config file
1356             # if -c option was set on command line, use specified config file
1357             if(defined $config_file) {
1358             open( $config, '<', $config_file )
1359             or $self->_usage("ERROR: Failed to open ".$config_file." file: ".$!);
1360             $self->{'config'}->{'exists'} = 1; # flag we are going to use a config file
1361             }
1362             # if config.xml exists, read it
1363             elsif( -e "config.xml" ) {
1364             open( $config, '<', "config.xml" )
1365             or $self->_usage("ERROR: Failed to open config.xml file: ".$!);
1366             $self->{'config'}->{'exists'} = 1; # flag we are going to use a config file
1367             }
1368              
1369             if( $self->{'config'}->{'exists'} ) { #if we have a config file, use it
1370              
1371             my @precomment = <$config>; #read the config file into an array
1372              
1373             #remove any commented blocks from config file
1374             foreach (@precomment) {
1375             unless (m~.*~mx) { # single line comment
1376             # multi-line comments
1377             if (//mx) {
1378             $comment_mode = 1;
1379             }
1380             elsif (m~~mx) {
1381             $comment_mode = 0;
1382             }
1383             elsif ( !$comment_mode ) {
1384             push( @configlines, $_ );
1385             }
1386             }
1387             }
1388             close($config);
1389             }
1390              
1391             #grab values for constants in config file:
1392             foreach (@configlines) {
1393              
1394             for my $key (
1395             qw/realserverip baseurl baseurl1 baseurl2 gnuplot proxy timeout output_dir
1396             globaltimeout globalhttplog standaloneplot max_redirect
1397             break_on_errors useragent/
1398             )
1399             {
1400              
1401             if (/<$key>/mx) {
1402             $_ =~ m~<$key>(.*)~mx;
1403             $self->{'config'}->{$key} = $1;
1404              
1405             #print "\n$_ : $self->{'config'}->{$_} \n\n";
1406             }
1407             }
1408              
1409             if (//mx) {
1410             $_ =~ m~(.*)~mx;
1411             if ( $1 ne "standard" ) {
1412             $self->{'config'}->{'reporttype'} = $1;
1413             $self->{'config'}->{'nooutput'} = "set";
1414             }
1415              
1416             #print "\nreporttype : $self->{'config'}->{'reporttype'} \n\n";
1417             }
1418              
1419             if (//mx) {
1420              
1421             $_ =~ m~(.*)~mx;
1422             $self->_set_http_auth($1);
1423              
1424             #print "\nhttpauth : @{$self->{'config'}->{'httpauth'}} \n\n";
1425             }
1426              
1427             if(//mx) {
1428             my $firstparse = $'; #print "$' \n\n";
1429             $firstparse =~ m~~mx;
1430             my $filename = $`; #string between tags will be in $filename
1431             #print "\n$filename \n\n";
1432             push @{ $self->{'casefilelist'} }, $filename; #add next filename we grab to end of array
1433             }
1434             }
1435              
1436             return;
1437             }
1438              
1439             ################################################################################
1440             # parse and set http auth config
1441             sub _set_http_auth {
1442             my $self = shift;
1443             my $confstring = shift;
1444              
1445             #each time we see an , we set @authentry to be the
1446             #array of values, then we use [] to get a reference to that array
1447             #and push that reference onto @httpauth.
1448              
1449             my @authentry = split( /:/mx, $confstring );
1450             if( scalar @authentry != 5 ) {
1451             $self->_usage("ERROR: httpauth should have 5 fields delimited by colons, got: ".$confstring);
1452             }
1453             else {
1454             push( @{ $self->{'config'}->{'httpauth'} }, [@authentry] );
1455             }
1456             # basic authentication only works with redirects enabled
1457             if($self->{'config'}->{'max_redirect'} == 0) {
1458             $self->{'config'}->{'max_redirect'}++;
1459             }
1460              
1461             return;
1462             }
1463              
1464             ################################################################################
1465             # get test case files to run (from command line or config file) and evaluate constants
1466             sub _processcasefile {
1467             # parse config file and grab values it sets
1468             my $self = shift;
1469              
1470             if( ( $#ARGV + 1 ) < 1 ) { #no command line args were passed
1471             unless( $self->{'casefilelist'}->[0] ) {
1472             if ( -e "testcases.xml" ) {
1473             # if no files are specified in config.xml, default to testcases.xml
1474             push @{ $self->{'casefilelist'} }, "testcases.xml";
1475             }
1476             else {
1477             $self->_usage("ERROR: I can't find any test case files to run.\nYou must either use a config file or pass a filename "
1478             . "on the command line if you are not using the default testcase file (testcases.xml).");
1479             }
1480             }
1481             }
1482              
1483             elsif( ( $#ARGV + 1 ) == 1 ) { # one command line arg was passed
1484             # use testcase filename passed on command line (config.xml is only used for other options)
1485             push @{ $self->{'casefilelist'} }, $ARGV[0]; # first commandline argument is the test case file, put this on the array for processing
1486             }
1487              
1488             elsif( ( $#ARGV + 1 ) == 2 ) { # two command line args were passed
1489             my $xpath = $ARGV[1];
1490             if ( $xpath =~ /\/(.*)\[/mx ) { # if the argument contains a "/" and "[", it is really an XPath
1491             $xpath =~ /(.*)\/(.*)\[(.*?)\]/mx; #if it contains XPath info, just grab the file name
1492             $self->{'xnode'} = $3; # grab the XPath Node value.. (from inside the "[]")
1493             # print "\nXPath Node is: $self->{'xnode'} \n";
1494             }
1495             else {
1496             $self->_usage("ERROR: Sorry, $xpath is not in the XPath format I was expecting, I'm ignoring it...");
1497             }
1498              
1499             # use testcase filename passed on command line (config.xml is only used for other options)
1500             push @{ $self->{'casefilelist'} }, $ARGV[0]; # first command line argument is the test case file, put this on the array for processing
1501             }
1502              
1503             elsif ( ( $#ARGV + 1 ) > 2 ) { #too many command line args were passed
1504             $self->_usage("ERROR: Too many arguments.");
1505             }
1506              
1507             #print "\ntestcase file list: @{$self->{'casefilelist'}}\n\n";
1508              
1509             return;
1510             }
1511              
1512             ################################################################################
1513             # here we do some pre-processing of the test case file and write it out to a temp file.
1514             # we convert certain chars so xml parser doesn't puke.
1515             sub _convtestcases {
1516             my $self = shift;
1517             my $currentcasefile = shift;
1518              
1519             my @xmltoconvert;
1520              
1521             my ( $fh, $tempfilename ) = tempfile();
1522             push @{$self->{'tmpfiles'}}, $tempfilename;
1523             my $filename = $currentcasefile;
1524             open( my $xmltoconvert, '<', $filename )
1525             or $self->_usage("ERROR: Failed to read test case file: ".$filename.": ".$!);
1526             # read the file into an array
1527             @xmltoconvert = <$xmltoconvert>;
1528             my $ids = {};
1529             for my $line (@xmltoconvert) {
1530              
1531             # convert escaped chars and certain reserved chars to temporary values that the parser can handle
1532             # these are converted back later in processing
1533             $line =~ s/&/{AMPERSAND}/gmx;
1534             $line =~ s/\\
1535              
1536             # convert variables to lowercase
1537             $line =~ s/(\$\{[\w\.]+\})/\L$1\E/gmx;
1538             $line =~ s/(varname=('|").*?('|"))/\L$1\E/gmx;
1539              
1540             # count cases while we are here
1541             if ( $line =~ /
1542             $self->{'result'}->{'casecount'}++;
1543             }
1544              
1545             # verify id is only use once per file
1546             if ( $line =~ /^\s*id\s*=\s*\"*(\d+)\"*/mx ) {
1547             if(defined $ids->{$1}) {
1548             $self->{'result'}->{'iswarning'} = 1;
1549             $self->_out("Warning: case id $1 is used more than once!\n");
1550             }
1551             $ids->{$1} = 1;
1552             }
1553             }
1554              
1555             close($xmltoconvert);
1556              
1557             # open file handle to temp file
1558             open( $xmltoconvert, '>', $tempfilename )
1559             or $self->_usage("ERROR: Failed to write ".$tempfilename.": ".$!);
1560             print $xmltoconvert @xmltoconvert; # overwrite file with converted array
1561             close($xmltoconvert);
1562             return $tempfilename;
1563             }
1564              
1565             ################################################################################
1566             # converts replaced xml with substitutions
1567             sub _convertbackxml {
1568             my ( $self, $string, $timestamp ) = @_;
1569             return unless defined $string;
1570             $string =~ s~{AMPERSAND}~&~gmx;
1571             $string =~ s~{LESSTHAN}~<~gmx;
1572             $string =~ s~{TIMESTAMP}~$timestamp~gmx;
1573             $string =~ s~{REALSERVERIP}~$self->{'config'}->{realserverip}~gmx;
1574             $string =~ s~{BASEURL}~$self->{'config'}->{baseurl}~gmx;
1575             $string =~ s~{BASEURL1}~$self->{'config'}->{baseurl1}~gmx;
1576             $string =~ s~{BASEURL2}~$self->{'config'}->{baseurl2}~gmx;
1577             return $string;
1578             }
1579              
1580             ################################################################################
1581             # converts replaced xml with parsed result
1582             sub _convertbackxmlresult {
1583             my ( $self, $string) = @_;
1584             return unless defined $string;
1585             $string =~ s~\{PARSEDRESULT\}~$self->{'parsedresult'}->{'parseresponse'}~gmx if defined $self->{'parsedresult'}->{'parseresponse'};
1586             for my $x (1..5) {
1587             $string =~ s~\{PARSEDRESULT$x\}~$self->{'parsedresult'}->{"parseresponse$x"}~gmx if defined $self->{'parsedresult'}->{"parseresponse$x"};
1588             }
1589             return $string;
1590             }
1591              
1592             ################################################################################
1593             # escapes difficult characters with %hexvalue
1594             sub _url_escape {
1595             my ( $self, @values ) = @_;
1596              
1597             # LWP handles url encoding already, but use this to escape valid chars that LWP won't convert (like +)
1598             my @return;
1599             for my $val (@values) {
1600             $val =~ s/[^-\w.,!~'()\/\ ]/uc sprintf "%%%02x", ord $&/egmx if defined $val;
1601             push @return, $val;
1602             }
1603             return wantarray ? @return : $return[0];
1604             }
1605              
1606             ################################################################################
1607             # write requests and responses to http.log file
1608             sub _httplog {
1609             my $self = shift;
1610             my $request = shift;
1611             my $response = shift;
1612             my $case = shift;
1613             my $output = '';
1614              
1615             # http request - log setting per test case
1616             if($case->{'logrequest'} && $case->{'logrequest'} =~ /yes/mxi ) {
1617             $output .= $request->as_string."\n\n";
1618             }
1619              
1620             # http response - log setting per test case
1621             if($case->{'logresponse'} && $case->{'logresponse'} =~ /yes/mxi ) {
1622             $output .= $response->as_string."\n\n";
1623             }
1624              
1625             # global http log setting
1626             if($self->{'config'}->{'globalhttplog'} && $self->{'config'}->{'globalhttplog'} =~ /yes/mxi ) {
1627             $output .= $request->as_string."\n\n";
1628             $output .= $response->as_string."\n\n";
1629             }
1630              
1631             # global http log setting - onfail mode
1632             if($self->{'config'}->{'globalhttplog'} && $self->{'config'}->{'globalhttplog'} =~ /onfail/mxi && $case->{'iscritical'}) {
1633             $output .= $request->as_string."\n\n";
1634             $output .= $response->as_string."\n\n";
1635             }
1636              
1637             if($output ne '') {
1638             my $file = $self->{'config'}->{'output_dir'}."http.log";
1639             open( my $httplogfile, ">>", $file )
1640             or $self->_usage("ERROR: Failed to write ".$file.": ".$!);
1641             print $httplogfile $output;
1642             print $httplogfile "\n************************* LOG SEPARATOR *************************\n\n\n";
1643             close($httplogfile);
1644             }
1645              
1646             return;
1647             }
1648              
1649             ################################################################################
1650             # write performance results to plot.log in the format gnuplot can use
1651             sub _plotlog {
1652             my ( $self, $value ) = @_;
1653              
1654             my ( %months, $date, $time, $mon, $mday, $hours, $min, $sec, $year );
1655              
1656             # do this unless: monitor is disabled in gui, or running standalone mode without config setting to turn on plotting
1657             if( ( $self->{'gui'} and $self->{'monitorenabledchkbx'} ne 'monitor_off')
1658             or (!$self->{'gui'} and $self->{'config'}->{'standaloneplot'} eq 'on')
1659             ) {
1660              
1661             %months = (
1662             "Jan" => 1,
1663             "Feb" => 2,
1664             "Mar" => 3,
1665             "Apr" => 4,
1666             "May" => 5,
1667             "Jun" => 6,
1668             "Jul" => 7,
1669             "Aug" => 8,
1670             "Sep" => 9,
1671             "Oct" => 10,
1672             "Nov" => 11,
1673             "Dec" => 12
1674             );
1675              
1676             $date = scalar localtime;
1677             ($mon, $mday, $hours, $min, $sec, $year) = $date =~ /\w+\ (\w+)\ +(\d+)\ (\d\d):(\d\d):(\d\d)\ (\d\d\d\d)/mx;
1678             $time = "$months{$mon} $mday $hours $min $sec $year";
1679              
1680             my $plotlog;
1681             # used to clear the graph when requested
1682             if( $self->{'switches'}->{'plotclear'} eq 'yes' ) {
1683             # open in clobber mode so log gets truncated
1684             my $file = $self->{'config'}->{'output_dir'}."plot.log";
1685             open( $plotlog, '>', $file )
1686             or $self->_usage("ERROR: Failed to write ".$file.": ".$!);
1687             $self->{'switches'}->{'plotclear'} = 'no'; # reset the value
1688             }
1689             else {
1690             my $file = $self->{'config'}->{'output_dir'}."plot.log";
1691             open( $plotlog, '>>', $file )
1692             or $self->_usage("ERROR: Failed to write ".$file.": ".$!); #open in append mode
1693             }
1694              
1695             printf $plotlog "%s %2.4f\n", $time, $value;
1696             close($plotlog);
1697             }
1698             return;
1699             }
1700              
1701             ################################################################################
1702             # create gnuplot config file
1703             sub _plotcfg {
1704             my $self = shift;
1705              
1706             # do this unless: monitor is disabled in gui, or running standalone mode without config setting to turn on plotting
1707             if( ( $self->{'gui'} and $self->{'monitorenabledchkbx'} ne 'monitor_off')
1708             or (!$self->{'gui'} and $self->{'config'}->{'standaloneplot'} eq 'on')
1709             ) {
1710             my $file = $self->{'config'}->{'output_dir'}."plot.plt";
1711             open( my $gnuplotplt, ">", $file )
1712             or _usage("ERROR: Could not open ".$file.": ".$!);
1713             print $gnuplotplt qq|
1714             set term png
1715             set output \"$self->{'config'}->{'output_dir'}plot.png\"
1716             set size 1.1,0.5
1717             set pointsize .5
1718             set xdata time
1719             set ylabel \"Response Time (seconds)\"
1720             set yrange [0:]
1721             set bmargin 2
1722             set tmargin 2
1723             set timefmt \"%m %d %H %M %S %Y\"
1724             plot \"$self->{'config'}->{'output_dir'}plot.log\" using 1:7 title \"Response Times" w $self->{'config'}->{'graphtype'}
1725             |;
1726             close($gnuplotplt);
1727              
1728             }
1729             return;
1730             }
1731              
1732             ################################################################################
1733             # do ending tasks
1734             sub _finaltasks {
1735             my $self = shift;
1736              
1737             $self->_clean_tmp_files();
1738              
1739             if ( $self->{'gui'} ) { $self->_gui_stop(); }
1740              
1741             # we suppress most logging when running in a plugin mode
1742             if($self->{'config'}->{'reporttype'} eq 'standard') {
1743             # write summary and closing tags for results file
1744             $self->_write_result_html();
1745              
1746             #write summary and closing tags for XML results file
1747             $self->_write_result_xml();
1748             }
1749              
1750             # write summary and closing tags for STDOUT
1751             $self->_writefinalstdout();
1752              
1753             #plugin modes
1754             if($self->{'config'}->{'reporttype'} ne 'standard') {
1755             # return value is set which corresponds to a monitoring program
1756             # Nagios plugin compatibility
1757             if($self->{'config'}->{'reporttype'} =~ /^nagios/mx) {
1758             # nagios perf data has following format
1759             # 'label'=value[UOM];[warn];[crit];[min];[max]
1760             my $crit = 0;
1761             if(defined $self->{'config'}->{globaltimeout}) {
1762             $crit = $self->{'config'}->{globaltimeout};
1763             }
1764             my $lastid = 0;
1765             my $perfdata = '|time='.$self->{'result'}->{'totalruntime'}.'s;0;'.$crit.';0;0';
1766             for my $file (@{$self->{'result'}->{'files'}}) {
1767             for my $case (@{$file->{'cases'}}) {
1768             my $warn = $case->{'warning'} || 0;
1769             my $crit = $case->{'critical'} || 0;
1770             my $label = $case->{'label'} || 'case'.$case->{'id'};
1771             $perfdata .= ' '.$label.'='.$case->{'latency'}.'s;'.$warn.';'.$crit.';0;0';
1772             $lastid = $case->{'id'};
1773             }
1774             }
1775             # report performance data for missed cases too
1776             for my $nr (1..($self->{'result'}->{'casecount'} - $self->{'result'}->{'totalruncount'})) {
1777             $lastid++;
1778             my $label = 'case'.$lastid;
1779             $perfdata .= ' '.$label.'=0s;0;0;0;0';
1780             }
1781              
1782             my($rc,$message);
1783             if($self->{'result'}->{'iscritical'}) {
1784             $message = "WebInject CRITICAL - ".$self->{'result'}->{'returnmessage'};
1785             $rc = $self->{'exit_codes'}->{'CRITICAL'};
1786             }
1787             elsif($self->{'result'}->{'iswarning'}) {
1788             $message = "WebInject WARNING - ".$self->{'result'}->{'returnmessage'};
1789             $rc = $self->{'exit_codes'}->{'WARNING'};
1790             }
1791             elsif( $self->{'config'}->{globaltimeout} && $self->{'result'}->{'totalruntime'} > $self->{'config'}->{globaltimeout} ) {
1792             $message = "WebInject WARNING - All tests passed successfully but global timeout (".$self->{'config'}->{globaltimeout}." seconds) has been reached";
1793             $rc = $self->{'exit_codes'}->{'WARNING'};
1794             }
1795             else {
1796             $message = "WebInject OK - All tests passed successfully in ".$self->{'result'}->{'totalruntime'}." seconds";
1797             $rc = $self->{'exit_codes'}->{'OK'};
1798             }
1799              
1800             if($self->{'result'}->{'iscritical'} or $self->{'result'}->{'iswarning'}) {
1801             $message .= "\n".$self->{'out'};
1802             $message =~ s/^\-+$//mx;
1803             }
1804             if($self->{'config'}->{'reporttype'} eq 'nagios2') {
1805             $message =~ s/\n/
/mxg;
1806             }
1807             print $message.$perfdata."\n";
1808              
1809             $self->{'result'}->{'perfdata'} = $perfdata;
1810             return $rc;
1811             }
1812              
1813             #MRTG plugin compatibility
1814             elsif( $self->{'config'}->{'reporttype'} eq 'mrtg' )
1815             { #report results in MRTG format
1816             if( $self->{'result'}->{'totalcasesfailedcount'} > 0 ) {
1817             print "$self->{'result'}->{'totalruntime'}\n$self->{'result'}->{'totalruntime'}\n\nWebInject CRITICAL - $self->{'result'}->{'returnmessage'} \n";
1818             }
1819             else {
1820             print "$self->{'result'}->{'totalruntime'}\n$self->{'result'}->{'totalruntime'}\n\nWebInject OK - All tests passed successfully in $self->{'result'}->{'totalruntime'} seconds \n";
1821             }
1822             }
1823              
1824             #External plugin. To use it, add something like that in the config file:
1825             # external:/home/webinject/Plugin.pm
1826             elsif ( $self->{'config'}->{'reporttype'} =~ /^external:(.*)/mx ) {
1827             our $webinject = $self; # set scope of $self to global, so it can be access in the external module
1828             unless( my $return = do $1 ) {
1829             croak "couldn't parse $1: $@\n" if $@;
1830             croak "couldn't do $1: $!\n" unless defined $return;
1831             croak "couldn't run $1\n" unless $return;
1832             }
1833             }
1834              
1835             else {
1836             $self->_usage("ERROR: only 'nagios', 'nagios2', 'mrtg', 'external', or 'standard' are supported reporttype values");
1837             }
1838              
1839             }
1840              
1841             return 1 if $self->{'result'}->{'totalcasesfailedcount'} > 0;
1842             return 0;
1843             }
1844              
1845             ################################################################################
1846             # delete any files leftover from previous run if they exist
1847             sub _whackoldfiles {
1848             my $self = shift;
1849              
1850             for my $file (qw/plot.log plot.plt plot.png/) {
1851             unlink $self->{'config'}->{'output_dir'}.$file if -e $self->{'config'}->{'output_dir'}.$file;
1852             }
1853              
1854             # verify files are deleted, if not give the filesystem time to delete them before continuing
1855             while (-e $self->{'config'}->{'output_dir'}."plot.log"
1856             or -e $self->{'config'}->{'output_dir'}."plot.plt"
1857             or -e $self->{'config'}->{'output_dir'}."plot.png"
1858             ) {
1859             sleep .5;
1860             }
1861             return;
1862             }
1863              
1864             ################################################################################
1865             # call the external plotter to create a graph (if we are in the appropriate mode)
1866             sub _plotit {
1867             my $self = shift;
1868              
1869             # do this unless: monitor is disabled in gui, or running standalone mode without config setting to turn on plotting
1870             if( ( $self->{'gui'} and $self->{'monitorenabledchkbx'} ne 'monitor_off')
1871             or (!$self->{'gui'} and $self->{'config'}->{'standaloneplot'} eq 'on')
1872             ) {
1873             # do this unless its being called from the gui with No Graph set
1874             unless ( $self->{'config'}->{'graphtype'} eq 'nograph' )
1875             {
1876             my $gnuplot;
1877             if(defined $self->{'config'}->{gnuplot}) {
1878             $gnuplot = $self->{'config'}->{gnuplot}
1879             }
1880             elsif($^O eq 'MSWin32') {
1881             $gnuplot = "./wgnupl32.exe";
1882             } else {
1883             $gnuplot = "/usr/bin/gnuplot";
1884             }
1885              
1886             # if gnuplot exists
1887             if( -e $gnuplot ) {
1888             system $gnuplot, $self->{'config'}->{output_dir}."plot.plt"; # plot it
1889             }
1890             elsif( $self->{'gui'} ) {
1891             # if gnuplot not specified, notify on gui
1892             $self->_gui_no_plotter_found();
1893             }
1894             }
1895             }
1896             return;
1897             }
1898              
1899             ################################################################################
1900             # fix a user supplied regex to make it compliant with mx options
1901             sub _fix_regex {
1902             my $self = shift;
1903             my $regex = shift;
1904              
1905             $regex =~ s/\\\ / /mx;
1906             $regex =~ s/\ /\\ /gmx;
1907              
1908             return $regex;
1909             }
1910              
1911             ################################################################################
1912             # command line options
1913             sub _getoptions {
1914             my $self = shift;
1915              
1916             my( @sets, $opt_version, $opt_help, $opt_configfile );
1917             Getopt::Long::Configure('bundling');
1918             my $opt_rc = GetOptions(
1919             'h|help' => \$opt_help,
1920             'v|V|version' => \$opt_version,
1921             'c|config=s' => \$opt_configfile,
1922             'o|output=s' => \$self->{'config'}->{'output_dir'},
1923             'n|no-output' => \$self->{'config'}->{'nooutput'},
1924             'r|report-type=s' => \$self->{'config'}->{'reporttype'},
1925             't|timeout=i' => \$self->{'config'}->{'timeout'},
1926             's=s' => \@sets,
1927             );
1928             if(!$opt_rc or $opt_help) {
1929             $self->_usage();
1930             }
1931             if($opt_version) {
1932             print "WebInject version $Webinject::VERSION\nFor more info: http://www.webinject.org\n";
1933             exit 3;
1934             }
1935             $self->_read_config_xml($opt_configfile);
1936             for my $set (@sets) {
1937             my ( $key, $val ) = split /=/mx, $set, 2;
1938             if($key eq 'httpauth') {
1939             $self->_set_http_auth($val);
1940             } else {
1941             $self->{'config'}->{ lc $key } = $val;
1942             }
1943             }
1944             return;
1945             }
1946              
1947             ################################################################################
1948             # _out - print text to STDOUT and save it for later retrieval
1949             sub _out {
1950             my $self = shift;
1951             my $text = shift;
1952             if($self->{'config'}->{'reporttype'} !~ /^nagios/mx and !$self->{'config'}->{'nooutput'}) {
1953             print $text;
1954             }
1955             $self->{'out'} .= $text;
1956             return;
1957             }
1958              
1959             ################################################################################
1960             # print usage
1961             sub _usage {
1962             my $self = shift;
1963             my $text = shift;
1964              
1965             print $text."\n\n" if defined $text;
1966              
1967             print <
1968             Usage:
1969             $0
1970             [-c|--config config_file]
1971             [-o|--output output_location]
1972             [-n|--no-output]
1973             [-t|--timeout]
1974             [-r|--report-type]
1975             [-s key=value]
1976             [testcase_file [XPath]]
1977             $0 --version|-v
1978             EOB
1979             exit 3;
1980             }
1981              
1982             ################################################################################
1983             # remove any tmp files
1984             sub _clean_tmp_files {
1985             my($self) = @_;
1986             for my $tmpfile (@{$self->{'tmpfiles'}}) {
1987             unlink($tmpfile);
1988             }
1989             return;
1990             }
1991              
1992             =head1 TEST CASES
1993              
1994             =head2 Parameters
1995              
1996             =over
1997              
1998             =item addcookie
1999              
2000             When added to a test case, this adds a cookie to the cookie jar prior to the test case request being sent (i.e. the test case this is attached to will include any cookies specified in this parameter). This is useful for cases where a cookie is set outside of a Set-Cookie directive in the response header. This parameter takes a comma-delimited list of fields that configure the cookie; the fields for this parameter are a direct one-to-one correllation with the parameters to the HTTP::Cookies::set_cookie method. As well, multiple cookies can be defined by separating with a '|' character as with the addheader parameter.
2001              
2002             The comma-delimited list of fields are as follows.
2003              
2004             addcookie="version,name,value,path,domain,port,path_spec,secure,maxage,discard"
2005              
2006             version - Cookie-spec version number
2007              
2008             name - Cookie name.
2009              
2010             value - Cookie value.
2011              
2012             path - The URL path where the cookie is set.
2013              
2014             domain - The domain under which the cookie is set.
2015              
2016             port - The port on which the cookie is set.
2017              
2018             path_spec - Boolean. Set if the cookie is valid only under 'path' or the entire domain.
2019              
2020             secure - Boolean. If true (1), the cookie is only sent over secure connections
2021              
2022             maxage - The time in seconds the cookie is valid for.
2023              
2024             discard - Boolean. Do not send in future requests and destroy upon the next cookie jar save.
2025              
2026             =item parseresponse
2027              
2028             Parse a string from the HTTP response for use in subsequent requests. This is mostly used for passing Session ID's, but
2029             can be applied to any case where you need to pass a dynamically generated value. It takes the arguments in the format
2030             "leftboundary|rightboundary", and an optional third argument "leftboundary|rightboundary|escape|decode" when you want
2031             to force escaping of all non-alphanumeric characters (in case there is a wrong configuration of Apache server it will
2032             push encoded HTML characters (/ = /, : = :, ... ) to the Webinject and decode serve to translate them into normal characters.
2033             See the "Session Handling and State Management - Parsing Response Data & Embedded Session ID's" section of this manual for details and examples on how to use this parameter.
2034              
2035             Note: You may need to prepend a backslash before certain reserved characters when parsing (sorry that is rather vague).
2036              
2037             Note: Newlines (\n) are also valid boundaries and are useful when you need to use the end of the line as a boundary.
2038              
2039             parseresponse1
2040             Additional parameter for response parsing.
2041              
2042             parseresponse2
2043             Additional parameter for response parsing.
2044              
2045             parseresponse3
2046             Additional parameter for response parsing.
2047              
2048             parseresponse4
2049             Additional parameter for response parsing.
2050              
2051             parseresponse5
2052             Additional parameter for response parsing.
2053              
2054             =back
2055              
2056              
2057             =head1 EXAMPLE TEST CASE
2058              
2059            
2060            
2061             id = "1"
2062             description1 = "Sample Test Case"
2063             method = "get"
2064             url = "{BASEURL}/test.jsp"
2065             verifypositive = "All tests succeded"
2066             warning = "5"
2067             critical = "15"
2068             label = "testpage"
2069             errormessage = "got error: {PARSERESPONSE}"
2070             />
2071            
2072              
2073             detailed description about the syntax of testcases can be found on the Webinject homepage.
2074              
2075              
2076             =head1 SEE ALSO
2077              
2078             For more information about webinject visit http://www.webinject.org
2079              
2080             =head1 AUTHOR
2081              
2082             Corey Goldberg, Ecorey@goldb.orgE
2083              
2084             Sven Nierlein, Enierlein@cpan.orgE
2085              
2086             =head1 COPYRIGHT AND LICENSE
2087              
2088             Copyright (C) 2010 by Sven Nierlein
2089              
2090             Copyright (C) 2004-2006 by Corey Goldberg
2091              
2092             This library is free software; you can redistribute it under the GPL2 license.
2093              
2094             =cut
2095              
2096             1;
2097             __END__