File Coverage

blib/lib/HTTP/DAV/Comms.pm
Criterion Covered Total %
statement 91 192 47.4
branch 19 60 31.6
condition 6 41 14.6
subroutine 20 36 55.5
pod 0 12 0.0
total 136 341 39.8


line stmt bran cond sub pod time code
1             package HTTP::DAV::Comms;
2              
3 4     4   5487 use strict;
  4         7  
  4         132  
4 4     4   15 use vars qw($VERSION $DEBUG);
  4         5  
  4         184  
5              
6             $VERSION = q(0.23);
7              
8 4     4   1189 use HTTP::DAV::Utils;
  4         6  
  4         88  
9 4     4   1303 use HTTP::DAV::Response;
  4         9  
  4         106  
10 4     4   509 use LWP;
  4         10633  
  4         69  
11 4     4   18 use URI;
  4         4  
  4         3010  
12              
13             ####
14             # Construct a new object and initialize it
15             sub new {
16 1     1 0 590 my $class = shift;
17 1   33     8 my $self = bless {}, ref($class) || $class;
18              
19             #print Data::Dumper->Dump( [$self] , [ '$self' ] );
20 1         3 $self->_init(@_);
21 1         3 return $self;
22             }
23              
24             # Requires a reusable HTTP Agent.
25             # and some default headers, like, the user agent
26             sub _init {
27 1     1   1 my ( $self, @p ) = @_;
28 1         4 my ( $headers, $useragent )
29             = HTTP::DAV::Utils::rearrange( [ 'HEADERS', 'USERAGENT' ], @p );
30              
31             # This is cached in this object here so that each http request
32             # doesn't have to invoke a new useragent.
33 1         2 $self->init_user_agent($useragent);
34              
35 1         26 $self->set_headers($headers);
36             }
37              
38             sub init_user_agent {
39 1     1 0 2 my ( $self, $useragent ) = @_;
40 1 50       2 if ( defined $useragent ) {
41 0         0 $self->{_user_agent} = $useragent;
42             }
43             else {
44 1         3 $self->{_user_agent} = HTTP::DAV::UserAgent->new;
45 1         3 $self->set_agent("DAV.pm/v$HTTP::DAV::VERSION");
46             }
47             }
48              
49             ####
50             # GET/SET
51              
52             # Sets a User-Agent as specified by user or as the default
53             sub set_agent {
54 1     1 0 1 my ( $self, $agent ) = @_;
55 1         3 $self->{_user_agent}->agent($agent);
56             }
57              
58             sub set_header {
59 0     0 0 0 my ( $self, $var, $val ) = @_;
60 0 0       0 $self->set_headers() unless defined $self->{_headers};
61 0         0 $self->{_headers}->header( $var, $val );
62             }
63              
64 0     0 0 0 sub get_user_agent { $_[0]->{_user_agent}; }
65 0     0 0 0 sub get_headers { $_[0]->{_headers}; }
66              
67             sub set_headers {
68 1     1 0 1 my ( $self, $headers ) = @_;
69              
70 1         1 my $dav_headers;
71              
72 1 50 33     9 if ( defined $headers && ref($headers) eq "HTTP::Headers" ) {
    50 33        
73 0         0 $dav_headers = HTTP::DAV::Headers->clone($headers);
74             }
75             elsif (defined $headers && ref($headers) eq "HASH") {
76 1         6 $dav_headers = HTTP::DAV::Headers->new();
77 1         5 for (keys %{ $headers }) {
  1         3  
78 1         4 $dav_headers->header($_ => $headers->{$_});
79             }
80             } else {
81 0         0 $dav_headers = HTTP::DAV::Headers->new;
82             }
83              
84 1         68 $self->{_headers} = $dav_headers;
85             }
86              
87 0     0   0 sub _set_last_request { $_[0]->{_last_request} = $_[1]; }
88 0     0   0 sub _set_last_response { $_[0]->{_last_response} = $_[1]; }
89              
90             # Save the Server: header line into this object instance
91             # We will want to use it later to workaround server bugs.
92             # For instance mod_dav has a bug in the Destination: header
93             # whereby it incorrectly throws "Bad Gateway" errors.
94             # The only way we can munge around this is if the copy() routine
95             # has some idea of the server it is talking to.
96             # So this routine stores the "Server: Apache..." line into a host:port hash (i.e. localhost:443).
97             # so $comms->_set_server_type( "host.org:443", "Apache/1.3.22 (Unix) DAV/1.0.2 ")
98             # yields
99             # %_server_type = {
100             # "host.org:443" => "Apache/1.3.22 (Unix) DAV/1.0.2 SSL"
101             # "host.org:80" => "Apache/1.3.22 (Unix) DAV/1.0.2 "
102             # };
103             # Note that this is an instance hash NOT a class hash.
104             # So each comms object will be learning independently.
105 0     0   0 sub _set_server_type { $_[0]->{_server_type}{ $_[1] } = $_[2]; }
106              
107             # $server = $comms->get_server_type( "host.org:443" )
108 0     0 0 0 sub get_server_type { $_[0]->{_server_type}{ $_[1] } }
109              
110             # Returns an HTTP::Request object
111 0     0 0 0 sub get_last_request { $_[0]->{_last_request}; }
112              
113             # Returns an HTTP::DAV::Response object
114 0     0 0 0 sub get_last_response { $_[0]->{_last_response}; }
115              
116             ####
117             # Ensure there is a Host: header based on the URL
118             #
119             sub do_http_request {
120 0     0 0 0 my ( $self, @p ) = @_;
121              
122 0         0 my ( $method, $url, $newheaders, $content, $save_to, $callback_func,
123             $chunk )
124             = HTTP::DAV::Utils::rearrange(
125             [ 'METHOD', [ 'URL', 'URI' ], 'HEADERS', 'CONTENT',
126             'SAVE_TO', 'CALLBACK', 'CHUNK'
127             ],
128             @p
129             );
130              
131             # Method management
132 0 0 0     0 if ( !defined $method || $method eq "" || $method !~ /^\w+$/ ) {
      0        
133 0         0 die "Incorrect HTTP Method specified in do_http_request: \"$method\"";
134             }
135 0         0 $method = uc($method);
136              
137             # URL management
138 0         0 my $url_obj;
139 0 0       0 $url_obj = ( ref($url) =~ /URI/ ) ? $url : URI->new($url);
140              
141 0 0       0 die "Comms: Bad HTTP Url: \"$url_obj\"\n"
142             if ( $url_obj->scheme !~ /^http/ );
143              
144             # If you see user:pass detail embedded in the URL. Then get it out.
145 0 0       0 if ( $url_obj->userinfo ) {
146 0         0 $self->{_user_agent}
147             ->credentials( $url, undef, split( ':', $url_obj->userinfo ) );
148             }
149              
150             # Header management
151 0 0 0     0 if ( $newheaders && ref($newheaders) !~ /Headers/ ) {
152 0         0 die "Bad headers object: "
153             . Data::Dumper->Dump( [$newheaders], ['$newheaders'] );
154             }
155              
156 0         0 my $headers = HTTP::DAV::Headers->new();
157 0         0 $headers->add_headers( $self->{_headers} );
158 0         0 $headers->add_headers($newheaders);
159              
160             #$headers->header("Host", $url_obj->host);
161 0         0 $headers->header( "Host", $url_obj->host_port );
162              
163 0 0       0 my $length = ($content) ? length($content) : 0;
164 0         0 $headers->header( "Content-Length", $length );
165              
166             #print "HTTP HEADERS\n" . $self->get_headers->as_string . "\n\n";
167              
168             # It would be good if, at this stage, we could prefill the
169             # username and password values to prevent the client having
170             # to submit 2 requests, submit->401, submit->200
171             # This is the same kind of username, password remembering
172             # functionality that a browser performs.
173             #@userpass = $self->{_user_agent}->get_basic_credentials(undef, $url);
174              
175             # Add a Content-type of text/xml if the body has
176 0 0 0     0 if ( $content && $content =~ /<\?xml/i ) {
177 0         0 $headers->header( "Content-Type", "text/xml" );
178             }
179              
180             ####
181             # Do the HTTP call
182 0         0 my $req
183             = HTTP::Request->new( $method, $url_obj, $headers->to_http_headers,
184             $content );
185              
186             # It really bugs me, but libwww-perl doesn't honour this call.
187             # I'll leave it here anyway for future compatibility.
188 0         0 $req->protocol("HTTP/1.1");
189              
190 0         0 my $resp;
191              
192             # If a callback is set and it is a ref to a function
193             # then pass it through to LWP::UserAgent::request.
194             # See man page of LWP for more details of callback.
195             # callback is primarily used by DAV::get();
196             #
197 0 0 0     0 if ( defined $save_to && $save_to ne "" ) {
    0          
198 0         0 $resp = $self->{_user_agent}->request( $req, $save_to );
199             }
200             elsif ( ref($callback_func) =~ /CODE/ ) {
201 0         0 $resp = $self->{_user_agent}->request( $req, $callback_func, $chunk );
202             }
203             else {
204 0         0 $resp = $self->{_user_agent}->request($req);
205             }
206              
207             # Redirect loop {{{
208 0         0 my $code = $resp->code;
209 0 0 0     0 if ( $code == &HTTP::Status::RC_MOVED_PERMANENTLY
210             or $code == &HTTP::Status::RC_MOVED_TEMPORARILY )
211             {
212              
213             # And then we update the URL based on the Location:-header.
214 0         0 my ($referral_uri) = $resp->header('Location');
215             {
216              
217             # Some servers erroneously return a relative URL for redirects,
218             # so make it absolute if it not already is.
219 0         0 local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1;
  0         0  
220 0         0 my $base = $resp->base;
221 0         0 $referral_uri
222             = $HTTP::URI_CLASS->new( $referral_uri, $base )->abs($base);
223             }
224              
225             # Check for loop in the redirects
226 0         0 my $count = 0;
227 0         0 my $r = $resp;
228 0         0 my $bad_loop = 0;
229 0         0 while ($r) {
230 0 0 0     0 if ( ++$count > 13
231             || $r->request->url->as_string eq $referral_uri->as_string )
232             {
233 0         0 $resp->header( "Client-Warning" => "Redirect loop detected" );
234              
235             #if ( $HTTP::DAV::DEBUG ) {
236             # print "*** CLIENT AND SERVER STUCK IN REDIRECT LOOP OR MOVED PERMENANTLY. $count. BREAKING ***\n";
237             # print "*** " . $r->request->url->as_string . "***\n";
238             # print "*** " . $referral_uri->as_string . "***\n";
239             #}
240 0         0 $bad_loop = 1;
241 0         0 last;
242             }
243 0         0 $r = $r->previous;
244             }
245 0 0       0 $resp = $self->do_http_request(
246             -method => $method,
247             -url => $referral_uri,
248             -headers => $newheaders,
249             -content => $content,
250             -saveto => $save_to,
251             -callback => $callback_func,
252             -chunk => $chunk,
253             ) unless $bad_loop;
254             }
255              
256             # }}}
257              
258 0 0       0 if ($HTTP::DAV::DEBUG > 1) {
259 4     4   17 no warnings;
  4         6  
  4         1192  
260             #open(DEBUG, ">&STDOUT") || die ("Can't open STDERR");;
261 0         0 my $old_umask = umask 0077;
262 0         0 open( DEBUG, ">>/tmp/perldav_debug.txt" );
263 0         0 print DEBUG "\n" . "-" x 70 . "\n";
264 0         0 print DEBUG localtime() . "\n";
265 0         0 print DEBUG "$method REQUEST>>\n" . $req->as_string();
266              
267 0 0       0 if ( $resp->headers->header('Content-Type') =~ /xml/ ) {
268 0         0 my $body = $resp->as_string();
269             #$body =~ s/>\n*/>\n/g;
270 0         0 print DEBUG "$method XML RESPONSE>>$body\n";
271             #} elsif ( $resp->headers->header('Content-Type') =~ /text.html/ ) {
272             #require HTML::TreeBuilder;
273             #require HTML::FormatText;
274             #my $tree = HTML::TreeBuilder->new->parse($resp->content());
275             #my $formatter = HTML::FormatText->new(leftmargin => 0);
276             #print DEBUG "$method RESPONSE (HTML)>>\n" . $resp->headers->as_string();
277             #print DEBUG $formatter->format($tree);
278             }
279             else {
280 0         0 print DEBUG "$method RESPONSE>>\n" . $resp->as_string();
281             }
282 0         0 close DEBUG;
283 0         0 umask $old_umask;
284             }
285              
286             ####
287             # Copy the HTTP:Response into a HTTP::DAV::Response. It specifically
288             # knows details about DAV Status Codes and their associated
289             # messages.
290 0         0 my $dav_resp = HTTP::DAV::Response->clone_http_resp($resp);
291 0         0 $dav_resp->set_message( $resp->code );
292              
293             ####
294             # Save the req and resp objects as the "last used"
295 0         0 $self->_set_last_request($req);
296 0         0 $self->_set_last_response($dav_resp);
297              
298 0         0 $self->_set_server_type( $url_obj->host_port,
299             $dav_resp->headers->header("Server") );
300              
301 0         0 return $dav_resp;
302             }
303              
304             sub credentials {
305 0     0 0 0 my ( $self, @p ) = @_;
306 0         0 my ( $user, $pass, $url, $realm )
307             = HTTP::DAV::Utils::rearrange( [ 'USER', 'PASS', 'URL', 'REALM' ],
308             @p );
309 0         0 $self->{_user_agent}->credentials( $url, $realm, $user, $pass );
310             }
311              
312             ###########################################################################
313             # We make our own specialization of LWP::UserAgent
314             # called HTTP::DAV::UserAgent.
315             # The variations allow us to have various levels of protection.
316             # Where the user hasn't specified what Realm to use we pass the
317             # userpass combo to all realms of that host
318             # Also this UserAgent remembers a user on the next request.
319             # The standard UserAgent doesn't.
320             {
321              
322             package HTTP::DAV::UserAgent;
323              
324 4     4   19 use strict;
  4         5  
  4         97  
325 4     4   12 use vars qw(@ISA);
  4         5  
  4         593  
326              
327             @ISA = qw(LWP::UserAgent);
328              
329             #require LWP::UserAgent;
330              
331             sub new {
332 3     3   1158 my $self = LWP::UserAgent::new(@_);
333 3         216370 $self->agent("lwp-request/$HTTP::DAV::VERSION");
334 3         154 $self;
335             }
336              
337             sub credentials {
338 7     7   1851 my ( $self, $netloc, $realm, $user, $pass ) = @_;
339              
340 7 50       17 $realm = 'default' unless $realm;
341              
342 7 50       14 if ($netloc) {
343 7 50       22 $netloc = "http://$netloc" unless $netloc =~ m{^http};
344 7         29 my $uri = URI->new($netloc);
345 7         11218 $netloc = $uri->host_port;
346             }
347             else {
348 0         0 $netloc = 'default';
349             }
350              
351             {
352 4     4   18 no warnings;
  4         3  
  4         676  
  7         385  
353 7 100       24 if ($HTTP::DAV::DEBUG > 2) {
354 4 100       8 if (defined $user) {
355 1         81 print "Setting auth details for $netloc, $realm to '$user', '$pass'\n";
356             }
357             else {
358 3         484 print "Resetting user and password for $netloc, $realm\n";
359             }
360             }
361             }
362              
363             # Pay attention to not autovivify the hash value (RT #47500)
364 7         10 my $cred;
365 7 100 100     40 if (
366             exists $self->{basic_authentication}->{$netloc} &&
367             exists $self->{basic_authentication}->{$netloc}->{$realm}) {
368 3         6 $cred = $self->{basic_authentication}->{$netloc}->{$realm};
369             }
370             else {
371 4         7 $cred = [];
372             }
373              
374             # Replace with new credentials (if any)
375 7 100       16 if (defined $user) {
376 2         9 $self->{basic_authentication}->{$netloc}->{$realm}->[0] = $user;
377 2         6 $self->{basic_authentication}->{$netloc}->{$realm}->[1] = $pass;
378 2         5 $cred = $self->{basic_authentication}->{$netloc}->{$realm};
379             }
380              
381             # Return current values
382 7 100       4 if (! @{$cred}) {
  7         16  
383 2 50       11 return wantarray ? () : undef;
384             }
385              
386             # User/password pair
387 5 100       13 if (wantarray) { return @{$cred} }
  2         3  
  2         6  
388              
389             # As string: 'user:password'
390 3         3 return join( ':', @{$cred} );
  3         11  
391             }
392              
393             sub get_basic_credentials {
394 0     0     my ( $self, $realm, $uri ) = @_;
395              
396 0           $uri = HTTP::DAV::Utils::make_uri($uri);
397 0           my $netloc = $uri->host_port;
398              
399 0           my $userpass;
400             {
401 4     4   14 no warnings; # SHUTUP with your silly warnings.
  4         4  
  4         420  
  0            
402 0   0       $userpass
403             = $self->{'basic_authentication'}{$netloc}{$realm}
404             || $self->{'basic_authentication'}{default}{$realm}
405             || $self->{'basic_authentication'}{$netloc}{default}
406             || [];
407              
408 0 0         print "Using user/pass combo: @$userpass. For $realm, $uri\n"
409             if $HTTP::DAV::DEBUG > 2;
410              
411             }
412 0           return @$userpass;
413             }
414              
415             # Override to disallow redirects. Also, see RT #19616
416             sub redirect_ok {
417 0     0     return 0;
418             }
419              
420             }
421              
422             ###########################################################################
423             # We make our own special version of HTTP::Headers
424             # called HTTP::DAV::Headers. This is because we want to add
425             # a new method called add_headers
426             {
427              
428             package HTTP::DAV::Headers;
429              
430 4     4   15 use strict;
  4         2  
  4         88  
431 4     4   11 use vars qw(@ISA);
  4         5  
  4         620  
432              
433             @ISA = qw( HTTP::Headers );
434             require HTTP::Headers;
435              
436             # $dav_headers = HTTP::DAV::Headers->clone( $http_headers );
437              
438             sub to_http_headers {
439 0     0     my ($self) = @_;
440 0           my %clone = %{$self};
  0            
441 0           bless {%clone}, "HTTP::Headers";
442             }
443              
444             sub clone {
445 0     0     my ( $class, $headers ) = @_;
446 0           my %clone = %{$headers};
  0            
447 0   0       bless {%clone}, ref($class) || $class;
448             }
449              
450             sub add_headers {
451 0     0     my ( $self, $headers ) = @_;
452 0 0 0       return unless ( defined $headers && ref($headers) =~ /Headers/ );
453              
454             #print "About to add headers!!\n";
455             #print Data::Dumper->Dump( [$headers] , [ '$headers' ] );
456 0           foreach my $key ( sort keys %$headers ) {
457 0           $self->header( $key, $headers->{$key} );
458             }
459             }
460             }
461              
462             1;