File Coverage

blib/lib/HTTP/DAV.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # Perl WebDAV client library
2              
3             package HTTP::DAV;
4              
5 22     22   47875 use strict;
  22         35  
  22         918  
6 22     22   83 use vars qw($VERSION $VERSION_DATE $DEBUG);
  22         24  
  22         1532  
7              
8             # Globals
9             $VERSION = '0.48';
10             $VERSION_DATE = '2015/03/26';
11              
12             # Set this up to 3
13             $DEBUG = 0;
14              
15             #use Carp (cluck);
16 22     22   97 use Cwd (); # Can't import all of it, cwd clashes with our namespace.
  22         26  
  22         371  
17 22     22   155501 use LWP;
  22         953572  
  22         744  
18 22     22   18528 use XML::DOM;
  0            
  0            
19             use Time::Local;
20             use HTTP::DAV::Lock;
21             use HTTP::DAV::ResourceList;
22             use HTTP::DAV::Resource;
23             use HTTP::DAV::Comms;
24             use URI::file;
25             use URI::Escape;
26             use FileHandle;
27             use File::Glob;
28             use File::Temp ();
29              
30             sub new {
31             my $class = shift;
32             my $self = bless {}, ref($class) || $class;
33             $self->_init(@_);
34             return $self;
35             }
36              
37             ###########################################################################
38             sub clone {
39             my $self = @_;
40             my $class = ref($self);
41             my %clone = %{$self};
42             bless {%clone}, $class;
43             }
44              
45             ###########################################################################
46             {
47              
48             sub _init {
49             my ( $self, @p ) = @_;
50             my ( $uri, $headers, $useragent )
51             = HTTP::DAV::Utils::rearrange( [ 'URI', 'HEADERS', 'USERAGENT' ],
52             @p );
53              
54             $self->{_lockedresourcelist} = HTTP::DAV::ResourceList->new();
55             $self->{_comms} = HTTP::DAV::Comms->new(
56             -useragent => $useragent,
57             -headers => $headers
58             );
59             if ($uri) {
60             $self->set_workingresource( $self->new_resource( -uri => $uri ) );
61             }
62              
63             return $self;
64             }
65             }
66              
67             sub DebugLevel {
68             shift if ref( $_[0] ) =~ /HTTP/;
69             my $level = shift;
70             $level = 256 if !defined $level || $level eq "";
71              
72             $DEBUG = $level;
73             }
74              
75             sub _tempfile {
76             my ($prefix, $tempdir) = @_;
77              
78             $prefix ||= 'dav';
79             $tempdir ||= '/tmp';
80              
81             my $template = $prefix . 'XXXXXXXXXXXXX';
82              
83             my $old_umask = umask 0077;
84             my ($fh, $filename) = File::Temp::tempfile($template,
85             DIR => $tempdir,
86             SUFFIX => '.tmp'
87             );
88             umask $old_umask;
89              
90             return wantarray
91             ? ($fh, $filename)
92             : $filename;
93             }
94              
95             ######################################################################
96             # new_resource acts as a resource factory.
97             # It will create a new one for you each time you ask.
98             # Sometimes, if it holds state information about this
99             # URL, it may return an old populated object.
100             sub new_resource {
101             my ($self) = shift;
102              
103             ####
104             # This is the order of the arguments unless used as
105             # named parameters
106             my ($uri) = HTTP::DAV::Utils::rearrange( ['URI'], @_ );
107             $uri = HTTP::DAV::Utils::make_uri($uri);
108              
109             #cluck "new_resource: now $uri\n";
110              
111             my $resource = $self->{_lockedresourcelist}->get_member($uri);
112             if ($resource) {
113             print
114             "new_resource: For $uri, returning existing resource $resource\n"
115             if $HTTP::DAV::DEBUG > 2;
116              
117             # Just reset the url to honour trailing slash status.
118             $resource->set_uri($uri);
119             return $resource;
120             }
121             else {
122             print "new_resource: For $uri, creating new resource\n"
123             if $HTTP::DAV::DEBUG > 2;
124             return HTTP::DAV::Resource->new(
125             -Comms => $self->{_comms},
126             -LockedResourceList => $self->{_lockedresourcelist},
127             -uri => $uri,
128             -Client => $self
129             );
130             }
131             }
132              
133             ###########################################################################
134             # ACCESSOR METHODS
135              
136             # GET
137             sub get_user_agent { $_[0]->{_comms}->get_user_agent(); }
138             sub get_last_request { $_[0]->{_comms}->get_last_request(); }
139             sub get_last_response { $_[0]->{_comms}->get_last_response(); }
140             sub get_workingresource { $_[0]->{_workingresource} }
141              
142             sub get_workingurl {
143             $_[0]->{_workingresource}->get_uri()
144             if defined $_[0]->{_workingresource};
145             }
146             sub get_lockedresourcelist { $_[0]->{_lockedresourcelist} }
147              
148             # SET
149             sub set_workingresource { $_[0]->{_workingresource} = $_[1]; }
150             sub credentials { shift->{_comms}->credentials(@_); }
151              
152             ######################################################################
153             # Error handling
154              
155             ## Error conditions
156             my %err = (
157             'ERR_WRONG_ARGS' => 'Wrong number of arguments supplied.',
158             'ERR_UNAUTHORIZED' => 'Unauthorized. ',
159             'ERR_NULL_RESOURCE' => 'Not connected. Do an open first. ',
160             'ERR_RESP_FAIL' => 'Server response: ',
161             'ERR_501' => 'Server response: ',
162             'ERR_405' => 'Server response: ',
163             'ERR_GENERIC' => '',
164             );
165              
166             sub err {
167             my ( $self, $error, $mesg, $url ) = @_;
168              
169             my $err_msg;
170             $err_msg = "";
171             $err_msg .= $err{$error} if defined $err{$error};
172             $err_msg .= $mesg if defined $mesg;
173             $err_msg .= "ERROR" unless defined $err_msg;
174              
175             $self->{_message} = $err_msg;
176             my $callback = $self->{_callback};
177             &$callback( 0, $err_msg, $url ) if $callback;
178              
179             if ( $self->{_multi_op} ) {
180             push( @{ $self->{_errors} }, $err_msg );
181             }
182             $self->{_status} = 0;
183              
184             return 0;
185             }
186              
187             sub ok {
188             my ($self, $mesg, $url, $so_far, $length) = @_;
189              
190             $self->{_message} = $mesg;
191              
192             my $callback = $self->{_callback};
193             &$callback(1, $mesg, $url, $so_far, $length) if $callback;
194              
195             if ($self->{_multi_op}) {
196             $self->{_status} = 1 unless $self->{_status} == 0;
197             }
198             else {
199             $self->{_status} = 1;
200             }
201             return 1;
202             }
203              
204             sub _start_multi_op {
205             my ($self, $mesg, $callback) = @_;
206             $self->{_multi_mesg} = $mesg || "";
207             $self->{_status} = 1;
208             $self->{_errors} = [];
209             $self->{_multi_op} = 1;
210             $self->{_callback} = $callback if defined $callback;
211             }
212              
213             sub _end_multi_op {
214             my ($self) = @_;
215             $self->{_multi_op} = 0;
216             $self->{_callback} = undef;
217             my $message = $self->{_multi_mesg} . " ";
218             $message .= ( $self->{_status} ) ? "succeeded" : "failed";
219             $self->{_message} = $message;
220             $self->{_multi_mesg} = undef;
221             }
222              
223             sub message {
224             my ($self) = @_;
225             return $self->{_message} || "";
226             }
227              
228             sub errors {
229             my ($self) = @_;
230             my $err_ref = $self->{_errors} || [];
231             return @{ $err_ref };
232             }
233              
234             sub is_success {
235             my ($self) = @_;
236             return $self->{_status};
237             }
238              
239             ######################################################################
240             # Operations
241              
242             # CWD
243             sub cwd {
244             my ( $self, @p ) = @_;
245             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
246              
247             return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" );
248             return $self->err('ERR_NULL_RESOURCE')
249             unless $self->get_workingresource();
250              
251             $url = HTTP::DAV::Utils::make_trail_slash($url);
252             my $new_uri = $self->get_absolute_uri($url);
253             ($new_uri) = $self->get_globs($new_uri);
254              
255             return 0 unless ($new_uri);
256              
257             print "cwd: Changing to $new_uri\n" if $DEBUG;
258             return $self->open($new_uri);
259             }
260              
261             # DELETE
262             sub delete {
263             my ( $self, @p ) = @_;
264             my ( $url, $callback )
265             = HTTP::DAV::Utils::rearrange( [ 'URL', 'CALLBACK' ], @p );
266              
267             return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" );
268             return $self->err('ERR_NULL_RESOURCE')
269             unless $self->get_workingresource();
270              
271             my $new_url = $self->get_absolute_uri($url);
272             my @urls = $self->get_globs($new_url);
273              
274             $self->_start_multi_op( "delete $url", $callback ) if @urls > 1;
275              
276             foreach my $u (@urls) {
277             my $resource = $self->new_resource( -uri => $u );
278              
279             my $resp = $resource->delete();
280              
281             if ( $resp->is_success ) {
282             $self->ok( "deleted $u successfully", $u );
283             }
284             else {
285             $self->err( 'ERR_RESP_FAIL', $resp->message(), $u );
286             }
287             }
288              
289             $self->_end_multi_op() if @urls > 1;
290              
291             return $self->is_success;
292             }
293              
294             # GET
295             # Handles globs by doing multiple recursive gets
296             # GET dir* produces
297             # _get dir1, to_local
298             # _get dir2, to_local
299             # _get dir3, to_local
300             sub get {
301             my ( $self, @p ) = @_;
302             my ( $url, $to, $callback, $chunk )
303             = HTTP::DAV::Utils::rearrange( [ 'URL', 'TO', 'CALLBACK', 'CHUNK' ],
304             @p );
305              
306             return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" );
307             return $self->err('ERR_NULL_RESOURCE')
308             unless $self->get_workingresource();
309              
310             $self->_start_multi_op( "get $url", $callback );
311              
312             my $new_url = $self->get_absolute_uri($url);
313             my (@urls) = $self->get_globs($new_url);
314              
315             return 0 unless ( $#urls > -1 );
316              
317             ############
318             # HANDLE -TO
319             #
320             $to ||= '';
321             if ( $to eq '.' ) {
322             $to = Cwd::getcwd();
323             }
324              
325             # If the TO argument is a file handle or a scalar
326             # then check that we only got one glob. If we got multiple
327             # globs, then we can't keep going because we can't write multiple files
328             # to one FileHandle.
329             if ( $#urls > 0 ) {
330             if ( ref($to) =~ /SCALAR/ ) {
331             return $self->err( 'ERR_WRONG_ARGS',
332             "Can't retrieve multiple files to a single scalar\n" );
333             }
334             elsif ( ref($to) =~ /GLOB/ ) {
335             return $self->err( 'ERR_WRONG_ARGS',
336             "Can't retrieve multiple files to a single filehandle\n" );
337             }
338             }
339              
340             # If it's a dir, remove last '/' from destination.
341             # Later we need to concatenate the destination filename.
342             if ( defined $to && $to ne '' && -d $to ) {
343             $to =~ s{/$}{};
344             }
345              
346             # Foreach file... do the get.
347             foreach my $u (@urls) {
348             my ( $left, $leafname ) = HTTP::DAV::Utils::split_leaf($u);
349              
350             # Handle SCALARREF and GLOB cases
351             my $dest_file = $to;
352              
353             # Directories
354             if ( -d $to ) {
355             $dest_file = "$to/$leafname";
356              
357             # Multiple targets
358             }
359             elsif ( !defined $to || $to eq "" ) {
360             $dest_file = $leafname;
361             }
362              
363             warn "get: $u -> $dest_file\n" if $DEBUG;
364              
365             # Setup the resource based on the passed url and do a propfind.
366             my $resource = $self->new_resource( -uri => $u );
367             my $resp = $resource->propfind( -depth => 1 );
368              
369             if ( $resp->is_error ) {
370             return $self->err( 'ERR_RESP_FAIL', $resp->message(), $u );
371             }
372              
373             $self->_get( $resource, $dest_file, $callback, $chunk );
374             }
375              
376             $self->_end_multi_op();
377             return $self->is_success;
378             }
379              
380             # Note: is is expected that $resource has had
381             # a propfind depth 1 performed on it.
382             #
383             sub _get {
384             my ( $self, @p ) = @_;
385             my ( $resource, $local_name, $callback, $chunk )
386             = HTTP::DAV::Utils::rearrange(
387             [ 'RESOURCE', 'TO', 'CALLBACK', 'CHUNK' ], @p );
388              
389             my $url = $resource->get_uri();
390              
391             # GET A DIRECTORY
392             if ( $resource->is_collection ) {
393              
394             # If the TO argument is a file handle, a scalar or empty
395             # then we
396             # can't keep going because we can't write multiple files
397             # to one FileHandle, scalar, etc.
398             if ( ref($local_name) =~ /SCALAR/ ) {
399             return $self->err( 'ERR_WRONG_ARGS',
400             "Can't retrieve a collection to a scalar\n", $url );
401             }
402             elsif ( ref($local_name) =~ /GLOB/ ) {
403             return $self->err( 'ERR_WRONG_ARGS',
404             "Can't retrieve a collection to a filehandle\n", $url );
405             }
406             elsif ( $local_name eq "" ) {
407             return $self->err(
408             'ERR_GENERIC',
409             "Can't retrieve a collection without a target directory (-to).",
410             $url
411             );
412             }
413              
414             # Try and make the directory locally
415             print "MKDIR $local_name (before escape)\n" if $DEBUG > 2;
416              
417             $local_name = URI::Escape::uri_unescape($local_name);
418             if ( !mkdir $local_name ) {
419             return $self->err( 'ERR_GENERIC',
420             "mkdir local:$local_name failed: $!" );
421             }
422              
423             $self->ok("mkdir $local_name");
424              
425             # This is the degenerate case for an empty dir.
426             print "Made directory $local_name\n" if $DEBUG > 2;
427              
428             my $resource_list = $resource->get_resourcelist();
429             if ($resource_list) {
430              
431             # FOREACH FILE IN COLLECTION, GET IT.
432             foreach my $progeny_r ( $resource_list->get_resources() ) {
433              
434             my $progeny_url = $progeny_r->get_uri();
435             print "Found progeny:$progeny_url\n" if $DEBUG > 2;
436             my $progeny_local_filename
437             = HTTP::DAV::Utils::get_leafname($progeny_url);
438             $progeny_local_filename
439             = URI::Escape::uri_unescape($progeny_local_filename);
440              
441             $progeny_local_filename
442             = URI::file->new($progeny_local_filename)
443             ->abs("$local_name/");
444              
445             if ( $progeny_r->is_collection() ) {
446             $progeny_r->propfind( -depth => 1 );
447             }
448             $self->_get( $progeny_r, $progeny_local_filename, $callback,
449             $chunk );
450              
451             # } else {
452             # $self->_do_get_tofile($progeny_r,$progeny_local_filename);
453             # }
454             }
455             }
456             }
457              
458             # GET A FILE
459             else {
460             my $response;
461             my $name_ref = ref $local_name;
462              
463             if ( $callback || $name_ref =~ /SCALAR/ || $name_ref =~ /GLOB/ ) {
464             $self->{_so_far} = 0;
465              
466             my $fh;
467             my $put_to_scalar = 0;
468              
469             if ( $name_ref =~ /GLOB/ ) {
470             $fh = $local_name;
471             }
472              
473             elsif ( $name_ref =~ /SCALAR/ ) {
474             $put_to_scalar = 1;
475             $$local_name = "";
476             }
477              
478             else {
479             $fh = FileHandle->new;
480             $local_name = URI::Escape::uri_unescape($local_name);
481             if (! $fh->open(">$local_name") ) {
482             return $self->err(
483             'ERR_GENERIC',
484             "open \">$local_name\" failed: $!",
485             $url
486             );
487             }
488              
489             # RT #29788, avoid file corruptions on Win32
490             binmode $fh;
491             }
492              
493             $self->{_fh} = $fh;
494              
495             $response = $resource->get(
496             -chunk => $chunk,
497             -progress_callback =>
498              
499             sub {
500             my ( $data, $response, $protocol ) = @_;
501              
502             $self->{_so_far} += length($data);
503              
504             my $fh = $self->{_fh};
505             print $fh $data if defined $fh;
506              
507             $$local_name .= $data if ($put_to_scalar);
508              
509             my $user_callback = $self->{_callback};
510             &$user_callback( -1, "transfer in progress",
511             $url, $self->{_so_far}, $response->content_length(),
512             $data )
513             if defined $user_callback;
514              
515             }
516              
517             ); # end get( ... );
518              
519             # Close the filehandle if it was set.
520             if ( defined $self->{_fh} ) {
521             $self->{_fh}->close();
522             delete $self->{_fh};
523             }
524             }
525             else {
526             $local_name = URI::Escape::uri_unescape($local_name);
527             $response = $resource->get( -save_to => $local_name );
528             }
529              
530             # Handle response
531             if ( $response->is_error ) {
532             return $self->err( 'ERR_GENERIC',
533             "get $url failed: " . $response->message, $url );
534             }
535             else {
536             return $self->ok( "get $url", $url, $self->{_so_far},
537             $response->content_length() );
538             }
539              
540             }
541              
542             return 1;
543             }
544              
545             # LOCK
546             sub lock {
547             my ( $self, @p ) = @_;
548             my ( $url, $owner, $depth, $timeout, $scope, $type, @other )
549             = HTTP::DAV::Utils::rearrange(
550             [ 'URL', 'OWNER', 'DEPTH', 'TIMEOUT', 'SCOPE', 'TYPE' ], @p );
551              
552             return $self->err('ERR_NULL_RESOURCE')
553             unless $self->get_workingresource();
554              
555             my $resource;
556             if ($url) {
557             $url = $self->get_absolute_uri($url);
558             $resource = $self->new_resource( -uri => $url );
559             }
560             else {
561             $resource = $self->get_workingresource();
562             $url = $resource->get_uri;
563             }
564              
565             # Make the lock
566             my $resp = $resource->lock(
567             -owner => $owner,
568             -depth => $depth,
569             -timeout => $timeout,
570             -scope => $scope,
571             -type => $type
572             );
573              
574             if ( $resp->is_success() ) {
575             return $self->ok( "lock $url succeeded", $url );
576             }
577             else {
578             return $self->err( 'ERR_RESP_FAIL', $resp->message, $url );
579             }
580             }
581              
582             # UNLOCK
583             sub unlock {
584             my ( $self, @p ) = @_;
585             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
586              
587             return $self->err('ERR_NULL_RESOURCE')
588             unless $self->get_workingresource();
589              
590             my $resource;
591             if ($url) {
592             $url = $self->get_absolute_uri($url);
593             $resource = $self->new_resource( -uri => $url );
594             }
595             else {
596             $resource = $self->get_workingresource();
597             $url = $resource->get_uri;
598             }
599              
600             # Make the lock
601             my $resp = $resource->unlock();
602             if ( $resp->is_success ) {
603             return $self->ok( "unlock $url succeeded", $url );
604             }
605             else {
606              
607             # The Resource.pm::lock routine has a hack
608             # where if it doesn't know the locktoken, it will
609             # just return an empty response with message "Client Error".
610             # Make a custom message for this case.
611             my $msg = $resp->message;
612             if ( $msg =~ /Client error/i ) {
613             $msg = "No locks found. Try steal";
614             return $self->err( 'ERR_GENERIC', $msg, $url );
615             }
616             else {
617             return $self->err( 'ERR_RESP_FAIL', $msg, $url );
618             }
619             }
620             }
621              
622             sub steal {
623             my ( $self, @p ) = @_;
624             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
625              
626             return $self->err('ERR_NULL_RESOURCE')
627             unless $self->get_workingresource();
628              
629             my $resource;
630             if ($url) {
631             $url = $self->get_absolute_uri($url);
632             $resource = $self->new_resource( -uri => $url );
633             }
634             else {
635             $resource = $self->get_workingresource();
636             }
637              
638             # Go the steal
639             my $resp = $resource->forcefully_unlock_all();
640             if ( $resp->is_success() ) {
641             return $self->ok( "steal succeeded", $url );
642             }
643             else {
644             return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url );
645             }
646             }
647              
648             # MKCOL
649             sub mkcol {
650             my ( $self, @p ) = @_;
651             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
652              
653             return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" );
654             return $self->err('ERR_NULL_RESOURCE')
655             unless $self->get_workingresource();
656              
657             $url = HTTP::DAV::Utils::make_trail_slash($url);
658             my $new_url = $self->get_absolute_uri($url);
659             my $resource = $self->new_resource( -uri => $new_url );
660              
661             # Make the lock
662             my $resp = $resource->mkcol();
663             if ( $resp->is_success() ) {
664             return $self->ok( "mkcol $new_url", $new_url );
665             }
666             else {
667             return $self->err( 'ERR_RESP_FAIL', $resp->message(), $new_url );
668             }
669             }
670              
671             # OPTIONS
672             sub options {
673             my ( $self, @p ) = @_;
674             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
675              
676             #return $self->err('ERR_WRONG_ARGS') if (!defined $url || $url eq "");
677             return $self->err('ERR_NULL_RESOURCE')
678             unless $self->get_workingresource();
679              
680             my $resource;
681             if ($url) {
682             $url = $self->get_absolute_uri($url);
683             $resource = $self->new_resource( -uri => $url );
684             }
685             else {
686             $resource = $self->get_workingresource();
687             $url = $resource->get_uri;
688             }
689              
690             # Make the call
691             my $resp = $resource->options();
692             if ( $resp->is_success() ) {
693             $self->ok( "options $url succeeded", $url );
694             return $resource->get_options();
695             }
696             else {
697             $self->err( 'ERR_RESP_FAIL', $resp->message(), $url );
698             return undef;
699             }
700             }
701              
702             # MOVE
703             sub move { return shift->_move_copy( "move", @_ ); }
704             sub copy { return shift->_move_copy( "copy", @_ ); }
705              
706             sub _move_copy {
707             my ( $self, $method, @p ) = @_;
708             my ( $url, $dest_url, $overwrite, $depth, $text, @other )
709             = HTTP::DAV::Utils::rearrange(
710             [ 'URL', 'DEST', 'OVERWRITE', 'DEPTH', 'TEXT' ], @p );
711              
712             return $self->err('ERR_NULL_RESOURCE')
713             unless $self->get_workingresource();
714              
715             if (!( defined $url && $url ne "" && defined $dest_url && $dest_url ne ""
716             )
717             )
718             {
719             return $self->err( 'ERR_WRONG_ARGS',
720             "Must supply a source and destination url" );
721             }
722              
723             $url = $self->get_absolute_uri($url);
724             $dest_url = $self->get_absolute_uri($dest_url);
725             my $resource = $self->new_resource( -uri => $url );
726             my $dest_resource = $self->new_resource( -uri => $dest_url );
727              
728             my $resp = $dest_resource->propfind( -depth => 1 );
729             if ( $resp->is_success && $dest_resource->is_collection ) {
730             my $leafname = HTTP::DAV::Utils::get_leafname($url);
731             $dest_url = "$dest_url/$leafname";
732             $dest_resource = $self->new_resource( -uri => $dest_url );
733             }
734              
735             # Make the lock
736             $resp = $resource->$method(
737             -dest => $dest_resource,
738             -overwrite => $overwrite,
739             -depth => $depth,
740             -text => $text,
741             );
742              
743             if ( $resp->is_success() ) {
744             return $self->ok( "$method $url to $dest_url succeeded", $url );
745             }
746             else {
747             return $self->err( 'ERR_RESP_FAIL', $resp->message, $url );
748             }
749             }
750              
751             # OPEN
752             # Must be a collection resource
753             # $dav->open( -url => http://localhost/test/ );
754             # $dav->open( localhost/test/ );
755             # $dav->open( -url => localhost:81 );
756             # $dav->open( localhost );
757             sub open {
758             my ( $self, @p ) = @_;
759             my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p );
760              
761             my $resource;
762             if ( defined $url && $url ne "" ) {
763             $url = HTTP::DAV::Utils::make_trail_slash($url);
764             $resource = $self->new_resource( -uri => $url );
765             }
766             else {
767             $resource = $self->get_workingresource();
768             $url = $resource->get_uri() if ($resource);
769             return $self->err('ERR_WRONG_ARGS')
770             if ( !defined $url || $url eq "" );
771             }
772              
773             my $response = $resource->propfind( -depth => 0 );
774              
775             #print $response->as_string;
776             #print $resource->as_string;
777              
778             my $result = $self->what_happened($url, $resource, $response);
779             if ($result->{success} == 0) {
780             return $self->err($result->{error_type}, $result->{error_msg}, $url);
781             }
782              
783             # If it is a collection but the URI doesn't end in a trailing slash.
784             # Then we need to reopen with the /
785             elsif ($resource->is_collection
786             && $url !~ m#/\s*$# )
787             {
788             my $newurl = $url . "/";
789             print "Redirecting to $newurl\n" if $DEBUG > 1;
790             return $self->open($newurl);
791             }
792              
793             # If it is not a collection then we
794             # can't open it.
795             elsif ( !$resource->is_collection ) {
796             return $self->err( 'ERR_GENERIC',
797             "Operation failed. You can only open a collection (directory)",
798             $url );
799             }
800             else {
801             $self->set_workingresource($resource);
802             return $self->ok( "Connected to $url", $url );
803             }
804              
805             return $self->err( 'ERR_GENERIC', $url );
806             }
807              
808             # Performs a propfind and then returns the populated
809             # resource. The resource will have a resourcelist if
810             # it is a collection.
811             sub propfind {
812             my ( $self, @p ) = @_;
813             my ( $url, $depth ) = HTTP::DAV::Utils::rearrange( [ 'URL', 'DEPTH' ], @p );
814              
815             # depth = 1 is the default
816             if (! defined $depth) {
817             $depth = 1;
818             }
819              
820             return $self->err('ERR_NULL_RESOURCE')
821             unless $self->get_workingresource();
822              
823             my $resource;
824             if ($url) {
825             $url = $self->get_absolute_uri($url);
826             $resource = $self->new_resource( -uri => $url );
827             }
828             else {
829             $resource = $self->get_workingresource();
830             }
831              
832             # Make the call
833             my $resp = $resource->propfind( -depth => $depth );
834             if ( $resp->is_success() ) {
835             $resource->build_ls($resource);
836             $self->ok( "propfind " . $resource->get_uri() . " succeeded", $url );
837             return $resource;
838             }
839             else {
840             return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url );
841             }
842             }
843              
844             # Set a property on the resource
845             sub set_prop {
846             my ( $self, @p ) = @_;
847             my ( $url, $namespace, $propname, $propvalue, $nsabbr )
848             = HTTP::DAV::Utils::rearrange(
849             [ 'URL', 'NAMESPACE', 'PROPNAME', 'PROPVALUE', 'NSABBR' ], @p );
850             $self->proppatch(
851             -url => $url,
852             -namespace => $namespace,
853             -propname => $propname,
854             -propvalue => $propvalue,
855             -action => "set",
856             -nsabbr => $nsabbr,
857             );
858             }
859              
860             # Unsets a property on the resource
861             sub unset_prop {
862             my ( $self, @p ) = @_;
863             my ( $url, $namespace, $propname, $nsabbr )
864             = HTTP::DAV::Utils::rearrange(
865             [ 'URL', 'NAMESPACE', 'PROPNAME', 'NSABBR' ], @p );
866             $self->proppatch(
867             -url => $url,
868             -namespace => $namespace,
869             -propname => $propname,
870             -action => "remove",
871             -nsabbr => $nsabbr,
872             );
873             }
874              
875             # Performs a proppatch on the resource
876             sub proppatch {
877             my ( $self, @p ) = @_;
878             my ( $url, $namespace, $propname, $propvalue, $action, $nsabbr )
879             = HTTP::DAV::Utils::rearrange(
880             [ 'URL', 'NAMESPACE', 'PROPNAME', 'PROPVALUE', 'ACTION', 'NSABBR' ],
881             @p );
882              
883             return $self->err('ERR_NULL_RESOURCE')
884             unless $self->get_workingresource();
885              
886             my $resource;
887             if ($url) {
888             $url = $self->get_absolute_uri($url);
889             $resource = $self->new_resource( -uri => $url );
890             }
891             else {
892             $resource = $self->get_workingresource();
893             }
894              
895             # Make the call
896             my $resp = $resource->proppatch(
897             -namespace => $namespace,
898             -propname => $propname,
899             -propvalue => $propvalue,
900             -action => $action,
901             -nsabbr => $nsabbr
902             );
903              
904             if ( $resp->is_success() ) {
905             $resource->build_ls($resource);
906             $self->ok( "proppatch " . $resource->get_uri() . " succeeded", $url );
907             return $resource;
908             }
909             else {
910             return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url );
911             }
912             }
913              
914             ######################################################################
915             sub put {
916             my ( $self, @p ) = @_;
917             my ( $local, $url, $callback, $custom_headers )
918             = HTTP::DAV::Utils::rearrange( [ 'LOCAL', 'URL', 'CALLBACK', 'HEADERS' ], @p );
919              
920             if ( ref($local) eq "SCALAR" ) {
921             $self->_start_multi_op( 'put ' . ${$local}, $callback );
922             $self->_put(@p);
923             }
924             else {
925             $self->_start_multi_op( 'put ' . $local, $callback );
926             $local =~ s/\ /\\ /g;
927             my @globs = glob("$local");
928              
929             #my @globs=glob("\"$local\"");
930             foreach my $file (@globs) {
931             print "Starting put of $file\n" if $HTTP::DAV::DEBUG > 1;
932             $self->_put(
933             -local => $file,
934             -url => $url,
935             -callback => $callback,
936             -headers => $custom_headers,
937             );
938             }
939             }
940             $self->_end_multi_op();
941             return $self->is_success;
942             }
943              
944             sub _put {
945             my ( $self, @p ) = @_;
946             my ( $local, $url, $custom_headers )
947             = HTTP::DAV::Utils::rearrange( [ 'LOCAL', 'URL', 'HEADERS' ], @p );
948              
949             return $self->err('ERR_WRONG_ARGS')
950             if ( !defined $local || $local eq "" );
951             return $self->err('ERR_NULL_RESOURCE')
952             unless $self->get_workingresource();
953              
954             # Check if they passed a reference to content rather than a filename.
955             my $content_ptr = ( ref($local) eq "SCALAR" ) ? 1 : 0;
956              
957             # Setup the resource based on the passed url
958             # Check if the remote resource exists and is a collection.
959             $url = $self->get_absolute_uri($url);
960             my $resource = $self->new_resource($url);
961             my $response = $resource->propfind( -depth => 0 );
962             my $leaf_name;
963             if ( $response->is_success && $resource->is_collection && !$content_ptr )
964             {
965              
966             # Add one / to the end of the collection
967             $url =~ s/\/*$//g; #Strip em
968             $url .= "/"; #Add one
969             $leaf_name = HTTP::DAV::Utils::get_leafname($local);
970             }
971             else {
972             $leaf_name = HTTP::DAV::Utils::get_leafname($url);
973             }
974              
975             my $target = $self->get_absolute_uri( $leaf_name, $url );
976              
977             #print "$local => $target ($url, $leaf_name)\n";
978              
979             # PUT A DIRECTORY
980             if ( !$content_ptr && -d $local ) {
981              
982             # mkcol
983             # Return 0 if fail because the error will have already
984             # been set by the mkcol routine
985             if ( $self->mkcol($target, -headers => $custom_headers) ) {
986             if ( !opendir( DIR, $local ) ) {
987             $self->err( 'ERR_GENERIC', "chdir to \"$local\" failed: $!" );
988             }
989             else {
990             my @files = readdir(DIR);
991             close DIR;
992             foreach my $file (@files) {
993             next if $file eq ".";
994             next if $file eq "..";
995             my $progeny = "$local/$file";
996             $progeny =~ s#//#/#g; # Fold down double slashes
997             $self->_put(
998             -local => $progeny,
999             -url => "$target/$file",
1000             );
1001             }
1002             }
1003             }
1004              
1005             # PUT A FILE
1006             }
1007             else {
1008             my $content = "";
1009             my $fail = 0;
1010             if ($content_ptr) {
1011             $content = $$local;
1012             }
1013             else {
1014             if ( !CORE::open( F, $local ) ) {
1015             $self->err( 'ERR_GENERIC',
1016             "Couldn't open local file $local: $!" );
1017             $fail = 1;
1018             }
1019             else {
1020             binmode F;
1021             while (my $line = ) { $content .= $line; }
1022             close F;
1023             }
1024             }
1025              
1026             if ( !$fail ) {
1027             my $resource = $self->new_resource( -uri => $target );
1028             my $response = $resource->put($content,$custom_headers);
1029             if ( $response->is_success ) {
1030             $self->ok( "put $target (" . length($content) . " bytes)",
1031             $target );
1032             }
1033             else {
1034             $self->err( 'ERR_RESP_FAIL',
1035             "put failed " . $response->message(), $target );
1036             }
1037             }
1038             }
1039             }
1040              
1041             ######################################################################
1042             # UTILITY FUNCTION
1043             # get_absolute_uri:
1044             # Synopsis: $new_url = get_absolute_uri("/foo/bar")
1045             # Takes a URI (or string)
1046             # and returns the absolute URI based
1047             # on the remote current working directory
1048             sub get_absolute_uri {
1049             my ( $self, @p ) = @_;
1050             my ( $rel_uri, $base_uri )
1051             = HTTP::DAV::Utils::rearrange( [ 'REL_URI', 'BASE_URI' ], @p );
1052              
1053             local $URI::URL::ABS_REMOTE_LEADING_DOTS = 1;
1054             if ( !defined $base_uri ) {
1055             $base_uri = $self->get_workingresource()->get_uri();
1056             }
1057              
1058             if ($base_uri) {
1059             my $new_url = URI->new_abs( $rel_uri, $base_uri );
1060             return $new_url;
1061             }
1062             else {
1063             $rel_uri;
1064             }
1065             }
1066              
1067             ## Takes a $dav->get_globs(URI)
1068             # Where URI may contain wildcards at the leaf level:
1069             # URI:
1070             # http://www.host.org/perldav/test*.html
1071             # /perldav/test?.html
1072             # test[12].html
1073             #
1074             # Performs a propfind to determine the url's that match
1075             #
1076             sub get_globs {
1077             my ( $self, $url ) = @_;
1078             my @urls = ();
1079             my ( $left, $leafname ) = HTTP::DAV::Utils::split_leaf($url);
1080              
1081             # We need to unescape it because it may have been encoded.
1082             $leafname = URI::Escape::uri_unescape($leafname);
1083              
1084             if ( $leafname =~ /[\*\?\[]/ ) {
1085             my $resource = $self->new_resource( -uri => $left );
1086             my $resp = $resource->propfind( -depth => 1 );
1087             if ( $resp->is_error ) {
1088             $self->err( 'ERR_RESP_FAIL', $resp->message(), $left );
1089             return ();
1090             }
1091              
1092             $leafname = HTTP::DAV::Utils::glob2regex($leafname);
1093             my $rl = $resource->get_resourcelist();
1094             if ($rl) {
1095             my $match = 0;
1096              
1097             # We eval this because a bogus leafname could bomb the regex.
1098             eval {
1099             foreach my $progeny ( $rl->get_resources() )
1100             {
1101             my $progeny_url = $progeny->get_uri;
1102             my $progeny_leaf
1103             = HTTP::DAV::Utils::get_leafname($progeny_url);
1104             if ( $progeny_leaf =~ /^$leafname$/ ) {
1105             print "Matched $progeny_url\n"
1106             if $HTTP::DAV::DEBUG > 1;
1107             $match++;
1108             push( @urls, $progeny_url );
1109             }
1110             else {
1111             print "Skipped $progeny_url\n"
1112             if $HTTP::DAV::DEBUG > 1;
1113             }
1114             }
1115             };
1116             $self->err( 'ERR_GENERIC', "No match found" ) unless ($match);
1117             }
1118             }
1119             else {
1120             push( @urls, $url );
1121             }
1122              
1123             return @urls;
1124             }
1125              
1126             sub what_happened {
1127             my ($self, $url, $resource, $response) = @_;
1128              
1129             if (! $response->is_error()) {
1130             return { success => 1 }
1131             }
1132              
1133             my $error_type;
1134             my $error_msg;
1135              
1136             # Method not allowed
1137             if ($response->status_line =~ m{405}) {
1138             $error_type = 'ERR_405';
1139             $error_msg = $response->status_line;
1140             }
1141             # 501 most probably means your LWP doesn't support SSL
1142             elsif ($response->status_line =~ m{501}) {
1143             $error_type = 'ERR_501';
1144             $error_msg = $response->status_line;
1145             }
1146             elsif ($response->www_authenticate) {
1147             $error_type = 'ERR_UNAUTHORIZED';
1148             $error_msg = $response->www_authenticate;
1149             }
1150             elsif ( !$resource->is_dav_compliant ) {
1151             $error_type = 'ERR_GENERIC';
1152             $error_msg = qq{The URL "$url" is not DAV enabled or not accessible.};
1153             }
1154             else {
1155             $error_type = 'ERR_RESP_FAIL';
1156             my $message = $response->message();
1157             $error_msg = qq{Could not access $url: $message};
1158             }
1159              
1160             return {
1161             success => 0,
1162             error_type => $error_type,
1163             error_msg => $error_msg,
1164             }
1165              
1166             }
1167              
1168             1;
1169              
1170             __END__