File Coverage

blib/lib/WWW/Webrobot/UserAgentConnection.pm
Criterion Covered Total %
statement 33 158 20.8
branch 0 70 0.0
condition 0 30 0.0
subroutine 11 20 55.0
pod 2 6 33.3
total 46 284 16.2


line stmt bran cond sub pod time code
1             package WWW::Webrobot::UserAgentConnection;
2 1     1   4 use strict;
  1         2  
  1         30  
3 1     1   5 use warnings;
  1         1  
  1         28  
4              
5             # Author: Stefan Trcek
6             # Copyright(c) 2004-2006 ABAS Software AG
7              
8              
9 1     1   938 use HTTP::Cookies;
  1         12271  
  1         25  
10 1     1   713 use HTTP::Request::Common;
  1         35772  
  1         98  
11 1     1   926 use Time::HiRes;
  1         1976  
  1         6  
12              
13 1     1   705 use WWW::Webrobot::Attributes qw(ua cfg encoding);
  1         3  
  1         5  
14 1     1   499 use WWW::Webrobot::MyUserAgent;
  1         6  
  1         33  
15 1     1   822 use WWW::Webrobot::Ext::General::HTTP::Response;
  1         2  
  1         23  
16 1     1   440 use WWW::Webrobot::AssertConstant;
  1         2  
  1         24  
17 1     1   423 use WWW::Webrobot::MyEncode qw/has_Encode legacy_mode octet_to_encoding/;
  1         3  
  1         2158  
