File Coverage

blib/lib/Net/DAV/LockManager.pm
Criterion Covered Total %
statement 109 111 98.2
branch 67 68 98.5
condition 37 39 94.8
subroutine 19 19 100.0
pod 7 7 100.0
total 239 244 97.9


line stmt bran cond sub pod time code
1             package Net::DAV::LockManager;
2              
3 13     13   358258 use strict;
  13         33  
  13         482  
4 13     13   72 use warnings;
  13         25  
  13         306  
5              
6 13     13   68 use File::Spec ();
  13         22  
  13         251  
7 13     13   8126 use Net::DAV::UUID;
  13         34  
  13         402  
8 13     13   8197 use Net::DAV::Lock;
  13         35  
  13         24199  
9              
10             our $VERSION = '1.305';
11             $VERSION = eval $VERSION;
12              
13             sub new {
14 55     55 1 1260 my ($class, $db) = (shift, shift);
15 55         111 my %obj = @_;
16              
17 55         126 $obj{'db'} = $db;
18              
19 55         362 return bless \%obj, $class;
20             }
21              
22             sub can_modify {
23 119     119 1 8644 my ($self, $req) = @_;
24              
25 119         272 _validate_lock_request( $req, 'user' );
26              
27 100         123 my ($resource, $token) = @{$req}{qw/path token/};
  100         208  
28 100   100     218 my $lock = $self->_get_lock( $resource ) || $self->_get_indirect_lock( $resource );
29              
30 100 100       496 return 1 unless $lock;
31 42 100       171 return 0 unless $token;
32              
33 25         55 return _is_permitted( $req, $lock );
34             }
35              
36             sub lock {
37 86     86 1 15423 my ($self, $req) = @_;
38              
39 86         276 _validate_lock_request( $req, 'user', 'owner' );
40              
41 60         93 my $path = $req->{'path'};
42              
43 60 100 100     147 return undef unless $self->can_modify( $req ) && !$self->_get_lock( $path );
44 51         186 foreach my $lock ( $self->{'db'}->list_descendants( $path ) ) {
45 4 100       8 return undef unless _is_permitted( $req, $lock );
46             }
47              
48 48 100       771 return $self->_add_lock(Net::DAV::Lock->new({
    100          
    100          
49             'path' => $path,
50             (defined $req->{'timeout'} ? ('expiry' => time() + $req->{'timeout'}) : ()),
51             'creator' => $req->{'user'},
52             'owner' => $req->{'owner'},
53             (defined $req->{'depth'} ? ('depth' => $req->{'depth'}) : ()),
54             (defined $req->{'scope'} ? ('scope' => $req->{'scope'}) : ()),
55             }));
56             }
57              
58             sub refresh_lock {
59 24     24 1 7847 my ($self, $req) = @_;
60 24         68 _validate_lock_request( $req, 'user', 'token' );
61              
62 5         15 my $lock = $self->_get_lock( $req->{'path'} );
63 5 100       17 return undef unless $lock;
64 4 100       10 return undef unless _is_permitted( $req, $lock );
65              
66 2   66     15 $lock->renew( time() + ($req->{'timeout'} || $Net::DAV::Lock::DEFAULT_LOCK_TIMEOUT) );
67              
68 2         7 return $self->_update_lock( $lock );
69             }
70              
71             sub unlock {
72 26     26 1 8971 my ($self, $req) = @_;
73 26         83 _validate_lock_request( $req, 'user', 'token' );
74              
75 7         19 my $lock = $self->_get_lock( $req->{'path'} );
76 7 100       27 return 0 unless $lock;
77 4 100       19 return 0 unless _is_permitted( $req, $lock );
78              
79 2         7 $self->_remove_lock( $lock );
80              
81 2         7 return 1;
82             }
83              
84             sub find_lock {
85 5     5 1 19 my ($self, $req) = @_;
86              
87 5         38 _validate_lock_request( $req );
88              
89 5         10 my $path = $req->{'path'};
90              
91 5   100     11 return $self->_get_lock( $path ) || $self->_get_indirect_lock( $path );
92             }
93              
94             sub list_all_locks {
95 6     6 1 19 my ($self, $req) = @_;
96              
97 6         44 _validate_lock_request( $req );
98              
99 6         10 my $path = $req->{'path'};
100 6         9 my @locks;
101 6         16 my $lock = $self->_get_lock( $path );
102 6 100       15 push @locks, $lock if defined $lock;
103              
104 6         83 while ( $path =~ s{/[^/]+$}{} ) {
105 16 100       37 $path = '/' unless length $path;
106              
107 16         32 my $lock = $self->_get_lock( $path );
108 16 100 100     75 push @locks, $lock if $lock && $lock->depth eq 'infinity';
109             }
110              
111 6         21 return @locks;
112             }
113              
114             #
115             # Retrieve a lock from the lock database, given the path to the lock.
116             # Return undef if none. This method also has the side effect of expiring
117             # any old locks persisted upon fetching.
118             #
119             sub _get_lock {
120 352     352   477 my ($self, $path) = @_;
121              
122 352         1048 my $lock = $self->{'db'}->get( $path );
123              
124 352 100       1226 return undef unless $lock;
125              
126 70 50       188 if (time() >= $lock->expiry) {
127 0         0 $self->_remove_lock($lock);
128              
129 0         0 return undef;
130             }
131              
132 70         156 return $lock;
133             }
134              
135             #
136             # Add the given lock to the database.
137             #
138             sub _add_lock {
139 48     48   73 my ($self, $lock) = @_;
140              
141 48         213 return $self->{'db'}->add($lock);
142             }
143              
144             #
145             # Update the lock provided.
146             #
147             sub _update_lock {
148 2     2   9 my ($self, $lock) = @_;
149              
150 2         9 return $self->{'db'}->update($lock);
151             }
152              
153             #
154             # Remove the lock object passed from the database.
155             #
156             sub _remove_lock {
157 2     2   4 my ($self, $lock) = @_;
158              
159 2         8 $self->{'db'}->remove($lock);
160              
161 2         5 return 1;
162             }
163              
164             #
165             # Get the lock of the nearest ancestor that applies to this resource.
166             # Returns undef if none found.
167             #
168             sub _get_indirect_lock {
169 85     85   135 my ($self, $res) = @_;
170              
171 85         410 while ( $res =~ s{/[^/]+$}{} ) {
172 161 100       365 $res = '/' unless length $res;
173              
174 161         284 my $lock = $self->_get_lock( $res );
175 161 100 100     780 return $lock if $lock && $lock->depth eq 'infinity';
176             }
177              
178 60         178 return;
179             }
180              
181             #
182             # Return true or false depending on whether or not the information reflected
183             # in the request is appropriate for the lock obtained from the database. In
184             # other words, make sure the token and user match the request.
185             #
186             sub _is_permitted {
187 37     37   52 my ($req, $lock) = @_;
188              
189 37 100       107 return 0 unless $req->{'user'} eq $lock->creator;
190 24 100       83 return 0 if !defined $req->{'token'};
191 23 100       60 if ( 'ARRAY' eq ref $req->{'token'} ) {
192 7 100       8 return 0 unless grep { $_ eq $lock->token } @{$req->{'token'}};
  17         40  
  7         12  
193             }
194             else {
195 16 100       52 return 0 unless $req->{'token'} eq $lock->token;
196             }
197              
198 15         71 return 1;
199             }
200              
201             #
202             # Perform general parameter validation.
203             #
204             # The parameter passed in should be a hash reference to be validated. The
205             # optional list that follows are names of required parameters besides the
206             # 'path' and 'user' parameters that are always required.
207             #
208             # Throws exception on failure.
209             #
210             sub _validate_lock_request {
211 279     279   7923 my ($req, @required) = @_;
212 279 100       807 die "Parameter should be a hash reference.\n" unless 'HASH' eq ref $req;
213              
214 271         450 foreach my $arg ( qw/path/, @required ) {
215 631 100       2173 die "Missing required '$arg' parameter.\n" unless exists $req->{$arg};
216             }
217              
218 256 100       1044 die "Not a clean path\n" if $req->{'path'} =~ m{(?:^|/)\.\.?(?:$|/)};
219 232 100 66     932 die "Not a clean path\n" if $req->{'path'} !~ m{^/} && !($req->{'path'} =~ s{^https?://[^/]+/}{/});
220 219 100 100     3452 if( defined $req->{'user'} && $req->{'user'} !~ m{^[0-9a-z_.][-a-z0-9_.]*$}i ) {
221 20         97 die "Not a valid user name.\n"; # May need better validation.
222             }
223              
224             # Validate optional parameters as necessary.
225 199 100 100     710 if( defined $req->{'scope'} && $Net::DAV::Lock::DEFAULT_SCOPE ne $req->{'scope'} ) {
226 2         19 die "'$req->{'scope'}' is not a supported value for scope.\n";
227             }
228              
229 197 100 100     719 if( defined $req->{'depth'} && '0' ne $req->{'depth'} && 'infinity' ne $req->{'depth'} ) {
      100        
230 2         13 die "'$req->{'depth'}' is not a supported value for depth.\n";
231             }
232              
233 195 100 100     676 if( defined $req->{'timeout'} && $req->{'timeout'} =~ /\D/ ) {
234 3         19 die "'$req->{'timeout'}' is not a supported value for timeout.\n";
235             }
236              
237 192 100       461 if ( defined $req->{'token'} ) {
238 53 100 100     237 unless ( !ref $req->{'token'} || 'ARRAY' eq ref $req->{'token'} ) {
239 1         7 die "Invalid token, not a string or array reference.\n";
240             }
241             }
242              
243             # Remove trailing / from path to make pathnames canonical.
244 191 100       1037 $req->{'path'} =~ s{/$}{} unless $req->{'path'} eq '/';
245              
246 191         512 return;
247             }
248              
249             1;
250              
251             __END__