18              
19              
20             =head1 NAME
21              
22             WWW::Webrobot::UserAgentConnection - create and configure a user agent
23              
24             =head1 SYNOPSIS
25              
26             WWW::Webrobot::UserAgentConnection -> new($cfg, user => $user);
27              
28             =head1 DESCRIPTION
29              
30             Helper class.
31              
32             =head1 METHODS
33              
34             =over
35              
36             =item WWW::Webrobot::UserAgentConnection -> new ($cfg, %opt)
37              
38             $cfg
39             Config, see L
40             %opt
41             user => "an id for a user agent"
42              
43             =cut
44              
45             sub new {
46 0     0 1   my $proto = shift;
47 0   0       my $class = ref($proto) || $proto;
48              
49 0           my ($cfg, %opt) = @_;
50              
51 0           my $str_agent = "Webrobot " . $WWW::Webrobot::VERSION;
52 0 0         $str_agent .= " - $opt{user}" if $opt{user} ne "";
53              
54             # setup user agent
55 0           my $cookie_jar = HTTP::Cookies -> new(File => "cookies_.txt", AutoSave => 0);
56 0           my $ua = WWW::Webrobot::MyUserAgent -> new();
57 0           $ua -> cookie_jar($cookie_jar);
58 0           foreach (keys %{$cfg -> {proxy}}) {
  0            
59 0 0         if (/^https$/) {
60             # OpenSSL's proxy handling is incompatible to LWP's proxy handling
61 0           my $https_proxy = $cfg->{proxy}->{$_};
62             # yet more incompatible: remove trailing slash, bug in Crypt::SSLeay
63 0           $https_proxy =~ s,/$,,;
64 0           $ENV{HTTPS_PROXY} = $https_proxy;
65             }
66             else {
67 0           $ua -> proxy($_, $cfg -> {proxy} -> {$_});
68             }
69             }
70 0 0         $ua -> no_proxy(@{$cfg -> {no_proxy}}) if $cfg -> {no_proxy};
  0            
71 0   0       $ua -> timeout($cfg -> {timeout} || 30);
72 0           $ua -> agent($str_agent);
73 0 0         $ua -> set_basic_realm($cfg -> {auth_basic}) if $cfg -> {auth_basic};
74 0 0         $ua -> client_302_bug(1) if $cfg->{client_302_bug};
75              
76 0           my $self = {
77             _ua => $ua,
78             _cfg => $cfg,
79             };
80 0           bless ($self, $class);
81              
82 0           return $self;
83             }
84              
85              
86             =item $conn -> ua
87              
88             Get the user agent, see L
89              
90             =item $conn -> cfg
91              
92             Get the (internal) config data structure
93              
94             =cut
95              
96              
97             #privat
98             sub norm_request {
99 0     0 0   my ($self, $r) = @_;
100 0           my $referrer = $self->ua->referrer();
101 0 0         if ($referrer) {
102 0 0         if ($self->cfg->{referrer_bug}) {
103 0           $r -> headers() -> header(Referer => $referrer);
104             }
105             else {
106 0           $r -> headers() -> referer($referrer);
107             }
108             }
109 0           $self->ua->referrer($r->uri);
110 0           return $r;
111             }
112              
113             #static private
114             sub norm_response {
115 0     0 0   my ($r) = @_;
116 0 0 0       if (defined $r && ($r->protocol || "") eq 'HTTP/0.9' && ($r->message || "") eq 'EOF') {
      0        
      0        
      0        
117             # ??? Dieses Verhalten sollte besser von einer Konfigurationsvariable
118             # ??? abhaengig gemacht werden.
119 0           $r->code(500);
120 0           $r->protocol("HTTP/1.0");
121 0           $r->message("Internal Server Error: unexpected EOF");
122 0           $r->headers->{webrobot_orig_response} = "HTTP/0.9 200 EOF";
123 0           $r->headers->{webrobot_message} = "converted to http code 500 by webrobot";
124             }
125 0           return $r;
126             }
127              
128              
129             sub convert_data {
130 0     0 0   my ($uac, $input_data) = @_;
131 0           my $encoding = $uac->encoding;
132 0 0         return $input_data if !$encoding;
133 0 0 0       if (has_Encode || legacy_mode) {
134 0           my %data = ();
135 0           foreach (keys %$input_data) {
136 0           $data{octet_to_encoding($encoding, $_)} =
137             octet_to_encoding($encoding, $input_data->{$_});
138             }
139 0           return \%data;
140             }
141             else {
142 0           return $input_data;
143             }
144             }
145              
146             my %HTTP_ACTION = map {$_=>1} qw/HEAD GET POST PUT DELETE TRACE/;
147              
148             my %ACTION = (
149             NOP => sub {
150             return undef;
151             },
152             HEAD => sub {
153             my ($uac, $arg, $sym_tbl) = @_;
154             my %header = ( %{$uac->cfg->{http_header}}, %{$arg->{http_header}} );
155             return norm_response($uac -> ua -> request($uac->norm_request(HEAD($arg->{url}, %header))));
156             },
157             GET => sub {
158             my ($uac, $arg, $sym_tbl) = @_;
159             my %header = ( %{$uac->cfg->{http_header}}, %{$arg->{http_header}} );
160             my $data = convert_data($uac, $arg->{data});
161             my $url = $arg->{url};
162             if ($data && scalar keys %$data) {
163             my $tmp_request = POST($arg->{url}, $arg->{data}, %header);
164             $url .= "?" . $tmp_request->content() if $tmp_request->content();
165             }
166             my $request = GET($url, %header);
167             return norm_response($uac -> ua -> request($uac->norm_request($request)));
168             },
169             POST => sub {
170             my ($uac, $arg, $sym_tbl) = @_;
171             my %header = ( %{$uac->cfg->{http_header}}, %{$arg->{http_header}} );
172             my $data = convert_data($uac, $arg->{data});
173             return norm_response($uac -> ua -> request($uac->norm_request(POST($arg->{url}, $data, %header))));
174             },
175             PUT => sub {
176             my ($uac, $arg, $sym_tbl) = @_;
177             my %header = ( %{$uac->cfg->{http_header}}, %{$arg->{http_header}} );
178             return norm_response($uac -> ua -> request($uac->norm_request(PUT($arg->{url}, $arg->{data}, %header))));
179             },
180             COOKIES => sub {
181             my ($uac, $arg, $sym_tbl) = @_;
182             my $ua = $uac->ua;
183             SWITCH: foreach ($arg->{url}) {
184             m/^on$/i and do {
185             my $cookie_jar = HTTP::Cookies -> new(File => "cookies.txt", AutoSave => 0);
186             $ua->cookie_jar($cookie_jar);
187             last;
188             };
189             m/^off$/i and do {
190             $ua->cookie_jar(undef);
191             last;
192             };
193             m/^clear$/i and do {
194             if (my $cookies = $ua->cookie_jar()) {
195             $cookies->clear();
196             }
197             last;
198             };
199             m/^clear_temporary$/i and do {
200             if (my $cookies = $ua->cookie_jar()) {
201             $cookies->clear_temporary_cookies();
202             }
203             last;
204             };
205             }
206             return undef;
207             },
208             REFERRER => sub {
209             my ($uac, $arg, $sym_tbl) = @_;
210             my $ua = $uac->ua;
211             SWITCH: foreach ($arg->{url}) {
212             m/^clear$/i and do {
213             $ua->referrer("");
214             last;
215             };
216             m/^on$/i and do {
217             $ua->enable_referrer(1);
218             last;
219             };
220             m/^off$/i and do {
221             $ua->enable_referrer(0);
222             last;
223             };
224             }
225             return undef;
226             },
227             BASIC_REALM => sub {
228             my ($uac, $arg, $sym_tbl) = @_;
229             $uac -> ua -> set_basic_realm($arg->{url});
230             return undef;
231             },
232             CONFIG => sub {
233             my ($uac, $arg, $sym_tbl) = @_;
234             eval {
235             foreach my $tmp (@{$arg->{_mode}}) {
236             my ($mode, $parm) = @$tmp;
237             SWITCH: foreach ($mode) {
238             /^filename$/ || /^script$/ and do {
239             my $filename = $parm;
240             $filename .= " |" if /^script$/;
241             my $err_msg = /^script$/ ? "Can't start script" : "Can't read file";
242              
243             my $handle = do {local *FH; *FH};
244             { # 'open' produces a warning if the shell script doesn't exist!
245 1     1   12 no warnings;
  1         2  
  1         1957  
246             open $handle, "$filename" or die "$err_msg: '$parm'";
247             }
248             my $new_variables = WWW::Webrobot::Properties -> new() -> load_handle($handle) or
249             die "Can't read data from external program '$parm'";
250             my @new_vars = map { [$_, $new_variables->{$_} || ""] } sort keys %$new_variables;
251             $arg->{new_properties} = \@new_vars;
252             foreach (keys %$new_variables) {
253             $sym_tbl->define_symbol($_, $new_variables->{$_});
254             }
255             close $handle;
256             last SWITCH;
257             };
258             die "found $_ in \$arg->{_mode}, expected 'filename', 'script'";
259             }
260             }
261             };
262             my $err = $@;
263             $arg->{assert} = new WWW::Webrobot::AssertConstant($err, $err ? ["0 $err"] : []);
264             return undef;
265             },
266             SLEEP => sub {
267             my ($uac, $arg, $sym_tbl) = @_;
268             sleep($arg->{url});
269             return undef;
270             },
271             "GLOBAL-ASSERTION" => sub {
272             # This is the definition of the global assertion.
273             # It has to be stored which has already been done,
274             # so there is nothing to do.
275              
276             #my ($uac, $arg, $sym_tbl) = @_;
277             return undef;
278             },
279             );
280              
281             sub check_assertion {
282 0     0 0   my ($r, $all_assert) = @_;
283 0           my $all_fail = 0;
284 0           my @tmp = ();
285 0           foreach my $assert (@$all_assert) {
286 0           my ($fail, $fail_str) = $assert -> check($r);
287 0 0         $all_fail = 1 if $fail;
288 0           push @tmp, @$fail_str;
289             }
290 0           return ($all_fail, \@tmp);
291             }
292              
293              
294             =item $user -> treat_single_url ($arg, $sym_tbl)
295              
296             C<$arg> is an entry of a testplan, see L.
297              
298             Returns the fail state
299              
300             =cut
301              
302             sub treat_single_url {
303 0     0 1   my ($self, $arg, $sym_tbl) = @_;
304             #use Data::Dumper; print STDERR Dumper $sym_tbl;
305              
306 0 0         sleep($self->{_cfg}->{delay}) if $self->{_cfg}->{delay};
307              
308 0           $self -> {_ua} -> clear_redirect_fail();
309 0           my ($r, $fail, $fail_str);
310 0   0       $self->cfg->{http_header} ||= {}; # ??? really necessary?
311 0   0       $arg->{data} ||= {}; # ??? really necessary?
312 0 0         my $METHOD = $ACTION{$arg->{method}} or
313             die "'$arg->{method}' is no valid method, expected: ", join ", ", keys %ACTION;
314              
315             # do test plan entry (usually HTTP request)
316 0           my ($sec, $usec) = Time::HiRes::gettimeofday();
317 0           eval {
318             # NOTE: $r may be undef depending on $METHOD
319 0           $r = $METHOD->($self, $arg, $sym_tbl);
320             };
321 0           my $exception = $@;
322 0           my $elaps = Time::HiRes::tv_interval([$sec, $usec], [ Time::HiRes::gettimeofday() ]);
323 0 0         $r->elapsed_time($elaps) if $r;
324              
325             # check result
326 0 0 0       if ($self -> {_ua} -> is_redirect_fail()) {
    0          
    0          
327 0           ($fail, $fail_str) = (0, []);
328             }
329             elsif ($exception) {
330 0           $r = undef;
331 0           ($fail, $fail_str) = (2, ["2 CALL TO METHOD '$arg->{method}', URL '$arg->{url}' FAILED: $exception"]);
332             }
333             elsif (! $r || ! defined $arg->{assert}) {
334             # Method like COOKIES that don't support assertions
335 0           ($fail, $fail_str) = (undef, []);
336             }
337             else {
338 0           ($fail, $fail_str) = check_assertion($r, $arg->{assert});
339             }
340              
341             # set encoding of response
342 0           my $coding = undef;
343 0 0         if ($HTTP_ACTION{$arg->{method}}) {
344 0 0 0       if ($r and my $ct = $r->headers->{'content-type'}) {
345             #'content-type' => 'text/plain; charset=utf-8',
346 0 0         $coding = $1 if ($ct =~ m/.*;\s*charset\s*=\s*(.*)$/);
347             }
348             }
349 0           $self->encoding($coding);
350              
351 0 0         if ($arg->{property}) {
352             # evaluate new names
353 0           my @new_vars = ();
354 0           foreach (@{$arg->{property}}) {
  0            
355 0           my ($mode, $name, $expr) = @$_;
356 0           SWITCH: foreach ($mode) {
357 0 0         /^value$/ and do {
358 0           push @new_vars, [$name, $expr];
359 0           last;
360             };
361 0 0         /^regex$/ and do {
362 0 0         next if ! $r;
363 0           my ($value) = $r->content =~ m/$expr/;
364 0           push @new_vars, [$name, $value];
365 0           last;
366             };
367 0 0         /^xpath$/ and do {
368 0 0         next if ! $r;
369 0           push @new_vars, [$name, $r->xpath($expr)];
370 0           last;
371             };
372 0 0         /^header$/ and do {
373 0 0         next if ! $r;
374 0           push @new_vars, [$name, $r->header($expr)];
375 0           last;
376             };
377 0 0         /^status$/ and do {
378 0 0         next if ! $r;
379 0     0     my %val = (
380             code => sub {$_[0]->code},
381 0     0     message => sub {$_[0]->message},
382 0     0     protocol => sub {$_[0]->protocol},
383 0           );
384 0 0         die("found status='$expr', expected " . join(", ", map {"'$_'"} keys %val)) if ! $val{$expr};
  0            
385 0           push @new_vars, [$name, $val{$expr}->($r)];
386 0           last;
387             };
388 0 0         /^random$/ and do {
389 0 0         next if ! $r;
390 0 0         $expr = 0 if $expr < 0;
391 0 0         $expr = 15 if $expr > 15;
392 0           push @new_vars, [$name, int rand 10**$expr];
393 0           last;
394             };
395 0           die "found attribute '$_', expected 'value', 'regex', 'xpath', 'header', 'status', 'random'";
396             }
397             }
398 0           foreach (@new_vars) {
399 0   0       $_->[1] ||= "";
400 0           $sym_tbl -> define_symbol($_->[0], $_->[1]);
401             }
402 0           $arg->{new_properties} = \@new_vars;
403             }
404              
405 0           return ($r, $fail, $fail_str);
406             }
407              
408              
409             =back
410              
411             =cut
412              
413             1;